26

I am using PHP's ZipArchive class to create a zip file containing photos and then serve it up to the browser for download. Here is my code:

/**
 * Grabs the order, packages the files, and serves them up for download.
 *
 * @param string $intEntryID 
 * @return void
 * @author Jesse Bunch
 */
public static function download_order_by_entry_id($intUniqueID) {

    $objCustomer = PhotoCustomer::get_customer_by_unique_id($intUniqueID);

    if ($objCustomer):

        if (!class_exists('ZipArchive')):
            trigger_error('ZipArchive Class does not exist', E_USER_ERROR);
        endif;

        $objZip = new ZipArchive();
        $strZipFilename = sprintf('%s/application/tmp/%s-%s.zip', $_SERVER['DOCUMENT_ROOT'], $objCustomer->getEntryID(), time());

        if ($objZip->open($strZipFilename, ZIPARCHIVE::CREATE) !== TRUE):

            trigger_error('Unable to create zip archive', E_USER_ERROR);

        endif;          

        foreach($objCustomer->arrPhotosRequested as $objPhoto):

            $filename = PhotoCart::replace_ee_file_dir_in_string($objPhoto->strHighRes);
            $objZip->addFile($filename,sprintf('/press_photos/%s-%s', $objPhoto->getEntryID(), basename($filename)));

        endforeach;

        $objZip->close();

        header('Last-Modified: '.gmdate('D, d M Y H:i:s', filemtime($strZipFilename)).' GMT',  TRUE, 200);
        header('Cache-Control: no-cache', TRUE);
        header('Pragma: Public', TRUE);
        header('Expires: ' . gmdate('D, d M Y H:i:s', time()) . ' GMT', TRUE);
        header('Content-Length: '.filesize($strZipFilename), TRUE);
        header('Content-disposition: attachment; filename=press_photos.zip', TRUE);

        header('Content-Type: application/octet-stream', TRUE);

        ob_start();
        readfile($strZipFilename);
        ob_end_flush();
        exit;

    else:

        trigger_error('Invalid Customer', E_USER_ERROR);

    endif;

}

This code works really well with all browsers but IE. In IE, the file downloads correctly, but the zip archive is empty. When trying to extract the files, Windows tells me that the zip archive is corrupt. Has anyone had this issue before?

Edit Update: After suggestion from @profitphp, I changed my headers to this:

header("Cache-Control: public");
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");
header("Content-Disposition: attachment; filename=\"pressphotos.zip\"");
//header("Content-Transfer-Encoding: binary");
header("Content-length: " . filesize($strZipFilename));

Also, here is a screenshot of the error in Windows after opening with Firefox:

alt text

This error occurs in both IE and Firefox on Windows. It works fine in Mac. Also, in Windows, the filesize appears to be correct:

alt text

Edit #2 This issue is sovled. See my answer below.

Jesse Bunch
  • 6,651
  • 4
  • 36
  • 59
  • Had the same issue and this answer was the solution for me: [ZIP Archive sent by PHP is corrupted][1] [1]: http://stackoverflow.com/a/13528263/557311 – MaRmAR Feb 27 '15 at 16:02

14 Answers14

31

I had this same problem, and my solution was similar to the correct answer on this thread. When you put a file in the archive, you can't have absolute files (files starting with a slash) or else it won't open in Windows for some reason.

So got it working not because he (Jesse Bunch, the selected answer at the time of this writing) removed the containing folder but because he removed the starting slash.

I fixed the issue by changing

$zip->addFile($file, $file); // $file is something like /path/to/file.png

to

// we make file relative by removing beginning slash so it will open in Windows
$zip->addFile($file, ltrim($file, '/'));

and then it was able to open in Windows!

That's probably the same reason pclzip (Plahcinski's answer) works. I bet it automatically strips off the beginning slash.

I wouldn't have figured this out without a particular comment on the PHP ZipArchive::addFile documentation page.

CWSpear
  • 3,230
  • 1
  • 28
  • 34
14

All of this suggestions may help you, but in my case I need to write an ob_clean(); before first header(''); because some file that I include before print some characters that broken zip file on windows.

$zip=new ZipArchive();
$zip->open($filename, ZIPARCHIVE::CREATE);
$zip->addFile($file_to_attach,$real_file_name_to_attach);
$zip->close();

ob_clean();
header("Cache-Control: no-cache, must-revalidate"); // HTTP/1.1
header("Expires: Sat, 26 Jul 1997 05:00:00 GMT"); // Date in the past
header('Content-Type: application/x-download');
header('Content-Disposition: attachment; filename="file.zip"');
readfile($filename);
exit;
Luca Camillo
  • 796
  • 9
  • 9
  • 1
    This worked for me, tried everything else with no luck. – llanato Jul 27 '15 at 13:34
  • This one worked for me too. Also tried everything else. This should be the accepted answer. Note: I only had to add the line: ob_clean() to get my code to work. Spent an hour or two trying to get zip files that could be opened. – mikekehrli Jun 10 '21 at 21:32
