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
Common MIME types
HTTP, caching and other stuff

Related posts :

111 Responses to “How To Force File Download With PHP”

  1. just another addict says:

    Thanks very much for this. You are a genius!!!

  2. [...] How To Force File Download With PHP | W-Shadow.com header( 'Pragma: private' ); [...]

  3. [...] 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 [...]

  4. hariharan says:

    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….

  5. twakspot says:

    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.

  6. Scotty says:

    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

  7. Jānis Elsts says:

    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.

  8. Often you will uncover Joann Fabric coupons
    suitable at the front end sign-up. Making the most of all these discount coupons
    could make you save hundreds or even thousands of dollars.
    Shop for your shoes and enter the discount code during checkout.

  9. Serwery www says:

    Hiya, You might have conducted a fantastic job. We’ll certainly bing this and in my estimation advocate to my local freinds. I believe they’ll be took advantage of this fabulous website.

  10. Nark Kims says:

    Hi there, I have seen that sometimes this webpage renders an 404 server error. I figured that you would be keen to know. Thanks

  11. Daidai says:

    TNX

Leave a Reply