How to Allow a User to Download a File Which Is Stored Outside of the Webroot

Allow users to download files outside webroot

Use

  • a symlink pointing to /var/uploads (tutorial here)

  • a Apache Alias directive Alias /uploads /var/uploads (must be in httpd.conf)

  • or a proxy PHP script that accepts a GET variable filename=upload.jpg and fetches the file e.g. using fpassthru()

the latter is the least preferable option because it is resource intensive, but sometimes it's the only alternative. It also needs proper securing to prevent an attacker from getting other files on your server through the proxy.

Downloading files outside the webroot

After much trial and error, I appear to have solved the problem.

The issue was in using jQuery/Ajax.

When I changed the way the downloadscript.php file is accessed to a direct $_GET request from the link on the page, it worked a treat.

Anyway, thanks for your help everyone!

Chris

Using php and jQuery to allow authenticated users to download a file outside apache's webroot directory

OK, after some more research and a lot of trial and error I got this to work, the code now looks like this:

Client Side (jQuery/ AJAX)

function(file) { 

$.ajax({
type:"POST",
url:"getFile.php",
data:{fbpath: file},
success: function(data, textStatus, XMLHttpRequest) {
var popup = window.open();
popup.document.write(data);
}

});
});

Server Side (getFile.php)

<?php

$filename = $_POST['fbpath'];
$path = $filename;

header('Content-Description: File Transfer');
header('Content-Type: application/force-download');
header('Content-Disposition: attachment; filename='.basename($path));
header('Content-Transfer-Encoding: binary');
header('Expires: 0');
header('Cache-Control: must-revalidate');
header('Pragma: public');
header('Content-Length: ' . filesize($path));
ob_clean();
flush();
readfile($path);
exit;

?>

In the browser, the User will get a popup window containing the contents of the file.
This is still not a very pretty solution: The user won't get a nice download prompt, he is thus confronted with the binary data in a new browser tab. Not exactly what I hoped for, and on top of that, it's ugly!

Some more reading revealed that jQuery/ AJAX need some more handling to provide a user- friendly file download. It seems that John Culviner encountered this issue a while ago ("You can certainly use an XMLHttpRequest object to download a binary (or otherwise) file but there is nothing you can do to the response to somehow get it saved on the user’s computer.") and has a nice and handy solution approach for this.

In terms of getting a file out of a safe location and getting it to the client's browser I consider this question answered however.

I hope this helps anyone else in the future!

Mike

File Uploads and Downloads when files are stored outside of the webroot? (PHP)

Concerning (1):

Theory is: You store the file somewhere outside the webroot to prevent direct access and to prevent execution on the server side. You have to be able to find it, when the user provides the correct parameters. (However, you should be careful, how do the files get presented to the user? A database could be helpful here) If security is a concern, you have to make sure that a user cannot access files that he should not have access to (when he has the right download link but doesn't have permissions for example, because so far your download links don't seem to be very cryptic).

The script you mentioned in your question is quite alright, although I'd probably just use fpassthru instead of the feof-fread-echo-loop. The idea here is essentially to figure out the mime type, add that to the headers and then dump the contents into the output stream.

Concerning (2):

Using a database especially with prepared statements is quite safe and provides some extra possibilities. (Like adding some comment to the attachment, a timestamp, filesize, reordering, ...)

You don't check the upload_name, this could very well be ../../your webroot/index.php or something similar. My advice would be to store the files that are uploaded as something unimaginative as "file_ID" and store that id with the original filename in the database. You should probably also remove any leading multiple dots, slashes ("directory") and similar.

Loading bars ... well that's taste I guess.

Upload files outside of webroot

you can upload where ever you want using the move_uploaded_file function, just make sure the webserver can write in the destination directory.

After you have to create a script that would read the file and pass it to the browser so you can make sure user have paid the file.

exemple

<?php
// insert your logic here to verify the user has access to this file.
// open the file in a binary mode
$name = 'yourfile';

$fp = fopen($name, 'rb');

// send the right headers
header("Content-Type: image/png");
header("Content-Length: " . filesize($name));

// dump the picture and stop the script
fpassthru($fp);
exit;

?>

You have to be careful about the content-type also make sure the user cannot every file of your server if you use a $_GET variable for getting the filename.

Where should I allow uploads and downloads to be saved to?

It all depends what you need to be able to do with them once they are uploaded.

Outside the web root is definitely best but if you then need to be able to show those files on webpages, you will need to write a handler or just save them inside the webroot.

Ideally you should never allow untrusted users to upload any file to a server and should certainly never allow untrusted users to upload files into a webroot as they could then use your server to spread malware or upload files that can be executed on the server and take control of your server or site.

You should always ensure that the minimum number of file types possible are allowed for upload so if a user is uploading an image, make sure they can only upload images

Allow users to download when authorised

  • Store your files outside the document root.
  • Have your own custom login mechanism
  • Serve the file with php after you check whether the user is logged in, either with readfile(), or better if it's installed or you can install it, with mod_xsendfile (which takes a load off PHP).


Related Topics



Leave a reply



Submit