views:

574

answers:

7

Hi, I'm currently creating a website for a client that will basically involve selling various files. This is obviously a really common thing to do, which is making me feel kind of foolish for not thinking of a method for doing it.

Once the purchase has been made the customer should be taken to a page containing the download link, as well as receiving emails that contain a download link and an email with information about an account that will be created for them (they will also be able to download from their account's control panel). What I'm trying to figure out is how I can hide/obscure the file's location on my server so that one person who buys it can't simply copy and paste the direct link to the file elsewhere. Even if I make the request to download a file a link of the format http://example.com/blah/download/454643, a URL which does not correspond to the actual location of the file, I think it might still be possible to locate the file on the server? I don't really understand too much about how permissions work on my server, which is why I ask. Thanks in advance :)

A: 

A lot of download urls I've seen that are based on purchase tend to use some guid and other dynamic information as part of the url to not make it as simple as guessing one id. You could end up with guid/datetimepurchased/id or something like that as part of the path.

An additional option would be to ensure the user is logged in before allowing the download to proceed which would given an additional layer of security.

Duncan
A: 

Well, first, you definitely don't want to link directly to the file. You'll probably want to send the user a link to a service you've created (or even just a page) with a generated id argument that results in a file download, if certain criteria are met.

Those criteria are difficult, really, as you need to allow the user to download the file more than once (in case he fails to download the complete file the first time, or deletes it accidentally, etc.), but once a link works, it works until you kill it.

I'd suggest either using time or IP to filter your download requests.

Time: When someone purchases the file from you, inform them that they'll be able to download the file for 1 day only, or some such. Yes, other people can download the file during that day, but only for 1 day. You could also put a download limit on this, so they can only download it 5 times (this is normal).

IP: When someone purchases the file from you, inform them that they'll only be able to download the file from that IP. Your download service can certainly check this when they attempt to download the file.

It seems both are easily usable at the same time, as well.

In either case (or both), be prepared to handle customers who didn't download the file in time, or want to get it again after the time limit (or from another computer/IP (some people don't get static ones)). They won't want to pay again, and probably shouldn't have to.

Bill James
+6  A: 

You basically don't give the users the direct URL to the file. Server based permissions have nothing to do here.

Say you have the required file(s) saved in /data/files/file.pdf (good practice to store files out of your web root). You can provide the users a link to download which looks something like /download.php?auth=32

When a user clicks the link, download.php will check if the session/cookie is authenticated and if the download id is valid (in case you have time based download expiry) Then download.php will read the required file from its location and send it to the browser with appropriate headers to force download.

Gaurav
+1  A: 

You can have the URL be an authorization code for the buyer. You get her to log in again, check which file the code is for, then pipe the file to her. Here is an exemple of PHP code from osCommerce (I wrote that a long time ago).

// Now send the file with header() magic
  header("Expires: Mon, 26 Nov 1962 00:00:00 GMT");
  header("Last-Modified: " . gmdate("D,d M Y H:i:s") . " GMT");
  header("Cache-Control: no-cache, must-revalidate");
  header("Pragma: no-cache");
  header("Content-Type: Application/octet-stream");
  header("Content-disposition: attachment; filename=" . $downloads['orders_products_filename']);

  if (DOWNLOAD_BY_REDIRECT == 'true') {
// This will work only on Unix/Linux hosts
    tep_unlink_temp_dir(DIR_FS_DOWNLOAD_PUBLIC);
    $tempdir = tep_random_name();
    umask(0000);
    mkdir(DIR_FS_DOWNLOAD_PUBLIC . $tempdir, 0777);
    symlink(DIR_FS_DOWNLOAD . $downloads['orders_products_filename'], DIR_FS_DOWNLOAD_PUBLIC . $tempdir . '/' . $downloads['orders_products_filename']);
    if (file_exists(DIR_FS_DOWNLOAD_PUBLIC . $tempdir . '/' . $downloads['orders_products_filename'])) {
      tep_redirect(tep_href_link(DIR_WS_DOWNLOAD_PUBLIC . $tempdir . '/' . $downloads['orders_products_filename']));
    }
  }
Christian Lescuyer
OMG - tep_ is that the evil osCommerce? ;)
Till
+2  A: 

If you have access to running Lighttpd, you should definitely check out the Mod_SecDownload module. I've used this on a previous project that sold video and image files securely.

As the webserver doesn't know anything about the permissions used in the app, the resulting URL would be available to every user who knows the URL.

mod_secdownload removes this problem by introducing a way to authenticate a URL for a specified time. The application has to generate a token and a timestamp which are checked by the webserver before it allows the file to be downloaded by the webserver.

The generated URL has to have the format:

<uri-prefix>/<token>/<timestamp-in-hex>/<rel-path> which looks like "yourserver.com/bf32df9cdb54894b22e09d0ed87326fc/435cc8cc/secure.tar.gz"

<token> is an MD5 of

  1. a secret string (user supplied)
  2. (starts with /)
  3. <timestamp-in-hex>

As you can see, the token is not bound to the user at all. The only limiting factor is the timestamp which is used to invalidate the URL after a given timeout (secdownload.timeout).

leek
+1  A: 

Here is sample code of what I have done for something quite similar:

// $mimeType is the mime type of the file
header('Content-type: ' . $mimeType);
// this will get the size of the file
// (helps for giving the size to the browser so a percent complete can be shown)
header('Content-length: ' . (string) (filesize($path)));
// disposition is either attachment (for binary files that can't be read by the browser) 
// or inline (for files that can be read by the browser
// some times you have play with this to get working so users get the download window in all browsers
// original filename is the name you want to users to see 
// (shouldn't have any special characters as you can end up with weird issues)
header('Content-Disposition: ' . $disposition . '; filename=' . $originalFilename);
// the next 2 lines try to help the browser understand that the file can't be cached
// and should be downloaded (not viewed)
header('Pragma: Public');
header('Cache-control: private');
// this will output the file to browser
readfile($path);

You can of course add to this any login checking and logging to ensure it isn't downloaded too many times.

Also, as said earlier, ensure you put the file outside (or above) the web server document root so people can't figure out the path. Or you could even put a password on the directory so only internal people could access the file list more easily, but wouldn't recommend this. (Only do this if you can put something outside the doc root.)

Darryl Hein
+2  A: 

Store the files out side your web root, but then be sure that the folder you store them in is in the "open_basedir" directive in your php.ini file, this will allow you to access them from a PHP script. Storing them outside the web root means that they wont ever be directly accessed via HTTP.

Have a PHP script, like the ones listed in these answers that can stream/read out a file. If its a large file you may need to change the "max_execution_time" to account for the extra time the script will take to read out the file. This script will allow you to authenticate the user and check that they have paid for the file.

Put a .htacces in the folder with the script that rewrites the file requested from that folder to a variable. That makes it appear as though they are directly accessing the file while that are not. Personally i would only put the single script in this folder just to keep things simple. So:

http://www.yourdomain.com/files/expensive_song.mp3

actually rewrites to:

http://www.yourdomain.com/files/download_file.php?filename=expensive_song.mp3

Good luck.

cole