Hello world! An update to a Php script “protecting multiple downloads using unique URLs” as a means to share Copyrighted Materials

Yes! Hello World! This is my first post and this post is about a nifty Php script which I was able to update the code/s to work with Php version 7.x. I have been constantly bugging a fellow librarian working in SEAFDEC Library not named Stephen Alayon. I’m not gonna mention his name because he is a shy guy and does not want to be in the limelight, but he is my go-to-guy when it comes to DSpace. Anyway, as gratitude to various buggings I’ve done to him in the past, I updated the code base of a post about a Php script for protecting multiple downloads using unique URLs. This Php script lets a sharer protect a download (or digital product) by generating a unique URL that can be distributed to authorized users via email. Additionally, this Php script masks the true file name of the record to be shared in the file system directory. 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 the conditions is exceeded. The idea is that distributing the unique URL will limit unauthorized downloads resulting from the sharing of legitimate download links. This is what the SEAFDEC Library is using when a researcher asks for copies of copyrighted materials. When a request for copyrighted materials is sent to the library, they open the program, find the corresponding file being requested, and then a URL is created that will be shared to the requestor. And that link would only be valid for seven day and once filed is retrieved, the file would be removed from the document request site (The last part I got from testing the request form of the Library IR). I guess they have been using it for quite some time or since they started using DSpace Institutional Repository.

How it works

The “how it works” aspect of this script can be found in the original post in https://ardamis.com/ here and I am copy-pasting it below.

There are five main components to this system:

  1. the MySQL database that holds each key, the key creation time, and the number of times the key has been used
  2. the downloadkey.php page that generates the unique keys and corresponding URLs
  3. the download.php page that accepts the key, verifies its validity, and either initiates the download or rejects the key as invalid
  4. a dbconnect.php file that contains the link to the database and which is included into both of the other PHP files
  5. 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

Create a new MySQL database named “download” or whatever name but be sure to adjust it accordingly in the php code. Below is the mysql database command to add the table.

CREATE TABLE `downloadkeys` (
  `uniqueid` varchar(12) NOT NULL default '',
  `timestamp` INT UNSIGNED,
  `lifetime` INT UNSIGNED,
  `maxdownloads` SMALLINT UNSIGNED, 
  `downloads` SMALLINT UNSIGNED default '0',
  `filename` varchar(60) NOT NULL default '',
  `note` varchar(255) NOT NULL default '',
  PRIMARY KEY (uniqueid)
);

Original post type for timestamp is INTEGER UNSIGNED with a length of “10”. For similar type from previous projects, I use TIMESTAMP type for timestamp. I did not touch this one because basing from the php source code it indeed uses the date function as “Seconds since the Unix Epoch”.

$time = date('U')

And if I do changed the type for timestamp, the code will break or I have to adjust the code accordingly. Since I want to be able to use this wonderful code in a jiffy, I did not change this part.

The downloadkey.php page

This page is where you generate the keys – the URL of what you will be sharing to your (outside) researchers or to those who want to have a copy of a file you want to share. This is the icing in the pudding. This is where we start all our workflows. As posted in by the original poster in https://ardamis.com/2008/06/11/protecting-a-download-using-a-unique-url/:

Never give out the location of this page – this is for only you to access.

So to be able to hide this page, you can rename downloadkey.php to whatever name you want it called. Below is the downloadkey.php page source code.

<!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="Oliver Baty | 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;
}
input {
	font-size: 1em;
}
select {
	min-height: 22px;
	min-width: 179px;
}
#submit {
	padding: 4px 8px;
}
</style>
</head>

<body>
<div id="wrapper">

<h2>Download Key Generator</h2>

<!-- add a count of keys created and downloads to date -->


<?php 
error_reporting(1);
require ('config.php');


		$link=mysqli_connect($db_host,$db_username,$db_password)
				or die ("Could not connect to database");
				mysqli_select_db($link,$db_name) or die ("Error");


$keys = htmlspecialchars($_POST['keys'], ENT_QUOTES);
$successkeys = 0;
?>

<?php if(is_numeric($keys) && $_POST['filename'] != "") {

if($keys > 20) { $keys = 20; }

//	echo $keys;

// A script to generate unique download keys for the purpose of protecting downloadable goods

	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), '/').'/';

	// Strip slashes if necessary
	if (get_magic_quotes_gpc()) {
		$filename = trim(stripslashes($_POST['filename']));
		$maxdownloads = trim(stripslashes($_POST['maxdownloads']));
		$lifetime = trim(stripslashes($_POST['lifetime']));
		$note = trim(stripslashes($_POST['note']));
	} else {
		$filename = trim($_POST['filename']);
		$maxdownloads = trim($_POST['maxdownloads']);
		$lifetime = trim($_POST['lifetime']);
		$note = trim($_POST['note']);
	}
	
	// Get the activation time
	$time = date('U');
