Protecting a download using a unique URL
A client asked me to develop a simple method for protecting a download (or digital product) by generating a unique URL that can be distributed to authorized users via email. The URL would contain a key that would be valid for a certain amount of time and number of downloads. The key will become invalid once the first of those conditions is exceeded. The idea is that distributing the unique URL will limit unauthorized downloads resulting from the sharing of legitimate download links.
In addition, once the key has been validated, the download starts immediately, preventing the visitor from seeing the actual location of the download file. What’s more, the filename of the download in the “Save as” dialogue box isn’t necessarily the same as the filename of the file on the server, making the file itself pretty much undiscoverable.
How it works
There are five main components to this system:
- the MySQL database that holds each key, the key creation time, and the number of times the key has been used
- the downloadkey.php page that generates the unique keys and corresponding URLs
- the download.php page that accepts the key, verifies its validity, and either initiates the download or rejects the key as invalid
- a dbconnect.php file that contains the link to the database and which is included into both of the other PHP files
- the download .zip file that is to be protected
Place all three PHP scripts and the .zip file into the same directory on your server.
The MySQL database
Using whatever method you’re comfortable with, create a new MySQL database named “download” and add the following table:
CREATE TABLE `downloadkey` (
`uniqueid` varchar(255) NOT NULL default '',
`timestamp` varchar(255) NOT NULL default '',
`downloads` varchar(255) NOT NULL default '0',
PRIMARY KEY (uniqueid)
);
The downloadkey.php page
This page generates the key, creates a URL containing the key, and writes the key to the database. Never give out the location of this page - this is for only you to access.
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<title>Download Key Generator</title>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
<meta name="author" content="http://www.ardamis.com/" />
<style type="text/css">
#wrapper {
font: 15px Verdana, Arial, Helvetica, sans-serif;
margin: 40px 100px 0 100px;
}
.box {
border: 1px solid #e5e5e5;
padding: 6px;
background: #f5f5f5;
}
</style>
</head>
<body>
<div id="wrapper">
<h2>Download Key Generator</h2>
<?php
// A script to generate unique download keys for the purpose of protecting downloadable goods
require ('dbconnect.php');
if(empty($_SERVER['REQUEST_URI'])) {
$_SERVER['REQUEST_URI'] = $_SERVER['SCRIPT_NAME'];
}
// Strip off query string so dirname() doesn't get confused
$url = preg_replace('/\?.*$/', '', $_SERVER['REQUEST_URI']);
$folderpath = 'http://'.$_SERVER['HTTP_HOST'].'/'.ltrim(dirname($url), '/').'/';
// Generate the unique download key
$key = uniqid(md5(rand()));
// echo "key: " . $key . "<br />";
// Get the activation time
$time = date('U');
// echo "time: " . $time . "<br />";
// Generate the link
echo "<p>Here's a new download link:</p>";
echo "<p><span class=\"box\">" . $folderpath . "download.php?id=" . $key . "</span></p>";
// Write the key and activation time to the database as a new row
$registerid = mysql_query("INSERT INTO downloadkey (uniqueid,timestamp) VALUES(\"$key\",\"$time\")") or die(mysql_error());
?>
<p> </p>
<p>Each time you refresh this page, a unique download key is generated and saved to a database. Copy and paste the download link into an email to allow the recipient access to the download.</p>
<p>This key will be valid for a certain amount of time and number of downloads, which can be set in the download.php script. The key will expire and no longer be usable when the first of these conditions is exceeded.</p>
<p>The download page has been written to force the browser to begin the download immediately. This will prevent the recipient of the email from discovering the location of the actual download file.</p>
</div>
</body>
</html>
The download.php page
The URL generated by downloadkey.php points to this page. It contains the key validation script and then forces the browser to begin the download if it finds the key is valid.
<?php
// Set the maximum number of downloads (actually, the number of page loads)
$maxdownloads = "2";
// Set the key's viable duration in seconds (86400 seconds = 24 hours)
$maxtime = "86400";
require ('dbconnect.php');
if(get_magic_quotes_gpc()) {
$id = stripslashes($_GET['id']);
}else{
$id = $_GET['id'];
}
// Get the key, timestamp, and number of downloads from the database
$query = sprintf("SELECT * FROM downloadkey WHERE uniqueid= '%s'",
mysql_real_escape_string($id, $link));
$result = mysql_query($query) or die(mysql_error());
$row = mysql_fetch_array($result);
if (!$row) {
echo "The download key you are using is invalid.";
}else{
$timecheck = date('U') - $row['timestamp'];
if ($timecheck >= $maxtime) {
echo "This key has expired (exceeded time allotted).<br />";
}else{
$downloads = $row['downloads'];
$downloads += 1;
if ($downloads > $maxdownloads) {
echo "This key has expired (exceeded allowed downloads).<br />";
}else{
$sql = sprintf("UPDATE downloadkey SET downloads = '".$downloads."' WHERE uniqueid= '%s'",
mysql_real_escape_string($id, $link));
$incrementdownloads = mysql_query($sql) or die(mysql_error());
// Debug echo "Key validated.";
// Force the browser to start the download automatically
/*
Variables:
$file = real name of actual download file on the server
$filename = new name of local download file - this is what the visitor's file will actually be called when he/she saves it
*/
ob_start();
$mm_type="application/octet-stream";
$file = "actual_download.zip";
$filename = "bogus_download_name.zip";
header("Cache-Control: public, must-revalidate");
header("Pragma: no-cache");
header("Content-Type: " . $mm_type);
header("Content-Length: " .(string)(filesize($file)) );
header('Content-Disposition: attachment; filename="'.$filename.'"');
header("Content-Transfer-Encoding: binary\n");
ob_end_clean();
readfile($file);
}
}
}
?>
The dbconnect.php script (database connection)
This is the PHP include referenced by both scripts that contains the database link.
<?php
// Connect to database "download" using: dbname , username , password
$link = mysql_connect('localhost', 'root', '') or die("Could not connect: " . mysql_error());
mysql_select_db("download") or die(mysql_error());
?>
That’s all there is to it. Whenever you want to give someone access to the download, visit the downloadkey.php page. It will generate a unique key code, save it to a database, and print out a URL that you can copy and paste into an email or whatever. The page at that URL checks to see if the key code is legit, then checks to see if the code is less than X hours old, then checks to see if it has been used less than X times. The visitor will get a descriptive message for the first unmet condition and the script will terminate. If all three conditions are met, the download starts automatically.










