I don’t think I have posted this snipped of code here yet. It is old as hell, but I use it all over the place lately so I figured I just post it here for future reference.
The script below solves the age old problem of making bunch of files accessible to your users without dumping them in a publicly accessible directory (files, not users). For example, you have bunch of PDF documents you don’t want to be indexed by Google, or directly linked to from other websites. What you want is to allow certain users to download them after logging into your web app and checking their cridentials. How do you do that?
Well, the simplest thing to do is to put them in some directory outside of your web root. Set up .htaccess (or whatever you use) to disallow any connection to that folder from the outside world. The only machine with access to these files should be localhost. Then you do some PHP magic in your application to stream the file into the browser upon successful authentication.
The streaming part is actually pretty straightforward – it is a simple combination of the print and fread commands. The convoluted part is convincing your browser to actually initiate file download instead of just dumping ASCII gibberish onto the page for binary files. This is accomplished by sending bunch of headers to the browser prior to streaming the files. The headers are different for IE and the rest of the world but this is pretty much what I ended up with after a lot of trial and error:
// change these to whatever is appropriate in your code
$my_place = "/path/to/the/file/"; // directory of your file
$my_file = "filename.ext"; // your file
$my_path = $my_place.$my_file;
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');
$browser = $_SERVER['HTTP_USER_AGENT'];
if(preg_match('/MSIE 5.5/', $browser) || preg_match('/MSIE 6.0/', $browser))
{
header('Pragma: private');
// the c in control is lowercase, didnt work for me with uppercase
header('Cache-control: private, must-revalidate');
// MUST be a number for IE
header("Content-Length: ".filesize($my_path));
header('Content-Type: application/x-download');
header('Content-Disposition: attachment; filename="'.$my_file.'"');
}
else
{
header("Content-Length: ".(string)(filesize($my_path)));
header('Content-Type: application/x-download');
header('Content-Disposition: attachment; filename="'.$my_file.'"');
}
header('Content-Transfer-Encoding: binary');
if ($file = fopen($my_path, 'rb'))
{
while(!feof($file) and (connection_status()==0))
{
print(fread($file, filesize($my_path));
flush();
}
fclose($file);
}
I found these headers to work for me. Your millage may vary. I tested the script above in IE 6 and 7, Firefox 2.x and 3.x, Konqueror 3.5.8 and Chrome 0.2.149.30 and experienced no problems. As usual, I’m always open to constructive criticism and better solutions in the comments. Let me know what you think!
Thanks for the header information.
I just thought I would mention the fpassthru function it cant just make the code easier to read having a single built in function that outputs the file.
@drubin: I didn’t think about that. Good catch! Thanks for the input.
Thanks you too much, this is very good and downloading time hiding file size and content headers. Very nice.
It not works in IE6 (I think that only in IE6 SP3 version), as every solution I’ve found on the Internet. It falls, when I want to OPEN the PDF file (not save – then it works). The Adobe Reader notify about error, that it can’t find the file. I haven’t found any working solution for this problem, yet.
Thanks for any tips to solve this.
Thanks.
But these script only works through HTTP. As it should be modified, that he would work through HTTPS?