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()
Related
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.
I am working on social networking site which has grooveshark integration for music and image sharing like Facebook. This is full ajaxify site. So in this site one can listen music and other side he can do the other things like he can share status , upload images , shares link like facebook. When user open any image there is button to download it.My problem is that when i click on download button to download image while music playing, it makes (if i see in the network by inspect element) streaming fails with the partial content (http code 206) as well as the call for download shows canceled but returns the http code 200ok(in the network by inspect element).
here is code:
if(file_exists($media_name))
$path='../webroot/img/user_media/original_uploaded_size/'.$user_id;
else
$path='../webroot/img/user_media/single_photo_878_549/'.$user_id;
header('Content-Description: File Transfer');
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename='.basename($media_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($path));
ob_clean();
flush();
readfile($path);
please help.
If someone not understands please comment i will tell you about my problem...
Are you using sessions? Try using session_write_close(); at the end of your request. This will basically finalize the request.
I guess I have to break down and ask for help. (Should have done it 3 days ago!)
Here's what happens...
PHP reads session & post variables, builds a .csv file from a mysql query.
it attempts to open a 'Save As' dialog box and when that's done, jump to another page.
I'm using nested functions but when run, the dialog box seems to get run over and never appears.
separately the functions work fine.
when run, the 'save as' dialog box doesn't wait for user input
Can anyone see what I've done wrong or can you redirect my thinking?
$filename points to the created CSV file on the server
$suggname is a default filename users should see in the dialog box.
The code:
holdit($filename,$suggname);
function holdit($filename,$suggname) {
$fp=#fopen($filename, 'rb');
if (strstr($_SERVER['HTTP_USER_AGENT'], "MSIE")) {
header('Content-Type: "application/octet-stream"');
header('Content-Disposition: attachment; filename="'.$suggname.'"' );
header('Expires: 0');
header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
header("Content-Transfer-Encoding: binary");
header('Pragma: public');
header("Content-Length: ".filesize($filename));
} else {
header('Content-Type: "application/octet-stream"');
header('Content-Disposition: attachment; filename="'.$suggname.'"' );
header("Content-Transfer-Encoding: binary");
header('Expires: 0');
header('Pragma: no-cache');
header("Content-Length: ".filesize($filename));
}
fpassthru($fp);
fclose($fp);
jump();
}
function jump() {
header('Location: return_from_csv.php');
}
You are adding lots of headers to your HTTP response. One of those is Location which instructs the browser to redirect. Obviously it is interpreting that as a higher priority than your other headers.
Decide if you want to redirect or serve a file in your response and do one or the other.
I suspect you have misunderstood the Location header. Read this: http://en.wikipedia.org/wiki/HTTP_location
By the looks of things you are trying to serve the CSV file and then redirect to another page. Sorry, you cannot do this. An HTTP response does one thing and one thing only. You might consider opening your link to the CSV file in another window using the target attribute of <a>.
Im having some major problems with an site im developing, basically whats happening is a user fills out a form, then jquery takes over and posts all the information to sendfile.php, it then is meant to force the user to download a specific file, but its just not doing anything at all and im not seeing any errors either, the file exists.
The code im using is as follows:
header('Content-Description: File Transfer');
header('Content-Type: application/octet-stream');
header('Content-disposition: attachment; filename="http://website.com/wp-content/uploads/2012/02/303lowe-logo.jpg"');
header('Content-Transfer-Encoding: binary');
header('Expires: 0');
header('Cache-Control: must-revalidate');
header('Pragma: public');
readfile("http://website.com/wp-content/uploads/2012/02/logo.jpg");
Any help would be awesome.
Based on what you said about using jQuery, I assume you are using AJAX to post the form results to the server. I think you will find that you cannot download a file using AJAX.
Perhaps consider doing the AJAX request then redirecting the user to a new page to download the file. If the redirected page serves the file directly, then the user won't even know they have been redirected (the browser will stay on the same page, usually).
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.