PHP create and download ZIP on the fly - php

I'm creating a ZIP file with several scripts in it (for example: test.php, functions.js and style.css).
The scripts works just fine, the only problem is that the ZIP file gets placed on my webserver. Is there a way to prevent this? I've read multiple similar questions: this one seems to work, but I can't figure it out how to use that.
So, I wan't to delete the file after it has been placed (even if user aborts it) or (even better) that my scripts never puts the file on the webserver.
download.php
$scriptId = checkNumeric($_GET['sid']);
//Check if user has access to the script
if(isLoggedIn() && hasScriptAccess($scriptId)) {
//Create ZIP
$zip = new ZipArchive();
$zipName = "script.zip";
if ($zip->open($zipName, ZIPARCHIVE::CREATE)!== TRUE) {
exit(); //Something went wrong while creating the ZIP
}
//Get associated codes
$query = $mysqli->query("SELECT * FROM code WHERE script_id = '{$scriptId}'");
while($code = $query->fetch_assoc()) {
$filename = $code['title'];
$content = $code['code'];
//Add file to ZIP
$zip->addFromString($filename, $content);
}
$zip->close();
//Set headers
header("Pragma: public");
header("Expires: 0");
header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
header("Cache-Control: public");
header("Content-Description: File Transfer");
header("Content-type: application/octet-stream");
header("Content-Disposition: attachment; filename='" . $zipName . "'");
header("Content-Transfer-Encoding: binary");
header("Content-Length: " . filesize($zipName));
clearstatcache(); //Make sure the file size isn't cached
readfile($zipName); //Output the file
$zip->deleteName($zipName);
}

From my understanding the zip file must be saved, it can not be stored in memory. $zip->close(); is what actually triggers the file creation. I am sure someone smarter than I will figure out how to write it to memory but for now there is a simple work around.
I just did something similar. The trick is to use:
// Keep script running even if user aborts
ignore_user_abort(true);
Add this as the first line of your script. What this does is allow your download script to run even if the user aborts the download. That will ensure your delete command gets called.
I am not sure if you are saying if the file is or is not deleting properly even if the user is not aborting. But if your current delete command is not working as expected you could use a simple:
unlink( $zipName);
Hope this helps.

Related

ERR_EMPTY_RESPONSE on downloading generated zip

So i was trying to generate a zip file with a bunch of pdfs in it.
After some time I got it working but the problem servers gives me ERR_EMPTY_RESPONSE.
Server's apache is 2.2 on windows server 2003...
PHP Version is 5.2.
This is the code:
<?php
$file_names=$pdfs; //This is an array of names of files in PDF format: 1.pdf, 2pdf, 3pdf...
$zip = new ZipArchive;
$zip_name = ("test.zip");
$path_zip = ("C:/www/test/docs/");
$zip->open($path_zip.$zip_name,ZipArchive::CREATE);
foreach ($file_names as $file) {
$zip->addFile($file,basename($file));
}
$zip->close();
//then send the headers to force download the zip file
header("Content-type: application/zip");
header("Content-Disposition: attachment; filename=$zip_name");
header("Pragma: no-cache");
header("Expires: 0");
#readfile($path_zip.$zip_name);
exit;
?>
THE PDFs ARE PREVIOUSLY DOWNLOADED FROM THE INTERNET WITH ANOTHER CODE
How do I fix this??
EDIT::
Adding a image showing the zip.
EDIT 2::
I removed the # at the last line and tested. Nothing works as intended.
So...
It's ugly but it works...
I just changed
header("Content-type: application/zip");
header("Content-Disposition: attachment; filename=$zip_name");
header("Pragma: no-cache");
header("Expires: 0");
readfile($path_zip.$zip_name);
exit;
for
header("Location: ".$path_zip.$zip_name);
As I say, it's pretty ugly, but it works, it make the user download the file and it doesn't get stuck or something like that.
If someone want to make another true fix, I will test it, as I don't want to change the location.
Bye.

Get zip file from url (PHP)

