Limit the number of downloads per client

These days a customers website has generated a large amount of traffic because a client site has offered some “bigger” file downloads to their visitors. Normally there is nothing wrong with this kind of downloads on most of the websites, but in this case there are lot of visitors using a file download manager. With the most of these download clients it’s possible to enter a number of maximum connections for each target server. Just imagine if the visitor is using a value of “99” for a file of only 10MB! This way one file download for one visitor will open 99 connections and the used bandwidth will be almost one gigabyte.

There is a apache module named mod_limitipconn which can handle this task, but what if the website is on some shared web hosting? In the last situation we need to write some extra function: In this small tutorial we are using some some mod_rewrite rule and a download script while the downloads are logged in a database.

First we need a database to store the file path, the IP address and the last download time:

CREATE TABLE IF NOT EXISTS `downloaded` (
`filepath` varchar(255) NOT NULL,
`ipadres` varchar(15) NOT NULL,
`last_access` datetime NOT NULL,
UNIQUE KEY `filepath` (`filepath`,`ipadres`),
KEY `ipadres` (`ipadres`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;

For example you need to limit the downloads for files in a folder names “files”, place the file .htaccess file in this folder with this rewrite rule:

RewriteEngine on
RewriteRule (.*)(pdf|zip)$ /files/dl.php [QSA]

This rule rewrites every file access inside the folder (and sub folders) to the downloader script dl.php which is also located in the same folder “files”. Inside the downloader file we need to check if the file was downloaded from the IP address before:

$path = addslashes($_SERVER['REQUEST_URI']);
$ip = addslashes($_SERVER['REMOTE_ADDR']);
$dl = false;
$sql = sprintf("SELECT UNIX_TIMESTAMP(last_access) last_time FROM downloaded WHERE filepath = '%s' AND ipadres = '%s' ORDER BY last_access DESC", $path, $ip);
$res = mysql_query($sql);
if (mysql_num_rows($res) > 0) {
	$last_xs = mysql_result($res, 0, 'last_time')+3600;
	if ($last_xs < time()) {
		mysql_query(sprintf("REPLACE downloaded SET filepath = '%s', ipadres = '%s', last_access = NOW()", $path, $ip));
		$dl = true;
	}
} else {
	$sql = sprintf("REPLACE downloaded SET filepath = '%s', ipadres = '%s', last_access = NOW()", $path, $ip);
	mysql_query($sql);
	$dl = true;
}

We used in this example a time interval of 3600 seconds before it’s allowed to the user to download the file again. If the boolean variable $dl is “true” its allowed to download the file using this PHP code:

if ($dl) {
	$fullPath = $_SERVER['DOCUMENT_ROOT'].$path;
	if ($fd = fopen ($fullPath, "r")) {
		$fname = basename($fullPath);
		header('Content-type: application/octet-stream');
		header('Content-Disposition: filename="'.$fname.'"');
		header('Content-length: '.filesize($fullPath));
		header('Cache-control: private');
		while(!feof($fd)) {
			$buffer = fread($fd, 2048);
			echo $buffer;
		}
		fclose ($fd);
		exit;
	}
} else {
	header('HTTP/1.0 503 Service Unavailable');
	die('Abort, you reached your download limit for this file.');
}

This PHP file download snippet is a very common example from the PHP manual. Notice that if the boolean variable is “false” no file download is possible and a forbidden message appears.

Published in: PHP Scripts

35 Comments

  1. Very nice, not sure I would send them a 403, simply because if their browser caches it, then they may have a problem when they try to reconnect I would send either 503 temporary error, or 307 temp redirect, the latter being the more sensible if it then redirects back to the main page after a moment.

    Just a thought.

  2. Great tutorial. This is so needed for sites that have files for downloading. If I ever add files for download on my blog, I’ll for sure use this code.

  3. I think the reason is in the “max_execution_timeâ€? PHP config value…

    add this function to the begin of your download script:

    set_time_limit(0);

  4. It wasn’t very usefull for me, I m hosting large files and this script break download after few seconds, I think the reason is in the “max_execution_time” PHP config value and I havn’t found the solution yet.

    Any idea?

  5. Hi,

    It was good, but I have another problem in IE it tries to open the file directly rather than downloading it.

  6. So, if all my file in forder files is music file like wma , mp3, and an other site i using this music file , so people can listening it online, and i only want to limit the download but not the listening online. When i use htaccess, i cant listenning online. Any idea to limit download music file but still can listening online? Please helpp me, Thanks

  7. So, i have a site music, people can listening online and download for free at abc.com , and all music are hosted at music.abc.com
    Then if i use this code, how can i do? put
    CREATE TABLE IF NOT EXISTS `downloaded` (
    `filepath` varchar(255) NOT NULL,
    `ipadres` varchar(15) NOT NULL,
    `last_access` datetime NOT NULL,
    UNIQUE KEY `filepath` (`filepath`,`ipadres`),
    KEY `ipadres` (`ipadres`)
    ) ENGINE=MyISAM DEFAULT CHARSET=latin1;

    to my music sql at abc.com and put

    to dl.php and upload to music forder at music.abc.com ?
    Can i do that, so if i do that, people can listening online? or not?
    Please help me.
    Thanks

  8. I would like to integrate this into my own php script instead of using a htaccess redirect. I assume i would place the contents of dl.php directly into the part of my file where the user presses the ‘download file’ button.

    the only problem is i would like to know how to limit the amount of data transfer per unit time (e.g. 5GB per day) as opposed to the number of file downloads. i need it to be as simple as this script because i am still learning php! any ideas?

  9. no, use for me because i’m on shared hosting, i have no access to apache directories or anything.

    i really wanted to know how to do this in php code, as above, except detecting bandwidth usage over file download numbers. :(

  10. Thanks a lot for this script. It took me only some minutes to test and it works like a charm!

    It allows me to use a download manager but with only one connection at a time for each file.

    Unfortunatelly it doesn’t limit the number of files being downloading at the same time. I’ll keep searching :)

  11. Thank you again Olaf. I’m afraid I cann’t use it because I’m on a shared server but it is on apache, so I’ll ask to my hosting company if they have implemented it on their servers. :)

  12. Do note that a PHP Script like this creates a lot of load on the server, especially if its a popular file. I dropped something like this in exchange for lighttpd’s handling, not the least which includes native traffic shaping.

  13. Hi bLuefRogX,

    at the moment you have some files downloaded on a busy server you need mod_bandwidth or something. As I said I wrote this tutorial because the downloaded files are very big (~100MB).

    maybe you can tell us more about the feature you’re using with lighttpd?
    (btw. most of the users on the net using Apache)

  14. Hmm, an alternative way would to to use symlinking to provide access to the file, instead of using php to ‘forward’ the files to the users. THat way you can use less memory resources. If you’re on shared hosting, chances are your host limits you to the absolute bare minimum anyway.

    Would you mind if I extend your tutorial on my own blog?

  15. sure why not (send a trackback)

    The direct output is one way, used the symlinks too and just used a script to register the “access data” in the database.

  16. How would I edit this script, so that a user is limited (by ip) on the total transfer amount per time (e.g. 5GB per day), regardless of what file they download?

    Assume this is done; then obviously if they download the same file over and over, it will still be added to the overall transfer bandwidth. So if I was to leave the file ‘downloaded’ record intact (in the MYSQL database), how would I then allow the same user (identified by IP) to download the same file over and over, without it being added to their bandwidth? What I mean is, if the connection somehow times out for a file they are downloading, how can I allow them to try download it again (without increasing their IP’s overall bandwidth transfer)?

    Also, has anyone any idea on how I could store IP addresses in a textfile as opposed to a database, for increased speed (or reduced load on the server – but could this pose a security risk)?

    cheers!

  17. Update:

    I solved the problem above, u can disregard it. :)

    However, a suggestion for finding the user’s IP.

    As you probably already know, finding an IP doesn’ always work, the user could be behind a proxy for example. Or maybe they have a dynamic IP. You could instead use this to find the IP:

    if(isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
    $ip = $_SERVER['HTTP_X_FORWARDED_FOR'];
    } else {
    $ip = $_SERVER['REMOTE_ADDR'];
    }

    Obviously it’s not foolproof, but it does work.

    Better than this would be to log both IP addresses associated with the one download, and limit it that way. :)

  18. Great tool :) I’ve been searching for a last few days for something like this one tool, becouse I have a few malignant bandwidth bloodsucker , who are trying overuse my bw.

  19. What about to implement an speedlimit for downloads ?? This was an feature i’m always looking for, and it was great when your script can also do this !

  20. Thanks for reply, but i’m also on a shared server. I know there a php script which can limit the downloadspeed, but it can’t also limit the numbers per client, what your script can…..

  21. Thanks for providing this tutorial. I’m having a problem with the database entries. File downloads fine but no entries in the database?

Comments are closed.