How To Force File Download With PHP
Say you want a PHP script that will make the browser to download a file instead of opening it. This is useful for common filetypes that would normally be displayed in a browser, like .html, images, PDFs and .doc files.
You can find a lot of scripts that do this on the web but one thing most of them don’t handle is download resuming and multi-thread downloading. If you don’t need that feature, feel free to use a simpler script. Personally I’ve found that a function that handles download resuming works more reliably across various browsers (what actually happened : a simpler script didn’t work with Opera + my weird Internet connection, so I found another script and decided to make broad generalizations about download resuming :p).
Note - most of the code below isn’t mine. I found it somewhere on the web and adjusted it for my needs; unfortunately I’ve lost the URL of the original page. If you know where it came from, let me know and I’ll add a link to this post.
function output_file($file, $name, $mime_type='') { /* This function takes a path to a file to output ($file), the filename that the browser will see ($name) and the MIME type of the file ($mime_type, optional). If you want to do something on download abort/finish, register_shutdown_function('function_name'); */ if(!is_readable($file)) die('File not found or inaccessible!'); $size = filesize($file); $name = rawurldecode($name); /* Figure out the MIME type (if not specified) */ $known_mime_types=array( "pdf" => "application/pdf", "txt" => "text/plain", "html" => "text/html", "htm" => "text/html", "exe" => "application/octet-stream", "zip" => "application/zip", "doc" => "application/msword", "xls" => "application/vnd.ms-excel", "ppt" => "application/vnd.ms-powerpoint", "gif" => "image/gif", "png" => "image/png", "jpeg"=> "image/jpg", "jpg" => "image/jpg", "php" => "text/plain" ); if($mime_type==''){ $file_extension = strtolower(substr(strrchr($file,"."),1)); if(array_key_exists($file_extension, $known_mime_types)){ $mime_type=$known_mime_types[$file_extension]; } else { $mime_type="application/force-download"; }; }; @ob_end_clean(); //turn off output buffering to decrease cpu usage // required for IE, otherwise Content-Disposition may be ignored if(ini_get('zlib.output_compression')) ini_set('zlib.output_compression', 'Off'); header('Content-Type: ' . $mime_type); header('Content-Disposition: attachment; filename="'.$name.'"'); header("Content-Transfer-Encoding: binary"); header('Accept-Ranges: bytes'); /* The three lines below basically make the download non-cacheable */ header("Cache-control: private"); header('Pragma: private'); header("Expires: Mon, 26 Jul 1997 05:00:00 GMT"); // multipart-download and download resuming support if(isset($_SERVER['HTTP_RANGE'])) { list($a, $range) = explode("=",$_SERVER['HTTP_RANGE'],2); list($range) = explode(",",$range,2); list($range, $range_end) = explode("-", $range); $range=intval($range); if(!$range_end) { $range_end=$size-1; } else { $range_end=intval($range_end); } $new_length = $range_end-$range+1; header("HTTP/1.1 206 Partial Content"); header("Content-Length: $new_length"); header("Content-Range: bytes $range-$range_end/$size"); } else { $new_length=$size; header("Content-Length: ".$size); } /* output the file itself */ $chunksize = 1*(1024*1024); //you may want to change this $bytes_send = 0; if ($file = fopen($file, 'r')) { if(isset($_SERVER['HTTP_RANGE'])) fseek($file, $range); while(!feof($file) && (!connection_aborted()) && ($bytes_send<$new_length) ) { $buffer = fread($file, $chunksize); print($buffer); //echo($buffer); // is also possible flush(); $bytes_send += strlen($buffer); } fclose($file); } else die('Error - can not open file.'); die(); } /********************************************* Example of use **********************************************/ /* Make sure script execution doesn't time out. Set maximum execution time in seconds (0 means no limit). */ set_time_limit(0); $file_path='that_one_file.txt'; output_file($file_path, 'some file.txt', 'text/plain');
Related info
Determining MIME type of a file automatically
More on determining the MIME type
Common MIME types
HTTP, caching and other stuff
March 6th, 2008 at 10:53 pm
OOPS! If anyone has a solution for the above problem, please email me at mrd3sai@gmail.com (Thanks)
March 6th, 2008 at 10:52 pm
For some reason it is not working for me. I have these attributes assigned in my downloadfile.php:
filename: test.12.docfiletype: application/msword
filepath: /files/15.attach
I call it using the function that is in this article using the following:
output_file($filepath, $filename, $filetype);However, I get this error:
File not found or inaccessible!February 22nd, 2008 at 9:26 am
hi
the code for download works fine for me…
thank you very much…
February 2nd, 2008 at 12:58 am
Thanks so much for this script! I was looking for a way to force the download of a vcard off my website and this did the trick!
January 25th, 2008 at 2:30 pm
I don’t have a complete solution, but here are some ideas :
* Use a HTTP header viewer and look what headers are sent by other websites that display the filename correctly. For example, if you have FireFox, you could use the LiveHTTPHeaders addon for this.
* When you set charset=whatever, are you sure you’re using the same charset in your PHP file? Maybe it’s actually Unicode?
* See this page - HTTP headers and non-ASCI characters
January 25th, 2008 at 6:24 am
Thank you for your script! I tried a few others, and no matter what I did, powerpoint files were coming through with a filesize of zero bytes. Using this script however, I am able to get the file to come through properly.
I do have one problem though. I am programming my site for Japanese (I work in a Japanese company), and when I call the function with $name using Japanese text, it comes through garbled. I have tried using header(’Accept-Charset: Shift_JIS’) and header(’Content-Type: ‘ . $mime_type ‘; charset=Shift_JIS’); and a combination of both, but they don’t seem to work. I have also tried using the other two types of Japanese encoding, and they dont seem to work either. Any ideas?
December 20th, 2007 at 10:30 pm
Strange indeed. Oh well
December 20th, 2007 at 8:36 pm
Strange. I’m still getting “unknown file size” with Firefox or IE7. Thanks for your help!
(Sorry for the late response. Forgot to check back here.)
December 13th, 2007 at 7:42 pm
The total size does appear for me when using Firefox, so I don’t know about that. However, your question prompted me to review the code and I found a (non-critical) bug, which I have now fixed (I think). You can try the modified function and see if that helps any.
December 13th, 2007 at 5:25 pm
Thanks for the function! One issue:
While the browser (Firefox) is downloading a file outputted by this function, it does not appear to know the total size of the file. So there is no download progress and the download status reads something like “downloaded 3.5MB of unknown”. Is that something we can fix?