67

I am using this for sending file to user

header('Content-type:  application/zip');
header('Content-Length: ' . filesize($file));
header('Content-Disposition: attachment; filename="file.zip"');
readfile($file);

I want to delete this file after user downloads it, how can i do this?

EDIT: My scenario is like that, when user hits download button, my script will create a temporary zip file and user download it then that temp zip file will be deleted.

EDIT2: OK best way seems running a cron job that will be cleaning temp files once an hour.

EDIT3: I tested my script with unlink, it works unless user cancel the download. If user cancel the download, zip file stays on the server. So that is enough for now. :)

EDIT4: WOW! connection_aborted() made the trick !

ignore_user_abort(true);
if (connection_aborted()) {
    unlink($f);
}

This one will delete the file even if user cancel the download.

Maik Lowrey
  • 15,957
  • 6
  • 40
  • 79
Utku Dalmaz
  • 9,780
  • 28
  • 90
  • 130
  • possible duplicate of [check if download is completed](http://stackoverflow.com/questions/1563187/check-if-download-is-completed) – hakre Mar 13 '13 at 12:53

7 Answers7

43
unlink($filename);

This will delete the file.

It needs to be combined with ignore_user_abort()Docs so that the unlink is still executed even the user canceled the download.

ignore_user_abort(true);

...

unlink($f);
hakre
  • 193,403
  • 52
  • 435
  • 836
brettkelly
  • 27,655
  • 8
  • 56
  • 72
  • 2
    Your best bet would be to not serve the file up directly from the disk and, instead, use a unique URL that you can disable after the download request. – brettkelly Apr 14 '10 at 23:13
  • 1
    my scenario is like that, when user hits download button, my script will create a temp. zip file and user download it then that temp zip file will be deleted – Utku Dalmaz Apr 14 '10 at 23:16
  • 1
    @Ahmet vardar: Why not just echo out the content of the Zip? – Billy ONeal Apr 14 '10 at 23:20
  • @Billy: cuz if i do that, there will no download navigation for user – Utku Dalmaz Apr 14 '10 at 23:21
  • @Ahmet vardar: What do you mean by that? You're calling `readfile`. `readfile` reads and echos a file. It makes more sense to just echo the data directly instead of saving it to a file and then calling `readfile` on it. – Billy ONeal Apr 14 '10 at 23:29
  • What about the accelerators that make multiple requests? – Nathan Osman Apr 14 '10 at 23:33
  • 2
    @George Edison: *Bill turns off resume support. Accelerators unhappy. Bill happy :D – Billy ONeal Apr 15 '10 at 00:05
31

I always use the following solution, using register_shutdown_function:

register_shutdown_function('unlink', $file);
reformed
  • 4,505
  • 11
  • 62
  • 88
  • Perfect for my situation where the file needed to exist while the function ran, but once the script was entirely done, it successfully deleted the temp file. – Reisclef Aug 17 '17 at 16:29
  • 5
    performs best with `ignore_user_abort(true);`, imho should be accepted anwser – Gall Annonim Aug 17 '17 at 19:14
  • This solution fixed my issue where calling ob_end_flush() seemed to end execution of the script (even though an unlink was called on the next line). – danielcraigie Sep 06 '17 at 10:29
9

There is no any correct way to detect whether file was completely downloaded by user or not.
So the best way will be to delete file after some period of inactivity.

zerkms
  • 249,484
  • 69
  • 436
  • 539
  • I think its better to setup a Cron job to delete file after some time, say 5 minutes after the download was initiated for the file. (assuming the download does not take more than 5 minutes though). – shasi kanth Mar 28 '14 at 10:27
8

connection_aborted() never worked for me. Although ob_clean() works exactly as it should. Hope this help others aswell

header('Content-type: application/pdf');
header('Content-Disposition: inline; filename="' . $file . '"');
header('Content-Transfer-Encoding: binary');
header('Accept-Ranges: bytes');
ob_clean();
flush();
if (readfile($file))
{
  unlink($file);
}
Marcello B.
  • 4,177
  • 11
  • 45
  • 65
Sina aria
  • 125
  • 1
  • 5
  • 1
    connection_aborted() didn't work for me, but this did the trick – Yuri Waki Aug 31 '18 at 22:59
  • @Sebi `readfile()` sends the file to the browser – reformed Dec 23 '20 at 22:41
  • Okay, but the creator of this post uses incorrect HTTP headers. `readfile` does not respect Byte Ranges nor is it safe to assume that the entire file has been transmitted to the user if `readfile` returns. `chunked` Encoding without `Accept-Ranges` would be a far better choice unless some Range Header processing is taking place. – Sebi2020 Dec 27 '20 at 15:39
  • It's the best option! Thank's. – carlaodev Nov 03 '22 at 03:55
4

I couldn't find something which worked for me, so I came up with this, which seems to work well for me:

header('Content-type: application/zip'); //this could be a different header 
header('Content-Disposition: attachment; filename="'.$zipName.'"');

ignore_user_abort(true);

$context = stream_context_create();
$file = fopen($zipName, 'rb', FALSE, $context);
while(!feof($file))
{
    echo stream_get_contents($file, 2014);
}
fclose($file);
flush();
if (file_exists($zipName)) {
    unlink( $zipName );
}

I hope that helps someone

Bren1818
  • 2,612
  • 1
  • 23
  • 28
3

I too have very similar functionality in one of my website. It will be like deleting randomly created folder & zip file after/cancelling download. I try to explain it here, may be someone find it useful.

skin.php:
This page contains download link such as "http://mysite.com/downoload/bluetheme"

.htaccess:
I have following rule in htaccess file to redirect the download request to a php file. [download.php].

RewriteRule ^download/([A-Za-z0-9]+)$ download.php?file=$1 [L]

download.php:

include "class.snippets.php";
$sn=new snippets();
$theme=$_GET['file'];
$file=$sn->create_zip($theme);
$path="skins/tmp/$file/$file.zip";
$config_file="skins/tmp/$file/xconfig.php";
$dir="skins/tmp/$file";

$file.=".zip";
header("Content-type: application/zip");
header("Content-Disposition: attachment; filename=$file");
header("Pragma: no-cache");
header("Expires: 0");
readfile($path);

//remove file after download  
unlink($path);
unlink($config_file);
rmdir($dir);

so on request download.php will create directory with a random name using snippets class. Inside the directory it will create a zip file. after the download/cancelling the request all the files and the directory will be deleted.

vkGunasekaran
  • 6,668
  • 7
  • 50
  • 59
  • If user cancels the download or closes his browser before the `readfile()` function completes, can you guarantee that script execution is not terminated resulting in `unlink()` never getting called? – reformed Apr 06 '18 at 16:07
0

For those who face the "extra bytes at the begining of file" issue, I would recommend to add buffer cleaning functions before readfile, as suggested in this post:

https://stackoverflow.com/a/51083411/16381972

@ob_start('');  //@ supresses a warning  
//header entries
ob_end_clean();
ob_clean();
readfile($file);
sporteiro
  • 1
  • 1