//	echo "time: " . $time . "<br />";
	
	echo '<div class="box">';
	
	for ($counter = 1; $counter <= $keys; $counter += 1) {
		// Generate the unique download key
		$key = substr(uniqid(md5(rand())), 0, 12);
//	echo "key: " . $key . "<br />";
		
		
		// Generate the link
		echo $folderpath . "download.php?id=" . $key . "<br />\n";
		
		// Sanitize the query
		$query = sprintf("INSERT INTO downloadkeys (uniqueid,timestamp,lifetime,maxdownloads,downloads,filename,note) VALUES('$key','$time','%d','%d','%d','%s','%s')",
		$lifetime,
		$maxdownloads,
		0,
		mysqli_real_escape_string($link,$filename),
		mysqli_real_escape_string($link,$note));
		
		// Write the key and other information to the DB as a new row
		$registerid = mysqli_query($link,$query) or die(mysqli_error());
		
		$successkeys++;
	}
	
	echo '</div>';
}
?>

<?php if($keys && !$_POST['filename']) { echo '<h2>You must supply a filename for the download.</h2>'; } ?>

<?php if(!$keys) {

	if($realfilenames != "") {
		$the_filenames = explode(',', $realfilenames);
	}

 ?>

<form action="<?php echo $_SERVER['PHP_SELF']; ?>" id="keyform" method="post">
	
	<table cellpadding="2">
		<tr>
			<td>Number of keys to generate</td>
			<td><input type="text" name="keys" id="keys" value="1" size="20" tabindex="1" /></td>
			<td>(maximum of 20)</td>
		</tr>
		<tr>
			<td>File to be downloaded</td>
            <?php if($the_filenames) {
				echo '<td><select name="filename" id="filename" tabindex="2">';
				foreach($the_filenames as $filename) {
					echo '<option value="' . $filename . '">' . $filename . '</option>' . "\n";
				}
				echo '</select></td>' . "\n";
			}else{
				echo '<td><input type="text" name="filename" id="filename" value="'. $realfilenames . '" size="20" tabindex="2" /></td>';
			} ?>
			<td>(required)</td>
		</tr>
		<tr>
			<td>Max allowable number of downloads</td>
			<td><input type="text" name="maxdownloads" id="maxdownloads" value="<?php echo $maxdownloads; ?>" size="20" tabindex="3" /></td>
			<td>(default = <?php echo $maxdownloads; ?>)</td>
		</tr>
		<tr>
			<td>Max age for download key, in seconds</td>
			<td><input type="text" name="lifetime" id="lifetime" value="<?php echo $lifetime; ?>" size="20" tabindex="4" /></td>
			<td>(default = <?php echo $lifetime; ?> seconds)</td>
		</tr>
		<tr>
			<td>Note</td>
			<td><input type="text" name="note" id="note" value="" size="20" tabindex="5" /></td>
			<td>(optional)</td>
		</tr>
		<tr>
			<td></td>
			<td><input type="submit" id="submit" value="Generate Keys" tabindex="6" /></td>
			<td></td>
		</tr>
	</table>
	
</form>

<?php } ?>

