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');
[/sourcecode]
Related info
Determining MIME type of a file automatically
Common MIME types
HTTP, caching and other stuff
Thanks very much for this. You are a genius!!!
[…] How To Force File Download With PHP | W-Shadow.com header( 'Pragma: private' ); […]
[…] header( 'Pragma: private' ); header( "Expires: Mon, 26 Jul 1997 05:00:00 GMT" ); header( "Cache-control: private" ); How To Force File Download With PHP | W-Shadow.com […]
Really Great … I have been searching nearly for a day to use ob_start with file download.. either one works but not both. At Last I found it from you to use @ob_end_clean() .. Which solved my problem….
Thanks once again….
after trying the 11th script without success, finally found this and how sweet it is! thank you so much. this is SOLID! all browsers happy.
Am trying to use this as it is entirely. Just modified the example use code to suit my needs like so
set_time_limit(0);
$file_path=’assets/mici_blossom.pdf’;
output_file($file_path, ‘mici_blossom.pdf’, ‘application/pdf’);
Keep getting this error.
C:\DOCUME~1\Scott\LOCALS~1\Temp\+6U2ePvN.pdf.part could not be saved, because the source file could not be read.
Try again later, or contact the server administrator.
The file is there and named correctly. It is a 25mb pdf. Can anyone tell me why? Or what am I missing?
Thanks
Scott
Your code looks fine to me. Maybe you could try watching the download with a HTTP debugger like Fiddler to see if there’s anything wrong with it.
Hi there, I have seen that sometimes this webpage renders an 404 server error. I figured that you would be keen to know. Thanks
TNX
I have rewritten your function and created a class, which can be used with composer. I have linked to your source. Anyone interested in this can find it on github: https://github.com/diversen/http-send-file
[…] I followed this guide here to build a more robust process for file downloads (http://w-shadow.com/blog/2007/08/12/how-to-force-file-download-with-php/), but there is a significant delay between when the script is executed and when the browser’s […]
Thank you! This is exactly what I was looking for!
Cool, Finally found what I was looking for. I’ve started new system on my website which require PHP File download and you give exactly what I wanted. Thanks a lot.
[…] http://w-shadow.com/blog/2007/08/12/how-to-force-file-download-with-php/ […]
Awesome blog. Thanks for sharing the great article with us.
This is exactly what I needed right now, as I am looking forward to implement the download option on my website as well! Thanks for sharing this info.
I like ur articles basically the way u present it Its very interesting n useful
Wow! Very helpful information I love your article and also your writing skill. Thanks for sharing these type of helpful information.
Thanks again
we can also try the “inspect” method to download file directly.
Your article is very interesting and meaningful