In response to the code from waldemar dot axdorph at gmail dot com
Going solely based on the number of slashes is only slightly better than doing no checks at all. That won't catch something like this, which is just as much a problem:
<?php
above_dir('../../../home/joe/.ssh/', '/var/www/filemgr')
?>
An easier (and more secure) way of accomplishing that is:
<?php
function isAboveBasePath( $path, $base )
{
$path = realpath( $path );
$base = realpath( $base );
return 0 !== strncmp( $path, $base, strlen( $base ) );
}
?>
Makes sure that the real search path matches 100% the real base path, up to the length of the base path. $path should always be longer than $base, which is fine as long as $path starts with the same string as $base.
Also note that if you have any symlinks in the $base dir that point outside of $base, this check will fail ... but that's a very rare case, and is still at least over-protective, rather than insecure.
realpath
(PHP 4, PHP 5)
realpath — Retorna o path absoluto canonicalizado
Descrição
realpath() expande todos os links simbólicos e resolve referências para '/./', '/../' e extra caracteres '/' na entrada pelo path , e retorna o path absoluto canonicalizado.
Parâmetros
- path
-
O caminho a ser verificado.
Valor Retornado
Retorna o path absoluto em sucesso. O path resultante não conterá nenhum link simbólico ou componentes '/./' e '/../'.
realpath() retorna FALSE em caso de falha, por exemplo, se o caminho não existir. Em sistemas BSD realpath() não falha se somente o último componente do path não existe, quando em outro sistema irá retornar FALSE.
Exemplos
Exemplo #1 Exemplo da realpath()
<?php
chdir('/var/www/');
echo realpath('./../../etc/passwd');
?>
O exemplo acima irá imprimir:
/etc/passwd
Exemplo #2 realpath() em Windows
Em Windows, realpath() modificará o estilo unix de diretórios para o estilo Windows.
<?php
echo realpath('/windows/system32');
?>
O exemplo acima irá imprimir:
C:\WINDOWS\System32
realpath
15-Oct-2008 10:20
27-Aug-2008 07:02
If you need to resolve a url against a base url, as the browser does with anchor tags, then realpath won't help, because it's not on your file system.
This function does that:
<?php
function resolve_href ($base, $href) {
// href="" ==> current url.
if (!$href) {
return $base;
}
// href="http://..." ==> href isn't relative
$rel_parsed = parse_url($href);
if (array_key_exists('scheme', $rel_parsed)) {
return $href;
}
// add an extra character so that, if it ends in a /, we don't lose the last piece.
$base_parsed = parse_url("$base ");
// if it's just server.com and no path, then put a / there.
if (!array_key_exists('path', $base_parsed)) {
$base_parsed = parse_url("$base/ ");
}
// href="/ ==> throw away current path.
if ($href{0} === "/") {
$path = $href;
} else {
$path = dirname($base_parsed['path']) . "/$href";
}
// bla/./bloo ==> bla/bloo
$path = preg_replace('~/\./~', '/', $path);
// resolve /../
// loop through all the parts, popping whenever there's a .., pushing otherwise.
$parts = array();
foreach (
explode('/', preg_replace('~/+~', '/', $path)) as $part
) if ($part === "..") {
array_pop($parts);
} elseif ($part) {
$parts[] = $part;
}
return (
(array_key_exists('scheme', $base_parsed)) ?
$base_parsed['scheme'] . '://' . $base_parsed['host'] : ""
) . "/" . implode("/", $parts);
}
?>
23-Jun-2008 03:43
Because realpath() does not work on files that do not
exist, I wrote a function that does.
It replaces (consecutive) occurences of / and \\ with
whatever is in DIRECTORY_SEPARATOR, and processes /. and /.. fine.
Paths returned by get_absolute_path() contain no
(back)slash at position 0 (beginning of the string) or
position -1 (ending)
<?php
function get_absolute_path($path) {
$path = str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $path);
$parts = array_filter(explode(DIRECTORY_SEPARATOR, $path), 'strlen');
$absolutes = array();
foreach ($parts as $part) {
if ('.' == $part) continue;
if ('..' == $part) {
array_pop($absolutes);
} else {
$absolutes[] = $part;
}
}
return implode(DIRECTORY_SEPARATOR, $absolutes);
}
?>
A test:
<?php
var_dump(get_absolute_path('this/is/../a/./test/.///is'));
?>
Returns: string(14) "this/a/test/is"
As you can so, it also produces Yoda-speak. :)
12-Jun-2008 05:03
//windows
realpath('smarty/'); //is D:\whatever\folder\smarty\
//unix
realpath('smarty/'); //is /whatever/folder/smarty
if you are on windows, use smarty with security on,
and set a secure_dir with an ending / this is why you receive the error:
Smarty error: (secure mode) accessing "file.tpl" is not allowed
08-Jun-2008 08:49
I made this function so that I could see if a user was trying to go above the current directory.
My top dir:
/var/www/dir/
User wanted
/var/www/
This comes in handy when you're making a php explorer/filemanager and you want your files in the directory above to be private.
Here it is
<?php
//usage
//$dir is the dir you are in
//$dir_top is the highest dir allowed.
//example
/*
if(above_dir('../', '/var/www/filemgr'))
die('Not allowed. Please stick to your directories');
*/
function above_dir($dir, $dir_top){
if($dir == $dir_top)
return false;
$dir = realpath($dir);
$dir_top = realpath($dir_top);
$dir = count(explode('/', $dir));
$dir_top = count(explode('/', $dir_top));
if($dir <= $dir_top){
return true;
}else{
return false;
}
}
?>
24-Apr-2008 03:21
Some users will notice that PHP applications malfunction when they upgrade to PHP 5.2.4 (included in Ubuntu 8.04 LTS) because realpath returns true for files that don't exist.
This is due to the inclusion of the Hardened-PHP Project's Suhosin Patch in many distributions by default. This patch replaces PHPs realpath function with the BSD implementation, which ignores the last path component.
The workaround is to use the file_exists function to verify that the file exists before using realpath to get its real path string.
e.g: instead of:
<?php
if (realpath($path)) {
$path = realpath($path);
} else {
throw new Exception('Path not found!');
}
?>
Do this:
<?php
if (file_exists($path)) {
$path = realpath($path);
} else {
throw new Exception('Path not found!');
}
?>
See http://www.hardened-php.net/suhosin/a_feature_list:realpath.html
23-Apr-2008 03:19
Somewhere between php v5.1.6 and 5.2.5, the implementation of realpath changed so that an empty value now returns the current working directory:
(v5.1.6) realpath('') == ''
(v5.2.5) realpath('') == `pwd`
21-Apr-2008 02:14
Looks like a lot of people are looking for an 'unrealpath' or 'relative-path' function, returning the relative path from a directory to another one (in most cases ./). Unfortunately it's kinda difficult to program such a function which really works correctly, with any type of path.
Most of the posted functions have some little bugs. I compared the following functions:
- unrealpath() by Zach Bernal zbernal at ucsc dot edu (http://ch2.php.net/manual/en/function.realpath.php#78567)
- UnRealPath() by alban dot lopez+php dot net at gmail dot com (http://ch2.php.net/manual/en/function.realpath.php#77968)
- rel_path() by Santosh Patnaik (http://ch2.php.net/manual/en/function.realpath.php#77203)
The file I used for testing is located at /var/www/test/test.php.
unrealpath(../test/test.php) -> ../test/test.php
UnRealPath(../test/test.php) -> ./var/www/test/test.php
rel_path(../test/test.php) -> ./../../../../../test/test.php
should actually return: ./test.php
UnRealPath(./) -> ./var/www/test/
rel_path(./): -> ./../../../../.
should actually return: ./
Exapmple on windows: the file I used for testing is located at C:/www/test/test.php.
unrealpath(C:/www/test/../) -> C:/www/test/../
UnRealPath(C:/www/test/../) -> .
rel_path(C:/www/test/../) -> ./../../../../C:/www/test/../
should actually return: ../
So here's my try, I use 3 functions to get the relative path between two directories:
- clean_path() -> replace / or \ with the correct directory separator etc.
- absolute_path() -> get absolute path of the given path (actually the same as realpath(), but using clean_path() to f.e. add a directory separator at the end if the path isn't a file)
- relative_path() -> get relative path from one to another path
<?php
define('DIR_DELIM',DIRECTORY_SEPARATOR); // in case you'd like to change it. Easier to define a constant instead of including a parameter in each function for the directory separator
function clean_path($path) {
$path = str_replace(array('/','\\'),DIR_DELIM,$path);
$path = preg_replace('/[' . preg_quote(DIR_DELIM,DIR_DELIM) . ']{2,}/',DIR_DELIM,$path);
return substr($path,-1) != DIR_DELIM ? $path . DIR_DELIM : $path;
}
function absolute_path($path) {
$filename = '';
if (is_file($path)) {
$filename = basename($path);
$path = dirname($path);
}
return clean_path(realpath($path)) . $filename;
}
function relative_path($path,$source = null) {
$filename = '';
if (is_file($path)) {
$filename = basename($path);
$path = dirname($path);
}
$path = absolute_path($path);
$source = absolute_path(is_null($source) ? '.' : $source);
if ($path == $source)
return '.' . DIR_DELIM . $filename;
$path_dirs = explode(DIR_DELIM,substr($path,0,-1));
$source_dirs = explode(DIR_DELIM,substr($source,0,-1));
for ($i = 0; $i < min(count($path_dirs),count($source_dirs)); $i++)
if ($path_dirs[$i] != $source_dirs[$i])
break;
if ($i == 0)
return false;
$relative_path = str_repeat('..' . DIR_DELIM,count($source_dirs) - $i);
$same_path = implode(DIR_DELIM,array_splice($path_dirs,0,$i));
if ($same_path != '' && strpos($path,$same_path) === 0)
$path = substr($path,strlen($same_path) + 1);
return $relative_path . ($path == DIR_DELIM ? '' : $path) . $filename;
}
?>
Of course my functions might have some bugs too, but I tested it with a lot of different paths and it seems to work with all of them, I think ;). I tried my best.
Please note that both directories ($path and $source) need to exist, otherwise the function won't return a correct path. If you ignore $source (in most cases I suppose) then the actual directory ./ is taken as $source.
Testing:
relative_path(/var/www/) -> ../
relative_path(/var/www/../test/../../) -> ../../../
relative_path(../) -> ../
relative_path(/) -> ../../../
relative_path(../test/test.php) -> ./test.php
I did a lot of testings on windows too (path like C:/www/ etc.) and it worked with all the paths I used.
I know my english isn't too good, but I hope you understood what I wanted to say ...
Have fun
19-Mar-2008 06:34
<?
//rp like, working with absolute/relative path & a little bit shorter :p
function rp($path) {
$out=array();
foreach(explode('/', $path) as $i=>$fold){
if ($fold=='' || $fold=='.') continue;
if ($fold=='..' && $i>0 && end($out)!='..') array_pop($out);
else $out[]= $fold;
} return ($path{0}=='/'?'/':'').join('/', $out);
}
17-Oct-2007 11:10
Santosh Patnaik's method worked in most cases, but when I gave it funky input with different directory separators it returned the wrong path. I liked julabz at laposte dot net the best, and modified it to use a similar signiture signature to Patnaik's function:
<?php
function unrealpath($start_dir, $final_dir, $dirsep = DIRECTORY_SEPARATOR){
//Directory separator consistency
$start_dir = str_replace('/',$dirsep,$start_dir);
$final_dir = str_replace('/',$dirsep,$final_dir);
$start_dir = str_replace('\\',$dirsep,$start_dir);
$final_dir = str_replace('\\',$dirsep,$final_dir);
//'Splode!
$firstPathParts = explode($dirsep, $start_dir);
$secondPathParts = explode($dirsep, $final_dir);
//Get the number of parts that are the same.
$sameCounter = 0;
for($i = 0; $i < min( count($firstPathParts), count($secondPathParts) ); $i++) {
if( strtolower($firstPathParts[$i]) !== strtolower($secondPathParts[$i]) ) {
break;
}
$sameCounter++;
}
//If they do not share any common directories/roots, just return 2nd path.
if( $sameCounter == 0 ) {
return $final_dir;
}
//init newpath.
$newPath = '';
//Go up the directory structure count(firstpathparts)-sameCounter times (so, go up number of non-matching parts in the first path.)
for($i = $sameCounter; $i < count($firstPathParts); $i++) {
if( $i > $sameCounter ) {
$newPath .= $dirsep;
}
$newPath .= "..";
}
//if we did not have to go up at all, we're still in start_dir.
if( strlen($newPath) == 0 ) {
$newPath = ".";
}
//now we go down as much as needed to get to final_dir.
for($i = $sameCounter; $i < count($secondPathParts); $i++) {
$newPath .= $dirsep;
$newPath .= $secondPathParts[$i];
}
//
return $newPath;
}
?>
The only error I found in julabz's function was that count is always 1 for any string, but strlen does what he intended in this case.
15-Oct-2007 04:18
eric at themepark dot com noted that realpath() strips off the trailing directory separator. This is only correct for PHP4!
PHP5 seems to have silently "corrected" this behaviour, now leaving the trailing separator. This can result in incorrect behaviour if your path isn't meant to contain a trailing separator.
eg:
PHP4: realpath("some/path/with/a/trailing/slash/") => "[root]/some/path/with/a/trailing/slash"
PHP5: realpath("some/path/with/a/trailing/slash/") => "[root]/some/path/with/a/trailing/slash/"
03-Oct-2007 08:25
Sometimes it is helpful to check for the existance of a file
which might be found by using the include_path like in
include("file_from_include_path.php");
A simple function iterates the include_path and tries all
possibilites, returns translated_local_path on success or false
if not found.
function mappath($path_to_translate){
$IncludePath=explode(PATH_SEPARATOR,get_include_path());
foreach($IncludePath as $prefix){
if(substr($prefix,-1)==DIRECTORY_SEPARATOR)
$prefix=substr($prefix,0,-1);
$try_path=sprintf("%s%s%s"
,$prefix,DIRECTORY_SEPARATOR,$path_to_translate);
if(file_exists($try_path))return($try_path);
}
return false;
}
21-Sep-2007 12:15
Here's a little function to return the shortest relative path between dest folder and current folder (you can be replace $_SERVER['PHP_SEFL'] by your variable folder) :
<?php
// if you run in /var/www/
echo UnRealPath ('./'); // return "./"
echo UnRealPath ('./ajax-browser/AJAX-B/scripts/'); // return "./ajax-browser/AJAX-B/scripts/"
echo UnRealPath ('/var/'); // return "./../"
echo UnRealPath ('/opt/picasa/wine/'); // return "./../../opt/picasa/wine/"
function UnRealPath ($dest)
{
$Ahere = explode ('/', realpath($_SERVER['PHP_SEFL']));
$Adest = explode ('/', realpath($dest));
$result = '.'; // le chemin retouné dois forcement commancé par ./ c'est le but
while (implode ('/', $Adest) != implode ('/', $Ahere))// && count ($Adest)>0 && count($Ahere)>0 )
{
if (count($Ahere)>count($Adest))
{
array_pop($Ahere);
$result .= '/..';
}
else
{
array_pop($Adest);
}
}
return str_replace('//', '/', $result.str_replace(implode ('/', $Adest), '', realpath($dest)).(@is_dir(realpath($dest))?'/':''));
}
?>
20-Aug-2007 10:50
Beware that the use of a NULL value for the $path argument will give different results depending on the platform:
On Windows, realpath(null) will return the current path (same as realpath(".")).
On Linux (tested on Kernel 2.6) it will return FALSE.
20-Aug-2007 02:52
Given real paths of two files, this function finds the relative path of one ($dest) with respect to the other ($root).
echo rel_path('a/b/c', 'd/e'); // './../../a/b/c'
echo rel_path('a/b/c', 'a/b/c/d/e'); // './../..'
echo rel_path('a/b/c/d/e', 'a/b/c'); // './d/e'
function rel_path($dest, $root = '', $dir_sep = '/')
{
$root = explode($dir_sep, $root);
$dest = explode($dir_sep, $dest);
$path = '.';
$fix = '';
$diff = 0;
for($i = -1; ++$i < max(($rC = count($root)), ($dC = count($dest)));)
{
if(isset($root[$i]) and isset($dest[$i]))
{
if($diff)
{
$path .= $dir_sep. '..';
$fix .= $dir_sep. $dest[$i];
continue;
}
if($root[$i] != $dest[$i])
{
$diff = 1;
$path .= $dir_sep. '..';
$fix .= $dir_sep. $dest[$i];
continue;
}
}
elseif(!isset($root[$i]) and isset($dest[$i]))
{
for($j = $i-1; ++$j < $dC;)
{
$fix .= $dir_sep. $dest[$j];
}
break;
}
elseif(isset($root[$i]) and !isset($dest[$i]))
{
for($j = $i-1; ++$j < $rC;)
{
$fix = $dir_sep. '..'. $fix;
}
break;
}
}
return $path. $fix;
}
09-Aug-2007 07:22
The function seems to work only with real path - it also checks whether given file or path exists on windows platform (not sure what it does on Linux).
In case the path is not real the function returns FALSE. I expected the function only convert path into canonical form and lets user decide whether the path exists in filesystem. I would expect the notice at least.
24-Jul-2007 07:22
There might be other solutions like mine in here,
but here is one more.
This function converts Virtual Paths into Relative Paths.
I needed that at one time, and I did'nt have much luck finding a solution amongst the PHP Functions in the manual.
<?php
function GetPath($RedPath) {
if(substr($RedPath, 0, 1) == "/") {
$SysPath = dirname($_SERVER['PHP_SELF']);
if(substr($RedPath, -1) == "/") $RedPath = substr($RedPath, 0, -1);
if(strlen($SysPath) == 1)
return ".".$RedPath;
elseif(strcmp($SysPath,$RedPath) == 0)
return "./";
else {
$s_tmp = split("/", $SysPath);
$r_tmp = split("/", $RedPath);
while(($r_tmp[$i] == $s_tmp[$i]) && $i < 10)
$i++;
$t_RedPath = end(split("/", $RedPath, ($i+1)));
if($i == count($s_tmp))
return "./".$t_RedPath;
else
return str_repeat("../", count($s_tmp)-$i).$t_RedPath;
}
}
else
return $RedPath;
}
?>
16-Jul-2007 11:49
Here's a little function to return the relative path between two urls:
<?php
function get_relative_path($start_dir, $final_dir){
//
$firstPathParts = explode(DIRECTORY_SEPARATOR, $start_dir);
$secondPathParts = explode(DIRECTORY_SEPARATOR, $final_dir);
//
$sameCounter = 0;
for($i = 0; $i < min( count($firstPathParts), count($secondPathParts) ); $i++) {
if( strtolower($firstPathParts[$i]) !== strtolower($secondPathParts[$i]) ) {
break;
}
$sameCounter++;
}
if( $sameCounter == 0 ) {
return $final_dir;
}
//
$newPath = '';
for($i = $sameCounter; $i < count($firstPathParts); $i++) {
if( $i > $sameCounter ) {
$newPath .= DIRECTORY_SEPARATOR;
}
$newPath .= "..";
}
if( count($newPath) == 0 ) {
$newPath = ".";
}
for($i = $sameCounter; $i < count($secondPathParts); $i++) {
$newPath .= DIRECTORY_SEPARATOR;
$newPath .= $secondPathParts[$i];
}
//
return $newPath;
}
?>
25-Jun-2007 11:55
This is my attempt at writing a realpath replacement. I needed to to run some Adobe code on a server with realpath disabled and this seemed to do the job. It is written for a unix server, I suppose it could be made cross platform using DIRECTORY_SEPARATOR. (With thanks to Marc Noirot for his code).
function myRealPath($path) {
// check if path begins with "/" ie. is absolute
// if it isnt concat with script path
if (strpos($path,"/") !== 0) {
$base=dirname($_SERVER['SCRIPT_FILENAME']);
$path=$base."/".$path;
}
// canonicalize
$path=explode('/', $path);
$newpath=array();
for ($i=0; $i<sizeof($path); $i++) {
if ($path[$i]==='' || $path[$i]==='.') continue;
if ($path[$i]==='..') {
array_pop($newpath);
continue;
}
array_push($newpath, $path[$i]);
}
$finalpath="/".implode('/', $newpath);
// check then return valid path or filename
if (file_exists($finalpath)) {
return ($finalpath);
}
else return FALSE;
}
28-May-2007 08:55
I have also a short solution.
But Im not sure, if it is performant.
Explode-Array solution seems to work very fast.
function buildRelativePath($path){
$oldP = "";
$newP = $path;
while($newP!=$oldP){
$oldP = $newP;
$newP = preg_replace("/([^\/]+\/)([^\/]+\/)(\.\.\/)/ms","$1",$newP);
}
return str_replace("//","",str_replace("./","",$newP));
}
18-May-2007 05:00
Who ever wrote the rp()-function. First of all, your function skips filenames or directories with "0" because you use == instead of === when comparing to an "empty" value. Second, the idea of realpath() isn't fixing useless "../" etc, but to resolve the full path of a relative directory/file or a symbolic link. So that if you run the script in '/usr/local/apache2/htdocs/' and exec realpath('uploads/test.txt') the output becomes '/usr/local/apache2/htdocs/uploads/test.txt' (as in the example)
I also wonder why the implementations aren't using the DIRECTORY_SEPARATOR constant and expect Unix.
03-May-2007 08:31
Yet another realpath replacement:
function myRealPath($path) {
// Check if path begins with "/". => use === with strpos
if (strpos($path,"/") === 0) {
$path = $_SERVER['DOCUMENT_ROOT'].$path;
}
else {
// Strip slashes and convert to arrays.
$currentDir = preg_split("/\//",dirname($_SERVER['PATH_TRANSLATED']));
$newDir = preg_split('/\//',$path);
// Drop one directory from the array for each ".."; add one otherwise.
foreach ($newDir as $dir) {
if ($dir == "..")
array_pop($currentDir);
elseif ($dir != ".") //test for "." which represents current dir (do nothing in that case)
array_push($currentDir,$dir);
}
// Return the slashes.
$path = implode($currentDir,"/");
}
return $path;
}
Tyron Madlener said "I just wonder why all the other solutions are so complex and that mine so simple".
Funny. I wonder why yours is so complex (26 lines, 2 functions) when it can be done in half that (13 lines, 1 function):
function rp($p) {
$p=explode('/', $p);
$o=array();
for ($i=0; $i<sizeof($p); $i++) {
if (''==$p[$i] || '.'==$p[$i]) continue;
if ('..'==$p[$i] && $i>0 && '..'!=$o[sizeof($o)-1]) {
array_pop($o);
continue;
}
array_push($o, $p[$i]);
}
return implode('/', $o);
}
With that, this:
print rp("../../xyz.txt")."\n";
print rp("a/b/c/d/../../xyz.txt")."\n";
print rp("a/b/c/d/../..//xyz.txt")."\n";
Prints:
../../xyz.txt
a/b/xyz.txt
a/b/xyz.txt
As expected.
12-Feb-2007 03:29
the precedent function does not work all the time : servers sometimes put a slash '/' at the end of $_SERVER['DOCUMENT_ROOT']
// transforme un chemin d'image relatif en chemin html absolu
function htmlpath($relative_path) {
$realpath = str_replace("\\", "/", realpath($relative_path));
$root = preg_replace(',/$,', '', $_SERVER['DOCUMENT_ROOT']);
if (strlen($root) && strpos($realpath, $root)===0)
return substr($realpath, strlen($root));
$dir = dirname(!empty($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] :
(!empty($_SERVER['SCRIPT_NAME']) ? $_SERVER['SCRIPT_NAME'] :
(!empty($_SERVER['PHP_SELF']) ? $_SERVER['PHP_SELF'] : str_replace('\\','/',__FILE__)
)));
return canonicalize($dir.'/'.$relative_path);
}
// retourne un chemin canonique a partir d'un chemin contenant des ../
function canonicalize($address) {
$address = str_replace("//", "/", $address);
$address = explode('/', $address);
$keys = array_keys($address, '..');
foreach($keys as $keypos => $key) array_splice($address, $key - ($keypos * 2 + 1), 2);
$address = implode('/', $address);
return preg_replace(',([^.])\./,', '\1', $address);
}
17-Jan-2007 06:52
Popular htmlpath function found below with str_replace to fix issues with PHP on Windows.
function htmlpath($relative_path) {
$realpath=str_replace("\\", "/", realpath($relative_path));
$htmlpathURL=str_replace($_SERVER['DOCUMENT_ROOT'],'',$realpath);
return $htmlpathURL;
}
22-Nov-2006 07:38
Here's a function to canonicalize a URL containing relative paths. Ran into the problem when pulling links from a remote page.
<?php
function canonicalize($address)
{
$address = explode('/', $address);
$keys = array_keys($address, '..');
foreach($keys AS $keypos => $key)
{
array_splice($address, $key - ($keypos * 2 + 1), 2);
}
$address = implode('/', $address);
$address = str_replace('./', '', $address);
}
$url = 'http://www.example.com/something/../else';
echo canonicalize($url); //http://www.example.com/else
?>
21-Sep-2005 11:31
Here's another function that resolves references to '/./', '/../' and extra '/' characters in the input path and returns the canonicalized pathname.
<?php
function cleanPath($path) {
$result = array();
// $pathA = preg_split('/[\/\\\]/', $path);
$pathA = explode('/', $path);
if (!$pathA[0])
$result[] = '';
foreach ($pathA AS $key => $dir) {
if ($dir == '..') {
if (end($result) == '..') {
$result[] = '..';
} elseif (!array_pop($result)) {
$result[] = '..';
}
} elseif ($dir && $dir != '.') {
$result[] = $dir;
}
}
if (!end($pathA))
$result[] = '';
return implode('/', $result);
}
echo 'input: ', $path = '..//./../dir4//./dir5/dir6/..//dir7/', '<br />';
echo 'output: ', cleanPath($path), '<br />';
?>
Will return:
input: ..//./../dir4//./dir5/dir6/..//dir7/
output: ../../dir4/dir5/dir7/
14-Sep-2005 03:19
Having problems with includes within include files,
particularly when the include files are in different directories?
This syntax allows:
* relative paths to be used in include strings.
* include files to include other files in different directories.
without getting file not found errors.
<?
include_once realpath(dirname(__FILE__)."/relative/path/to/include.inc.php");
?>
14-Aug-2005 12:53
None of the functions provided here worked for me. All of them somehow had the problem with pathes like '../../downloads/bar/../foo/..' or didn't provide relative paths.
So I coded my one that justs removes '.'-dirs from the paths and simplifies stuff like 'dir/..'
function SimplifyPath($path) {
$dirs = explode('/',$path);
for($i=0; $i<count($dirs);$i++) {
if($dirs[$i]=="." || $dirs[$i]=="") {
array_splice($dirs,$i,1);
$i--;
}
if($dirs[$i]=="..") {
$cnt = count($dirs);
$dirs=Simplify($dirs, $i);
$i-= $cnt-count($dirs);
}
}
return implode('/',$dirs);
}
function Simplify($dirs, $idx) {
if($idx==0) return $dirs;
if($dirs[$idx-1]=="..") Simplify($dirs, $idx-1);
else array_splice($dirs,$idx-1,2);
return $dirs;
}
I just wonder why all the other solutions are so complex and that mine so simple, because in general the problem of simplifieng isn't that hard to solve. Well, I hope that there aren't any cases on which my solution fails. Otherwise, feel free to improve :D
22-Jul-2005 07:01
I have written this function so that it does not matter whether the folder exists on the system. It simply traverses the given path moving up directories at every "/.." that it encounters. Though this can be used with a file at the end I would be cautious about using it with a query attached as any "/.." in the query will remove the text proir until the previous "/".
<?php
function htmlpath($relative_path)
{
$realpath = '';
$q = 0;
// Remove any ./
$relative_path = str_replace('/./', '/', $relative_path);
// Remove trailing /
if ($relative_path[strlen($relative_path)-1] == '/')
{
$relative_path = substr($relative_path, 0, -1);
}
$p = strpos($relative_path, '/..', $q);
while ($p !== false)
{
// Get the next part of the path
if ($p != $q) $realpath .= substr($relative_path, $q, $p);
// Find the edge of the previous directory
$i = strrpos($realpath, '/');
if ($i === false)
{
return false; // Not enough directories to go up any further.
}
// Remove the directory
$realpath = substr($realpath, 0, $i);
// Take out the "/.."
$relative_path = substr($relative_path, 0, $p) . substr($relative_path, $p+3);
// Find the next "/.."
$q = $p;
$p = strpos($relative_path, '/..', $q);
}
// Get the rest of the relative path.
$realpath .= substr($relative_path, $q);
return $realpath;
}
?>
20-Jul-2005 08:55
You have a realpath.
Now you want a htmlpath.
First Suggestion:
<?php
function htmlpath($relative_path) {
$realpath=realpath($relative_path);
$htmlpath=str_replace($_SERVER['DOCUMENT_ROOT'],'',$realpath);
return $htmlpath;
}
?>
But this does not work on some servers.
Second Suggestion:
<?php
function htmlpath($realpath) {
$i = substr_count($_ENV["SCRIPT_URL"],'/')."<br>";
$baserealpath=realpath(str_repeat('../',$i-1));
$htmlpath=str_replace($baserealpath,'',$realpath);
return $htmlpath;
}
?>
08-Jun-2005 10:44
This function is also nice to test for security-breaches. You can forbid the script to access files below a certain directory to prevent "../../../etc/shadow" and similar attacks:
<?php
// declare the basic directory for security reasons
// Please do NOT attach a "/"-suffix !
$basedir = '/var/www/cgi-bin/scriptfolder';
// compare the entered path with the basedir
$path_parts = pathinfo($_REQUEST['file_to_get']);
if (realpath($path_parts['dirname']) != $basedir) {
/* appropriate action against crack-attempt*/
die ('coding good - h4x1ng bad!');
}
?>
The url "script.php?file_to_get=../../../etc/shadow" will now result in an error.
20-Dec-2004 01:43
Sometimes you may need to refer to the absolute path of a file in your website instead of a relative path, but the realpath() function returns the path relative to the server's filesystem, not a path relative to your website root directory.
For example, realpath() may return something like this:
/home/yoursite/public_html/dir1/file.ext
You can't use this in an HTML document, because the web server will not find the file. To do so, you can use:
<?php
function htmlpath($relative_path) {
$realpath=realpath($relative_path);
$htmlpath=str_replace($_SERVER['DOCUMENT_ROOT'],'',$realpath);
return $htmlpath;
}
echo '<img src="',htmlpath('../../relative/path/to/file.ext'),'" border=1>';
?>
It will return something like:
<img src="/dir1/relative/path/to/file.ext" border=1>
16-Dec-2004 03:52
In my last function, it will always discard "../" if there are no more parent dirs to go back. But it's a mistake! For example, relative paths like "../../downloads" returns "downloads/", and it should return exactly the same entry (because parent directories for "downloads" may exist in this case). Well, I've corrected this bug. Now:
"C:/downloads/../../../" returns "C:/"
"downloads/../../../" returns "../../"
<?php
function real_path($path)
{
if ($path == "")
{
return false;
}
$path = trim(preg_replace("/\\\\/", "/", (string)$path));
if (!preg_match("/(\.\w{1,4})$/", $path) &&
!preg_match("/\?[^\\/]+$/", $path) &&
!preg_match("/\\/$/", $path))
{
$path .= '/';
}
$pattern = "/^(\\/|\w:\\/|https?:\\/\\/[^\\/]+\\/)?(.*)$/i";
preg_match_all($pattern, $path, $matches, PREG_SET_ORDER);
$path_tok_1 = $matches[0][1];
$path_tok_2 = $matches[0][2];
$path_tok_2 = preg_replace(
array("/^\\/+/", "/\\/+/"),
array("", "/"),
$path_tok_2);
$path_parts = explode("/", $path_tok_2);
$real_path_parts = array();
for ($i = 0, $real_path_parts = array(); $i < count($path_parts); $i++)
{
if ($path_parts[$i] == '.')
{
continue;
}
else if ($path_parts[$i] == '..')
{
if ( (isset($real_path_parts[0]) && $real_path_parts[0] != '..')
|| ($path_tok_1 != "") )
{
array_pop($real_path_parts);
continue;
}
}
array_push($real_path_parts, $path_parts[$i]);
}
return $path_tok_1 . implode('/', $real_path_parts);
}
?>
19-Nov-2004 09:37
sarizmendi is right, and this is very frustrating. I'm trying to use a script with a different virtual root, which process folders/contents. This works perfectly with asp's server.mappath() but realpath() fails, because it's mapping to the same doc root (C:\inetpub\wwwroot) when it should be from a different virtual path (i.e. D:\content). Although I could force this to the static path, I'm trying to use it with a $_GET, and this could point to any of a number of virtual roots.
11-Mar-2004 11:43
I've tested several examples given by other ppls but none were working, at least on windows. so I spent time and wrote my own function for realpath( ) replacement that will work with non-existing paths also.
Here it is. Please, check it, if you have time and let me know about the results. Thanks.
<?php
define( "_PL_OS_SEP", "/" );
define( "_CUR_OS", substr( php_uname( ), 0, 7 ) == "Windows" ? "Win" : "_Nix" );
function checkCurrentOS( $_OS )
{
if ( strcmp( $_OS, _CUR_OS ) == 0 ) {
return true;
}
return false;
}
function isRelative( $_dir )
{
if ( checkCurrentOS( "Win" ) ) {
return ( preg_match( "/^\w+:/", $_dir ) <= 0 );
}
else {
return ( preg_match( "/^\//", $_dir ) <= 0 );
}
}
function unifyPath( $_path )
{
if ( checkCurrentOS( "Win" ) ) {
return str_replace( "\\", _PL_OS_SEP, $_path );
}
return $_path;
}
function getRealpath( $_path )
{
/*
* This is the starting point of the system root.
* Left empty for UNIX based and Mac.
* For Windows this is drive letter and semicolon.
*/
$__path = $_path;
if ( isRelative( $_path ) ) {
$__curdir = unifyPath( realpath( "." ) . _PL_OS_SEP );
$__path = $__curdir . $__path;
}
$__startPoint = "";
if ( checkCurrentOS( 