This tutorial is aimed at people who have a decent degree of competency in PHP. Do not attempt it if you are only just beginning, there are plenty of learning PHP tutorials out there and I am not going to go much in to that. This tutorial is written for the Linux implementation of PHP. The information may be valid for use on other versions however that is of no concern to the author. I assume you know enough about the linux operating system that you are aware of what a symbolic link is, and that you have some understanding of what hash functions are. If you do not, please look them up or ask a friend before continuing.
Welcome to the first official Tank Software PHP tutorial :). Firstly, I shall explain what file leeching and why many people don't like it. Lets say if I have created a web page about a program on which I let people download that program off my server. Other web pages are also capable of directally linking that file which is on my server, allowing users to download it without going to the page first. This may be bad for several reasons: Firstly, if you have any advertising on the page, you miss out on the hit but you still incur the bandwidth cost of the download, secondly, there may be some vital information about the program or license agreements etc which you wish the user to see and lastly you may in the future update your program (still keeping the old version for historic reasons) and don't want people downloading the incorrect version. This tutorial discusses several ways in which you can prevent this from happening. You can see a finished download script in action that I created using the concepts from this tutorial at Tank Software. The script is part of the Tank Software reX system. NB It is not the intent of this tutorial to give you a download script - more to give you enough information and ideas so that you can create a download script taylor made for your site, and your needs.
One way of preventing leeching is only allowing access to certain files on your server (such as .html, .php etc) unless the user was referred to that file by your site. This is achieved using the HTTP referrer header which is sent everytime you request a document over HTTP. However due to privacy reasons it is possible to get your browser to disable this, and some browsers do not support it. Therefore you may annoy and loose visitors. The second way is to use a download script. Lets say for arguments sake "dl.php". Then you index all of your files (eg. "example1.exe" as '1', "example2.exe" as '2'), then you simply link to your files like so: "dl.php?file=1" and that forwards them to the download. In principle that is how it works, but it is a bit more complicated - you first have to make it so that someone can't just link the file which the download script redirects you to, and that is where this tutorial comes in. Another issue is you may not whant people linking directally to the download script either for the reasons stated above. All these issues can be visited - read on.
How do we create a link which works for a given peroid of time, but then ceases to exist? Well one pretty neat way is using Symbolic Links (Symlinks). So you create a symbolic link to the actual directory containing the file - let the person download it and delete that link. For example: If you have a file "myfiles/example/file.tar.gz" - create a symbolic link in your download directory (for agurments sake /dl/) to point to the "example" directory above - then pass the user the links "dl/symlinkToExample/file.tar.gz" and that will let them download the file. Likewise using that symbolic link they could download any other file in that directory just as if they were in the real directory it self. Then all you have to do is delete that symbolic link - and they can't access it anymore. So how do we do all of this nicly? Well you want the link to change on a periodic basis (we shall use daily) - so one way would be to prepend the day of the month (eg 14) onto the symlink to get "14symlinkToExample" and then every so often get a script to search for any symbolic links which are not current date (eg "12symlinkToExample") and remove them. Of course, only changing the numbers at the front isn't very smart as someone could easily write a script which could generate on a daily basis what your link would be. So, we do some maths to get the link.
So we want our symbolic link to look somthing like this: "14a4h1b" - where the "14" is the current date which we use later when we are removing the old ones, and "a4h1b" is a bunch of seemingly meeningless characters which can be generated form the link. How do we do this? Actually it is quite easy. Look at this function:
function generateSymLink($directory, $offset = 0) {
$day = "";
//todays day of the month, eg "14"
$day = date("d", time() + (24*3600*$offset));
// A string which would look somthing like this "Monday, 14 Jan 2003MySecretKey/myfiles/example"
$tohash = date("D, d M Y", time() + (24*3600*$offset))."My Secret Key".$directory
// The first seven digits of the hash of the above string which would look somthing like
// "a4h1b".
$smallkey = substr(md5($tohash),0,5);
return $day.$smallkey;
}
ref:generateSymLink("myfiles/example");
The offset parameter can be used to generate future or historic symlinks - eg. tomorrow's would be
generateSymLink("/myfiles/example",1); etc.
Ok so we have a nice way of generating our symbolic link directories - but now we need to actually create the symbolic link itself. To clarify - we have a file "myfiles/example/file.tar.gz" and we want to make a link "dl/14a4h1b/file.tar.gz". Lets have some more info - the site we are running is "yourdomain.com" and those links are "yourdomain.com/myfiles/example/file.tar.gz" - which reside on the server in the users's home directory ie. "/home/username/public_html/myfiles/example/file.tar.gz". It is important to note that we are only symlinking the directory of the file not the actual file itself!. Ok, so lets seperate the "myfiles/example/file.tar.gz" into two parts - the directory and the file.
// extracts the file details
$fullpath = "myfiles/example/file.tar.gz";
$seperated = explode("/", $row['Filename']);
$actualfile = $seperated[sizeof($seperated)-1];
$path = substr($fullpath, 0,(strlen($fullpath)-strlen($actualfile)));
ref:$path being just the path and $actualfile
being the file ("myfiles/example/", and "file.tar.gz" respectivly);
Now the easy bit - creating the symlink.
// Gets the current working directory
$wd = getcwd();
// Enters the download directory
chdir("dl") or die("Unable to enter the download directory");
// Creates a new symlink if nessesary
if (!file_exists(getSymLink($path)))
symlink("../".$path, getSymLink($path)) or die("Unable to create download link");
// changes back to the directory of the script
chdir ($wd);
ref:Almost done now - one last thing. You probably want to clean up the old symlinks otherwise you arn't really doing much to prevent hotlinking. Here's how I do it.
// Returns the current day of the month
function getDnum($offset = 0) {
return date("d", time() + (24*3600*$offset));
}
// removes old symbolic links
function cleanUpOldSymlinks () {
// Only execute this code 1/10th of the time (to conserve CPU time).
$randomNumber = rand(0,10);
if ($randomNumber != 6)
return;
// enters the download directory
$wd = getcwd();
chdir("dl") or die ("Unable to enter directory dl");
// Deletes all symlinks that are not current
// As a symlink is only current for a maximum of 48 hours - and due to the
// hashing they are never repeated - this should effectivly prevent leeching.
if ($handle = opendir('.')) {
while (false !== ($file = readdir($handle))) {
if ($file != "." && $file != "..") {
// extracts the date of the symlink
$dateOfLink = substr($file,0,2);
// checks to see if it's current
if (($dateOfLink != getDnum(0)) && ($dateOfLink != getDnum(-1)) && strlen($file) > 2) {
// Removes the symlink
unlink($file);
}
}
}
closedir($handle);
}
chdir ($wd);
}
ref:substr($file,0,2)
would give us those characters. If it isn't current - then delete it.
Now we can create a temporary link of any file. The story doesn't end here however.
Now we want to let the user download a file using a script. For example - if they go to the script:
"dl.php?file=myfiles/example/file.tar.gz" - you could create a link, and redirect them to it.
However letting the user see "myfiles/example/" lets them know what your directory structure is
and they could possibly work out where your file really was and avoid all our anti-leech work.
So we need to do better. One way would be to index all of your files, so they only see an index.
I shall explain a bit how to do this. Instead of the link above - you give them "dl.php?fileid=1"
which you know links to myfiles/example/file.tar.gz" but they don't. So all you have to do is
store these indexes. I shall not spoon feed you and tell you how, but I shall suggest two ways.
1. Have a txt file which lists all of your files - the index being the line number, or 2. the better way
would be to have a mySQL table which lists them (along with other info if you wish like a description).
I shall assume you will be able to do this yourself - and have a method say getFilepathFromID($id)
A usefull method would also be getIdFromFilePath($filepath) - not essential but useful, I shall use it a bit later.
Ok so use your handy getFilepathFromID($id) to get the file path from the ID which is passed in -
then you use the Part 1 info to create the link, now all you have to do is give that link to the user.
There are three ways which I know work.
Firstly, you can just generate a page with the link on it (boring). Seconldy, you can get the page
to redirect to the link after a few seconds (probably prefrable), and thirdly you can redirect to the
link immediatally (good for stuff like images etc).
How? Here's some code:
// Get the path name
$relative_url = getSymLink($path).$actualfile;
// redirect
header("Location: http://".$_SERVER['HTTP_HOST']
.dirname($_SERVER['PHP_SELF'])
."/".$relative_url);
ref:<html> <head> <meta http-equiv="refresh" content="4;URL=<?php echo $relative_url; ?>"> </head> Redirecting... <br /><br />if your browser does not redirect, click <a href="<?php echo $relative_url; ?>">here</a>Now - so long as you always give the "dl.php?fileid=1" as your links and never the file, the user will never know where exactally your file is - and will only ever have temporary links to it. You have now prevented file leeching. There is a little bit more you can do if you wish. Read Part 3.
Ok so nobody can hotlink your file - what about your download script? What if you don't want anyone hotlinking your "dl.php?fileid=1" either. Ok you are a hard customer to please - but I shall present one way of stopping this. Make a function which gives you a unique key (ie. one that changes on a daily basis) - then make sure that key is passed along side the file id - eg "dl.php?fileid=1&key=ab23c". How do you make that key? easy - use the date and hash functions like we did above. Infact you could use the exact method we used above - to get a key like "14a4hbc", that will suffice. Now some very simply code (you will probably want a more pretty error message)
function getDownloadKey($file, $offset) {
return generateSymLink($file, $offset);
}
$file = getFilepathFromID($_GET['fileid']);
$key = $_GET['key'];
if ($key != getDownloadKey($file, 0) && $key != getDownloadKey($file, -1))
die ("invalid download key");
// else - download the file
// ...
You may notice that this also allows keys to be one day old.
Now all you have to do is place the "getDownloadKey" and "generateSymLink" functions in their own php file (eg. dl_util.php) which your download script includes. And include them in any PHP page which links to your download script so that all your links look like this: <a href="<?php echo "dl.php?fileid=".getIdFromFilePath("myfiles/exmaple/file.tar.gz")."&key=".getDownloadKey("myfiles/example/file.tar.gz",0)"; ?>">Download Link</a> If you don't want to repeat the "myfiles/example/file.tar.gz" then just make a getDownloadKey() method that returns the same key for every file on a give day. Preventing hotlinking of the download script like so is probably too much of a pain in the arse for you do by hand, I personally use it for download links that are being generated from data out of a database.
One thing I also have is where I don't mind people hotlinking the download script - if they are only somthing like images or documentation, so I have a check where if the file extention is ".txt" or ".jpg" the download key isn't checked (but the symlink etc are still created).
I hope that by reading this you have some idea of how to use PHP to write a download script and prevent hotlinking and leeching of files. If you have any questions or comments, I direct you to the Tank Ammo programming forums http://tankammo.net/forums/. The most imporant thing however is use your brain and adapt this script to suit your own needs.
Author: William Denniss
Version: 1.0
Last Modified: 10 Jan 2003
Created: 9 Jan 2003