Control, Necessity and Code
Handling file downloads through a server-side scripting language is usually done by web developers to control access to files, the download process or due to necessity depending on their setup.Control
By using PHP to control file downloads you can:
- hide the real file path (if you store files in doc root)
- store files outside of doc root (so they're not directly fetchable)
- protect files behind sessions (authenticate before sending)
- increment download counters (so you can do a little tracking)
- simplify the download process (eliminate download pages)
Necessity
Depending on your setup, using PHP to handle file downloads may be a necessity. Consider the example on the right where there is an application server (Apache, PHP and NFS mounts), file server (Apache and NFS exports) and a database server.With this setup you have to allow clients to download files directly from the file server or have download requests sent to the application server and handled via PHP. The first method makes it difficult to protect files, track the number of downloads and force download dialog boxes on images, music, movies and so on whereas the latter method can do all of these things and makes the download process very simple for users.
Using PHP to handle file downloads isn't always the best approach but if you need to control access to your files or track activity in a database it's a good solution.
Code
Before we look at the code I want to touch on security by providing a few recommendations. The big thing here is validating user uploads and using a safe mechanism for determining which file a user is trying to download.
- Thoroughly validate file uploads (file name, extension, mime-type, size etc)
- When users upload files have your app name them unless there is a good reason not to
- Use alpha, numeric, underscore or hyphens in your file names -- keep it simple (no spaces)
- Don't use file names in download links (use ID's from a database and query the names)
Okay, let's look at the code. This code was tested on Mozilla Firefox 3.0.5, Internet Explorer 6 and 7, Google Chrome 1.0.154.36, Opera 9.27 and Safari 3.2.
- // validate the request, fetch info from database etc
- $fileName = 'itnewb.jpg'; // retrieved from a database
- $targetFile = "/path/to/file/{$fileName}"; // absolute path
- // output a nice error page
- exit;
- }
- // increment counter and whatever else you need to do
- // $fileName could be replaced in the header below with any
- // name you like so long as it has the proper file extension
Notes
If you compress files with Zlib, mod_deflate and so on the Content-Length header won't be accurate so you'll end up seeing "Unknown size" and "Unknown time remaining" when downloading files.
You may want to tweak the cache control headers if they don't suit your needs, just be sure and leave the post-check=0 and pre-check=0 directives or it won't work in Internet Explorer (Microsoft apparently "added support" for these directives beginning with IE 5 -- and no, they aren't part of the HTTP/1.0 or HTTP/1.1 specifications).
If you plan on offering downloads over SSL you may need to add the private directive to the Cache-Control and Pragma headers as follows:
That's it, enjoy!
