views:

572

answers:

3

I am passing a filename to a download page.
ie somefile.xls

The download page adds back in the full directory path onto the filename.
ie c:\temp\somefile.xls

The problem is that now setting the 'Content-Disposition' of the header doesn't work. The filename it wants to download is the full directory-filename path. ie c_temp_somefile

Can the Content-Disposition handle a full path?

If it can how do I get my script to properly download the file?

Code is:

$myad = $_GET['myad'];
$glob_string =  realpath('/foldera/folderb/folderc'). DIRECTORY_SEPARATOR .$myad;

header('Content-Type: application/excel');
$headerstring = 'Content-Disposition: attachment; filename='.$glob_string;
header($headerstring);
readfile($myad);

UPDATED code (from answers):

$myad = $_GET['myad'];
$glob_string =  realpath('/mit/mit_tm/mrl_bol'). DIRECTORY_SEPARATOR .$myad;

header('Content-Type: application/excel');
$headerstring = 'Content-Disposition: attachment; filename='.$myad;
header($headerstring);
readfile($glob_string);
+2  A: 

Never ever. If a browser accepts full paths it is time to file a bug, quickly: this would be a major security hole.

ntd
+1  A: 

Don't pass the full path via the header string, but use the base name ($myad) instead.

You should really use a better validation for $_GET['myad'], since your script will pass arbitrary paths to the user (readfile() gets the unfiltered user input). This is a security hole!

Calculate the real path using realpath, make sure that the file is within a allowed folder, then use basename() on the full path to get the plain file name. Pass this substring via the Content-Disposition header, but use the real path for readfile().


UPDATE: Your updated code still contains a security hole. If $_GET['myad'] contained ../../../some/full/path, your script would happily send any requested readable file to the client.

You should use something along the lines of the following snippet:

$myad = $_GET['myad'];

$rootDir = realpath('/mit/mit_tm/mrl_bol');
$fullPath = realpath($rootDir . '/' . $myad);

// Note that, on UNIX systems, realpath() will return false if a path
// does not exist, but an absolute non-existing path on Windows.
if ($fullPath && is_readable($fullPath) && dirname($fullPath) === $rootDir) {
    // OK, the requested file exists and is in the allowed root directory.
    header('Content-Type: application/excel');
    // basename() returns just the file name.
    header('Content-Disposition: attachment; filename=' . basename($fullPath));
    readfile($fullPath);
}
Ferdinand Beyer
Why was this downvoted? It sure looks like it is indeed a significant security hole.
qid
Ferdinand is right, that's a big security hole: a malicious user could download almost anything the web server can access (e.g. database passwords). About the Content-Disposition header, however, even with a valid path, the browser will still ignore it.
Patonza
Thanks - I will update my question with the updated code.
John M
+5  A: 

You can put almost everything you want in the Content-Disposition header but most browsers, for security reasons, will ignore or replace paths and convert them to a valid filename for the operating system they're running on.

Content-Disposition is only a hint to the browser, it is not mandatory for the web client to respect this setting.

So, no, you can't force the download to a specific directory on the client computer.

Patonza
Yep. In fact, the HTTP 1.1 spec specifically excludes directory path information from the Content-disposition header: http://www.w3.org/Protocols/rfc2616/rfc2616-sec19.html#sec19.5.1
Jordan
+1 - thanks. makes sense - my code is for a intranet site so the security hole doesn't mattery too much.
John M
Even if it's for an intranet, i'd follow Ferdinand advice: it's far better to avoid holes than to patch them later :)
Patonza