PHP Force Download – Keep Track of What’s Going Down

By  on  

A force-download script can give you more control over a file download than you would have providing a direct link. Using a force-download script, you can:

  • Validate that a person is logged in
  • Increment a counter in a text file
  • Connect to your database and log IP information, increment a counter, and so on.

The code itself is pretty basic:

The Code

// grab the requested file's name
$file_name = $_GET['file'];

// make sure it's a file before doing anything!
if(is_file($file_name)) {

	/*
		Do any processing you'd like here:
		1.  Increment a counter
		2.  Do something with the DB
		3.  Check user permissions
		4.  Anything you want!
	*/

	// required for IE
	if(ini_get('zlib.output_compression')) { ini_set('zlib.output_compression', 'Off');	}

	// get the file mime type using the file extension
	switch(strtolower(substr(strrchr($file_name, '.'), 1))) {
		case 'pdf': $mime = 'application/pdf'; break;
		case 'zip': $mime = 'application/zip'; break;
		case 'jpeg':
		case 'jpg': $mime = 'image/jpg'; break;
		default: $mime = 'application/force-download';
	}
	header('Pragma: public'); 	// required
	header('Expires: 0');		// no cache
	header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
	header('Last-Modified: '.gmdate ('D, d M Y H:i:s', filemtime ($file_name)).' GMT');
	header('Cache-Control: private',false);
	header('Content-Type: '.$mime);
	header('Content-Disposition: attachment; filename="'.basename($file_name).'"');
	header('Content-Transfer-Encoding: binary');
	header('Content-Length: '.filesize($file_name));	// provide file size
	header('Connection: close');
	readfile($file_name);		// push it out
	exit();

}

This file alone isn't secure. You will want to validate that the file doesn't provide access to your website code, files you don't want downloaded, and so on. That code will be specific to your website and needs.

Do you use a force-download script? What processing do you do inside the script?

Recent Features

  • By
    Conquering Impostor Syndrome

    Two years ago I documented my struggles with Imposter Syndrome and the response was immense.  I received messages of support and commiseration from new web developers, veteran engineers, and even persons of all experience levels in other professions.  I've even caught myself reading the post...

  • By
    Vibration API

    Many of the new APIs provided to us by browser vendors are more targeted toward the mobile user than the desktop user.  One of those simple APIs the Vibration API.  The Vibration API allows developers to direct the device, using JavaScript, to vibrate in...

Incredible Demos

  • By
    Web Audio API

    The Web Audio API allows developers to load and decode audio on demand using JavaScript.  The more I evaluate awesome games for Firefox OS TVs, the more I get to learn about these APIs that I normally wouldn't touch.  The following is a very basic introduction to the WebAudio API...

  • By
    HTML5’s window.postMessage API

    One of the little known HTML5 APIs is the window.postMessage API.  window.postMessage allows for sending data messages between two windows/frames across domains.  Essentially window.postMessage acts as cross-domain AJAX without the server shims. Let's take a look at how window.postMessage works and how you...

