0

I want to download a file from a folder above web root. Let's say I want to get this file:

/var/pdfs/test.pdf

My code is:

$name = "test.pdf";
$url = "/var/pdfs/" . $name;
if (file_exists($url)) {
    header("Content-Type: application/pdf");
    header("Content-Transfer-Encoding: Binary");
    header("Content-disposition: attachment; filename='" . basename($url) . "'");
    readfile($url);
} else {
    echo "Not found: $url";
}

The problem is that I always get the Not found: $url echo.

My file has read permissions:

thecrafter@vps136166:/var/pdfs$ ls -la
total 6484
drwxr--r--  2 thecrafter root          4096 Jul  7 00:19 .
drwxr-xr-x 13 root       root          4096 Jul  7 00:16 ..
-rw-r--r--  1 thecrafter thecrafter 6629018 Jul  7 00:18 test.pdf

and directory too:

thecrafter@vps136166:/var$ ls -la | grep pdfs
drwxr--r--  2 thecrafter root  4096 Jul  7 00:19 pdfs

Do I miss something? Where is the problem?

EDIT 1

Krzysztof Duszczyk noted that the problem is solved if I give execute rights to pdfs directory. Indeed I gave it 755 and it's working. Any ideas why that happens?

TheCrafter
  • 1,909
  • 2
  • 23
  • 44
  • Does pdfs folder have read rights? – Krzysztof Duszczyk Jul 06 '16 at 22:48
  • IMHO there is the same problem, check it. http://stackoverflow.com/a/17896538/6557808 – Sylogista Jul 06 '16 at 22:49
  • @KrzysztofDuszczyk Yes, I edited my question. – TheCrafter Jul 06 '16 at 22:51
  • 2
    Hmmm adding execute rights to pdfs folder helped when I reproduce the problem. Really don't know why :) – Krzysztof Duszczyk Jul 06 '16 at 22:56
  • @KrzysztofDuszczyk Wtf. You're right! It's working. Okay now I'm confused. – TheCrafter Jul 06 '16 at 23:56
  • I too wonder why that is. I find very little discussion of this aside from https://forums.opensuse.org/showthread.php/471819-Why-is-Linux-execute-bit-needed-for-PHP-s-file_exists()-function-to-work-properly – BeetleJuice Jul 07 '16 at 00:03
  • What OS are you using and is SELinux enabled? I replicated your situation and `file_exists` returns true for a file with permissions of 644 in a directory with permissions of 755. Under the hood, on Linux, PHP just calls the function [`access()`](http://pubs.opengroup.org/onlinepubs/009695399/functions/access.html) to determine file accessibility, so something in the OS is saying no access to the file unless execute is also set. – drew010 Jul 07 '16 at 00:53
  • @drew010 I'm running `Debian 8.5` and no, I don't have SELinux. – TheCrafter Jul 07 '16 at 06:00
  • ubuntu 14.04 here. When permissions are 644 for file and 755 for folder it works the problem is when folder has 644 permissions. – Krzysztof Duszczyk Jul 07 '16 at 07:34
  • 1
    See bullet point 3 on [this answer](http://unix.stackexchange.com/a/21252/88823). Basically the execute but on the directory is what allows the user to enter the directory and list / read contents. – drew010 Jul 07 '16 at 15:25
  • @drew010 Whoa, interesting! I did not know that thanks! Could you please, write it in an answer so I can accept it? – TheCrafter Jul 07 '16 at 21:40

1 Answers1

1

The Problem (not in this case, but I leave it to help others)

If you know that the file actually exists on disk, your file_exists() is likely blocked by security measures meant to protect files within /var/pdfs/ from unauthorized access. This is from the docs on file_exists:

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.

The solution (again not in this case, but could help similar symptoms)

You want to add the directory to safe_mode_include_dir setting. Find the setting in your php.ini, uncomment it, and add the directory:

safe_mode_include_dir = /var/pdfs/

If this still does not work, you may also need to add the directory to your PHP include path, either by setting the include_path in php.ini, or at the top of your script, with the line below:

$path = '/var/pdfs'
set_include_path(get_include_path() . PATH_SEPARATOR . $path);

Note that PHP removed the safe mode settings with version 5.4, so if you have a recent PHP installation, your problem is likely elsewhere.

BeetleJuice
  • 39,516
  • 19
  • 105
  • 165
  • I tried adding `ini_set("safe_mode_include_dir", "/var/pdfs/");` on top of my php script. I didn't work so I tried your second solution but it didn't work either. – TheCrafter Jul 06 '16 at 23:39
  • I think you misunderstood. I didn't suggest you use `ini_set` to change safe_mode_include_dir. I suggested you make the change in `php.ini` directly. The 2nd suggestion is in addition to the first (not an alternative), so either way you need to first change `php.ini`. – BeetleJuice Jul 06 '16 at 23:41
  • Okay. I opened my `/etc/php5/apache2/php.ini` but there is no `safe_mode_include_dir` option. Not even commented out. Should I just append it? – TheCrafter Jul 06 '16 at 23:48
  • First you need to know that it is the `php.ini` actually loaded when PHP starts (lots of discussion on how elsewhere) because there are often several copies of this file. Once you have the right file, yes add the setting if it's not there. You'll then have to restart Apache for the changes to take effect. By the way what version of PHP do you run? – BeetleJuice Jul 06 '16 at 23:50
  • I'm running `PHP 5.6.20-0+deb8u1`. I appended it to php.ini but still, nothing. – TheCrafter Jul 06 '16 at 23:54
  • Apparently, when I add execute rights to pdfs folder (credits to Krzysztof Duszczyk) it's working. Any ideas why? – TheCrafter Jul 06 '16 at 23:56
  • Ok. PHP 5.6.20 doesn't use `safe mode` anymore (removed since 5.4). You can undo your edit. Something else may be going on. – BeetleJuice Jul 06 '16 at 23:56