5

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
DudeOnRock
  • 3,685
  • 3
  • 27
  • 58
  • 5
    can you post the specific code you're comparing here? `file_exists()` should handle paths like that just fine. – scrowler Feb 26 '14 at 21:46
  • 1
    I am unable to reproduce this behavior – I tried both with a relative path and an absolute path both including '..' – kojiro Feb 26 '14 at 21:46
  • @ whoever voted to close for "not clear what you are asking," please specify what you find unclear, I would love to address your concern. – DudeOnRock Feb 26 '14 at 22:08

2 Answers2

5

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.

Community
  • 1
  • 1
elixenide
  • 44,308
  • 16
  • 74
  • 100
  • Hmm... Do you have open_basedir set in php.ini? – elixenide Feb 26 '14 at 21:55
  • Yeah, leave open_basedir turned off -- it would potentially interfere with your code, which is why I asked. – elixenide Feb 26 '14 at 22:12
  • The results of file_exists are cached. Have you tried just clearing the cache? http://us1.php.net/manual/en/function.clearstatcache.php – elixenide Feb 26 '14 at 22:13
  • The only other theories I have are file size or permissions problems. How big is the file? Sometimes that can cause a failure because of how *nix `stat` works. – elixenide Feb 26 '14 at 22:22
  • And this is a local file, not a remote file or symlink, but a real file? – elixenide Feb 26 '14 at 22:32
  • Much appreciated; I'll keep thinking because this bothers me. – elixenide Feb 26 '14 at 22:45
  • Okay, I was able to reproduce this, and I'm 99% sure I've got it. Does the full path `/a/path/to/a/` actually exist? For example, if I have a file `/foo/bar/file.php` and a directory `/foo/bar/woot/`, then file_exists works with `/foo/bar/woot/../file.php`, but not with `/foo/bar/someDirectoryThatIsntReal/../file.php`. – elixenide Feb 27 '14 at 00:33
1

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;
}
DudeOnRock
  • 3,685
  • 3
  • 27
  • 58
  • 1
    `realpath` does not work with non-existing files within the relative path on Unix-like systems. In opposite to Windows, each file within the path must exist. – Gumbo Feb 27 '14 at 08:26
  • You might be misunderstanding the problem I am having. The file exists. A relative path, like the one in my original post, does not get recognized by `realpaht`, `is_file` or `file_exists`. `file_get_contents` successfully retrieves the content though. – DudeOnRock Feb 27 '14 at 08:29
  • 1
    In that case you should at least use a proven algorithm like the [*remove dot segments* algorithm used for URIs](http://stackoverflow.com/a/21424232/53114). – Gumbo Feb 27 '14 at 08:35