I have a zip files that I want users to be able to download. The trick is I don't want the users to see what the url is and I don't want to download the file to my server.
So I want users to click a link like this:
http://example.com/download/4
which server-side accesses my S3 bucket with this url:
https://s3.amazonaws.com/my-bucket/uploads/4.zip
I've tried cURL, using S3 methods, and various headers() in my download($file_id) function but can't get this to work. This has to be easy, right?
Your right, its quite easy. Probably you will have to write something like this:
$path = '/my-bucket/uploads/4.zip'; // the file made available for download via this PHP file
$mm_type="application/x-compressed"; // modify accordingly to the file type of $path, but in most cases no need to do so
header("Pragma: public");
header("Expires: 0");
header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
header("Cache-Control: public");
header("Content-Description: File Transfer");
header("Content-Type: " . $mm_type);
header("Content-Length: " .(string)(filesize($path)) );
header('Content-Disposition: attachment; filename="'.basename($path).'"');
header("Content-Transfer-Encoding: binary\n");
readfile($path); // outputs the content of the file
exit();
You set various headers to make your user download the .zip. Afterwards you put your file into the output buffer with readfile() Afterwards you end your script with exit() for security's sake. This should work for you! Remember to change the path to your file.
Thanks #Xatenev for the help. This is actually what worked for me:
$path = '/my-bucket/uploads/4.zip'; // the file made available for download via this PHP file
$mm_type="application/zip"; // modify accordingly to the file type of $path, but in most cases no need to do so
header("Content-Type: " . $mm_type);
header('Content-Disposition: attachment; filename="'.basename($path).'"');
readfile($path); // outputs the content of the file
exit();

Create a zip file and download it error

I am trying to download some files by creating the zip file on a local-server. The file is downloaded in zip format, but when I try to extract it, it gives me the error:
End-of-central-directory signature not found. Either this file is not
a zip file, or it constitutes one disk of a multi-part archive. In the
latter case the central directory and zip file comment will be found
on the last disk(s) of this archive.
This is the code I am using for this:
function download_gallery($id) {
$this->load->library('zip');
$path = '/uploads/post/' . $id;
$this->zip->read_dir($path, FALSE);
$filename = $id . '.zip';
// http headers for zip downloads
header("Pragma: public");
header("Expires: 0");
header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
header("Cache-Control: public");
header("Content-Description: File Transfer");
header('Content-Type: application/zip'); // mime type
header('Content-Disposition: attachment;filename="' . $filename . '"'); // tell browser what's the file name
header('Cache-Control: max-age=0'); // no cache
header("Content-Transfer-Encoding: binary");
header("Content-Length: " . filesize($path, $filename));
// Download the file to your desktop. Name it "my_backup.zip"
$this->zip->download($filename);
}
I checked the values of all variables which are being passing into the function, and all are fine.
I'm using codeigniter for this by the way.
I see three mistakes.
You never call $this->zip->archive($filename); therefore the zip file doesn't exist at any time.
filesize($path, $filename) is wrong usage of this function. filesize takes only one parameter.
I believe $this->zip->download($filename); already setup the required headers for the download, there is no need to do that yourself.

Create a zip file and download it

