Downloading a zip file from a folder on my web server - php

I have an application that allows users to download mulitple files from a S3 bucket through an EC2 instance in one zip folder.
The process works fine, however, I would like the system to create a folder for each user when they want to download files, and then the files are downloaded to this folder and then put in a zip folder, within the user's folder, and then this zip folder is downloaded.
The files are downloading to this folder fine, and they are also zipping correctly, but my problem is that it is not downloading the zip file from the user's folder, and the download is showing as an empty zip file.
Below is my 'download all' code:
mkdir($userId);
$allName = $vshort . "v" . $number . "_All.zip";
$allName = str_replace(" ", "_", $allName);
if (isset($_POST['downloadAll']))
{
$zip = new ZipArchive();
$zip->open("{$userId}/{$allName}", ZipArchive::CREATE);
$s3->registerStreamWrapper();
foreach ($items as $item)
{
$fid = $item->item_file_id;
$file = ItemFile::locateId($fid);
$a = $file[0]->item_file_type;
$b = $file[0]->item_file_app;
$c = $file[0]->item_file_name;
$fileName = $a . "/" . $b . "/" . $c;
$download = $s3->getCommand('GetObject', [
'Bucket' => AWS_BUCKET,
'Key' => $fileName
]);
$streamFile = $c;
$user = User::locate($_SESSION['email']);
$uid = $user->user_id;
$cid = $user->user_company_id;
$history = new History();
$history->insert($fid, $uid, $cid);
$req = $s3->createPresignedRequest($download, '+1 minutes');
$link = (string)$req->getUri();
$stream = file_get_contents($link);
header($_SERVER["SERVER_PROTOCOL"] . " 200 OK");
header("Cache-Control: public"); // needed for internet explorer
header("Content-Type: application/'$a'");
header("Content-Disposition: attachment; filename='$c'");
file_put_contents($streamFile, $stream);
$zip->addFile($streamFile);
$downArray[] = $streamFile;
}
$zip->close();
$dLink = "https://mywebsite/" . $userId . "/" . $allName;
$size = filesize($allName);
header("Content-type: application/zip");
header("Content-Disposition: attachment; filename=$allName");
header("Content-length: " . $size);
header("Expires: 0");
ob_end_clean();
stream_context_set_default([
'ssl' => [
'verify_peer' => false,
'verify_peer_name' => false,
]
]);
readfile($dLink);
unlink($allName);
foreach ($downArray as $down)
{
unlink($down);
}
}
I have a feeling it is something to do with my headers but I'm not sure, any help will be greatly appreciated.
NOTE: I will be deleting the folder once the user has exited the page.
Below is what I am seeing on the web server
The folder '6' is the user id for the user in question

You appear to be calling readfile() on your download link ($dLink), which would serve the file throught HTTP. You probably meant to call readfile on $allName, the filesystem path of the same file. My guess is that your http request to $dLink is failing somehow. Also, you mkdir($userId), but never use that directory.
Apart from that, it seems your $allName variable contains the basename of the zipfile (i.e 'myzip-v-20_ALL.zip'). I would recommend opening your zipfile to a full path to your data directory, so you keep filesystem and http paths separate.
$allNameFilesystem = "{$userId}/{$allName}";
$allNameHttp = "https://mywebsite/{$userId}{$allName}";
// ...
$zip->open($allNameFilesystem, ZipArchive::CREATE);
// ...
// The download link is $allNameHttp
That is, if you actually need the http path. In this case, if you change readfile to use the filesystem path, you really don't use the http path anymore in your code.

Related

Code to create ZipArchive file on PHP is not working, once downloaded file is damaged

