Ever had this thought: “I want to fire something extra up when somebody downloads a certain file.”? This can be easily fixed by making all you download links link to a php page and output the download file for you. But what if the file is called directly in the browser, say http://www.gayadesign.com/scripts/photonav/photonav.zip? Apache will happily give the file to the user, without me noticing it. Luckily for us, .htaccess is a great place to mess around with Apache.
This article will explain how to make pre-download conditions in php using .htaccess. I’ll make a download counter in this one.
Think of the possibilities! You could build a download counter, a place to check if the user has rights to download a certain file, create a bandwidth limit, and the list goes on and on.
Keeping track of downloads is a feature many developers want, even firing “something” (read: doStuff()) before a file is requested for download is handy.
Okay, what do you need?
- Apache web server with rewrite module enabled (probably the case)
- a decent text editor (like notepad++)
- optional: in this tutorial I’ll be using PHP
First, we need to get an .htaccess rule going. To do this you have to adjust your .htaccess file, or create a new one. To create a new .htaccess file in Notepad++ just open a new file and save as .htaccess. This is a hidden file on a unix system, but don’t worry.
Put the following lines in the .htaccess file:
#Let's do rewriting!
RewriteEngine on
RewriteRule ^(.*).(rar|zip|pdf)$ /download.php?file=$1.$2 [R,L]
This redirects every .rar, .zip and .pdf to the download.php file and include the file path as a parameter. I already hear developers think: "And what if I prompted it to output a .php file though a browser call?". We'll fix that in the PHP file.
To add more extensions, separate them with a | (shift + ).
A few notes on .htaccess:
The file will work from the root of the folder you'll put this into at your web server. All folders beneath the root will be affected. If you place the file in say /files/, the path will not be included in the file parameter. You'll have to either add this in the download.php file or .htaccess file.
Upload the file in your webroot.
Next up, we are going to create a table in MySQL. You can do this in phpMyAdmin, it's a small table.
If you already have a table called download, you have to change the SQL code.
CREATE TABLE `download` (
`filename` varchar(255) NOT NULL,
`stats` int(11) NOT NULL,
PRIMARY KEY (`filename`)
)
Execute this SQL statement in the SQL window, it'll create a new table.
Now it's time to get the PHP to work.
The following code allows you to keep track of download requests in the table you created above:
<?php
//put connection to database here
mysql_connect("localhost", "username", "password")
or die ("Sorry, can't connect to database.");
mysql_select_db("dbname");
$filename = mysql_real_escape_string($_GET['file']);
$path = $_SERVER['DOCUMENT_ROOT']."/"; //path of this file
$fullPath = $path.$filename; //path to download file
$filetypes = array("rar","zip","pdf");
if (!in_array(substr($filename, -3), $filetypes)) {
echo "Invalid download type.";
exit;
}
if ($fd = fopen ($fullPath, "r")) {
//add download stat
$result = mysql_query("SELECT COUNT(*) AS countfile FROM download
WHERE filename='" . $filename . "'");
$data = mysql_fetch_array($result);
$q = "";
if ($data['countfile'] > 0) {
$q = "UPDATE download SET stats = stats + 1 WHERE
filename = '" . $filename . "'";
} else {
$q = "INSERT INTO download (filename, stats) VALUES
('" . $filename . "', 1)";
}
$statresult = mysql_query($q);
//the next part outputs the file
$fsize = filesize($fullPath);
$path_parts = pathinfo($fullPath);
header("Content-type: application/octet-stream");
header("Content-Disposition: filename=".$path_parts["basename"]."");
header("Content-length: $fsize");
header("Cache-control: private"); //use this to open files directly
while(!feof($fd)) {
$buffer = fread($fd, 2048);
echo $buffer;
}
}
fclose ($fd);
exit;
?>
Let me explain what you can change in the code in short; in the first part of the file, you'll connect to your database. Please change the values to match your settings.
You can change the $filetypes to your likings, this will contain the accepted file extensions.
The php script will eventually output the file to your browser, neat eh? Download the zip file for a quick look in the files.
I hope this will help you to create pre-conditions on your downloads.
Good luck!

THX!
I looking for long time this kind of script.Working very well!Great job!
Using .htaccess of this way has only rewrite-effect, and all other works are done by PHP. So that, when I read the title, I wondered why htaccess file can be used to delivered files? This post is the same as “PHP download manager”.
There are better ways to check a file extension. http://us2.php.net/pathinfo is one. It would be easy to sneak a file past your check.
First of all: thanks for all the comments!
@Tuan Anh
Maybe we interpret the title differently. I hope you still liked the article.
@david
Isn’t that a better to check user uploaded files?
Because I uploaded the files on my server, I am sure I gave them the right extension.
Or what did you mean?
well ! The script is great , but i think .htaccess is difficult to use .
Thanks Adam. htaccess can be hard some times. So much possibilities, and a great chance to get a 500 error :(
Hey guys… I got it working but I wanted to add mp3, so I can track podcasts. But for some reason it doesnt work any ideas why?
Works fine with zip files.
Hey Rob,
Have you tried adding mp3 to the .htaccess and download.php file?
Good luck!
Great! Thank you!
…
Excellent :)
Well … actually one problem here is
Content-type: application/octet-stream
This is not working for my site, I would like to use the content-type that I entered in one of the Apache configuration files
and another small problem – the extension is not always 3 character (I removed that code completely)
Your code can be very unsecure. If you add “.php” extension to download-allowed-extension you can try download.php?file=../config.php for instance ;)
Imho, it’s better to leave only “[L]” option in .htaccess and add code like this:
# .htaccess:
RewriteRule ^downloads/(.*)$ /download.php?file=$1&tkn=secretTok3n [L]
# download.php:
if ($_GET['tkn']!=’secretTok3n’) { die(‘Naughty boy, naughty…’); }
$path = $_SERVER['DOCUMENT_ROOT'].”/downloads/”;
$fullPath = $path.$filename; //path to download file
if (strstr(realpath($fullPath), ‘/downloads/’) === FALSE) { die(’404 Not Found’); }
// Here file can not exists (realpath returns FALSE if it’s not here) or someone moved “above” level of downloads. (hacking…)
—
Also this function – http://pl.php.net/mime_content_type – would be usable here (for example, if I want to download “txt” file, your script will force me to download it) :)
Woot!
This was EXACTLY what i was looking for. AWESOME.
The program fails to download files more than 700MB. What to do?
Use this function to download file w/o prob of filesize.
Thanks for posting a great! I like to read it, you could be a great author.I always bookmark your blog, and may come back later in life. I want to encourage you to continue your great work, have a nice weekend!
Hi, I am running into errors with the php code. First error was on like 26, where you code says > which I figured out what a messed up >. However, I am stuck on the next error, which is line 40. The error is “Parse error: syntax error, unexpected ‘”‘ ”
Any idea what I might be getting that? My line 40 is an exact copy of yours
header(“Content-Disposition: filename=”".$path_parts["basename"].”"”);
Thanks, Douglas
Awesome! Thanks for this.. I had to tweak it slightly for our exact needs but we are using it on our site http://miui.us and it’s tracking our downloads perfectly.
looks like someone needs to implement a spam script! i’m going to wager you’re not a pedophile.
Wow! Never seen these before haha. Going to delete them. Don’t know how these slipped through
Not work. This is appear in my screen “Invalid download type.” I am using this:
$filename = mysql_real_escape_string($_GET['pusmeong.zip']);
$path = $_SERVER['http://howto.ina.0lx.net/pustaka'].”/”; //path of this file
This won’t work with big download files, because whether MAX EXEC TIME will exceeded and/or it’s not resumable and not suitable for multiparts download slots as what every download manager does!
so it’s only usable for small size files
Hello, i got error below:
Notice: Undefined index: file in /home/xxxxx/public_html/counter.php on line 5 Invalid download type.
Please help. thanks.
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
hello there and thank you for your info – I have certainly picked up something
new from right here. I did however expertise
several technical points using this web site, since I experienced to reload the site
lots of times previous to I could get it to load correctly.
I had been wondering if your web host is OK? Not that I’m complaining, but slow loading instances times will often affect your placement in google and could damage your high-quality score if ads and marketing with Adwords. Anyway I’m adding this RSS to my e-mail and could look out for a
lot more of your respective exciting content. Make sure you
update this again very soon.
hi there, wot about if we want to send one time link to somebody, do you have any example with that, so far i know that can be witk a token. but i do not know how to do it.
Hi there thanks for that… how do I look at the output file, and what is its name?