Download file using php script, one at time - php

This is question about downloading files from server using php script. When user clicks on download link, it processed to download.php file, and using header it started downloading.
After downloading file there is one function in download.php file, which updates mysql database about file downloaded and deduct money from user's account. Everything works fine.
Now, the problem occurs when user have download manager installed in pc. Sometimes download started in browser and download manager both. So, at the end there are two download entries in database, And money deducted from user's account two times.
Question : Is there any way to start only one download at time? Or any other way to do this thing?
Download link I provide to user.
<a href='download.php?id=1'>Download Test Video</a>
The script I am using for downloading file. (download.php)
$file = "c:/test.avi"; // $file = $_GET['id'];
$title = "Test Video";
header("Pragma: public");
header('Content-disposition: attachment; filename='.$title);
header("Content-type: ".mime_content_type($file));
header("Content-Length: " . filesize($file) ."; ");
header('Content-Transfer-Encoding: binary');
ob_clean();
flush();
$chunksize = 1 * (1024 * 1024); // how many bytes per chunk
if (filesize($file) > $chunksize) {
$handle = fopen($file, 'rb');
$buffer = '';
while (!feof($handle)) {
$buffer = fread($handle, $chunksize);
echo $buffer;
ob_flush();
flush();
}
fclose($handle);
} else {
readfile($file);
}
record_download('user id', 'file id');

