Terminally Incoherent

Utterly random, incoherent and disjointed rants and ramblings...

Monday, November 07, 2005

Force File Download in PHP

I'm writing a secure file pickup web-app for my company. The basic idea behind this is to allow people to go on the website, log in, provide authentication and download a confidential file through an SSL connection. Authentication, and authorization are easy. I'm planning to use username+pwd scheme combined with a expiring download ticket that can be optionally bound to an IP. In other words, we will allow a client to download some file from given IP address once within some time period, after authenticating with the username and pwd.

Easy peasy - I just need to crank out the database and some kind of user interface to allow people to set up tickets and add users. My main concern was downloading the files. I wanted to serve these files to users from a website without revealing their location in the file system. Furthermore I do not want to keep these files anywhere in the web server directory tree. I want it to be store as far away from the webserver as possible. Ideally on a mounted remote file system.

To accomplish this, I would need to use a script read the file dump the bytestream into the client browser hoping to trigger file download action. PHP is great for this. All you need to do is sent few headers and then just dump out the file... That is if you are using a real browser. If you are using IE, the task is more difficult.

After fiddling with headers for few hours and trying every single possible header combo found on the net, I came up with this script which works both on IE and Firefox (note the server at work is a win box - I didn't make that choice, but that's what I'm stuck with):



// the name of the file as you want it to appear
// in the dialog
$name = "somefile.doc";

// fully qualified path
$filepath = "C:\\test\\somefile.doc";

// I know these headers look crazy - they are
// but this is the only way I could force IE to download the
// file uppon clicking the button

header("Pragma: public"); header("Expires: 0");
header('Cache-Control: no-store, no-cache, must-revalidate');
header('Cache-Control: pre-check=0, post-check=0, max-age=0'
, false);
header('Last-Modified: '.gmdate('D, d M Y H:i:s') . ' GMT');

// get the browser info
$browser = $_SERVER['HTTP_USER_AGENT'];

if (preg_match('/MSIE 5.5/', $browser) || preg_match('/MSIE 6.0/',
$browser))
{
header('Pragma: private');
header('Cache-control: private, must-revalidate');
header("Content-Length: ".filesize($filepath));
header('Content-Type: application/x-download');
header('Content-Disposition: attachment; filename="'.$name.'"');
}
else
{
header("Content-Length: ".(string)(filesize($filepath)));
header('Content-Type: application/x-download');
header("Content-Disposition: attachment; filename=".$name."");
}

header('Content-Transfer-Encoding: binary');

if ($file = fopen($filepath, 'rb'))
{
while(!feof($file) and (connection_status()==0))
{
print(fread($file, 1024*8));
flush();
//usleep(100000); // can be used to limit download speed
}

fclose($file);
}


Phew... These headers are crazy! I hope someone will find this useful :)

0 Comments:

Post a Comment

<< Home