Generating unique download link to download once only - php

I wanna create a few unique download link for my users. The reason is that I wanted to let them download once only, so that they can use back the same link to download again.
I've generate a few of the keys (example, qwertyasdfghzxcbn. As in the download link will be like www.xxxxx.com/download.php?qwertyasdfghzxcbn) in the database and flag field where when the user downloaded, it will update 1 to the flag field.
I did a search on the net and found this.
http://www.webvamp.co.uk/blog/coding/creating-one-time-download-links/
But that only works when you go to the page first then only the page will generate the unique link. I've already pre-generate the link inside my database, I don't need to regenerate again, if fact if I generate the key when user go the page, they will able to download multiple times by refreshing the page.

The solution would be to make the link target itself a PHP script.
You'd hide the actual file somewhere inaccessible from the browser (i.e., somewhere where you can reach the file via fopen(), but isn't within the document root), and put a download.php file to download files.
The download script itself would look something like this:
$fileid = $_REQUEST['file'];
$file = file_location($fileid); // you'd write this function somehow
if ($file === null) die("The file doesn't exist");
$allowed = check_permissions_for($file, $fileid) // again, write this
// the previous line would allow you to implement arbitrary checks on the file
if ($allowed) {
mark_downloaded($fileid, $file); // so you mark it as downloaded if it's single-use
header("Content-Type: application/octet-stream"); // downloadable file
echo file_get_contents($file);
return 0; // running a return 0; from outside any function ends the script
} else
die("You're not allowed to download this file");
Any link you point would simply point to download.php?fileid=712984 (whatever the fileid actually is). That would be the actual download link, since that script does transfer the file; but only if the user is allowed to retrieve it. You'd have to write the file_location(), check_permissions_for() and mark_downloaded() functions yourself though.

I would suggest using uniqid() function, and store unique ids with the expiration date in a database, while returning to the user url with something like this: ...?file_id=$id
When the link is being opened, you may delete it from the database or mark it to be deleted 'soon' (just in case user wants to refresh the page.)

Related

Prevent users to download other files by changing the path in a url query

i have a download function receiving the filename by $_GET and i want to prevent users of downloading other files changing the path and accessing other files in the system.
method:
function actionDownload($arquivo) {
try {
$filepath = \Yii::getAlias('#webroot') . '/files/coordenadas/'. $arquivo;
if (file_exists($filepath)){
return \Yii::$app->getResponse()->sendFile(\Yii::getAlias('#webroot') . '/files/coordenadas/'. $arquivo, $arquivo);
}
}
catch (\Exception $exception) {
throw new NotFoundHttpException("Arquivo não encontrado");
}
}
the route to download the method:
http://example.com/converter-coordenadas/download?arquivo=geografica-utm-20200830171051.xlsx
if someone change the arquivo variable to another valid path it will be able to download other files. How prevent that, but keeping the function receiving the file name in a url param?
the situation that i have is:
the user upload a file through ajax
i convert this file and return the filename
create a download button with the link to the new file.
I don't have any other information to make a relation with the file, like an user id.
As #GetSet explained in the comments, the biggest problem is procedural. One way to do this correctly is as follows:
Upload the file to your server and save the reference in database (you already doing) and generate an unique ID for this file (or for this download). This ID will be saved in a database field, for example with the name: "donwload_id"
Then in the view (when you are creating the link for the download):
Html::a('Download', [Url::to('donwload-action'), 'download_id' => $model- >download_id]);
In your controller, You will know how to find the file by its unique identifier (download_id).
No one knows how you have generated this ID and therefore it is more difficult for anyone to be able to generate it again. Also you can limit the time available to download the file by setting an expiration date to the link.

Allow a PHP script access to PDFs in a folder - but prevent direct URL references