14

I recently had a similar issue as you described. I found ZipArchive to be unstable at best.

I solved my problems with this simple library

http://www.phpconcept.net/pclzip

include_once('libs/pclzip.lib.php');

...

function zip($source, $destination){
$zipfile = new PclZip($destination);
$v_list = $zipfile->create($source, '', $source); }

$source = folder i wanted to zip $destination = zip file location

I spent 2 days looking to ZipArchive and then solved all problems with PCLZip in 5 minutes.

Hope this helps you and anyone else having this issue (as this is near the top google result on the issue).

Plahcinski
  • 335
  • 2
  • 6
  • in a Window environment. On my webpage, unfortunately it did work very well: it only zipped just one file in stead of several files, – Dev.Jaap Apr 16 '12 at 18:55
  • did work for me on windows. like you said, nearly an hour looking at ZipArchive, and this worked in 2 mins. tnx! – Flion Aug 26 '13 at 14:02
  • Great library. To use it on PHP7.1 server, just change the method "PclZip" to "__construct" and add "(int)" where integer is required. – Phoca Jul 17 '17 at 16:34
  • Dead link for the library – Ravi Mattar Aug 18 '22 at 14:57
6

Ok, after much strife, I figured out the problem. The issue comes from the following line of code:

$objZip->addFile($filename,sprintf('/press_photos/%s-%s', $objPhoto->getEntryID(), basename($filename)));

For some reason, the /press_photos/ part of that path for the local (internal) file name inside the zip archive was causing Windows to think the zip file was corrupt. After modifying the line to look like what is below, Windows opened the zip files correctly. Phew.

$objZip->addFile($filename,sprintf('%s-%s', $objPhoto->getEntryID(), basename($filename)));
Jesse Bunch
  • 6,651
  • 4
  • 36
  • 59
  • 3
    I had success with renaming the file after adding to archive. This is a good solution if you don't need to maintain directory structures within your archive: $zip->addFile($uri, basename($uri) ); $zip->renameName(basename($uri), str_replace('_', '-', basename($uri))); – doub1ejack Mar 29 '12 at 17:00
5

The use of special characters, such as underscore, causes problems because ZipArchive requires IBM850 encoded entrynames. See comments in the online PHP manual: http://www.php.net/manual/en/function.ziparchive-addfile.php#95725 .

David Spector
  • 51
  • 1
  • 1
  • I had a long zip file name with spaces, dashes, and date characters. It turns out once I changed filenames to something simple, like export.zip, it worked fine. – Nathan Hangen Mar 21 '14 at 14:38
3

I've been having this problem for an hour. After trying 10 different solutions, I solved it by making sure the script exists after outputting the ZIP file:

            readfile($zip_name);
            unlink($zip_name);
            **exit();**
Mark
  • 31
  • 1
3

I've had issues with this before. Try taking off the content type header. here is the code i came up with for it that worked in IE and FF. Notice the commented lines, was having the same issues with different combos of those being on.

header("Cache-Control: public");
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");
header("Content-Disposition: attachment; filename=\"adwords-csv.zip\"");
//header("Content-Transfer-Encoding: binary");
header("Content-length: " . filesize($filename)); 
profitphp
  • 8,104
  • 2
  • 28
  • 21
  • I changed my headers and no dice, the zip file is still "corrupt" according to windows, but is fine on my Mac. – Jesse Bunch Jan 06 '11 at 21:56
  • Try getting headers to look exactly like the ones I posted, same order and all. I've also had similar issues when using certain filenames inside the zip. Stick to a very basic character set. Now you're mentioning windows. If you use firefox, chrome, etc, on windows does the same thing happen? Is it a browser issue, or a zip reader issue? – profitphp Jan 06 '11 at 21:58
  • Thanks for your time, I've updated this issue with more information. – Jesse Bunch Jan 06 '11 at 22:08
  • well its not a header thing probably, seems like its something with the windows default zip reader. Have you tried something like winrar to unzip it? I just used zip from the command line in my implementation rather than the zip archive class. If worst comes to worst you might want to try that. – profitphp Jan 06 '11 at 22:14
  • You're right, when I copy that "corrupted" zip file from windows to my mac, it opens without a hitch. – Jesse Bunch Jan 06 '11 at 22:27
  • Thanks for your help, turns out it wasn't an issue with headers at all. In fact, my original headers work fine. – Jesse Bunch Jan 07 '11 at 14:14
2

In addition to what others suggested, it's important pay Attention to your file and directory names as Windows does not necessarily like Linux file path and names. It sometimes also escapes them differently when zipping. Examples are numerous, but most importantly

  • *dot files (. and ..), files with only case differences (name.txt and NAME.txt),
  • absolute file paths (/tmp/file.txt)*.
  • Some other characters which are allowed in file names on Windows could cause issues when Windows Explorer is used to open files. In my case ':' character was the deal breaker but took a lot of work to find this out.

