Solved

Security for Downloadables

Posted on 2012-12-20
11
150 Views
Last Modified: 2013-01-03
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
Comment
Question by:OmniUnlimited
  • 3
  • 3
  • 2
  • +2
11 Comments
 
LVL 35

Expert Comment

by:gr8gonzo
ID: 38711721
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
 
LVL 17

Author Comment

by:OmniUnlimited
ID: 38711790
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
 
LVL 35

Expert Comment

by:gr8gonzo
ID: 38711795
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
Surfing Is Meant To Be Done Outdoors

Featuring its rugged IP67 compliant exterior and delivering broad, fast, and reliable Wi-Fi coverage, the AP322 is the ideal solution for the outdoors. Manage this AP with either a Firebox as a gateway controller, or with the Wi-Fi Cloud for an expanded set of management features

 
LVL 110

Expert Comment

by:Ray Paseur
ID: 38711814
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
 
LVL 17

Author Comment

by:OmniUnlimited
ID: 38712018
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
 
LVL 31

Expert Comment

by:Marco Gasi
ID: 38712054
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
 
LVL 110

Expert Comment

by:Ray Paseur
ID: 38712833
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
 
LVL 35

Accepted Solution

by:
gr8gonzo earned 475 total points
ID: 38713372
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
 
LVL 31

Expert Comment

by:Marco Gasi
ID: 38713440
lol @gr8gonzo: sometimes I'm really blind: I just didn't see it! :-)

Bye.
0
 
LVL 34

Assisted Solution

by:Slick812
Slick812 earned 25 total points
ID: 38714362
@  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
 
LVL 17

Author Closing Comment

by:OmniUnlimited
ID: 38742761
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

Featured Post

Technology Partners: We Want Your Opinion!

We value your feedback.

Take our survey and automatically be enter to win anyone of the following:
Yeti Cooler, Amazon eGift Card, and Movie eGift Card!

Question has a verified solution.

If you are experiencing a similar issue, please ask a related question

The next five years are sure to bring developments that are just astonishing, and we will continue to try to find the balance between connectivity and security. Here are five major technological developments from the last five years and some predict…
Recently, Microsoft released a best-practice guide for securing Active Directory. It's a whopping 300+ pages long. Those of us tasked with securing our company’s databases and systems would, ideally, have time to devote to learning the ins and outs…
The viewer will learn how to look for a specific file type in a local or remote server directory using PHP.
This tutorial will teach you the core code needed to finalize the addition of a watermark to your image. The viewer will use a small PHP class to learn and create a watermark.

730 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question