I am trying this piece of code found on the internet, which helps to create a ZipArchive with files on PHP.
I am trying this on my server, which is an AWS EC2 with Linux Ubuntu where a web server is running. I run the following code :
<?php
function createZipAndDownload($files, $filesPath, $zipFileName)
{
// Create instance of ZipArchive. and open the zip folder.
$zip = new ZipArchive();
$r = $zip->open($zipFileName, ZipArchive::CREATE);
var_dump($r); echo "<br>";
// Adding every attachments files into the ZIP.
foreach ($files as $file) {
$r = $zip->addFile($filesPath . $file, $file);
var_dump($r); echo "<br>";
}
$r = $zip->close();
var_dump($r); echo "<br>";
// Download the created zip file
header("Content-type: application/zip");
header("Content-Disposition: attachment; filename = $zipFileName");
header("Content-length: " . filesize($zipFileName));
header("Pragma: no-cache");
header("Expires: 0");
readfile("$zipFileName");
exit;
}
// Files which need to be added into zip
$files = array('twiglet-1120x720.jpg','22vwqq.jpg','fb4eb7f8aa5431b0b4e26365ebd59933-239x300.jpg');
// Directory of files
$filesPath = 'https://peakon.com/wp-content/uploads/2018/08/';
// Name of creating zip file
$zipName = 'document.zip';
echo createZipAndDownload($files, $filesPath, $zipName);
?>
Then, I have this output :
bool(true)
bool(false)
bool(false)
bool(false)
bool(true)
I understand that the ZipArchive is created, but files are not sent inside the ZipArchive. Nevertheless, the file is downloaded once finished and when I want to open it, it's written that "the file is damaged" and I can't open it.
Can you help me ? Do you know why it is not working ? I could like to insert theses images inside and download it ? (images are just examples)
Thank you in advance for your help.
addFile takes a file system path, not an HTTP URL. There's several ways to fix it but for your case, it might just be easiest to use file_get_contents along with addFromString. file_get_contents does accept URLs, weirdly enough. Note that the parameters for addFromString aren't in the same order as addFile.
$r = $zip->addFromString($file, file_get_contents($filesPath . $file));
In the long run, however, I'd recommend downloading the files, checking the results and throwing error messages as needed, but this should get you started at least.
edit
The parameter ZipArchive::CREATE will only create an archive if it doesn't exist already, so it might actually try opening an existing file and, with your previous attempts, this might result in you opening a corrupt file. Instead, I would recommend using either ZipArchive::OVERWRITE to start over, or ZipArchive::EXCL to guarantee that an existing file is not touched.
Putting this all together, this code, exactly as-is, works when I test it:
function createZipAndDownload($files, $filesPath, $zipFileName)
{
// Create instance of ZipArchive. and open the zip folder.
$zip = new ZipArchive();
$r = $zip->open($zipFileName, ZipArchive::OVERWRITE);
var_dump($r);
echo "<br>";
// Adding every attachments files into the ZIP.
foreach ($files as $file) {
$r = $zip->addFromString($file, file_get_contents($filesPath . $file));
var_dump($r);
echo "<br>";
}
$r = $zip->close();
var_dump($r);
echo "<br>";
// Download the created zip file
header("Content-type: application/zip");
header("Content-Disposition: attachment; filename = $zipFileName");
header("Content-length: " . filesize($zipFileName));
header("Pragma: no-cache");
header("Expires: 0");
readfile($zipFileName);
exit;
}
// Files which need to be added into zip
$files = array('twiglet-1120x720.jpg', '22vwqq.jpg', 'fb4eb7f8aa5431b0b4e26365ebd59933-239x300.jpg');
// Directory of files
$filesPath = 'https://peakon.com/wp-content/uploads/2018/08/';
// Name of creating zip file
$zipName = 'document.zip';
createZipAndDownload($files, $filesPath, $zipName);

PHP ZipArchive isn't creating a gs:// zip file in Google AppEngine, Warning: filesize(): stat failed for gs://

