Zip PDF-Files download not working - php

I'm building an interface that allows collecting PDF-Files from a server-path to download them zipped at once which works fine most of the time. Now, some PDF-Files are not working, means if the zip contains one of the non-working files the whole download will not performed.
Now i tried many files with different sizes. I can tell most of the files larger than 10MB are not working, but one file (15mb) is working, so it can't be the file-size causing this error - right?
What happens is there's just no download at all. Additionally tried reading the results of "$zip->addFile" and "$zip->close" but it's all true, even if the download will not be performed. ANY ideas which may cause this bug or at least some hints on how to find this out?
The (x)debug inspection of the variables during process does not differ between the time it worked and the time its not working. Only differences are filesizes and -paths.
Here's the Code i'm using to build the ZIP:
public function downloadFiles() {
$myFiles = $this->Session->read('Files.MyFiles');
if(count($myFiles)) {
// build zip
$zipname = 'amaz_photos_' . time() . '.zip';
$zip = new ZipArchive();
$zip->open($zipname, ZipArchive::CREATE);
foreach($myFiles as $file) {
if($file['type'] == 'directory') {
$dirName = str_replace('/', '', substr($file['path'], strrpos($file['path'], '/'), (strlen($file['path']) - strrpos($file['path'], '/'))));
$zip->addEmptyDir($dirName);
$this->addFolderToZip($file['path'] . '/', $zip, $dirName . '/');
} else {
$zip->addFile($file['path'], str_replace('/', '', substr($file['path'], strrpos($file['path'], '/'), (strlen($file['path']) - strrpos($file['path'], '/')))));
}
}
$zip->close();
// get(send) zip
header('Content-Type: application/zip');
header('Content-disposition: attachment; filename=' . $zipname);
header('Content-Length: ' . filesize($zipname));
readfile($zipname);
ignore_user_abort(true);
unlink($zipname);
}
// clear "myFiles"
//$this->Session->write('Files.MyFiles', array());
$this->redirect(array(
'controller' => 'pages',
'action' => 'display'
));
}
After all i tried opening the affected files with several PDF-Readers including Adobe's original to determine if its broken, but ̶i̶̶t̶̶s̶ it seems not.
This will occur in openable zip-files but the download does not start anyways. I found out that in that special cases where the download does not start, there is no response-header ("application/zip") being set on the network-frame compared to the times it worked.
What I found out else:
Lets say I have a folder with 20 files in it, that's not downloading. I tried selecting file 1-10: downloading, then I tried downloading file 11-20: downloading, then I tried to download file 1-20: not downloading, then I tried two times mixing the patterns, first the even files 2,4,6,8.. (1-20), after that the odd files 1,3,5,7,.. (1-20): each not downloading.
This states out the error does not belong to a specific file.
Any suggestions how to determine the trigger for this error?
Update:
I was able to download the generated zip-file doing these steps:
Trigger 'create & download zip-file'
It creates a zip-file but not download it
Type the generated zip-files filename in the code to download it on the next procedure (e. g. "$zipname = "generatedOnStep1.zip").
Trigger 'create & download zip-file' again
It does not download the (recently) generated file, but the file i generated in step 1. So it seems that: Generating Zip-Files is working AND downloading that same zip-File is working, but not at once and only in some unknown combinations.
...
Update 2:
Since I was not able to determine the issue nor fix it, I ended up redesigning the whole process, to redirect to the generated zip-file instead of "readfile" it. Some features had to be sourced out but anyways it works this way.

Try this... Remove readfile($zipname); and replace it with these:
/* make sure no other functions
holding this file */
$filehandle = fopen($zipname, "r");
while (!feof($filehandle)) {
/* lets read the file by pieces and
send it to user's browser */
echo fread($filehandle, 8192);
}
fclose($filehandle);
Hope this helps.

Related

Downloaded zip archive empty after adding csv file

Can skip About and Goal if necessary. They provide context/background about program.
The Problem:
When I download a zip archive that is supposed to have a csv file in it, I get an empty archive or invalid archive. Now, I can make the zip archive with the csv in it, on the server and download via FileZilla. This is not the desired use case. User wants to download automatically after clicking a button.
About program:
Agents sign up for event (i.e. RSVP to it) and provide a set of photos.
For every agent, there are +1 photos. User wants to download a list of all RSVPs for each event (i.e. the sign up data). For each RSVP, User also wants to see the photos they uploaded to the server.
Goal of program:
Create csv file on server of mysql query (each row is an RSVP)
For each row, create zip archive for that row's/RSVP's photos.
Wanted: 1 csv file and multiple zip archives all in one big archive
End result: Add all files into one zip archive and download to User.
Addendum: This big zip file needs to be freshly generated every time it is requested.
Current Status of Program:
CSV file created. Below it is added to ZipArchive object. Downloaded zip file and found it empty.
//store the result of the query. Included to show process
$result = mysqli_query($dbh, $sql);
//open the file to write the information to.
$file = fopen('event-report.csv', 'w');
$txt .= 'Title Company,' .'Event ID,' . 'Event Time,' . 'First Name,' . 'Last Name,' . 'Email,' . 'Agent Company,' . 'Phone Number,' . 'Listing ID,' . 'URL,' . 'Address,' . 'ZIP,' . "\n";
while ($row = mysqli_fetch_row($result)){
for ($i=0; $i<12; $i++){
$txt .= $row[$i] . ','; //About the agent attending
}
$txt .= "\n";
$listings[$listing_count] = $row[8];
}
fputs($file, $txt); //store into csv file
fclose($file); //verified on server; contains desired output
$zip = new ZipArchive();
$dir = 'tmp'; //create temporary directory with write access
if ( !file_exists($dir) ) {
mkdir ($dir, 0755); //Everything for owner, read/execute for others
}
if($zip->open('test.zip', ZIPARCHIVE::CREATE) === TRUE ){
$zip->addFile('event-report.csv');
if (file_exists($zip->getFromName('event-report.csv'))){
echo "Exists";
}
else{
echo "Does Not Exist";
echo $_SERVER['DOCUMENT_ROOT'];
}
}
else {
echo "Failed code";
}
$zip->close();
if(!$return){
echo " You do not have write access to current folder.";
}
else{
echo "You do have access."; //without headers, the zip containing csv successfully created on server.
}
header('Content-disposition: attachment; filename=download.zip');
header('Content-type: application/zip');
readfile($dir.'/test.zip'); //When downloaded: still empty/invalid.
Possible Problem(s)
Zip is not finding the file because it is being created at the same time.
1a. Solved:. Files created can be put into zip archive right after closing.
addFile parameter(s) are incorrect or lacking.
2a. Man page says for this parameter: "path to the file to add"
2b. Tried putting the path after the domain name but no success:
$zip->addFile('/f1/f2/f3/f.csv');
2c. PHP Manual > ZipArchive::addFile i. Solved: The parameter is just the name of the file. No path is needed in this case.
Questions
How do I add a csv file, a zip archive, or any file in general to
another zip archive so that it will not be empty when I download it?
Is this implementation incorrect because I am trying to add a file
to a zip archive right after creating it? See Possible Problem(s) 1.
If this question can be solved by a little reading, then what resources can you link that can guide me along? I have spent an
entire day reading in regards to this issue and do not feel like I
understand what I am missing here.
3a. file permissions
You need to be explicit with absolute paths when adding to the archive.
$zip->addFile($abs_path, $relative_path);
Solution: flush headers before calling readfile(). I learned that I do not need flush() but I left it in there anyways. See SO post that helped me include these two statements here
header('Content-type: application/zip');
header('Content-disposition: attachment; filename=test.zip');
ob_clean(); // discard any data in the output buffer (if possible)
flush(); // flush headers (if possible)
readfile('tmp/test.zip');
exit();
I also changed some minor things with the error checking that I thought I would mention below:
I made a directory with certain permissions that will hold the files I want to put into the archive. This is done at the beginning before the csv and zip are created.
$dir = 'tmp/';
if ( !file_exists($dir) ) {
mkdir ($dir, 0777);
}
Opening the file has changed too. It now involves the new directory I make:
$zip = new ZipArchive;
if($zip->open($dir.'test.zip', ZIPARCHIVE::CREATE) === TRUE ){
$res = $zip->addFile('tmp/event-report.csv', 'event-report.csv'); //add the report csv file.
if ($res){
echo "Exists ";
}
else{
echo "Does Not Exist ";
//echo $_SERVER['DOCUMENT_ROOT'];
}
}
Thanks to everyone that contributed or just viewed this question.

php get file content of downloadable file

Coult not find any similar problem solved on the web, so here's my situation:
I have a .jsp "webpage" that generates a .csv file based on specific parameters.
As an example, if I use my browser to open the site, I type in:
redownloadsubmitter.jsp?id=225&batch_id=2013_11_20&orgshort=NEP
The script then uses the data in the query string and generates the matching .csv file, named: NEP_DETAILS_2013_11_20.csv
Now what I want is to not manually having to use my browser, open the script and download the file to my local harddrive. Instead I want to use a PHP script that grabs the content and then can further format it, based on my needs.
I thought about the following code, but that did not work. Instead it returns nothing, empty website when I try it..
$download = file_get_contents('redownloadsubmitter.jsp?id=225&batch_id=2013_11_20&orgshort=NEP');
echo $download;
Any other ideas?
NOTE: just in case someone has this question: I have no access to the .jsp file and I therefore cannot change how it operates.
file_get_contents() isn't smart and doesn't know that's a URL you're passing in. It's trying to literally open a local file whose name is redownloadsubmitted.jsp.etc......
If you want f_g_c() to do an HTTP operation, then you'll have to include a full-blown URL:
$download = file_get_contents('http://example.com/redownloadsubmitter.jsp etc....');'
Try this code for download file.
<?php
/**
* $filename filename in server
* $downloadname filename when download file
*/
$filename = __FILE__;
$dowloadname = 'PHPDownload.php';
Header("content-type:application/octet-stream");
Header("Accept-Ranges: bytes");
Header("Accept-Length: ".filesize($filename));
Header("Content-Disposition: attachment; filename=".$dowloadname);
if(file_exists($filename) && $fp=fopen($filename,"r")) //file exists and open it
{
echo fread($fp,filesize($filename)); //read write to the browser
fclose($fp);
}
//End_php

How to keep a Zip file from becoming corrupted when streamed

I'm having an issue with streaming a Zip file for download with PHP. Here is my code:
$arrFiles = $this->getRequest()->getPost('files', array());
$objFacultyClubDownloads = new FacultyClub_Downloads();
$objFCDownloads = $objFacultyClubDownloads->saveFacultyClubDownloadsInfo($arrFiles);
// create the zip file
$strZipName = 'download' . time() . '.zip';
$strZipSavePath = PUBLIC_PATH . '/downloads/temp/';
$objZip = new ZipArchive;
if ($objZip->open($strZipSavePath . $strZipName, ZipArchive::CREATE) === TRUE) {
foreach ($arrFiles['file_name'] as $intKey => $strValue) {
$strFilePath = PUBLIC_PATH . $strValue;
if (file_exists($strFilePath)) {
$strFileName = strtolower(basename($strValue));
$objZip->addFile($strFilePath, $strFileName);
}
}
$strZipName = $objZip->filename;
$objZip->close();
}
// strip out the layout and view
$this->getHelper('layout')->disableLayout();
$this->getHelper('viewRenderer')->setNoRender();
// stream the zip file
header('Content-Type: application/zip');
header("Content-Disposition: attachment; filename=Faculty-Club-Download.zip");
//header('Content-Length: ' . filesize($strZipName));
readfile($strZipName);
This is in Zend Framework. The issue seems to be the streaming. The download is properly created in the temp folder. I can unzip that Zip just fine. But when the file is streamed for download, it unzips a file with the extension .cpgz. From what I am reading, this means the Zip is corrupt. When I double click it to unzip it, I just get another verison of the same file with the .cpgz extension -- it duplicates itself.
I've tried adding ob_clean and flush before readfile, but that didn't help. Any ideas what is going on here?
If the file on the server is valid, but upon transferring over http becomes corrupt, I would look at everything between PHP and the browser. Assuming you're using Apache, start there and disable gzip for this request. We have run into similar issues with reverse proxies and double compression.
Probably have to call this pretty early in your PHP code:
apache_setenv('no-gzip', '1');
http://php.net/apache_setenv

I want to search a directory for multiple files using PHP and start a download them over HTTP

Just to start, I'm a PHP noob.
I have an Apache server which hosts my files. I have a device which can only point to one PHP file. What I need it to do is have my PHP file read in the name of the file I want to download, and point it towards the directory it is stored. Currently, I have it pointing to one file, but I need it to be able to point to multiple. Is this possible in PHP?
Here's what I have so far:
<?php
$file_name = 'file.img';
$size = filesize($file_name);
$file_url = 'http://192.168.0.5/' . $file_name;
header("Content-length: $size");
header("Content-Type: text/plain");
header("Content-Transfer-Encoding: binary");
header("Content-disposition: attachment; filename=\"".$file_name."\"");
readfile($file_url);?>
Edit:
The commands I want to input in order to download the file are close enough to as follows:
cmd=download+-a+$$.img+altimage
cmd=download+-a+$$.conf+altconfig
and the download directory is the .php file. I am open to other suggestions in how to do this.
Edit2:
Here's what an exact sample URL is:
myserver.com/cgi-bin/va/cmd?hdl+fullconfig.ini+altconfig
the hdl is a predefined function which points to the download directory, in order to download the file from the server, so the layout of what you mean isn't exactly the same.
I have trouble understanding what exactly you're trying to do, but I guess that you want a user to be able to download multiple files. If that is correct, here is one way to achieve this:
You can let PHP create a ZIP archive using the ZIP extension. For it to work, you have to load the extension php_zip.dll inside your php.ini.
$ZIP = new ZipArchive();
// Use the current time as the filename to prevent two users to use the same file
$ZIPName = microtime().".zip";
// Create a new ZIP file
$ZIP->open($ZIPName, ZIPARCHIVE::CREATE);
// Loop through all files - $Files needs to be an array with the names of the files you want to add, including the paths
// basename() will prevent the creation of folders inside the ZIP
foreach ($Files as $File) {
$ZIP->addFile($File, basename($File));
}
// Close the archive
$ZIP->close();
// Send the archive to the browser
readfile($ZIPName);
I hope this is what you were looking for.

Zip all files in directories and force download

I have the following code running on my site. The only problem I have with it is that it makes a zip file on the server and then user downloads.
I would like to know what should I do in order for the zip file to be generated "on the fly" without being dumped on the server disk first. I would also like to make possible for the user to pause/resume the download.
//function for zip
function zipFilesAndDownload($file_names,$archive_file_name,$file_path)
{
//create the object
$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.str_replace('./','',$files),translit($files).".mp3");
}
$zip->close();
//then send the headers to foce 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;
}
There are three requirements you mention:
zip is available for download on fly - I presume that by this, you mean "zip file is created on the fly". This is already happening. Indeed, it is what your script does, it's raison d'etre, if you will.
zip file should not be created on server - You have to create a file on the server, even if it's only temporarily, because that is how the Zip extension works. You can delete it after the user has downloaded it (just add unlink($archive_file_name); on the line before exit;).
user can also resume it if paused - This requirement is (mostly) incompatible with zip file should not be created on server. Resumable downloads are implementable in PHP, but it is quite difficult to do and requires access to the Range: header of the request - which not every server will allow you to have. Also, you would have to generate the whole file for even a partial request, because you have deleted it from the server. Apache has an implementation of resumable downloads, but it requires (AFAIK) that the file be static on the hard drive, and requested directly. This would mean that deleting the file after it was downloaded (at the end of the PHP script) would break the resumability.
Reading between the lines, I suspect the problem you are having is that your server's hard drive space is getting used up by all the Zip archives you are creating and not deleting. The solution to this (while still allowing resumable downloads) is to implement some form of TTL checker on the server and periodically deleting files that are older than, for example, 1 day. You could do this with a cron job, or by running the check when you go to create a new arhive.
At the moment, your code does not specify where the zip files will be created, and this is something you would need to do. Here is an example that assumes your script is in the root directory of your site, and that there is a directory called zips in the root directory of your site.
The basic flow is:
Loop through the /zips directory, and delete all files that are older than 1 day.
Create a new archive in the /zips directory
Redirect the user to the path of that static file.
function zipFilesAndDownload($file_names, $archive_file_name, $file_path) {
// Archive directory
$archiveDir = 'zips';
// Time-to-live
$archiveTTL = 86400; // 1 day
// Files to ignore
$ignoreFiles = array('.', '..');
// Loop the storage directory and delete old files
if ($dp = opendir($archiveDir)) {
while ($file = readdir($dp)) {
if (!in_array($file, $ignoreFiles) && filectime("$archiveDir/$file") < (time() - $archiveTTL)) {
unlink("$archiveDir/$file");
}
}
}
// Re-format the file name
$archive_file_name = "$archiveDir/".basename($archive_file_name);
// Create the object
$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 file of $file_name array to archive
foreach($file_names as $file) {
$zip->addFile($file_path.str_replace('./', '', $file), translit($files).".mp3");
}
$zip->close();
// Then send the headers to redirect to the ZIP file
header("HTTP/1.1 303 See Other"); // 303 is technically correct for this type of redirect
header("Location: http://{$_SERVER['HTTP_HOST']}/$archive_file_name");
exit;
}

Categories