• Status: Solved
  • Priority: Medium
  • Security: Public
  • Views: 162
  • Last Modified:

Security for Downloadables

Hello Experts!

I thought I'd tap into this great pool of genius to find out what the latest in technology is with relation to security of downloadable items.

A client of mine wants me to create a site in PHP where he can sell ebooks, videos and audio files and I wanted to know what is the best way to prevent the download of these items from off the server unless the person has paid.

Extra points to the person who can share a personal success story and how he did it.

Thanks!
0
OmniUnlimited
Asked:
OmniUnlimited
  • 3
  • 3
  • 2
  • +2
2 Solutions
 
gr8gonzoConsultantCommented:
I've created this setup several times. The methodology is secure and sound, so it hasn't evolved much over the past few years. The basic idea is done in 3 basic steps:

#1. Never keep the actual files in a web-accessible directory.

#2. Use a PHP script to access the file contents from outside the document root, and then stream it to the browser (combination of header() and readfile() lines - about 10-15 lines of code).

#3. Use the script to validate the user's permissions to access the requested file.

The bulk of the work is in step 3, because the "validation" varies per project. The most common and easy method is to have a database backend that gets updated whenever a user makes a purchase of an item, and the script simply checks the user ID when they're logged in to see if they purchased the item. If so, it streams the file. If not, it rejects with a message to indicate they need to purchase the item first.

If you want to be ultra-safe about it, then you generate a random token when the person buys the item. This token is good for one use. When they click on a link that passes the token to the download script, the download script first marks the token as used and then streams the file. This prevents the end user from sharing his credentials and letting others log in and copy the file, too.

However, all this does is handle the server-side permissions. Trying to prevent the file from being physically copied and distributed after it's been downloaded is a different story and that gets FAR FAR more complicated.
0
 
OmniUnlimitedAuthor Commented:
Wow gr8gonzo, what fantastic information!  To be honest with you, this is my first time, so be gentle. :)

What command would you recommend using to access a non-web accessible file?
0
 
gr8gonzoConsultantCommented:
There is a PHP command called readfile(). All it does is take in a filename as a parameter and then pushes all the data back to the browser.

Try creating a script like this to see how it works.

<?php
$file = "/path/to/some/file.txt";
$filename = basename($file);

if(file_exists($file))
{
	// Send download headers
	header('Pragma: public'); 
	header('Last-Modified: '.gmdate('D, d M Y H:i:s') . ' GMT'); 
	header('Cache-Control: no-store, no-cache, must-revalidate'); // HTTP/1.1 
	header('Cache-Control: pre-check=0, post-check=0, max-age=0'); // HTTP/1.1 
	header("Content-Transfer-Encoding: none");
	header("Content-Type: application/octetstream; name=\"" . $filename . "\""); // IE & Opera 
	header("Content-Type: application/octet-stream; name=\"" . $filename . "\""); // The rest  
	header("Content-Disposition: attachment; filename=\"" . $filename . "\"");
	header('Content-Disposition: inline; filename="' . $filename . '"'); 
	header("Content-length: " . filesize($file)); 
	
	// Send file contents
	readfile($file);
}
else
{
	echo "Invalid request";
}
?>

Open in new window

0
Network Scalability - Handle Complex Environments

Monitor your entire network from a single platform. Free 30 Day Trial Now!

 
Ray PaseurCommented:
No points for this, please, but @gr8gonzo has the right answer.  And if you want to save a lot of money and time, hire him to build it for you!
0
 
OmniUnlimitedAuthor Commented:
Wow gr8gonzo, to get an endorsement from Ray must mean you really know what you are talking about.  Course, I could get the gist of that from the response you gave to my question.

@Ray, thanks for your always succinct comments, and you are absolutely correct.  If I want the job done right, I probably should hire gr8gonzo to do this for me.  But I am a curious, and very hands on type of person.  I want to know why things work, and so the only way I can learn is by asking and by trial and error.  I'm grateful to gr8gonzo for such great help.

@gr8gonzo, your code is freaking fantastic!!!  I attempted a test on a php file I stored in a web inaccessible section of the server and your code output its contents to the screen.  How would I do it if I wanted the browser to allow the user to download it to their computer?
0
 
Marco GasiFreelancerCommented:
No points for this please. The only two differences I see between my code and the code posted by gr8gonzo are

1 - I don't use the line header('Content-Disposition: inline; filename="' . $filename . '"');
2 - I use basename: header ("Content-Disposition: attachment; filename=" . basename($file));

I'm not an expert about headers, but I suspect that Content-Disposition: inline make the file content to be printed on the screen.

Cheers
0
 
Ray PaseurCommented:
If you want to force a download, this code will almost always work perfectly.  But you still need to test it on all browsers.
<?php // RAY_force_download.php
error_reporting(E_ALL);


// DEMONSTRATE HOW TO CAUSE A FILE DOWNLOAD


// REQUIRED FOR USE WITH THE PHP date() FUNCTIONS
date_default_timezone_set('America/New_York');

// A FILE TO DOWNLOAD - THIS LINK COULD COME IN THE URL VIA $_GET, OR COULD BE GENERATED INSIDE THE SCRIPT
$url = "http://www.LAPRBass.com/piechart.png";