I am trying to download a 2 files by creating the zip file on local-server.the file is downloaded in zip format but when i try to extract it.it gives error:
End-of-central-directory signature not found. Either this file is not
a zip file, or it constitutes one disk of a multi-part archive. In the
latter case the central directory and zip file comment will be found on
the last disk(s) of this archive.
the following code i am using for this:
<?php
$file_names = array('iMUST Operating Manual V1.3a.pdf','iMUST Product Information Sheet.pdf');
//Archive name
$archive_file_name=$name.'iMUST_Products.zip';
//Download Files path
$file_path=$_SERVER['DOCUMENT_ROOT'].'/Harshal/files/';
zipFilesAndDownload($file_names,$archive_file_name,$file_path);
function zipFilesAndDownload($file_names,$archive_file_name,$file_path)
{
//echo $file_path;die;
$zip = new ZipArchive();
//create the file and throw the error if unsuccessful
if ($zip->open($archive_file_name, ZIPARCHIVE::CREATE )!==TRUE) {
exit("cannot open <$archive_file_name>\n");
}
//add each files of $file_name array to archive
foreach($file_names as $files)
{
$zip->addFile($file_path.$files,$files);
//echo $file_path.$files,$files."
}
$zip->close();
//then send the headers to force download the zip file
header("Content-type: application/zip");
header("Content-Disposition: attachment; filename=$archive_file_name");
header("Pragma: no-cache");
header("Expires: 0");
readfile("$archive_file_name");
exit;
}
?>
i checked the values of all variables which are passing into the function,all are fine.so please look this.Thanks in advance.
Add Content-length header describing size of zip file in bytes.
header("Content-type: application/zip");
header("Content-Disposition: attachment; filename=$archive_file_name");
header("Content-length: " . filesize($archive_file_name));
header("Pragma: no-cache");
header("Expires: 0");
readfile("$archive_file_name");
Also make sure that there is absolutely no white space before <? and after ?>. I see a space here:
↓
<?php
$file_names = array('iMUST Operating Manual V1.3a.pdf','iMUST Product Information Sheet.pdf');
$zip = new ZipArchive;
$tmp_file = 'assets/myzip.zip';
if ($zip->open($tmp_file, ZipArchive::CREATE)) {
$zip->addFile('folder/bootstrap.js', 'bootstrap.js');
$zip->addFile('folder/bootstrap.min.js', 'bootstrap.min.js');
$zip->close();
echo 'Archive created!';
header('Content-disposition: attachment; filename=files.zip');
header('Content-type: application/zip');
readfile($tmp_file);
} else {
echo 'Failed!';
}
// http headers for zip downloads
header("Pragma: public");
header("Expires: 0");
header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
header("Cache-Control: public");
header("Content-Description: File Transfer");
header("Content-type: application/octet-stream");
header("Content-Disposition: attachment; filename=\"".$filename."\"");
header("Content-Transfer-Encoding: binary");
header("Content-Length: ".filesize($filepath.$filename));
ob_end_flush();
#readfile($filepath.$filename);
I found this soludtion here and it work for me
One of the error could be that the file is not read as 'archive' format. check out ZipArchive not opening file - Error Code: 19. Open the downloaded file in text editor, if you have any html tags or debug statements at the starting, clear the buffer before reading the file.
ob_clean();
flush();
readfile("$archive_file_name");
I just ran into this problem. For me the issue was with:
readfile("$archive_file_name");
It was resulting in a out of memory error.
Allowed memory size of 134217728 bytes exhausted (tried to allocate 292982784 bytes)
I was able to correct the problem by replacing readfile() with the following:
$handle = fopen($zipPath, "rb");
while (!feof($handle)){
echo fread($handle, 8192);
}
fclose($handle);
Not sure if this is your same issue or not seeing that your file is only 1.2 MB. Maybe this will help someone else with a similar problem.
I have experienced exactly the same problem. In my case, the source of it was the permissions of the folder in which I wanted to create the zip file that were all set to read only. I changed it to read and write and it worked.
If the file is not created on your local-server when you run the script, you most probably have the same problem as I did.
but the file i am getting from server after download it gives the size
of 226 bytes
This is the size of a ZIP header. Apparently there is no data in the downloaded ZIP file. So, can you verify that the files to be added into the ZIP file are, indeed, there (relative to the path of the download PHP script)?
Consider adding a check on addFile too:
foreach($file_names as $file)
{
$inputFile = $file_path . $file;
if (!file_exists($inputFile))
trigger_error("The input file $inputFile does not exist", E_USER_ERROR);
if (!is_readable($inputFile))
trigger_error("The input file $inputFile exists, but has wrong permissions or ownership", E_USER_ERROR);
if (!$zip->addFile($inputFile, $file))
trigger_error("Could not add $inputFile to ZIP file", E_USER_ERROR);
}
The observed behaviour is consistent with some problem (path error, permission problems, ...) preventing the files from being added to the ZIP file. On receiving an "empty" ZIP file, the client issues an error referring to the ZIP central directory missing (the actual error being that there is no directory, and no files).
Make sure that the path inside $archive_file_name is correct and does exist. In your code, you updated the $archive_file_name variable by putting an undeclared prefix variable - $name.
$archive_file_name = $name.'iMUST_Products.zip';
If the path is not correct and cannot be found, the created zipped file have nowhere to go to and the two files have nowhere to store to to begin with. The path to the files to be stored should also exist.
Let's assume that you just want to customize the name of the zipped folder to be downloaded that is why there is a $name variable to prevent redundant file name. But you have to consider the URI where you run your script.
For example, you're using URI routing, and you load the script at https://sample.co/file-management/download-zipped-111. With your current script, the zipped folder will be stored at file-management folder. If it does not exist, you will mostly encounter the error you are experiencing right now.
The solution is to explicitly declare the path to the folder where the zipped file will be stored.
For example, you want to store the zipped file at Harshal/files/zipped/. Make sure that this folder exist by creating a folder named zipped inside files folder and that you have access to write on this folder.
$file_names = array('iMUST Operating Manual V1.3a.pdf', 'iMUST Product Information Sheet.pdf');
// $name should be declared before this line
$archive_file_name = $name.'iMUST_Products.zip';
//Download Files path
$file_path = $_SERVER['DOCUMENT_ROOT'].'/Harshal/files/';
zipFilesAndDownload($file_names, $archive_file_name, $file_path);
function zipFilesAndDownload($file_names, $archive_file_name, $file_path)
{
$zip = new ZipArchive();
// WE REUSED THE $file_path VARIABLE HERE THEN ADDED zipped FOLDER
if ($zip->open($file_path.'zipped/'.$archive_file_name, ZIPARCHIVE::CREATE )!==TRUE) {
exit('cannot open <'.$archive_file_name.'>');
}
//add each files of $file_name array to archive
foreach($file_names as $files)
{
$zip->addFile($file_path.$files, $files);
}
$zip->close();
//then send the headers to force download the zip file
header("Content-type: application/zip");
header("Content-Disposition: attachment; filename=$archive_file_name");
header('Content-length: '.filesize($file_path.'zipped/'.$archive_file_name));
header("Pragma: no-cache");
header("Expires: 0");
readfile("$archive_file_name");
exit;
}
People may suggest - "Why not directly put the full path to the $archive_file_name variable?
$archive_file_name = $file_path.'zipped/'.$name.'iMUST_Products.zip';
If we put it directly to $archive_file_name variable, the file that will be downloaded will mostly likely be named /var/www/Harshal/files/zipped/xxx-iMUST_Products.zip.

