2

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");
Ben Cahan
  • 63
  • 2
  • 7
  • possible duplicate of http://stackoverflow.com/questions/5856001/how-to-tell-if-a-file-was-really-downloaded-and-saved-despite-browser-prefetch – j08691 Jan 07 '12 at 16:42

3 Answers3

1

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
    }
}
apaderno
  • 28,547
  • 16
  • 75
  • 90
Patrick Evans
  • 41,991
  • 6
  • 74
  • 87
  • What you are saying is NOT to use readfile at all, that is the upshot here, right? But will the fopen and fread strategy still give the user the same Save/Cancel dialog box for sagin the file to disk? – Ben Cahan Jan 07 '12 at 17:15
  • yes as long as you have the correct headers set the browser will download instead of display. The way the file is outputed doesnt determine if the file is "downloaded" or "displayed" thats controlled by the headers. Also if you use this way, make sure to have a exit,die, or break call in the conneciton_aborted check as otherwise it will continue to send the file to the servers output buffer – Patrick Evans Jan 07 '12 at 17:22
  • I am wondering if the connection-aborted only works if the file is still being downloaded to the temp file. The code above doesn't change the dynamic and still shows as completed if I click the cancel button in the Save As box. The file I am using to test is on 50K, so it downloads to a temp file in the downloads folder far faster than I would be able to hit the cancel button, and besides relying on that isn't a solution, really – Ben Cahan Jan 07 '12 at 18:02
  • Nice. I didn't know about that connection_aborted() function. That will help me a lot. I'll find out whether my visitors are patient enough to download some big files even with the small download rate my webserver can offer. – pagliuca Oct 20 '12 at 19:15
1

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
Eugen Rieck
  • 64,175
  • 10
  • 70
  • 92
  • Well, that is not good news. Heck, there is a button that says Cancel in the save as box, but if all that does is not rename the tempfile as the saved file, what good is it? It would seem to me that anyone who needs a one-time-only download has to be able to know if it really worked so they can mark it as done and remove the download privilege – Ben Cahan Jan 07 '12 at 17:58
0

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()

Timur
  • 6,668
  • 1
  • 28
  • 37