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;
}
realpath
(PHP 4, PHP 5)
realpath — Löst einen Pfad in einen absoluten und eindeutigen auf
Beschreibung
$path
)
realpath() löst alle symbolischen Verweise,
'/./', '/../' und mehrere aufeinanderfolgende '/' Zeichen,
die in path enthalten sind, auf und gibt
den so entstehenden absoluten Pfad zurück.
Parameter-Liste
-
path -
Der Pfad, der aufgelöst werden soll.
Hinweis:
Der Pfad muss angegeben werden, kann allerdings auch ein leerer String oder
NULLsein, in diesem Fall wird dann das aktuelle Verzeichnis benutzt.
Rückgabewerte
Gibt bei Erfolg den eindeutigen und absoluten Pfadnamen zurück. Dieser wird keine symbolischen Links, '/./' oder '/../' mehr enthalten.
realpath() gibt FALSE zurück, wenn ein Fehler auftritt,
beispielsweise wenn die Datei nicht existiert.
Hinweis:
Das ausführende Script muss alle Verzeichnisse im Pfad öffnen können (auf unixoiden Systemen wäre das das "execute" Recht). Ist dies nicht der Fall, gibt realpath()
FALSEzurück.
Hinweis: Weil PHPs Integer Typ vorzeichenbehaftet ist und viele Platformen 32bit Integer verweden, können einige Dateisystem-Funktionen für Dateien größer als 2GB unerwartete Ergebnisse liefern.
Changelog
| Version | Beschreibung |
|---|---|
| 5.3.0 |
Vor dieser Version schlug realpath() auf BSD Systemen
nicht fehl, wenn nur die letzte path Komponente
nicht existierte. Jetzt schlägt realpath() auch in diesem
Fall fehl.
|
| 5.0.0 |
Vor dieser Version gab realpath() das Verzeichnis des aktuellen
Scripts zurück, wenn ein leerer String oder NULL übergeben wurden.
|
Beispiele
Beispiel #1 realpath()
<?php
chdir('/var/www/');
echo realpath('./../../etc/passwd');
?>
Das oben gezeigte Beispiel erzeugt folgende Ausgabe:
/etc/passwd
Beispiel #2 realpath() auf Windows
Unter Windows wandelt realpath() Unix-artige Pfade in gültige Windows-Pfade um:
<?php
echo realpath('/windows/system32');
?>
Das oben gezeigte Beispiel erzeugt folgende Ausgabe:
C:\WINDOWS\System32
Siehe auch
- basename() - Gibt letzten Namensteil einer Pfadangabe zurück
- dirname() - Beschreibung
- pathinfo() - Liefert Informationen über einen Dateipfad
realpath
26-Jun-2007 08:55
28-May-2007 05: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 02: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 05: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.
13-Feb-2007 12: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);
}
18-Jan-2007 03: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;
}
23-Nov-2006 04: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 08: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/
15-Sep-2005 12: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 09: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 04: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 05: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 07: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 10: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 12: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 06: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 08: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( "Win" ) ) {
list( $__startPoint, $__path ) = explode( ":", $__path, 2 );
$__startPoint .= ":";
}
# From now processing is the same for WIndows and Unix, and hopefully for others.
$__realparts = array( );
$__parts = explode( _PL_OS_SEP, $__path );
for ( $i = 0; $i < count( $__parts ); $i++ ) {
if ( strlen( $__parts[ $i ] ) == 0 || $__parts[ $i ] == "." ) {
continue;
}
if ( $__parts[ $i ] == ".." ) {
if ( count( $__realparts ) > 0 ) {
array_pop( $__realparts );
}
}
else {
array_push( $__realparts, $__parts[ $i ] );
}
}
return $__startPoint . _PL_OS_SEP . implode( _PL_OS_SEP, $__realparts );
}
echo "getRealpath ../../x: ". getRealpath( "../../x" ) . "<BR>\n";
echo "getRealpath ./../x: ". getRealpath( "./../x" ) . "<BR>\n";
echo "getRealpath x: ". getRealpath( "x" ) . "<BR>\n";
echo "getRealpath /../x: ". getRealpath( "/../x" ) . "<BR>\n";
echo "getRealpath d:/../../x: ". getRealpath( "d:/../../x" ) . "<BR>\n";
echo "getRealpath ./../xx/xxx/.////../yyy: ". getRealpath( "./../xx/xxx/.////../yyy" ) . "<BR>\n";
?>
18-Jul-2002 03:15
note that realpath() will chop any trailing delimiter like \ or / ...don't forget to add it back on if you need it.
Eric Mueller - themepark.com
06-Jul-2001 12:38
realpath() seems to be equivalent to ASP's Server.MapPath. On my Win2k box I have successfully used realpath() to give me the full path for a file outside of the document_root. This will be very useful in conjunction with is_dir and/or is_file, which require a full path.
28-Aug-2000 09:03
mkdir (and realpath) did not work because i'd used virtual() function to replace server side include in my file.
And i've just seen that virtual() function changes the current directory ... that's why !
jerome ;)