<p><?php if($successkeys > 0) { echo $successkeys . ' keys successfully created.  Click to <a href="' . $_SERVER['PHP_SELF'] . '">create more keys</a>.'; } ?>&nbsp;</p>
<?php if($successkeys == 0) { ?>
<p>Use this page to generate up to 20 unique download keys at a time and save them to the database.  The default values for max allowable downloads and max age are set in config.php.  See that file for more details.</p>
<p>The note is optional and will be attached to each key, if multiple keys are created.  (For example, if you generate 5 keys, the same note will be attached to each of those 5 keys.)</p>
<p>The download links can be copied and pasted into emails or whatever to allow the recipient access to the download.</p>
<p>Each key will be valid for a certain amount of time and number of downloads.  The key will no longer be usable when the first condition is exceeded.</p>
<p>The download page has been written to force the browser to begin the download immediately.  This will prevent the user of the key from discovering the location of the actual download file.</p>
<?php } ?>
<p>You can also <a href="report.php">generate a report</a> of all keys created so far.  (This might time out if you've generated thousands of keys.)</p>

<p style="padding-top:40px;">Brought to you by <a href="http://www.ardamis.com/2009/06/26/protecting-multiple-downloads-using-unique-urls/">ardamis</a>.</p>

</div>
</body>
</html>

Before we proceed to download.php page, I would like to reiterate that the files you want to share must reside within the directory of the downloadkey.php and that file should be zipped or in zip file. But, there is a way to instead reference them from another directory and to be able to add other file types like .pdf, .xlsx, etc. however, that is not a part of this write-up. See the images below.

You will see from the above images that I have documentdelivery.zip, _first

The download.php page

Lifted from ardamis.com:

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.

The key takeaway from this code is the conversion of mysql code into mysqli since PHP7 has removed the Mysql extension. Using the MySQLi procedural methods, there are some changes in the positions of link. Below is the code for the download.php page.

<?php

error_reporting(1);
require ('config.php');

	$link=mysqli_connect($db_host,$db_username,$db_password)
		or die ("Could not connect to second database");
		mysqli_select_db($link,$db_name) or die ("Error");
		
		$fakefilename = "download.zip";		

	// GET the unique key
	if(get_magic_quotes_gpc()) {
        $id = stripslashes($_GET['id']);
	}else{
		$id = $_GET['id'];
	}
	
	//echo $id;	
	
	// Reduce it to 12 characters, because a legit key is exactly 12 characters
	$id = substr(trim($id), 0, 12);

	// Check for tables
	$query = "SHOW TABLES FROM $db_name";
	$result = mysqli_query($link,$query);
	if (!$result) {
		echo 'The database is not correctly configured.  No tables were found in the database.';
		exit;
	}
	
	// Check for the downloadkeys table
	$query = "SELECT * FROM downloadkeys LIMIT 1";
	$result = mysqli_query($link,$query);
	if (!$result) {
		echo 'The database is not correctly configured.  Check the name of the table.';
		exit;
	}
					
	// Query the database for a match to the key
	$query = sprintf("SELECT * FROM downloadkeys WHERE uniqueid = '%s'",
	mysqli_real_escape_string($link, $id));
	$result = mysqli_query($link,$query) or die(mysqli_error());	
	
	//print $result;	
	
	// Write the result to an array
	$row = mysqli_fetch_array($result);
	
	#print $row[0];
	
	// Begin checking validity of the key
	if (mysqli_num_rows($result) == 0) {
		// If no match is found, return an error message and exit
		echo 'The download key you are using is invalid.';
		exit;
	}else{
		// Calculate the age of the key
		$age = date('U') - $row['timestamp'];
		$lifetime = $row['lifetime'];
		// Compare the age of the key against the allowed age
		if ($age >= $lifetime) {
			// The key is too old, so exit
			echo 'This key has expired (exceeded time allotted).';
			exit;
		}else{
			// The valid key has not expired, so check the number of downloads
			$downloads = $row['downloads'];
			$maxdownloads = $row['maxdownloads'];
			if ($downloads >= $maxdownloads) {
				// The number of downloads meets (or exceeds) the allowed number of downloads, so exit
				echo 'This key has expired (exceeded allowed downloads).';
				exit;
			}else{
				// The key has passed all validation checks
				// Retrieve the filename of the download
				$realfilename = $row['filename'];
				echo $realfilename;
				//echo $fakefilename;
				// Increment the download counter
				$downloads += 1;
				$sql = sprintf("UPDATE downloadkeys SET downloads = '" . $downloads . "' WHERE uniqueid = '%s'",
	mysqli_real_escape_string($link, $id));
				$incrementdownloads = mysqli_query($link,$sql) or die(mysqli_error());
				
// Debug		echo "Key validated.";	
				

// Force the browser to start the download automatically
// Consider http://www.php.net/manual/en/function.header.php#86554 for problems with large downloads

				header('Content-Description: File Transfer');
				header('Content-Type: application/octet-stream');
				
				header('Content-Disposition: attachment; filename="' . basename($fakefilename) . '"');
				header('Content-Transfer-Encoding: binary');
				header('Expires: 0');
				header('Cache-Control: must-revalidate');
				header('Pragma: public');
				//header('Content-Length: ' . filesize($realfilename));
				header('Content-Length: ' . filesize($realfilename));
				ob_clean();
				flush();
				readfile($realfilename);
				exit;
			}
		}
	}
?>

The config.php script (database connection)

This is the database connection configuration. You need to edit this file to reflect your database connections. The part with the replace ‘db_username’ with the correct database username, ‘db_password’ with the password of used by db_username and ‘db_name’ with the name of your database.

<?php
// Variables: 

// Database
$db_host = 'localhost'; // Hostname of database
$db_username = 'db_username'; // Username
$db_password = 'db_password';  // Password
$db_name = 'db_name'; // Database name

// Set the maximum number of downloads (actually, the number of page loads)
$maxdownloads = "2";

// Set the keys' viable duration in seconds (86400 seconds = 24 hours)
$lifetime = "86400";

// Set the real names of actual download files on the server as a comma-separated list (this is optional; you can use a single filename or just leave it as empty double-quotes: "")
$realfilenames = "_first.zip, _second.zip";

// Set the name of local download file - this is what the visitor's file will actually be called when he/she saves it
$fakefilename = "bogus_download_name.zip";

// Connect:

// Connect to the MySQL database using: hostname, username, password
//$link = mysqli_connect($db_host, $db_username, $db_password) or die("Could not connect: " . mysqli_error());
//mysqli_select_db($link,$db_name) or die(mysqli_error());
?>

That’s it, you now have a working platform to give someone access to the download. Visit the downloadkey.php page and generate a unique key code, saved into the database, and a URL is printed out that you can copy and paste into an email or to whatever you want it to be shared (e.g. social media). You can see below it in action, in a gif file below.

One thought on “Hello world! An update to a Php script “protecting multiple downloads using unique URLs” as a means to share Copyrighted Materials”

Leave a Reply

Your email address will not be published. Required fields are marked *