I previously posted here:
Controlling Access for Trial Subscription
Since this is a new question based on suggestions there, I thought I should start a new post. If this should have been an edit, please let me know for the future.
I think the solution I'm going with to control access will be to upload a file and hash the name. The file will be in the format:
/uploads/#############.pdf
A link will be sent to subscribers. The first time they come to the site, they'll be asked to create a "pin" and a "hint" to remember. Then can then access a landing page to list their items via an email address/pin combo.
My question is: I know I can control the access to the page that shows what items they can view, but is there a way to control the /uploads/[file] to only be download-able after some kind of programmatic check? I can't think of any way to do this....
Thanks again.
D.
Your pdf files don't have to be in a user viewable directory. They can be outside your web root. That way, noone can actually browse to www.yoursite.ext/uploads/2395wrfhgt.pdf to download it himself or share the link with others.
In order to download the pdf you'll have a dedicated script that will do all the access checks on the user that's requesting it, and if all ok it will set the appropriate headers, read the file from the filesystem, and print it out to the user.
So, lets say your site is at /var/www/site/htdocs/ and you upload every pdf into /var/www/site/uploads/ . You don't even need to hash the filenames but instead can keep them nicely named for easy organization.
Then, all the links to download a file will be made to point to www.yoursite.ext/download.php?id={fileid}
Your download.php will do all the access checks (properly logged in, has permissions to download the file etc), and then do the following:
$pathToPdf = '/var/www/site/uploads/some.pdf' ;
header('Content-Type: application/pdf');
header('Content-Length: ' . filesize($pathToPdf));
header('Content-Disposition: attachment; filename=' . 'some.pdf');
readfile($pathToPdf) ;
And that's pretty much it. Once you get this working you can look into improving a few things:
use mod_rewrite or similar to have the actual pdf filename in the link, which helps some browsers realize they should download it: www.yoursite.ext/download/{fileid}.pdf
consider using the web server instead of php to serve files, eg X-Sendfile header
Related
I am deploying a website from where user can purchase the pdfs. now i am searching for the way for storing the pdfs so that it only can be downloaded when payment is done.
I have came across one way in which i can store the pdfs in to the Mysql database and generate the path to it when required credentials fulfill.
Is there any other way to do this and link to the pdf file should be dynamic and encrypted so that other links to the other books can't be predicted.
and the server side language I am using is PHP
You need to store the files somewhere outside your website root like mentioned by Dagon. When file is uploaded use move_uploaded_file to move it. You can name the file anything you want (within OS limits) and keep the real name in the database.
Then when the user has payed for the books, add the books the user has payed for to a table in a db.
Give the user a list of all the books he has payed for like: /download/filename.pdf
Add a mod_rewrite if you use Apache (or equivalent for other web servers) where /download/.* is redirected to download.php or a controller.
On the download page, check if user is logged in and has access to the file. If not, redirect to purchase page for that book.
If download is ok set header for the http status you need: Content-Length, Content-Type, Date, Status (200), maybe Content-Encoding.
Use readfile to output the file to the end user.
I would :
Deny any access to the files -- i.e. use a .htaccess file (That way, no-one has access to the file)
Develop a PHP script that would :
receive a file identifier (a file name, for instance ; or some identifier that can correspond to the file)
authenticate the users (with some login/password fields), against the data stored in the database if the user is valid, and has access to the file (This is if different users don't have access to the same set of files), read the content of the file from your PHP script, and send it the the user.
The advantage is that your PHP script has access to the DB -- which means it can allow users to log-in, log-out, it can use sessions, ...
Here is another answer from a stack user that fits this problem: Creating a Secure File Hosting Server for PDFs
is there any other way to do this and link to the pdf file should be dynamic and encrypted so that other links to the other books can't be predicted.
The best way, is after payment generate a key to the file.
create a page like this www.site.com/download.php?key=key (and here you don't need to have id of the book, because by the key you can check on the database what is the book the customer purchased.
inside the download.php read the key, query the database to find which file is linked with the key
read the file, and send it to the customer. This is, if the key is valid, you will send the php headers as content type as being pdf, and (the php code) read the file in binary and send it in the message body.
I hope this code helps
<?php
// We'll be outputting a PDF
header('Content-type: application/pdf');
// It will be called downloaded.pdf
header('Content-Disposition: attachment; filename="downloaded.pdf"');
// The PDF source is in original.pdf
readfile('original.pdf');
?>
This question already has answers here:
how to hide the actual download folder location
(3 answers)
Closed 8 years ago.
I'm trying to make the URL of a downloadable PDF document invisible to the user, so that they can't access it from anywhere else. I need to hide the URL from the bottom left of the page (when they mouse over) and the URL from the browser's address bar when they open it. I need it to work on all browsers.
My HTML looks like this:
View PDF
And the link should look like this:
View PDF
The reason is the user must provide a code to be able to download their document, but if they can see the URL they could easily download someone else's documents (They only have to change a digit in the "a34501.pdf" part).
I read something about using a JavaScript function to encrypt the URL, or use an external PHP file. However, I don't know how to do that.
Thanks.
Hiding the url will baffle the least tech savvy users, but not anyone who is willing to download your files and have a very minimal tech knowledge, if you need to hide your files behind a code (or pay wall) you can use a PHP script that authenticates the user and spits out the corresponding file, a small example is like this:
if($validUser)
{
$path = $fileName;
$size = filesize($path);
$fp = fopen($path, "rb");
$content = fread($fp, $size);
fclose($fp);
header("Content-length: ".$size);
header("Content-type: application/octet-stream");
header("Content-disposition: attachment; filename=".$fileName.";" );
echo $content;
}
exit();
This assumes you have the files physically in the server, but you can modify it if you have them in a database or any other storage medium. Of course, you must first validate if the user have the right to download that file but this is up to you.
You can use a php script to provide the document, while still allowing php to authenticate the user's session information/etc.
The process goes like this:
User enters a unique code (after additional authentication required to validate the user).
A unique document link is generated, such as: http://domain/download.php?file=58afg71057ga82157 (example)
download.php validates the user request against stored session information -- if everything checks out, it sends the file header() and passes along the file contents.
This basic file download tutorial provides the very basics of providing a file in this way. You will need to improve upon this basic tutorial, but it should give you an idea of how the process works.
Suggestions:
Use a unique "key" per user (allowing the same user to re-download); or,
A single-use key which only allows a single download, ever; or,
Require user authentication, so that you know whether they should be "allowed" to use the key.
Do not use a "filename.ext" to locate the file to download, either store the name in the session or use a unique identifier stored in a database.
Don't just copy paste an example scripts, they are often extremely insecure.
With PHP, I am developing a script that generates a contract once the script is validated.
The contract is a pdf document generated with TCPDF and I save it to the server in a subdirectory with the user's ID. For example, 'contracts/132/1.pdf' would be bill #1 of user with ID 132.
However, I want only user 132 to be able to access that file, because it contains personal information. How can I limit the access to pdf documents in each subfolder to their respective user (using php or htaccess, whichever works best - I'm not very familiar with htaccess)?
The easiest way is probably just to have a PHP script that requires the existence of a valid session (like the generator script does), whose function is to readfile("/path/to/contract.pdf");
That way, you can have your PDF wrapper script verify that the contract being downloaded is the RIGHT contract for the person in the sesion, not just that it's a contract that is in the directory.
The problem with a .htaccess-based solution on the directory is that anyone with read access to the directory can download ANY contract.
Given a URL like http://example.com/contract.php?user=132&bill=1 you could:
<?php
$user = $_GET['user'];
$bill = $_GET['bill'];
# do input validation on $user and $bill. No really, do it.
if ($user != $_SESSION['user']) {
die("Security error; the black choppers are on their way.");
}
header("Content-type: application/pdf");
header('Content-Disposition: attachment; filename="Contract-$user-$bill.pdf"');
readfile("/path/to/pdfspool/$user/Contract-$user-$bill.pdf");
The if () chunk in the middle verifies that the $user being requested is valid for the current user. Obviously, you'll want to store $_SESSION['user'], probably when this user first logs in.
Of course, you don't NEED to keep spool files, really. If the process of generating a PDF isn't going to overwhelm your web server (and if it does you have other problems), it may just be easier to re-generate each PDF from scratch, on request. That's what I do with company invoices now, and each invoice gets a 6 point footer saying when it was generated and by a request from what IP address. :)
First of all, you'd place the PDF files outside of the site's web root and retrieve them via a script.
This can be made seamless by combining a Mod_Rewrite rule that passes requests to where the PDFs would be retrieved from to a PHP script that gets user and document IDs from the URL and performs access control, which if it succeeds, outputs the file's contents.
See the PHP Manual for details on how to perform HTTP-level authentication using PHP code.
EDIT: We allow our product download after submitting a simple form.
To avoid the owners of "illegal websites" learning where our product downloads are located, I was thinking about renaming the download folder after every download.
How can I do this in PHP?
Instead, verify an authenticated session and then stream the download via PHP.
Then if any illegal website tries to download your file, they will find they need to be authenticated first.
Don't rename the directory. It will break if multiple users download the file simultaneously. Instead serve file via PHP like:
<?php
// We'll be outputting a PDF
header('Content-type: application/pdf');
// It will be called downloaded.pdf
header('Content-Disposition: attachment; filename="downloaded.pdf"');
// The PDF source is in original.pdf
readfile('original.pdf');
?>
Yes, - you can do it by using rename PHP function, but what a strange approach? You better customize access settings for files which can be accessed via HTTP request and which can not. Let just registered users download files and identify real users from robots using user session cookies and, probably, add captcha if those file are so significant.
A possible solution might be to do a PHP system call to create a temporary softlink to your installer, and remove that softlink after a fixed amount of time.
But indeed, it seems you've got the wrong idea when it comes to safeguarding your installer.
A good way is to use a combination of .htaccess & key
.htaccess
RewriteEngine On
RewriteCond %{SCRIPT_FILENAME} !-d
RewriteRule ^([^\.]+)/([a-z0-9]+/i)$ serve.php?file=$2&key=$1 [NC,L]
After someone purchases your file set a key and ip in there customer account in mySQL send them the link to download via email (http://yoursite.com/key1234567/your.pdf) note they can only download once
person clicks download
then with serve.php
you check the key against there ip in there account and mark as downloaded
sql check on ip and key match
if($allowedToDownload===true){
header('Content-type: application/pdf');
// It will be called downloaded.pdf
header('Content-Disposition: attachment; filename="downloaded.pdf"');
// The PDF source is in original.pdf
readfile('hidDen_ArEa/'.basename($_REQUEST['file']));
}
Nothing personal, but it's not most clever idea.
Check $_SERVER['HTTP_REFERER'] - if this variable is not empty and not contains address of pre-download page, redirect user to some your pre-download page.
edit: checking HTTP_REFERER is not 100% protection against bots, but other methods (such as authentication) can be more annoying for users.
Also, sessions or cookies (with temporary token) can be used.
Best solution so far is the following: http://www.devshed.com/c/a/PHP/Simple-and-Secure-PHP-Download-Script-with-Limits-Tutorial/.
Thanks to all of you for directing me to this approach.
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 :)
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.
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.
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
a secret string (user supplied)
(starts with /)
<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).
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']));
}
}
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.)
Some webservers, such as Lighty and Nginx, implement the X-Sendfile header. Let's say you have a Django app, you can have your view return an X-Sendfile header which points to the file you want to ship out. lighttpd will then serve that file instead.
The file can be in a non web-accessible location (this is not a 301 redirect) and because your app is serving the header, you can do authorisation first.
This is much better than serving static files from your application. The webserver is optimised for static files and will be faster, and much lighter on resources. If you're handling more than a few requests you should consider using X-Sendfile.
There's quite a good blog post about it here:
http://blog.zacharyvoase.com/2009/09/08/sendfile/
Lighttpd/PHP instructions can be found here:
http://redmine.lighttpd.net/wiki/1/X-LIGHTTPD-send-file
NGINX instructions can be found here:
http://wiki.nginx.org/XSendfile
There also appears to be an early release Apache mod that does the same thing:
https://tn123.org/mod_xsendfile/
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.
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.