So before you resume to using using a lot of parameters through exec('zip ...'), I suggest follow a simple procedure:

  1. Locate the folder or file your website zips up.
  2. run: zip -9 -r -k zip-modified-names.zip /path/to/your/folder
  3. pay attention to what the console spits out. In my case ':' in file names were stripped out.
  4. Move the zip file to a windows machine and attempt to open it.

If this works, you may be better off removing the characters that have been stripped off by -k option from your file/directory names try zipping normally. Note some parameters such as -k have side effects. In this case -k contradicts with -q option (for sym links).

Also -k option may render your file names unreadable. In my case my files were named based on creation time (e.g. 10:55:39.pdf) to facilitate easily locating the required record from archives, but -k option turned it to 105539.pdf which is not easily readable by users. I hence changed the names to 10_55_39.pdf which opens on Windows without using -k option but is still readable.

In addition to this, using PCLZip would make your life a lot easier as you can add a whole folder at once and also modify path of files in one simple line. In my case I remove /tmp/directory/ from my zip files with the second and third parameters which avoids another windows compatibility issue ( having absolute path in zip files ):

$v_list = $zip->create( $sourceFolder, PCLZIP_OPT_REMOVE_PATH, $sourceFolder . DIRECTORY_SEPARATOR);
if ($v_list == 0) {
    throw new \Exception($zip->errorInfo(true));
}
Shakus
  • 421
  • 5
  • 8
2
            ob_clean(); //very important
            // http headers for zip downloads
            header("Pragma: public");
            header("Expires: Sat, 26 Jul 1997 05:00:00 GMT"); // Date in the past
            header("Cache-Control: no-cache, must-revalidate"); // HTTP/1.1
            header("Cache-Control: public");
            header('Content-Type: application/x-download');
            header("Content-Disposition: attachment; filename=\"filename.zip\"");
            header("Content-Length: ".filesize($filepath ));

            @readfile($filepath );
            unlink($filepath);  //very important
            exit;//very important

This worked for me while trying the above solutions.

Joshy Francis
  • 340
  • 7
  • 13
1

I had the same problem. This code worked for me, but I HAD TO PUT IN THE FIRST LINE in my php-File! If i put the code in the middle of the File i didn't work. Maybe some encoding issues ?!

// Prepare File
$file = tempnam("tmp", "zip");
$zip = new ZipArchive();
$zip->open($file, ZipArchive::OVERWRITE);

// Staff with content
$zip->addFile($filepathOnServer, 'mypic.jpg');

// Close and send to users
$zip->close();
header('Content-Type: application/zip');
header('Content-Disposition: attachment; filename="filename.zip"');
readfile($file);
unlink($file);
pu-c
  • 11
  • 1
0

I was using time stamp in zip file name. Actually windows file system doesn't support special characters like " : \ / * ? < > | "

After removing ":" from the time part it works like a charm

0

Just in case somebody else is banging his/her had against a brick wall for a few hours and suffer like I did. I had the same issue, and none of the solutions helped, till I realized that I load some libraries in my PHP and one of them had an empty line after the ?> code. When calling the library with include(/path/to/lib/lib.php); an empty line was outputted to the browser, causing the zip to be classed as corrupted by Windows. (Winzip, Total Commander, etc had no issues with it). So make sure there is no imported library or if there is any, it has no spaces or empty lines....

Emil Borconi
  • 3,326
  • 2
  • 24
  • 40
0

For those of you still banging your head after trying all this and it still didn't work I was able to fix my issue like this.

$zipname = "download.zip";
$zip = new ZipArchive;
$zip->open($zipname, ZipArchive::CREATE);
foreach ($files as $file) {
    # download file
    $download_file = file_get_contents($file); <- important

    #add it to the zip
    $zip->addFromString(basename($file), $download_file); <- important  
}
$zip->close();

header('Content-Type: application/zip');
//header('Content-disposition: attachment; filename='.$zipname);
header("Content-Disposition: attachment; filename=\"$zipname\"");
header('Content-Length: ' . filesize($zipname));
readfile($zipname);
unlink($zipname);
exit;

My issue was that my file paths were coming in as strings. Even though I tried (string)$path it still did not work. For me it was using file_get_contents of the file then the built in function addFromString that did it for me.

Cesar Bielich
  • 4,754
  • 9
  • 39
  • 81
-3

Change your code:

 header('Content-Length: '.filesize($strZipFilename), TRUE);

With:

header('Content-Length: '.file_get_contents($strZipFilename));
Antonio
  • 1,181
  • 4
  • 15
  • 34
  • This would output the entire contents of the file as the `Content-Length`, and not the actual size (which is what they want). This is definitely not what they want to do (and not related to the issue). They have it correct. – CWSpear Sep 15 '17 at 19:06