Tracking and Protecting Web-Based Downloads (In PHP)

There’s a consistent need to either protect a file from being downloaded unless a user has been properly authorized/authenticated to access it or to track those files using a JavaScript tracking system like Google Analytics. I ran into this issue a few years ago and came up with what I thought was an elegant solution given the parameters – access those files using a PHP page.

The idea is simple: Send the filename as part of a request to a specified php page (in this case, download.php). That page will perform whatever authorization/authentication checks are necessary, ensure the file exists and – if everything passes muster – immediately begin sending the file to the user’s browser. You can use Google Analytics to track these downloads as well either by using the PHP CURL library or creating a redirect page.

Here’s some generic code to show this in action. Please Note: this code is an edited version from a full set of code. I’ve tried to make this standalone, but I haven’t actually tested it. You’ll want to fill in the blanks and give this a shot yourself – this is not a simple drop-and-go solution.

<?php
	//If you need to confirm that this user is logged in, etc.
	//you should handle the authentication/authorization stuff
	//here

	//Set this to wherever you will be storing the actual files
	define('DOWNLOAD_PATH', '/home/username/protected_files');

	//Format of URL: http://www.mysite.com/download.php?f=
	$filename = $_REQUEST["f"];
	$filePath = DOWNLOAD_PATH."/".$filename;

	//Handle this error however you want.
	if(file_exists($filePath)) {
		trigger_error("Could not find file: ".$filePath, E_USER_ERROR);
		exit();
	}

	$pathParts = pathinfo($filePath);

	header("Server: Apache/PHP", true);
	header("Cache-Control: no-store, no-cache, must-revalidate", true);
	header("Keep Alive: timeout=15; max=89", true);
	header("Connection: Keep-Alive", true);
	//NOTE: mime_type is a custom function - see below
	header("Content-Type: ".mime_type($filePath), true);
	header("Content-Disposition: attachment; filename=\"".$pathParts["basename"]."\"", true);

	//readfile() outputs the given file directly to the buffer which, in this case, goes to the browser.
	if(!@readfile($filePath)) {
		//Handle this error however you want
		trigger_error("Failed to open and read file ".$filePath, E_USER_ERROR);
		exit();
	}

	/**
	 Determines the MIME type of the given file. Requires a valid mime types file.

	 @param $file The file name/filepath fo the file being checked.
	 @returns string a mime-type string for a content header based on the file extension if found, otherwise returns false.
	*/
.
	function mime_type($file) {
		//Replace with the location of your mime.type file (I got mine from Apache)
		$mimeFile = "./mime.type";
		$mimeFound = false;
		$mime = -1;
		$fileInfo = pathinfo($file);
		if(is_file($mimeFile)) {
			if($fp = fopen($mimeFile, 'r')) {
				while ((!feof($fp)) && !$mimeFound) {
					$string = fgets($fp, 4096);
					$tempArray = preg_split("/\s+/", $string);
					//Eliminate comment lines
					if(!(strstr($tempArray[0], '#') == 1)) {
						for ($n = 1; $n < sizeof($tempArray); $n++) {
							if(strtolower($tempArray[$n]) == strtolower($fileInfo["extension"])) {
								//Found the mime type!
								$mime = $tempArray[0];
								$mimeFound = true;
								break;
							}
						}
					}
				}
				fclose($fp);
			} else {
				trigger_error("Cannot open ".$mimeFile, E_USER_WARNING);
			}
		} else {
			trigger_error("Cannot find ".$mimeFile, E_USER_WARNING);
		}
		return $mime;
	}

?>

Leave a Reply

 

 

 

You can use these HTML tags

<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>