I'm able to directly write and read files in Google Storage, but when I try to use ZipArchive to create a file, it fails. Google says that the zip extension is enabled in the GAE.
$tmpdirectory .= 'gs://#default#/tmp/user-'.$uid;
$uniqueid = uniqid() . time();
$user_visible_filename = 'export.zip';
$output_path = sprintf("%s/export.%s.zip", $tmpdirectory, $uniqueid);
$zip = new ZipArchive;
$res = $zip->open($output_path, ZipArchive::CREATE);
if ($res === true) {
foreach ($data as $datatype => $records) {
$filename = sprintf("%s/%s.csv", $tmpdirectory, $datatype);
write_csv_to_filename($records, $filename);
$localname = basename($filename);
$fileresult = $zip->addFromString($localname, file_get_contents($filename));
print "adding $localname... num files in zip: ".($fileresult ? "true" : "false")." -> ".$zip->numFiles."<br/>\n";
}
}
$closeresult = $zip->close();
print "user_visible_filename: $user_visible_filename<br/>\n";
print "zip filename: $output_path<br/>\n";
print "file size: ".filesize($output_path)."<br/>\n";
header('Content-Type: application/zip');
header('Content-Length: '.filesize($output_path));
header('Content-Disposition: attachment; filename=' . $user_visible_filename);
The above code writes some csv files and I want to bundle them together in a zip file and have their browser download it. I know that the above headers() won't work because I'm printing stuff about before them; I'm going printing the stuff out to debug what's going wrong.
I am able to write each of the CSVs to gs:// and I'm able to access their correct filesize and read their contents back after writing them.
However, when I try to read the filesize() of the zip file (gs://#default#/tmp/user-152/export.5b4565bda18481531274685.zip) it craps out with a big warning message (cannot stat file) and stack trace as if the file doesn't exist.
$zip->close(); returns false which means there was a failure, but I have no idea why it failed.
I found an alternative solution that worked: TbsZip
require_once '../inc/tbszip.php';
$tmpfile = tempnam('/tmp', 'export');
$zip = new clsTbsZip();
$zip->CreateNew($tmpfile);
foreach ($data as $datatype => $records) {
$filename = sprintf("%s/%s.csv", $tmpdirectory, $datatype);
write_csv_to_filename($records, $filename);
$localname = basename($filename);
//$fileresult = $zip->addFile($filename, $localname);
$zip->FileAdd($localname, file_get_contents($filename), TBSZIP_STRING);
}
// Flush() will send all the required headers for downloading the file.
$zip->Flush(TBSZIP_DOWNLOAD, $user_visible_filename, 'application/zip');
$zip->Close();

Why zip file can't be opened after downloaded but fine via manual transfer?

I searched and tried a few previous questions/example codes, but couldn't get this to work.
I'm trying to deliver the results to end-users via PHP code. Here is my code.
$varZipFile = $varBaseNameParts[0] . '.' . $varDate . '.zip';
$varZipDir = $varBaseNameParts[0] . '_' . $varDate;
$zip = new ZipArchive();
$zip->open($varZipFile, ZipArchive::CREATE | ZipArchive::OVERWRITE);
$zip->addFile('008.csv');
$zip->addFile('002.csv');
$zip->close(); // mark line xxx
header("Content-Type: application/zip");
header("Content-disposition: attachment;filename=$varZipFile");
header('Content-Length: ' . filesize($varZipFile)); // with or without this line, got the same error
header("Content-Type: application/force-download"); // with or without this line, got the same error
readfile($varZipFile);
I got the .zip file in my browser. However WinZip cannot open it, neither can 7-Zip. WinZip complains that "Error: Central directory not found".
Interestingly, when I manually transfer the file via WinSCP from my server to my Windows machine, I can open the file with either WinZip or 7-Zip. This indicates it works all fine to 'mark line xxx', and problems occurs in the header lines.
TIA!
Your ouput buffer may not be cleared before you try to serve the download. Try to clean the output buffer before serving the download with the ob_clean() function like this:
$zip->close(); // mark line xxx
ob_clean();
Your code is working well on my side, debug this $varBaseNameParts[0] to see if the value is correct or try the below code.
// $name = name of the archive
// $archive_directory = directory to save the archive
// $file_array = list of files
function create_archive($name, $archive_directory, $file_array) {
$target_file = $archive_directory . '/' . $name . date('m-d-y').date('h-i-sa') . '.zip';
$zip = new ZipArchive();
if (count($file_array)) {
if ($zip->open($target_file, ZIPARCHIVE::CREATE | ZIPARCHIVE::OVERWRITE)) {
foreach ($file_array as $file) {
$zip->addFile($file);
}
$zip->close();
chmod($target_file, 0777);
return $target_file;
}
}
return null;
}
function create_download($archive) {
$file_name = basename($archive);
header("Content-Type: application/zip");
header("Content-Disposition: attachment; filename=$file_name");
header("Content-Length: " . filesize($archive));
readfile($archive);
}
// create the archive file
$archive = create_archive('test', 'archive', ['008.csv', '002.csv']);
if ($archive) {
create_download($archive);
}

ZIP file download php readfile() error

I know this question is posted in this forum for many times, but trust me I have tried all the possible solutions suggested but it didn't work for me.
I am trying to download multiple files using zip, though the zip file gets downloaded successfully, its corrupt and I encounter error after opening it in notepad:
Warning: readfile(E:\Downloads/IMG-20140831-WA0000.zip) [function.readfile]: failed to open stream: No such file or directory in...
I tried all the possible solutions mentioned in the forum such as header-check, web server user has write permission to the folder where I am creating the ZIP file, error-checking before downloading etc. but it did't worked out.
After performing error checking, I encountered something like
Error creating ZIP file : IMG-20140831-WA0000.zip
My code snippet:
function zipFilesDownload($file_names, $archive_file_name, $file_path) {
$zip = new ZipArchive;
if ($zip->open($archive_file_name, ZipArchive::CREATE) !== TRUE) {
exit("cannot open <$archive_file_name>\n");
}
foreach($file_names as $files) {
$zip->addFile($file_path . $files, $files);
}
if ($zip->close() === false) {
exit("Error creating ZIP file : " . $archive_file_name);
}
if (file_exists($archive_file_name)) {
header("Content-Description: File Transfer");
header("Content-type: application/zip");
header("Content-Disposition: attachment; filename=" . $archive_file_name . "");
header("Pragma: no-cache");
header("Expires: 0");
readfile("E:\Downloads/" . $archive_file_name);
ob_clean();
flush();
exit;
} else {
exit("Could not find Zip file to download");
}
}
$fileNames = array(
'D:\\xampp\htdocs\BE\Multimedia/' . $fullName,
'D:\\xampp\htdocs\BE\Decrypt/' . $decrypt_file
);
$zip_file_name = $actualName . '.zip';
$file_path = dirname("E:\Downloads") . '/';
zipFilesDownload($fileNames, $zip_file_name, $file_path);
Please suggest some solutions.
The problem looks to be this line:
$file_path = dirname("E:\Downloads") . '/';
The dirname function "Returns parent directory's path". This means that $file_path will be E:\.
In your function you are using $file_path in the $zip->addFile() method, to refer to the file that should be added to the ZIP archive.
In other words, if you have an array of files, like:
$files = array(
'file1.txt',
'file2.txt',
'file3.txt',
);
Then the files that will be added to the archive will be:
E:\file1.txt
E:\file2.txt
E:\file3.txt
What you probably want is to have these files added:
E:\Downloads\file1.txt
E:\Downloads\file2.txt
E:\Downloads\file3.txt
As far as I can see, to fix your code you simply need to not use dirname(), like this:
$file_path = "E:\Downloads\\";

Zip file not downloading in PHP

I've implemented the code to Create Zip Folder of Files (from db path) and download zipped folder in PHP. I am using Ubuntu OS.
public function actionDownload($id) {
$model = $this->loadModel($id, 'Document');
$results = array();
$results = $this->createZip($model);
$zip = $results[0];
$size = filesize($zip->filename);
if ($zip->filename) {
header("Content-Description: File Transfer");
header("Content-type: application/zip");
header("Content-Disposition: attachment; filename=\"" . $model->name . "\"");
header("Content-Transfer-Encoding: binary");
header("Content-Length: " . $size);
ob_end_flush();
flush();
readfile($zip->filename);
// To Store User Transaction Data
//$this->saveTransaction();
//ignore_user_abort(true);
unlink($zip->filename);
$zip->close();
// Delete newly created files
foreach ($results[1] as $fl) {
unlink($fl);
}
}
}
public function createZip($model) {
$data = Document::model()->findAll('parent_folder=:id', array(':id' => (int) $model->document_id));
$fileArr = array();
foreach ($data as $type) {
$fileArr[] = $type->path;
}
$filestozip = $fileArr; // FILES ARRAY TO ZIP
$path = Yii::app()->basePath . DS . 'uploads' . DS . Yii::app()->user->id;
//$model->path = trim(DS . $path . DS); // DIR NAME TO MOVE THE ZIPPED FILES
$zip = new ZipArchive();
$files = $filestozip;
$zipName = "USR_" . Yii::app()->user->id . "_" . $model->name . "_" . date("Y-m-d") . ".zip";
$fizip = $path . DS . $zipName;
if ($zip->open($fizip, ZipArchive::CREATE) === TRUE) {
foreach ($files as $fl) {
if (file_exists($fl)) {
$zip->addFile($fl, basename($fl)) or die("<p class='warning'>ERROR: Could not add file: " . $fl . "</p>");
}
}
}
$resultArr = array();
$resultArr[] = $zip;
$resultArr[] = $files;
return $resultArr;
}
The Zip creation code working fine and its creating zip file there but the issue is owner of the file is www-data and file permission is Read-Only.
When I am trying to set chmod($zip->filename, 0777) permission to that zipped folder then its showing an error?
Error 500
chmod(): No such file or directory
In fact file is present there.
If I am trying without chmod() then its showing me error of
Error 500
filesize(): stat failed for /home/demo.user/myapp/public_html/backend/uploads/1/USR_1_kj_2013-12-23.zip
and then its not downloading the zip file.
This is really weird issue I am facing. It seem to be some permission issue of zip file that's why filesize() is not able to perform any operation on that file but strange thing is chmod() also not working.
Need Help on this.
Thanks
If www-data has read permissions, the file permissions are properly set. No chmod required.
You need to call $zip->close() before the download, as only on $zip->close() the file will be written to disk. readfile() will not work unless the file is written to disk.
A smarter way, could be to create the archive in memory only using the php://memory stream wrapper. However this is only an option if you need the archive for a single download only.

Categories