On a godaddy hosted website using CPanel, I have a small PHP script that shows each line in a text file that's on the server. Each line contains a private href link to a PDF that only the logged-in user can see. The links points to various PDFs in the same folder on the server. The code works fine and I can click on the link and see each PDF.
The problem is that each PDF can also be seen by using a direct URL query (i.e. website/folder/pdfname.pdf). As these are private PDFs, I don't want them public. I've tried changing CPanel permissions on the folder to "owner" - but that seems to prevent the PHP script from opening the PDFs also.
Is there a way to allow a PHP script access to PDFs in a folder - but prevent direct URL references?
NOTE: I'm not particularly adept at PHP or CPanel - sorry.
Code...
$fname = "PDF-" . $user_name.".txt";
$fnum = fopen($fname,"r");
echo "<tr>";
While (($str = fgets($fnum)) !== false) {
$arr = explode("|",$str);
for ($x = 0 ; $x < count($arr); $x++) {
echo "<td>$arr[$x]</td>";
}
echo "</tr>";
}
echo "</tr>";
fclose($fnum);
File contents...
Xyz Company|21 Jan 2018| website link
Xyz Company|21 Jan 2018| website link
Xyz Company|21 Jan 2018| website link
Xyz Company|21 Jan 2018| website link*
Asside from removing the files from the root, if you are running apache, you can change your .htaccess (I'm sure windows-based system have a web.config equivalent) to forbid access to certain files directly. If you add this snippet to that file, it will deny files with .pdf extension:
<FilesMatch "\.(pdf)$">
Order Allow,Deny
Deny from all
</FilesMatch>
From there, inside your app, you can create some sort of system for curating your PDF links, so if you store the real path in a database and use the id as the link similar to:
http://www.example.com/?file=1
or if you just do a simple scan:
<?php
# The folder that the PDFs are in
$dir = __DIR__.'/website/folder/';
# Loop over a scan of the directory (you can also use glob() here)
foreach(scandir($dir) as $file):
# If file, create a link
if(is_file($dir.$file)): ?>
<?php echo $file ?>
<?php
endif;
endforeach;
Then, if the user tries to download using the link, you check they are first logged in and if they are, download the file by doing a script like so BEFORE you output anything else to the browser (including spaces):
<?php
session_start();
# First check that the user is logged in
if(empty($_SESSION['username']))
die('You must be logged in to download this document.');
# Not sure which directory you are currently in, so I will assume root
# I would do basename() here incase the user tries to add in something like:
# ../index.php and tries to download files they are not supposed to
$file = __DIR__.'/website/folder/'.basename($_GET['file']);
if(!is_file($file))
die('File does not exist.');
# Double check that the file is a pdf
elseif(strtolower(pathinfo($file, PATHINFO_EXTENSION)) != 'pdf')
die('File appears to be invalid.');
# Start download headers
header('Content-Description: File Transfer');
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename="'.basename($file).'"');
header('Expires: 0');
header('Cache-Control: must-revalidate');
header('Pragma: public');
header('Content-Length: ' . filesize($file));
readfile($file);
exit;
One simpler and basic example (and derivative of previous answer) is to use two separate PHP files, where one is evaluating a set cookie (set to expire soon) in the browser upon link click (set via JS or PHP or other). If the cookie was read correctly, the first PHP page imports a second page that utilizes the PHP header() redirect containing your original file name forcibly downloaded with another name. Using the Content Disposition header field.
In action this works like this
1: Original page with download links - we set the cookie to work for 2 minutes
<a onclick="setCookie(1, 1, 2, 60)" href="php-secure-files-delivery-page.php">Download My Final PDF name.pdf</a>
<script type="text/javascript">
// set a cookie with your own time limits.
function setCookie(days, hours, minutes, seconds) { // Create cookie
var expires;
var date = new Date();
date.setTime(date.getTime()+(days*hours*minutes*seconds*1000));
expires = "; expires="+date.toGMTString();
document.cookie = "my_cookie_name"+"="+"my_cookie_value"+expires+"; path=/";
}
</script>
On the link page we include a hyperlink with the evaluating PHP page. Here we use JavaScript to set a cookie using the custom function setCookie(days, hours, minutes, seconds), that will receive your wishes for expiry. Just note that 1 is the minimum number. Not 0.
2: Download page - evaluating cookie and presenting texts, or simply downloading the file
(php-secure-files-delivery-page.php)
<?php
// if the cookie is set correctly, load the file downloader page.
if (isset($_COOKIE['my_cookie_name'] && $_COOKIE['my_cookie_name'] === 'my_cookie_value')) {
require_once 'file-downloader.php'; // the file will force the download upon import.
} else {
die('The link expired, go to your downloads section and click on the link again.');
}
?>
Here we evaluate the cookie, present either the correct info or die(). Using require_once we get the PHP page into the current one.
3: Imported file includer PHP page
(file-downloader.php)
<?php
// We'll be outputting a PDF
header('Content-Type: application/pdf');
// It will be downloaded as your-downloaded.pdf
header('Content-Disposition: attachment; filename="your-downloaded.pdf"');
// The PDF source is in your own specified long name
readfile('original-with-really-weird-original-name.pdf');
?>
Results
User always go to the same page, being presented with the appropriate information.
You can name your original files on your server anything you want, like "my_really_difficult_and_long_file_name.pdf", while the user sees only the nice pretty file name when the file is downloaded.
for more files, use an extra input in the cookie function to take the file name too, and some if statements in the php downloader page, that looks for separate end PHP pages to require_once.
If you go to the browsers "Downloads" section to try to get the url of the downloaded file, you see the initiating PHP page, the second page, that leaves you empty with a die() if no correct cookie was set. That cookie is only set when you want it to. On your pages. You can of course do this in JavaScript too, but that will expose the cookie, still, for most unauthorized sharing, that takes care of it.
Lastly, easy security for your folder (without Apache/Nginx/.htaccess stuff)
Using .htaccess files on local folders or directives on your server is the best and most secure way. But that´s not transferable to your other applications on other systems. Instead use a index.php and a default.php page on your PDF file´s parent folder, where they are located, including this header redirect to wear off unwanted visits:
<?php
header("Location: http://yoursite.com/some-other-page/"); /* Redirect browser here */
?>

php read file from api call

I am connecting to a 3rd party's API service to get a list of files attached to a given ticket. One of the calls is get_attachment.
call($client,'get_attachment',$data);
$client and $data are just info about connection and filename, etc...
the result is the actual file itself. if i print_r(call(....)), i get the file itself in my browser window. How can i present this to a user to download from my page? I would like to provide a hyper link, so the user can choose to click link and download this file.
foreach ($attachments as $id => $fileName){
echo "<a href='???'>".$fileName."</a>";
}
If i need to save the file locally that is fine, how would i go about referencing this file??
This is the only note i have for the 3rd party's api related to this call:
"This method will output file data for the specified attachment."
One approach to solving this is by calling the API on two separate requests, once to get the list, and another to download the file.
If your listing page ended up like the below, it would show the attachments, and clicking the link would open the attachment on a new tab and download the file
// include this at the top of your file before any other HTML is rendered.
if (isset($_GET['file')) {
// assuming $data will contain some reference to the filename now stored in $_GET['file']
echo call($client,'get_attachment',$data);
return;
}
// assuming $attachments contains the list of the files
foreach ($attachments as $id => $fileName){
echo "<a href='thisPage.php?file=" . $fileName . "'>" . $fileName . "</a>";
}
So your link ends up linking to the same page, but rather than rendering any HTML once someone clicks the link at the top of your script you would make the API call to get the file contents and then terminate the script (either return or exit will do the trick) - assuming you can call the contents of the file by its file name.
You may need to pass more than the filename, maybe the id? Whatever you need to make the API call for the attachment, you can pass through on the link.

PHP link/request to download file then delete it immediately

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.

php file uploading and storing

I want to upload a file on my PHP server. I am currently able to upload it on server using the following code but I don't know how I can store it on the server.
How can I store the file in a specific directory?
I also want the users to be able to download the files but only once they log in not before that.
For example i store the file in directory /myfiles no-one must be able to download it
unless he is logged in
e.g. someone can download the file if he knows the file location
like www.example.com/temp/myfile.txt
I don't want that - user must not be able to download it unless he is logged in.
I have one page B.php in which there will be the download link. When the user clicks on that link he must be able to download the file. In short, he must get a Save as/Open pop up of browser when he clicks my link. How do I do that?
Check the PHP documentation about move_uploaded_file() here: http://de.php.net/manual/en/function.move-uploaded-file.php
function UploadData()
{
$yourpath ="yourfoldername";
createafolder($yourpath ); // if not present then create it (its custom function)
$target_path = $yourpath . basename( $_FILES['fileupload']['name']);
if(move_uploaded_file($_FILES['fileupload']['tmp_name'], $target_path)) {
//write if any processing
}
else echo "Upload sucessful!";
}

Categories