Very, very helpful. Clear and concise, just what I needed! Thanks!
June 15th, 2008
How would I allow resume through a download manager? I use a similar system and allow my tickets to be used for up to a week but I cannot get it to resume.
June 21st, 2008
Hmm. That’s a good question. I’m not at all sure. I’ll look into it, though.
June 27th, 2008
Clear and concise. Good snippet. Thx!
July 12th, 2008
This is really useful - was looking for something like that for ages. Thanks a lot for sharing! Much appreciated.
July 17th, 2008
You are THE MAN! This is the best script I have have ever found relating to digital content. If you combine this script with a solid chunk of .htaccess and .htpassword security you got yourself a top notch setup!
Thank you so much!
July 24th, 2008
Listen my friend! This is great!!!
August 2nd, 2008
Hi,
I have tried the above code but I get the error message below:-
——————————————————————
Warning: mysql_connect() [function.mysql-connect]: Access denied for user ‘root’@'localhost’ (using password: NO) in /home/alanpotts93/public_html/hiddenfiles/dbconnect.php on line 3
Could not connect: Access denied for user ‘root’@'localhost’ (using password: NO)
——————————-
I have already created a mysql table as was instructed with the default value for the third field being a zero.
I am a novice at website building and only know html, but any help would be welcome.
Thanks in advance
September 25th, 2008