PHP file download problem: Server freezes for several minutes when user cancels a download then retries

I'm using the following the PHP script to download a 20mb file:
(filepath & filename are set earlier in the script)
$fullPath = $filepath.$filename;
if ($fd = fopen($fullPath, "r")) {
// http headers for zip downloads
header("Pragma: public");
header("Expires: 0");
header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
header("Cache-Control: public");
header("Content-Description: File Transfer");
header("Content-type: application/octet-stream");
header("Content-Disposition: attachment; filename=\"".$filename."\"");
header("Content-Transfer-Encoding: binary");
header("Content-Length: ".filesize($fullPath));
while(!feof($fd)) {
echo(fread($fd, 1024*8));
flush();
if (connection_status()!=0) {
fclose($fd);
die();
}
}
}
fclose($fd);
exit();
It works fine if the download finishes, but if the download is canceled by the user, and they click on the link to re-download, the server is completely unresponsive for several minutes, and then the download will begin. It seems like it is waiting for the script to time out...but isn't the "if (connection_status()!=0)..." part supposed to kil the script?
Any help is much appreciated! Thank you in advance!
I think you're over-engineering your solution somewhat. Try using readfile() instead of your fopen/fread loop.
Better yet, unless there's a compelling reason why you need PHP to mediate the file transfer, don't use a PHP script at all and simply provide a direct link to the file in question.
The best code is always the code you don't have to write.

Categories