Discussion

  1. Hey there! I have a problem with the force-download script. When I click the link to download the file, the file DOES download, but i cant navigate on any of the other pages of my site, while the download is in progress. (only when i stop downloading, does the site work again)..

    any advice,

    thanks in advance,

    benji

    • this has to do with your session. you need to make sure a session doesn’t get started on the same page as your force download script, let alone anything that might take a long time to access, ie. streaming content. this is an old comment, so you might have figured this out by now, but that’s the solution.

    • I am having the exact same issue as Ben. I was hoping if he figured it out you or he could share the knowledge. Isn’t it necessary to start the session on the page to make the download take effect if you’re trying to enforce user permission based downloads?

  2. Odd problem Ben. What browser & version are you using? This sounds more like a browser problem than a problem with the script.

  3. Ben

    hey David :)

    I’ve tried in Internet Explorer AND in firefox! The only possible reason I can think of is that I have 2 hosts, one for my multimedia & one for my site.. Media-files & php pages (&db) are not on the same host, so that’s already a reason I can’t get the download size of the file, so maybe that’s a reason?

    I really have no clue.. I can email you the exact script i’m using if you like..

    thanks for your time :)

  4. Hi there, that’s a good tutorial. However. I have a problem.
    The problem is I can download the file, but its file size is 0. The real one is about 506891 bytes.
    I tried to google, but couldn’t find the solution yet.

    Could you suggest any solutions?
    Thanks in advance.

  5. Hi david,

    I found the solution by myself. The reason is that I didn’t give the read permission to the user apache (Others) which is not the file owner or the group of the file owner.

    Thanks!

    • dumto

      how did u give the read permission to the user apache

  6. This file is not Secure attacker may exploit this like : http://yourdomain.com/download.php?file_name=index.php

    • Truls

      You know that this script was made as an example usage only? Obviously you would have to implement your own security to it.

  7. Franz

    Hi,

    thanks for the nice script. A perhaps dull comment:
    seems to me that you never use the mime type you determine at the beginning of
    your script. Isn’t one supposed to use that piece of information in the ‘content-type’ header?

  8. @Franz: I’ve updated this to included the mime type. I had a default setting in there.

  9. sumanchalki

    hi, its a great script no doubt. i want a bit further. can u improve the code to get download start time and end time so that i can control user’s simultaneous download as in website like rapidshare etc?

  10. I have a force download script that looks exactly like yours but does not work properly – the only thing that works is a plain text file. pdfs and docs present an error messages. The files (documents) are stored in mysql as mediumblobs. PHP mysqli query to pull them out….Just to test different issues – I created a simple “email that to me” php script which executes the same query and attaches the doc to the email (using phpmailer) and send it to you as an attachment (base64). You can successfully open up the desired word doc or pdf. However when you try to force download with the same query and the same file (without writing to a temp file) it does not work. Is this an encoding issue? Googling is how I landed on this page…

    $sql = "SELECT form_name,form_size,form_type,file_content FROM emp.reference_materials WHERE form_name='$fn'";
    
    list($name, $size, $type, $data) = mysqli_fetch_row($result);
    
    // (I stripslashes)
    
    header("Content-type:  application/vnd.ms-word");
    header("Content-length: $size");
    header("Content-Disposition: attachment; filename=$name");
    header("Content-transfer-encoding: binary");
    echo $data;
    

    **I’ve tried encoding: binary and base64

  11. Hi,

    I don’t know if it’s possible, but Is there a way to add Google Analytics script at the end of the script ? I think no, due to the header already present, that will not allow that, but maybe there is a workaround…

  12. How do I implement this script.
    I am a newbie to php and need to provide some download buttons on a website for artwork without zipping them.

    I have a php file that has a text link to the file( i want users to download) already, but I am unsure how to relate this link tot he code above.
    Any help would be great.

  13. Ricardo

    You saved my day… only for that instruction of “required of IE”, i was driving nuts trying to discover why i was having a problem with IE (well, that’s already normal :P).

    Thank you

  14. Alexandru Ionescu

    Thanks, great script!

  15. Anonymice

    Hi David,
    I would like to know why you written so many types of headers in your code? Your code could had been simply shortened to –

    function forceDownload($file_name){
    $mime = 'application/force-download';
    header('Content-Type: '.$mime);
    header('Content-Disposition: attachment; filename="'.basename($file_name).'"');
    readfile($file_name);
    }
    

    The headers apart from content-type and content-disposition as in this case are just like those Handle With Care messages(for browsers) over a carrier. :)

    But if I am wrong please rectify me and also let me know the various uses of the different headers you have used.

    Thanks

  16. JP

    @Bec0de: Have you tried it?

    To be honest with you, I always hardcode the file name instead of using $_GET.

    I feel it’s more secure

  17. Ish

    Well handy, thanks for that.

    to those worried about security, no one in their right mind would use it as is!

    The $_GET[‘file’] is just to give you an idea of how it works.

    Personally mine is being given a key to a db table that contains the filename needed.

  18. Bec0de

    @JP: Its obvious from the code.

  19. anielka

    @Bec0de:
    David says:
    This file alone isn’t secure. You will want to validate that the file doesn’t provide access to your website code, files you don’t want downloaded, and so on. That code will be specific to your website and needs.

    @Anonymice
    you are wrong, headers says to browser many usable things (not to cache,length of file to calculate time elapse, and may other), many browsers can connect mime type of downloaded file with default action for that type (for ex: jpeg -> open with photoshop; pdf -> save to “myEbooks” folder). if you use force-download them some of headers aren’t necessary, but browser always will ask where to download file (even if you define “default action” for downloaded file type). generally RTFphpM ;) no offence

    nice job DW, thx

    sorry for my poor English.

  20. Bec0de

    @anielka: I know, I didn’t meant me, There are many people that will just copy the code and start using it on their website…

  21. anielka

    @Bec0de: i suppose, that those peoples don’t read comments under the code ;) and of course that’s only their problem. IMHO in good tone is try to understand “foreign” code before implementing it anywhere to own project. even if this project probably never leave “home-developer” localhost.
    and again: i’m sorry if my English isn’t understandable ;)

  22. anielka

    @subh: can you show us js function that initiates downloading? I guess that it waits for onComplete or something like that.

  23. Subh

    @anielka : thanks for your attention :). there is no js code for downloading. it is in php, similar to the code above.

    below this is the html where i call the php function
    Download

    the code inside the function is same as the code above.

    i am using a different server for storing the files, so i am downloading the files from server2 while the code is in server1.

    Is that creating a problem while sending ajax requests to server1??

  24. anielka

    simplest way to fix this problem is setting target="_blank" to the download link. but if you are maniac of good validation i suggest to use simple js code to do that:

    $$('a[rel="download"]').addEvent('click',function(e){
    e.stop();
    window.open(this.get('href'));
    });
    

    just give your download link rel="download". you can of course use another selector if you wanna ;D

    whole problem isn’t problem with your js or php script. browser just try to open downloaded document in the same window.

  25. anielka

    if you create links via js you can just give them attribute target – validation of code do not check content generated via js. in that case you can use this code:

    $$('a[rel="download"]').set('target','_blank');
    

    or it variation.

  26. Subh

    @anielka: thanks for your suggestion. i have already tried using target=”_blank” and the js code but didn’t get any positive result.

    all the ajax calls simply halts until the download is completed, as soon as file is downloaded, i get all the responses. my site is completely ajax based, hence navigation is through ajax rather than traditional page load.

    so basically my site is seized until the whole file is downloaded.

    any idea about the dual servers being the origin of this issue?

  27. anielka

    you can try to use curl, read_file or something like that and connect to server 2 by php. in this case ajax request is being sent to server1. of course it’s only theory based on assumption that problematic lays in connecting to foregin server.
    and agan:foregive me my poor english

  28. anielka

    small mistake: fopen intead of readfile :D

  29. gerchomskie

    Geniooooooooo… Sos un capo, che.

  30. Jove Sharma

    Hi everyone.
    Thanks for the great implementation.

    Well, someone from you can tell me the thing that as we have added the additional content file names and date/times using headers like:

    header(“Content-Disposition: attachment; filename=$name”);

    …the same can we send headers with any mp3 file with updated tags and their custom album art photos?

  31. Jove if you mean ID3 tags written IN mp3 file you should interest with id3_get_tag and id3_set_tag functions. header function sends only “identification of content” to the browser, so if your script offer to download mp3 file you can use it to identify file, but to edit it content you need to find another way. (i suggest to read this http://php.net/manual/en/ref.id3.php)

  32. This is what I did…

    To call it, all you need to do is this:

    Download

    WARNING: The tally system I use is for a site that has very few downloads. If you have a site where you have many downloads at a time, the tally will not work properly, as it reads the file then writes to it, so for just split second, it assumes that the file has not changed. Write in what you want.

  33. sam

    A little tip :
    if you have big file you’ll get “Allowed memory size” issue by using readfile it’s better to use fread and to flush memory while reading like this.

    $fd = fopen($myFile, "r");
    while(!feof($fd)) {
          echo fread($fd, 4096);
          ob_flush();
    }
    

    regards.

  34. moses

    I´ve discovered the same strange ajax-problem while using a force-download php script. I´m using sammy.js for navigation and it is broken after a download. I can’t find a solution on this, and I simply don`t understand the above described solutions… sorry.
    But I would be very happy if someone could point me in the right direction.

    As a small hint, I’ve found a very simple solution for securing force-downloads:
    Take a look at this simple string encryption:
    http://www.phpbuilder.com/board/showthread.php?t=10326721

    with a call to the force-download-script like this:

    $enc_filepath = urlencode(convert($filepath, $key));
    Click to Download
    

    and the following decryption in the force-download-script:

    $filepath = urldecode(convert($_REQUEST["filepath"], $key);

    … no one can simply enter a url to download some of your sourcefiles…

    Greetings,

    – mo –

  35. moses

    oh damn… i wrapped my code in -tags, so there's a link, which isn't a link... but should be displayed like ...

    sorry

  36. moses

    grrrrrrr…… your comment-section needs some replacing of html-tags… please someone fix my comments…

  37. Someone

    Will the script be secure if I hard code the directory which only contains the files that I want to be downloaded?

  38. Clark

    Hi.
    Will this script prevent users on my site from downloading more than 1 file at a time?
    If not, does anybody knows where can I find such a script?

  39. Why don’t use mime_content_type() function rather than checking the type on the basis of extension?
    Example code:

    $file="test.docx";
    header("Pragma: public");
    header('Content-disposition: attachment; filename='.$file);
    header("Content-type: ".mime_content_type($file));
    header('Content-Transfer-Encoding: binary');
    ob_clean(); 
    flush(); 
    readfile($file);
    

    Use $file to map to whichever type of file.
    Note: the mime types should already be defined in apache settings

  40. HeartDisk

    HI,

    while i am trying to create DOCX Files it’s show all content on download page and nothing happen :(

  41. Awesome script – using it to track the number of download from a site of mine. Thank-you for putting it together and sharing!

    R

  42. I am using the following download script:
    http://www.zubrag.com/scripts/download.php

    It already has code written for security checks and to prevent hotlinking. It is a very useful script! However, I would love to be able to integrate analytics code directly in the file without having to use “events” tracking. Any thoughts?

  43. codegreen

    Hi there..!
    I get a problem in downloading files..
    I forced to download the file in pdf .
    What happens is that, when i click download link it downloads the file in pdf , but when I try to open the file it shows an error message in Adobe Reader like the file has been damaged and cannot be opened.. here’s the coding..

    Plz help me..

  44. codegreen

    <

    //”FROM upload WHERE id = ‘$id'”;

    // send the current file part to the browser
    //print fread($file, round($download_rate * 1024));

    // flush the content to the browser

    // sleep one second

    >

  45. Worked like a champ. Thanks so much!

  46. Thanks for this very useful code!

  47. moin

    if my download fails for internet interruption or power failure or something like this then I should not update database, in this case what should I do? I mean how can I track that my downlaod success or not?

    Thanks

  48. All is well.
    How to know whether the file has downloaded or not? Somebody had already asked this in this comments section but i couldn’t see a favorable (+/-) answer.
    any code after readfile does not work.
    is this the default behavior of Apache?

    is there any gimmick do do such?

  49. Ryan

    I have a web server running apache and i want to download the php file. when i access it thru url, the php file is blank. Off course because of web servers security. However, I am trying to download the php file and I want the exact content which is a php code. I tried some codes on different blogs/forums but the file is empty/blank after downloaded. Is there a way I can do it? Thanks

  50. Thanks David. I am using this script for allowing force download of MP3 files rather than being automatically played in the browser. Previously I had to zip the file to make sure that people are able to download it. Thanks for the script once again

  51. Amir Khan

    @David,

    Can you please help me out.
    I am stuck in a strange situation my files are getting downloaded in different formats but they aren’t exporting the image i have used.
    The scenario is i have an HTML File containing some php variables for displaying report.
    I call this view file in my MVC based on the Format Type as PDF/Word/TIFF/Excel but the problem is Except for PDF format it is not exporting the image in any other format however other data in the table is getting print.

    The headers i am using are as follows:
    EXCEL :

    header("Content-type: application/octet-stream");
    header("Content-Disposition: attachment; filename=FILE.xls");
    header("Pragma: no-cache");
    header("Expires: 0");
    

    WORD :

    header("Content-Type: application/ms-word");
    header("Expires: 0");
    header("Cache-Control:  must-revalidate, post-check=0, pre-check=0");
    header("Content-disposition: attachment; filename="FILE.doc");
    header("Content-Transfer-Encoding: binary");
    

    PDF : Using Library

    TIFF: Using Image Magick

  52. I use the same script to download a file..and I’m using javascript post method to execute my php but the file not downloading in browser rather its downloading in “Internet download manager (Downloading application for windows)”.

    Why this is happening??

  53. $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',
    'csv' => 'text/csv',
    'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
    'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
    'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
    );
    

    Source: http://w3webtools.com/simple-page-download-file-using-php-and-jquery/

    Hope helps you!

  54. Mario

    You might also be interested in adding something like:

    ob_clean();
    ob_end_flush();
    

    to avoid out of memory issues. This happened to me when trying to download big files.

    Have also a look at: http://stackoverflow.com/questions/31277672/allowed-memory-exhausted-when-using-readfile

  55. Hari Prasad

    Hi David,
    Is that possible to directly download the file from the different origin?
    Using HTML5 download attribute.
    e.g:-
    My origin is test1.com and my pdf file in test2.com/filename.pdf

Wrap your code in <pre class="{language}"></pre> tags, link to a GitHub gist, JSFiddle fiddle, or CodePen pen to embed!