// THE USE CASE FOR THE FUNCTION
force_download($url);



// FUNCTION TO FORCE A DOWNLOAD FROM A FILE
function force_download($filename)
{
    // GET THE CONTENTS OF THE FILE
    $filedata = file_get_contents($filename);

    if ($filedata)
    {
        // GET A NAME FOR THE FILE
        $basename = basename($filename);

        // THESE HEADERS ARE USED ON ALL BROWSERS
        header("Content-Type: application-x/force-download");
        header("Content-Disposition: attachment; filename=$basename");
        header("Content-length: ".(string)(strlen($filedata)));
        header("Expires: ".gmdate("D, d M Y H:i:s", mktime(date("H")+2, date("i"), date("s"), date("m"), date("d"), date("Y")))." GMT");
        header("Last-Modified: ".gmdate("D, d M Y H:i:s")." GMT");

        // THIS HEADER MUST BE OMITTED FOR IE 6+
        if (FALSE === strpos($_SERVER["HTTP_USER_AGENT"], 'MSIE '))
        {
            header("Cache-Control: no-cache, must-revalidate");
        }

        // THIS IS THE LAST HEADER
        header("Pragma: no-cache");

        // FLUSH THE HEADERS TO THE BROWSER?
        flush(); // NOT SURE THIS IS NECESSARY

        // CAPTURE THE FILE IN THE OUTPUT BUFFERS - WILL BE FLUSHED AT SCRIPT END
        ob_start();
        echo $filedata;
    }

    // ERROR
    else
    {
        die("ERROR: UNABLE TO OPEN $filename");
    }
}

Open in new window

You can wrap that script with some kind of PHP authentication, perhaps integrating a data base that contains a record showing that the client has paid for the download.  You might use a design that is sort of like the "handshake" in this article.
http://www.experts-exchange.com/Web_Development/Web_Languages-Standards/PHP/A_3939-Registration-and-Email-Confirmation-in-PHP.html

There is another issue that may or may not matter to you, and that is the issue of copyright and redistribution.  Because it is so easy to take electronic information and distribute it, enforcement of online electronic copyright is essentially impossible.  Thus we resort to shaming the thieves.  The general design pattern I've seen for this includes adding the following information to the document:  The source URL, date and time of download, the identity of the individual who performed the download, and the statement of copyright, along with a URL link to report theft and abuse.  In the case of images, this information is often put into a watermark.  With PDF documents, it's usually put into the document footer on every page.  With video files it may be in the introduction or trailer and is usually repeated at some point in a semi-transparent watermark.  I have no idea how to do it with pure audio, so I would probably just make a video.
0
 
gr8gonzoConsultantCommented:
Yes, my apologies. marqusG is correct. I copied and pasted that code from another script that displays PDFs inline in the browser (and other valid types), and prompts downloads for other kinds of files, but I accidentally copied that "inline" line along with it. Take it out and it should prompt you for a download.

I appreciate the hiring comment, but I completely understand the DIY sort of mentality that most people here have. (My schedule's a bit booked anyway!)

I'm actually packing for a trip so I gotta run, but wanted to chime in and concur with marqus's comment. Also, marqus mentioned basename() but that is already performed on the second line of code. I think most of the veterans here have the same approach - nothing terribly unique about my code. It's just tried and true. :)
0
 
Marco GasiFreelancerCommented:
lol @gr8gonzo: sometimes I'm really blind: I just didn't see it! :-)

Bye.
0
 
Slick812Commented:
@  OmniUnlimited
if you're doing a commercial "pay for a file" kind of thing, my hint for you, which may save you some head ache at first, is to have a user friendly ERROR recovery for unsuccessful paid file retrieval, as one example here just has -
echo "Invalid request";
if there is no file, easy to guess that the paid user will be six shades of angry, OR having a -
"please contact administrator about error"
won't be much better,
you might consider an internal error alert, ,  so if they do not get their file, or have other system (dumb user) generated trouble, your site techno would be alerted. Also some marketing language (sales oriented) for error messages are a great help -

"Our system can't even find this file, we're not bringing you what you paid for, So Sorry!
Our Geek Techies have been alerted to get you what you paid for, If you need to contact us
about this big blunder, please click here -> HELP!"
0
 
OmniUnlimitedAuthor Commented:
My apologies to everyone for the delay.  Between the holidays and some system failures, I wasn't able to respond until now.

Thank you to all who participated.  gr8gonzo, your solution is absolutely fantastic, it works like a charm.  Thank you for the code.

Here's wishing the very best and prosperous New Year to everyone!
0
Question has a verified solution.

Are you are experiencing a similar issue? Get a personalized answer when you ask a related question.

Have a better answer? Share it in a comment.

Join & Write a Comment

Featured Post

Managing Security Policy in a Changing Environment

The enterprise network environment is evolving rapidly as companies extend their physical data centers to embrace cloud computing and software-defined networking. This new reality means that the challenge of managing the security policy is much more dynamic and complex.

  • 3
  • 3
  • 2
  • +2
Tackle projects and never again get stuck behind a technical roadblock.
Join Now