Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

file_exists and paths that include relative paths ("/../")

When I use file_get_contents on a path like /a/path/to/a/../file.php, it gets the content just fine. If I call file_exists first (or is_file or realpath), the return values indicate that the file does not exist. What seems to be the issue?


Edit: Here is some additional information condensed from comments to answers:

  • I am running Mac OS X 10.9 with php 5.5.6, so safe mode should not be an issue (it was removed in version 5.4)
  • I tried clearing the file cash by calling clearstatcache(true, $dir1)
  • The file in question is 362 bytes in size, but I reproduced this issue with several different files in a medley of locations.
  • open_basedir is commented out in the php.ini
  • The file is local (the first file I tried was in the same directory as the script)
  • The issue exists in the command line (phpUnit) and in the browser.
  • The permissions on the file in questions are -rwxrwxrwx (I sudo-chmod-777ed the file)

This is a code snippet that creates the behavior:

$dir1 = '/a/path/to/a/../file.php';
$dir2 = '/a/path/to/file.php';

echo "File content dir1:\n";
echo file_get_contents($dir1);
echo "\ndir1 exists: ".(int)file_exists($dir1);

echo "\n\nFile content dir2:\n";
echo file_get_contents($dir2);
echo "\ndir2 exists: ".(int)file_exists($dir2);

the output is:

File content dir1:
The actual content of the file. I promise!

dir1 exists: 0

File content dir2:
The actual content of the file. I promise!

dir2 exists: 1
like image 452
DudeOnRock Avatar asked Feb 26 '14 21:02

DudeOnRock


2 Answers

It sounds like you have safe mode turned on and are attempting to access a file that PHP would consider unsafe when running in safe mode. From the manual:

Warning

This function returns FALSE for files inaccessible due to safe mode restrictions. However these files still can be included if they are located in safe_mode_include_dir.

EDIT: You can also reproduce this behavior if /a/path/to/a/ is not a real path. For example:

<?php

$dir1 = '/realDir/realDir2/filetoinclude.php';
echo "File content dir1:\n";
echo file_get_contents($dir1); // outputs file contents
echo "\ndir1 exists: ".(int)file_exists($dir1); // outputs 1

$dir2 = '/realDir/realDir2/realDir3/../filetoinclude.php';
echo "\n\nFile content dir2:\n";
echo file_get_contents($dir2); // outputs file contents
echo "\ndir2 exists: ".(int)file_exists($dir2); // outputs 1

$dir3 = '/realDir/realDir2/NotARealDirectory/../filetoinclude.php';
echo "\n\nFile content dir3:\n";
echo file_get_contents($dir3); // outputs file contents
echo "\ndir3 exists: ".(int)file_exists($dir3); // outputs 0

This is because file_exists needs to traverse the entire path, literally, so it looks for the missing directory and fails. I'm not sure exactly what file_get_contents does that is different, and I can't find much on Google, but it clearly does some parsing of the path that is different from what file_exists does.

like image 172
elixenide Avatar answered Nov 07 '22 16:11

elixenide


I am providing the workaround that I developed with a regex, if others have this same issue. I hate to be using this hack, and I still don't understand why I am having this issue, but hopefully someone will come up with an actual solution.

Before calling file_exists I now call this function:

function resolve($path) {
    $regex = "/(.?)(\/[^\/]*\/\.\.)(.*)/";
    $result = preg_replace($regex, "$1$3", $path);
    if ($result != $path) {
        $result = resolve($result);
    }
    return $result;
}
like image 35
DudeOnRock Avatar answered Nov 07 '22 15:11

DudeOnRock