Moving the comment down here for anyone interested.
The function that I had to come up with was a unique download link after a payment had been processed. These are the steps that were taken.
Process the Payment and capture IP address, file and path of the downloadable file - save this information to the database.
Once payment has been deducted successfully, trigger a function that generates a unique token, e.g: sha1(microtime().$transactionid), and save this to the database (note: please don't use microtime() in production, use a random string generator).
Using .htaccess we generated a download link, e.g.: http://domain.com/download/<token> the .htaccess contents:
RewriteRule ^download/([a-z0-9-]) /download.php?token=$1
Optional: If their IP matches what we have in the database, go ahead and allow the user to download the file. If it doesn't, we ask the user to log in so we can update their IP address and begin downloading.
Once you have the token, you can pretty much do any form of validation you would like from here, such as preventing multiple downloads by adding a column in the database download_downloaded INT(1) DEFAULT 0 where if it is set to 1, then it has been downloaded. I would suggest giving the user about a day before locking them out after downloading, just in case their file was corrupt in the process.
Any other additional items, such as download counter etc.
Finally use your code above after to start the download. I would have it structured a little differently though.
download.php
$token = $_GET['token'];
$allow_download = FALSE; // Can never be too careful..
...
//Database lookup to get the file ID
...
$file = "c:/test.avi"; // now from the database call
$title = "Test Video";
// Do any additional validation here
// returns TRUE or FALSE (Boolean) -- Custom function to query the database
$allow_download = check_if_downloadable('user_id', 'file_id', 'token_id');
record_download('user id', 'file id');
// After validation is complete, allow them to download
if ($allow_download === TRUE){
header("Pragma: public");
...
If a user lost their download link (as it has happened many times), we show their download link on their member home page once they have logged in, and they can start downloading again (if you allow it).
I hope this helps. I'm sorry that I can't give out some code examples at this time.
Most of this is just querying the database.

Sometime in our website , need a download link. When click this link then any type of file,image, pdf will be download.You can do this using a simple php script.
want to download a picture name “shafiq_photo.jpg” so parameter is file name “shafiq_photo.jpg”.
Then Create a php file name “download.php” which you use in above file.
<?php
$file = $_GET["file"];
if (file_exists($file)) {
header('Content-Description: File Transfer');
header('Content-Type: application/octet-stream');
header("Content-Type: application/force-download");
header('Content-Disposition: attachment; filename=' . urlencode(basename($file)));
// header('Content-Transfer-Encoding: binary');
header('Expires: 0');
header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
header('Pragma: public');
header('Content-Length: ' . filesize($file));
ob_clean();
flush();
readfile($file);
exit;
}
?>

I would recommend to alter the logic:
Deduct money first.
Generate a random string. Save file and user info with it.
In the client profile dump a download link with the random identifier. I mean http://www.example.com/download.php?file=254fd1f5df4df2sd5fsd5f4sdfsd0fsdf5sd4fsdf5dfsdf
Onload complete remove the database entry.
But there is a security hole!
If two people start download that file with that link, then they can bypass payment for one.
But what if someone download that file and send it to other. So, that isn't so much problem. isn't it?
But there is a option attach CSRF token if you think you should really safe.

Related

How I can bypass MySQL triger

I made a small website just for fun for file sharing, each account limited to 20 Files per day,
All members data stored inside MySQL table.
Username, Password, and Download number.
When someone click on download, a function will trigger and store +1 inside the table. and the downloading will begin.
I thought everything working great, after I saw the log file on my server, and I found someone who download a file without triggering the function and without leaving history Inside the database!
How is that possible! and how I can do the same so I can block this bug!
Here is the code:
if (isset($_GET['download']) && !empty($_GET['download'])){
if (!(isset($_GET['username']) && !empty($_GET['username']))){
echo 'Only a member of this website can download this file. However, no username was specified in this download. Sorry for inconvenience.';
die;
}
$dl_username = $this->decrypt($_GET['username']);
if (gator::getUser($dl_username) == false){
echo 'Only a member of this website can download this file. However, the username provided does not exist in the database. Sorry for inconvenience.';
die;
}
$dl_user = gator::getUser($dl_username);
if ($dl_user['downloads'] > 20){
echo 'Cannot download more files for today! You have crossed the limit of downloading 20 files.';
die;
}
gator::updateUser($dl_user['username'], array('downloads' => $dl_user['downloads'] + 1));
$filename = $this->filterInput($this->decrypt($_GET['download']));
if (in_array($filename, gatorconf::get('restricted_files'))) die;
if (!file_exists($_SESSION['cwd'].DS.$filename)) die;
// Set headers
header("Cache-Control: public");
header("Content-Description: File Transfer");
header("Content-Disposition: attachment; filename=\"$filename\"");
header("Content-Type: application/octet-stream");
header("Content-Transfer-Encoding: binary");
// output file
set_time_limit(0);
$file = #fopen($_SESSION['cwd'].DS.$filename,"rb");
while(!feof($file))
{
print(#fread($file, 1024*8));
ob_flush();
flush();
}
gator::writeLog('download - '.$filename);
echo 'Downloaded';
die;
}
EDIT: There is two log files, one for actions that record members actions on the website and the other one on my Apache server, that record everything and all connections even for non users.
And here is an example of how the file link look like: "no one can use hot links"
https://www.example.com/?download=MLB%20820-2186%20schematic%20diagram.pdf&username=Linda

Issue with readfile() and database insert

I have a really weird problem regarding a small piece of code in a CodeIgniter application. Basically, there's a page with links to various PDF files. When a user clicks on the link, the request is parsed by PHP, an observer is notified, writing the click event in the database (activity log), and then the file is outputted by using readfile().
So far, so good. Tested it, it works like a charm. The PDF is outputted for download, and the event is written in the database as it should.
The problem comes when a user clicks on such link, then cancels the download and clicks on another link no later than 9-10 seconds. When that happens, the event is registered in the database twice.
I did triple check of the observers that record the event, but they appear to be fine. Besides, there's a similar function for a video links, only it redirects to another page instead of outputting the file directly, and it works just fine.
After a few hours of scratching my head, I figured there's an issue with the readfile() function, because, if I put a var_dump();die(); or anything that outputs some text before the download and force it to come as text, the download event is recorded only once.
Here's the code in question:
public function downloadPDF($id = NULL)
{
if (($id == NULL) OR (!$this->validateId($id))) {
// redirect with error
}
$item = // code for fetching the PDF properties from the DB
$this->notify('ActivityObserver'); // writes the download event in the DB
$file = '.' . urldecode($item['link']);
$size = filesize($file);
$name = urldecode(basename($file));
header('Content-Description: File Transfer');
header('Content-Type: application/pdf');
header("Content-Disposition: attachment; filename=\"$name\"");
header('Content-Transfer-Encoding: binary');
header('Expires: 0');
header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
header('Pragma: public');
header('Content-Length: ' . filesize($file));
readfile($file);
exit();
}
Tried to test it with different browsers, the behaviour is the same. All inspector tools show only 1 request being made on click.
What am I missing in this big ugly picture? Why could it sometimes write twice instead of only once?
Thanks for your time to read this wall of text.

making uploaded file externally inaccessible php/apache

I made a form that allows user to upload a file (text documents). I’m using an unique (?) file name made from a combination of time() and the user id (only logged user can upload).
My problem is that the file cannot be accessed externally. That is, only the user who uploaded it or an admin can see it, while it can’t be reached while simply typing www.domain.com/uploads/file_name.txt
I know I can prevent the access to file through htaccess, but if I did understand it correctly, in that way I couldn’t open it even after I am logged in as admin (or as the user who sent the file).
I know I could open the file locally through php so I could show up the content through my admin panel, but that’s a pain since I could output only plain text files without problems. Also I could not download the file.
I could generate on the fly pdf or rtf versions in some cases, however that would quite a long way since I would need to elaborate the content in a complex way. And anyway, I would have no idea how to handle Word or OpenOffice files, which are likely to be the most common cases, and how to not loose formatting or other possible features.
Any ideas?
Why not display a download link for logged in users, like www.domain.com/download.php?file=... The code could look something like:
if( isset($_GET['file']) && user_is_logged_in() ) {
$file = DIR_SOME_WHERE .'/'. basename($_GET['file']);
if( file_exists($file) && user_has_file_access( $file ) ) {
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('Content-Length: ' . filesize($file));
ob_clean();
flush();
readfile($file);
exit;
}
}

Determining successful download using php readfile

I need to know if a user selected download then clicked the cancel button, which is not the same as readfile having an error. I have inspected the count returned by the readfile function, but it shows the bytes in the file even if the user canceled the download from the Save As dialog.
The reason this is needed is because my site has a one-time download, where a member gives permission for another use to download their file one time, then the permission goes away. But if a member clicks the download button then decides not to download it right then, I dont' want my database to get updated to show they got the file.
This deals with intellectual property protection since the files are the property of the member who uploaded them, and I need to keep an audit trail of exactly what other members downloaded the file in case they start floating around the internet. But if the readfile function always reflects the filesize (meaning those bytes were transferred in some way), I have no way to know if the file was actually downloaded.
I have seen a number of posts about this subject, but no real solutions to what has to be a frequent need - did they download it or not? Just knowing that they clicked the download button doesn't really say whether they decided to go through with it since the Save As dialog box allows someone to cancel the actual completion of the download.
For completeness, here is my download code up until the readfile function:
header('Content-Description: File Transfer');
header('Content-Type: application/octet-stream');
header("Content-Disposition: attachment; filename=$download_name");
header('Content-Transfer-Encoding: binary');
header('Expires: 0');
header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
header('Pragma: public');
header('Content-Length: ' . filesize("sub/$doc_file"));
ob_clean();
flush();
$wasdownloaded = readfile("sub/$doc_file");
I fear the correct answer is "Impossible" - let me explain: You might be able to correctly figure out, when the file has crossed the wire, but you can't figure out reliably, whether the client threw it away or not.
Example (chronological sequence):
A user on MSIE clicks download and is presented with the "Save where" Dialog.
While this dialog is open, the download is started in the background.
The user navigates around in the dialog or simply does nothing (phone rang, he talks)
The background download is finished, your script sees the download as complete
The user clicks on "cancel"
MSIE deletes the tempfile, the download is never stored in a user-accessible form
Result:
The user sees the file as "not downloaded" - and he is correct
Your app sees the file as "correctly downloaded" - and it is correct
You would first need ignore_user_abort().
This would allow your script to continue on after the user has hit cancel, or escape.
You would then have to print out the file and continuously check with connection_aborted().
header('Content-Description: File Transfer');
header('Content-Type: application/octet-stream');
header("Content-Disposition: attachment; filename=$download_name");
header('Content-Transfer-Encoding: binary');
header('Expires: 0');
header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
header('Pragma: public');
header('Content-Length: ' . filesize("sub/$doc_file"));
ob_clean();
flush();
$fp=fopen("sub/$doc_file","rb");
while(!feof($fp))
{
print(fread($fp,1024*8));
flush();
ob_flush();
if( connection_aborted() )
{
//do code for handling aborts
}
}
Use this comment on php.net : http://www.php.net/manual/en/function.fread.php#72716
On fclose you would be able to determine if file has been downloaded successful, because your are checking if user aborted connection with connection_status()

Restriction of 1 download at a time in PHP

I am working on a video site that has different movies and videos which users can stream and download. Now I am being asked to implement a download restriction in such a way that only 1 video can be downloaded at a time. There are two servers: my files and database are on one server and the videos are on the other.
What I am doing for downloading is to send a request from the first server for a file on the other server. If the requested video exists, it is downloaded.
Now I want to restrict the users so that if they are already downloading a video, they cannot download another until the current download completes. Once the current download has completed, the user can download the next video. I have not seen any function that enables a developer to know when the download has completed.
I have a few things in my mind about storing the information of the download time in the database. But storing the time of download is not my requirement.
What is the best way to implement this? Is there an event from which we can detect the download end time? Is there any solution to this? I am using PHP and here is the code that I have used for downloading the file from the second (videos) server. This file sends a request with a file name and full path. The $real_file variable contains the file name along with full path on the second server.
if(file_exists($real_file))
{
header("Pragma: public");
header("Cache-Control: private");
header("Expires: 0");
header('Cache-Control: no-store, no-cache, must-revalidate');
header('Cache-Control: pre-check=0, post-check=0, max-age=0');
header('Content-Transfer-Encoding: binary');
header('Content-Encoding: none');
header("Content-Disposition: attachment; filename=".urlencode(basename($real_file)));
header("Content-Type: application/force-download");
header("Content-Type: application/octet-stream");
header("Content-Type: application/download");
header("Content-Description: File Transfer");
header("Content-Length: " . filesize($real_file));
header("Accept-Length: ".filesize($real_file));
$fp = #fopen($real_file, "rb");
while(!feof($fp))
{
$buffer= fread($fp, 8192);
echo $buffer;
}
#flush();
#ob_flush();
die();
}
If you stream the file through a php-script, it would maybe be able to obtain a lock for a specific user (logged in of course) before you start to read the file and outputting to the stream:
(pseudocode)
obtain_lock_somehow();
readfile('yourvideofile.mpg');
release_lock();
I don't know how the script would respond to a closed connection, and it might force the script to end prematurely.
Another option would be to read the file and pass on to the stream in "chunks", and in between every chunk you update the status of the visitors "lock", so that you can identify at which last timestamp the visitor actually downloaded something.
(pseudocode)
while(file_is_not_finished) {
update_lock_status();
pass_thru_buffer();
}
But do note that streaming huge amount of data in a php-script like this is probably not the best way to go, and you might be better off with a native server module for it.

Categories