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;
}
?>
