I put my files on my VPS and user can direct download all files. but I want to hide my actual file paths and make time limited download links. I googled it and find some solutions but most of them was for files that were on same server and some of them has some coding in VPS side, but i can't write any php code on my VPS because it doesn't support php.
also I try some script that works well but generated link wasn't resumable and didn't show file size until download finished. How can I solve these problems?
You could use mod_auth_token (http://code.google.com/p/mod-auth-token/) apache module, if you are running apache as web frontend.
This is how you can handle the PHP side of the token generation process:
<?php
// Settings to generate the URI
$secret = "secret string"; // Same as AuthTokenSecret
$protectedPath = "/downloads/"; // Same as AuthTokenPrefix
$ipLimitation = false; // Same as AuthTokenLimitByIp
$hexTime = dechex(time()); // Time in Hexadecimal
//$hexTime = dechex(time()+120); // Link available after 2 minutes
$fileName = "/file_to_protect.txt"; // The file to access
// Let's generate the token depending if we set AuthTokenLimitByIp
if ($ipLimitation) {
$token = md5($secret . $fileName . $hexTime . $_SERVER['REMOTE_ADDR']);
}
else {
$token = md5($secret . $fileName. $hexTime);
}
// We build the url
$url = $protectedPath . $token. "/" . $hexTime . $fileName;
echo $url;
?>
If you cant make changes to the actual downloadlinks they will stay available for download until they are deleted from the server.
Of course you can make a Script which encrypts the download URL based on the system time, but once the user calls them within time he gets the decrypted URL from the script.
Related
I'm a sysadmin for a small firm and I manage the server for them.
I've setup a portal for our customers to view their bills in pdf format, they are initially set with 0600 file permissions. For security reasons I cannot have all the pdf's 'visible' to everyone so I need a way to show them to the customer only when a pdf is clicked on the customers' account.
I have tried using the following, but it doesn't work and I'm getting a 'Forbidden' error...
chmod($filename, 0755);
echo "<td><iframe src='" . $filename . "' width=645 height=600 frameborder=0></iframe></td>";
chmod($filename, 0600);
The php script and the pdf files have the same owner set.
Any ideas what I'm doing wrong, I need to get this working?!
Many thanks! :)
This can not possibly work:
chmod($filename, 0755);
echo "<td><iframe src='" . $filename . "' width=645 height=600 frameborder=0></iframe></td>";
chmod($filename, 0600);
You're making the file readable only for the amount of time it takes PHP to echo that one line of HTML. I.e., by the time the user clicks the link, permissions have already been revoked again. On top of that, the file is world-readable for that period of time, so anybody on the Internet can see it.
To do this more securely, do not have the web server serve the files directly, as you will not be able to control who has access to them. Instead, put them outside the document root so that they can not be seen at all by the web server, and then proxy them through a PHP script (via readfile() or similar) that performs an ownership check.
In your script that generates the link:
echo 'PDF Download';
Where $fileId is some unique identifier for the file, but not the full file name.
Then, in download.php, something like this:
function getLoggedInUser() {
// return the logged-in user
}
function getFileForId($fileId) {
// get the full path to the file referenced by $fileId
}
function getOwnerOfFile($fileId) {
// get the user allowed to see the file referenced by $fileId
}
$fileId = $_GET['fileId'];
$file = getFileForId($fileId);
if (!file_exists($file)) {
header('HTTP/1.1 404 Not Found');
exit;
}
if (getLoggedInUser() !== getOwnerOfFile($fileId)) {
header('HTTP/1.1 403 Forbidden');
exit;
}
header('Content-type: application/pdf');
header('Content-Disposition: attachment; filename="whatever.pdf"');
readfile($file);
[UPDATE]
and I have <a href="/viewbill.php?bid=<?php echo $invoice_number; ?>" title="View PDF Invoice"> where the $invoice_number is the name of the file.
That's fine, just make sure that viewbill.php performs a check to ensure that the logged-in user is the same as the user that the bill is for, otherwise any customer can view any other customer's bills simply by changing the invoice number in the URL.
When you say 'put them outside the document root' where do you mean exactly;
Let's say that your Apache document_root directive points to /var/htdocs/public/. In this case, everything in that directory and everything under it can be seen by Apache and potentially served directly to a client. E.g., if you have a PDF file in /var/htdocs/public/pdfs/12345.pdf then a user can simply request the URL /pdfs/12345.pdf in their browser, regardless of what PHP structures are in place. Often this is mitigated with the use of .htaccess files but this is not ideal. So, if you have files that you want to keep controlled, you should not put them anywhere under the document_root. For example, put them in /var/htdocs/docs/ instead. This way, Apache can not possibly see them, but you can still use readfile() to pull their contents.
I am using drupal as a back end.
in drupal I get some .pdf files. and then zip them using drupal zip archive. then saving this archive in my server tmp folder, i get the tmp folder using php sys_get_temp_dir()
now...
what should i return to the front end (Angular) so that the user can download this folder..
this is an example code i used for zipping:
$nodesIds = [1024, 1023, 1022]; // those are just some example ids, i get the real ids from the front end post request.
$zipName = $this->generateUniqueName();
$zip = new \ZipArchive;if(!$zip->open(sys_get_temp_dir() . '/' . $zipName, constant("ZipArchive::CREATE"))) {
return new JsonResponse('could not open zip');
}
foreach ($nodesIds as $id) {
$node = \Drupal\node\Entity\Node::load($id);
$uri = $node->filed_file->entity->getFileUri();
$name = $node->field_file->entity->id() . '_'. $node->field_file->entity->getFilename();
$url = $file_system->realpath($uri);
$zip->addFile($url, $name);
}
$zip->close();
i tried returning a link to the zipped folder in the server:
return new JsonResponse(sys_get_temp_dir() . '/' . $zipName);
but i dont know what to do with that from angular.
Also tried to return a stream using symfony:
$stream = new Stream(sys_get_temp_dir() . '/' . $zipName);
$response = new BinaryFileResponse($stream);
a stream and not a file because the user chooses which files to zip.. so they can choose as much as they want.. it might get to even a 100 .pdf files ..
every thing works fine and i get the zipped folder in tmp.. but now what ?
I want to download this zipped folder to the user browser..
but I do not know how!. should I return the zipped folder, then in angular use it as a blob or somehow deal with it and serve it to the user,,
or maybe the right way is to send back the link to its location in the tmp folder,, and in angular i only access that location and somehow get the folder (i dont know if this is even possible due to permissions and security), or is there another better way that I do not know about.
thank you very much.
I would like to download file from remote NAS server, I am not able to force download to client. I am using this function:
public function download(){
// Set IP and port
define("FTP_CONNECT_IP", "xxx");
define("CONNECT_PORT", "21");
// Set username and password
define("FTP_LOGIN_USER", "username");
define("FTP_LOGIN_PASS", "password");
$remote_file = 'ftp://' . FTP_LOGIN_USER . ':' . FTP_LOGIN_PASS . '#' . FTP_CONNECT_IP . '/' .'PathToFile.avi';
$response = $this->response->withFile($remote_file,['download' => true]);
return $response;
}
It starts read something but never browser asks me for download. Please What is wrong?
You cannot use Response::withFile() for remote files, it only works with local files.
If you want to serve remote files, then you either have to temporarily store them on your server, or build a proper download response on your own, using for example CakePHPs callback stream for the response body to output the data manually.
Here's a quick example (doesn't support range requests):
return $this->response
->withType(pathinfo($remote_file, \PATHINFO_EXTENSION))
->withDownload(basename($remote_file))
->withLength(filesize($remote_file))
->withBody(new \Cake\Http\CallbackStream(function () use ($remote_file) {
ob_end_flush();
ob_implicit_flush();
readfile($remote_file);
}));
See also
Cookbook > Request & Response Objects > Response > Setting the Body
I face a case I never did, and I dont know how to properly do it.
I have a php script which generate files for clients. At the end of the script, I echo the path for them to download the file, simply.
How can I do to provide the file - or the path or any what - for downloading it, and be sure to delete the file once downloaded.
Widely, I'd like to make the file available for one/unique download only. How to ?
EDIT
I cannot use headers
There are a few components to getting this to work. Without knowing which framework you use, I'll use comments as placeholders.
There is no way to do it without using the header function, though.
Here is the source for a file that outlines the process:
<?php
$fileid = $_GET['fileid'];
$key = $_GET['key'];
// find the file in the database, and store it in $file
if ($keyMatches) {
// it is important for security to only use file paths from the database
$actualPath = $file->getPathOnDisk();
$fileInfo = finfo_open(FILEINFO_MIME_TYPE);
$mime = finfo_file($fileInfo, $actualPath);
$fp = fopen($actualPath, 'rb');
header("Content-Type: " . $mime);
header("Content-Length: " . filesize($actualPath));
fpassthru($fp);
}
else
{
http_response_code(403); // forbidden
}
You'll use this by linking to download.php?fileid=1234&key=foobar, and generating the URL at the same time you generate the key and store it in the database.
For security, you'll keep the files outside of the web root, meaning they cannot be accessed through the web server without going through a script.
fpassthru is reasonably fast, and will not likely have a performance impact.
You must do a download file gateway, like download.php?id=XXX
Where XXX is the unique ID of each file you will store in DB. And of course, the file to be downloaded.
Then, each time a user will visit the page, you can :
- Check if he has already downloaded the file
- If no, redirect it to the real path of file
- If yes, display 403 message.
When a user download a file, update the DB, generate or copy the file to a new name, you play with headers, and delete file upon download or after a small timeout.
I recently moved a website that was written for a LAMP environment to Windows Server 2008. I've managed to get just about everything working now, but I've got one last problem that I can't seem to solve.
I am letting the admin user upload a photo that will get resized to a large file and small file by the PHP script. Both files are getting uploaded perfectly but the large file won't display and will result in a "500 internal server error" when viewed?
I can log onto the server and open both the small and large file, but only the small file is showing on the website? I've copied the PHP script below but the permissions on both files seem to be the same.
I'm using PHP, IIS7 and Windows Server 2008. Hope someone can help,
Steven.
// only process if the first image has been found
if(isset($image_file)) {
// get photo attributes
$image_filename = $image_file['name'];
$image_temp = $image_file['tmp_name'];
$image_ext = substr($image_filename,strpos($image_filename,'.'),strlen($image_filename)-1);
// validate photo attributes
if(strtolower($image_ext) == '.jpg' && filesize($image_temp) <= 4194304) {
// create custom timestamp
$image_timestamp = date('dmYHis');
// clean up filename
$image_filename = trim(str_replace('\'','',$image_filename));
$image_filename = str_replace('\\','',$image_filename);
$image_filename = str_replace('&','',$image_filename);
$image_filename = str_replace(' ','-',$image_filename);
// set file names
$image_large_file = strtolower($image_timestamp . '-large-1-' . $image_filename);
$image_small_file = strtolower($image_timestamp . '-thumb-1-' . $image_filename);
// image url source
$image_source = $_SERVER['DOCUMENT_ROOT'] . '/images/';
// upload image file
if(move_uploaded_file($image_temp,$image_source . $image_large_file)) {
// resize, save & destroy LARGE image
list($image_width,$image_height) = getimagesize($image_source . $image_large_file);
$image_container = imagecreatetruecolor(420,315);
$image_temp = imagecreatefromjpeg($image_source . $image_large_file);
imagecopyresampled($image_container,$image_temp,0,0,0,0,420,315,$image_width,$image_height);
imagejpeg($image_container,$image_source . $image_large_file,75);
imagedestroy($image_container);
// resize, save & destroy SMALL image
list($image_width,$image_height) = getimagesize($image_source . $image_large_file);
$image_container = imagecreatetruecolor(90,68);
$image_temp = imagecreatefromjpeg($image_source . $image_large_file);
imagecopyresampled($image_container,$image_temp,0,0,0,0,90,68,$image_width,$image_height);
imagejpeg($image_container,$image_source . $image_small_file,100);
imagedestroy($image_container);
}
else
$status = '<h3 class="red">Sorry, but there was a problem uploading one of the images to the server</h3>';
}
else
$status = '<h3 class="red">Please check that all the image size\'s are less than 4MB and they\'re all in JPG format</h3>';
}
I know this questions was asked 4 years ago, but I just ran into this same problem, and thought I'd leave an answer for anyone who may come here later.
I found an answer here, but the basic premise is to modify the permissions of the temp folder that PHP initially uploads into. Allowing the IUSR account read access to the temp folder will allow them to view the file when it hits its final destination. Supposedly IIS7 will grant the permissions from the temp folder to the temporary upload file, which, when moved to your website directory, will keep those temp folder permissions.
Security-wise, you are allowing read access to your temp folder; so if you have sensitive information that ends up there at any point, you may have to find another solution.
A little more information can be found here
I got stuck into the same problem and i think this will help somebody
Right-Click uploads directory / folder and select ‘Properties’
Go to ‘Security’ tab
Click Edit
Select ‘IUSR’ under group or user names
Select ‘Read & Execute’ under permissions for IUSR
Click ‘Apply’ and ‘Ok’
Found this on http://wingedpost.org/2016/07/preventing-500-internal-server-error-uploaded-files-iis-php-sites/