Creating ZIP files with PHP

UPDATE: You are better off using the library mentioned in this post.

As part of my previously mentioned OpenVPN CA I want to deliver keys, certs and config files to users in a single zip file that they can just extract onto their computers. PHP's own ZIP File Functions only supports reading zip files and not making them.

Some Googling discovered an article by John Coggeshall that can create zip files. It does this by creating the binary data on the fly and can output the zip files directly to the browser from memory or by writing it to disk.

I had some troubles getting hold of a usable version of this code since all these PHP code collection sites have this annoying habit of only showing the syntax highlighted versions of the code rather than give a download link. Eventually got one though and I figured I'll host a mirror of it here to help people out.

Using it is very simple, this is a quick sample that will create a ZIP file and add one directory and one file into then send it directly to the client.

<?
        require ("incl/zipfile.inc.php");

        $zipfile = new zipfile();
        
        $filedata = implode("", file("incl/zipfile.inc.php"));

        $zipfile->add_dir("incl/");
        $zipfile->add_file($filedata, "incl/zipfile.inc.php");

        header("Content-type: application/octet-stream");
        header("Content-disposition: attachment; filename=zipfile.zip");
        echo $zipfile->file();
?>

29 Comments

What's wrong with using gzcompress()/gzdeflate()/gzencode() in the zlib extension?

In the class it uses the gzcompress() function to compress the actual data, but this also puts all the various bits in to make a actual ZIP file as usable by Windows rather than gzip files.

some aprovement .. anyhow .. thx a lot

Hendrik

>> zipfile.inc.php datasec[] = $fr;

$new_offset = strlen(implode("", $this->datasec));

// ext. file attributes mirrors MS-DOS directory attr byte, detailed
// at http://support.microsoft.com/support/kb/articles/Q125/0/19.asp

// now add to central record
$cdrec = "\x50\x4b\x01\x02";
$cdrec .="\x00\x00"; // version made by
if (strtoupper($type) == "DIRECTORY") {
$cdrec .="\x0a\x00"; // version needed to extract
$cdrec .="\x00\x00"; // gen purpose bit flag
$cdrec .="\x00\x00"; // compression method
}
if (strtoupper($type) == "FILE") {
$cdrec .="\x14\x00"; // version needed to extract
$cdrec .="\x00\x00"; // gen purpose bit flag
$cdrec .="\x08\x00"; // compression method
}
$cdrec .="\x00\x00\x00\x00"; // last mod time & date
$cdrec .= pack("V",$crc); // crc32
$cdrec .= pack("V",$c_len); // compressed filesize
$cdrec .= pack("V",$unc_len); // uncompressed filesize
$cdrec .= pack("v", strlen($name) ); // length of filename
$cdrec .= pack("v", 0 ); // extra field length
$cdrec .= pack("v", 0 ); // file comment length
$cdrec .= pack("v", 0 ); // disk number start
$cdrec .= pack("v", 0 ); // internal file attributes

if (strtoupper($type) == "DIRECTORY") {
$cdrec .= pack("V", 16 ); // external file attributes - 'directory' bit set
}
if (strtoupper($type) == "FILE") {
$cdrec .= pack("V", 32 ); // external file attributes - 'archive' bit set
}

$cdrec .= pack("V", $this -> old_offset ); // relative offset of local header
// echo "old offset is ".$this->old_offset.", new offset is $new_offset";
$this -> old_offset = $new_offset;

$cdrec .= $name;
// optional extra field, file comment goes here
// save to central directory
$this -> ctrl_dir[] = $cdrec;
}

function file() {
// dump out file

$data = implode("", $this -> datasec);
$ctrldir = implode("", $this -> ctrl_dir);

return
$data.
$ctrldir.
$this -> eof_ctrl_dir.
pack("v", sizeof($this -> ctrl_dir)). // total # of entries "on this disk"
pack("v", sizeof($this -> ctrl_dir)). // total # of entries overall
pack("V", strlen($ctrldir)). // size of central dir
pack("V", strlen($data)). // offset to start of central dir
"\x00\x00"; // .zip file comment length
}
}

#--------------------------------------------- Hendrik Muus
# require ("zipfile.inc.php");
#
# $zipfile = new zipfile();
#
# $zipfile->add_entry("directory", "testme/", "");
# $zipfile->add_entry("file", "testme/test.file.bin", implode("", file("test.file.bin")));
#
# header("Content-Type: archive/zip\n\n"); // this gives you a nice open / save dialog ;o)
# header("Content-disposition: attachment; filename=zipfile.zip");
#
# echo $zipfile->file();
#---------------------------------------------

?>

Hi, can we use this class in commercial products ?

I don't see any licence info.

Unsure, but like I said on the first line of this posting, its shitty, use the other one, see the link right on top of this post.

Here you can find class + demo + class manual
http://www.smiledsoft.com/demos/phpzip/
for class which you will start using in minutes after installation.
Clear demos explain how to use every function.
A lot of features.
Free version is capable of creating ZIPs only.
Pro is capable to extract files and more.
Please visit
http://www.smiledsoft.com/demos/phpzip/

How can you put all the files of a directory into a zip file without knowing the files?

Hello,

Is there a limit to the number of files that can be zipped up as I am using this script and I get an error "Allowed memory size of 16777216 bytes exhausted" when trying it with a large number of files.

Thanks

Do somebody nkow how could we ask the script to copy the generated zipped file somewhere else on the server rather than user the headers to force the download of the file?

The poster commented in his alternate solution:

"I did some more testing with the code I posted yesterday and found it isn't 100% compatible with some unzip programs. Works with unix unzip, Mac OS X default tool, WinZip, WinRAR but annoyingly not with the default XP zip folder thing."


I had this problem as well but then I discovered that I was accidentally putting superfluous data in my zip file -- some php warning messages were being included as part of the zip file that the user was downloading.

I cleaned up the warnings and now my zip files are working even with the default XP zip folder thing (I don't actually have a better name for that than the one used above ;).

If you have a problem with the zip file you create using this script open the zip file with wordpad and take a look to see if you aren't accidentally putting in data that you don't need.

W

Does this lib support password protection for the zip.?

Problem at hand was to use PEAR Spreadsheet Excel Writer to have php generate multiple spreadsheets, put them in a zip file, and provide the zip file as a download to a user. After about 2 days I got it all to work including using IE.

Basically, first I generate the spreadsheets and save them in a tmp location on the server. Then I insert them into a zip file class one by one and delete the original spreadsheet files. After repeating the process for a few spreadsheets, I dump the zip file.

Hope it helps someone. Here's the code:

===================================================================
export.php

require_once 'class.zipfile.php';
require_once 'Spreadsheet/Excel/Writer.php';

// Generate zip file with containg current spreadsheet file
$zipfile = new zipfile();
//$filedata = 'testing direct input';

// Zip file http headers
$user_browser = $root->check_user_browser();
if ($user_browser == 'IE') header("Content-type: application/zip");
else header("Content-type: application/octet-stream");
header("Content-disposition: attachment; filename=\"filename.zip\"");
header("Expires: 0");
header("Cache-Control: must-revalidate, post-check=0,pre-check=0");
header("Pragma: public"); // IE doesn't work without this

foreach($report_names as $name) {
$filename = $name;

// Generate report for current name and save to /tmp
include './export.excelfile.php';

// Add temp spreadsheet from the include above to the zip file
$filedata = implode("", file('/tmp/'.$filename.'.xls'));

// Add the filedata to the zip file with the correct filename
$zipfile->add_file($filedata, $filename.'.xls');

// delete the current file from temporary storage
unlink('/tmp/'.$filename.'.xls');
}

// Send the zip file to browser
echo $zipfile->file();

===================================================================
export.excelfile.php
Note: "/tmp" must be world writable, ie. apache must be able to write (777)

// Save current spreadsheet to the server hard drive
unset($workbook);
$workbook = new Spreadsheet_Excel_Writer('/tmp/'.$filename.'.xls');
$workbook->setVersion(8);
$workbook->setTempDir(/tmp);

// Set styles and formats
include "export.format.php";
$worksheet =& $workbook->addWorksheet('sheet_name');
$worksheet->setLandscape();

// Grab actual data
$query = 'SELECT DISTINCT * FROM `' . $table_name . '`';
$res = $db->_query($query);

// Data Header Row and similar for data
for ($i = 0; $i mysql_num_fields($res); ++$i) {
$worksheet->writeString(0, $i, $db->mysql_field_name($res, $i), $formats[DATA_HEADER]);
}

// Close the file
$workbook->close();

===================================================================
export.format.php

// defines and custom colours
define('DATA_HEADER', 1);
$workbook->setCustomColor(63, 117, 151, 170); // header text color

// Global style and font
$formats = array();
$common_format = array('font' => 'Arial',
'size' => 8,
'align' => 'center',
'valign' => 'bottom',
'textwrap' => 1
);

// EXCEL DATA HEADER
$formats[DATA_HEADER] =& $workbook->addformat($common_format);
$formats[DATA_HEADER]->setColor(1); // text color
$formats[DATA_HEADER]->setBold();
$formats[DATA_HEADER]->setFgColor(63); // cell color
$formats[DATA_HEADER]->setBorderColor(60); // border styling
$formats[DATA_HEADER]->setTop(2);
$formats[DATA_HEADER]->setBottom(2);
$formats[DATA_HEADER]->setLeft(1);
$formats[DATA_HEADER]->setRight(1);

===================================================================
class.zipfile.php

by Eric Mueller
http://www.themepark.com

http://www.devco.net/archives/2005/05/24/creating_zip_files_with_php.php
http://www.devco.net/code/zipfile.inc.txt

but where is the zipfile.inc.php

Oh thats nice, i was in search of such a code that can generate dynamically the .zip file, i got it here, thanks for sharing

Hi,

Here is code for a web page to download multiple files in a zip.


require_once('includes/zipfile.inc.php');

$zipname = $_GET['setname'];

$zipfile = new zipfile();

$dir = $zipname . '/';
$zipfile->add_dir( $dir );

$aFileNamesToZip = getFileArray( PATH_OUT . '/' . $zipname . '/', TRUE);

if ( is_array( $aFileNamesToZip ) ) {
foreach ( $aFileNamesToZip as $fileName ) {
$filePathSys = PATH_OUT . '/' . $zipname . '/' . $fileName;
$filePathZip = $zipname . '/' . $fileName;
$filedata = implode('', file( $filePathSys));
$zipfile->add_file($filedata, $filePathZip);
}
} else {
die('No files to zip.');
}

header('Content-type: application/octet-stream');
header('Content-disposition: attachment; filename=' . $zipname . '.zip');
echo $zipfile->file();

I am trying to get this to work, I have a list of image names being queried from a table. It prompts me to save/open a zip file and the zipfile appears to have all the image *names* that I queried for but the files are all 7,136 bytes which is WRONG, it appears to *not* be pulling the actual filedata into the zip. image1 below is the name of the image file.

Here is my code:

if (tep_db_num_rows($results) > 0) {

while ($row = tep_db_fetch_array($results)) {

$zipfile->add_file($filedata, "images/".$row['image1']);

}

}

header("Content-type: application/octet-stream");

header("Content-disposition: attachment; filename=images.zip");

echo $zipfile->file();

This post and number of comments to this site proves that sleep research/knowledge is lacking in humanity. The researchers need to focus on this subject and enlighten us all. Most of us spend one third of our lives in sleep, we should know more about it.

Great code. It works perfect to send the zip to the browser, but did anyone figure out how to save this zip file to the server rather than send it to the browser?

I would like to create the zip, save it to the server, email it to an address, then delete it from the server.

Hi Gerry
Please Find the Modified script for saving to disk and not downloading:

require ("incl/zipfile.inc.php");
$zipfile = new zipfile();
$filedata = implode("", file("incl/zipfile.inc.php"));
$zipfile->add_dir("incl/");
$zipfile->add_file($filedata, "incl/zipfile.inc.php");
// set file to write
$file = 'incl/zipfile.zip';
// open file
$fh = fopen($file, 'w') or die('Could not open file for writing!');
// write to file
fwrite($fh, $zipfile->file()) or die('Could not write to file');
// close file
fclose($fh);

Hi Gerry

Please note PHPmailer is does not come with php by deafult

Please Find the Modified script for saving to disk,mailing and deleteing:
require ("incl/zipfile.inc.php");
require ("incl/class.phpmailer.php");

$zipfile = new zipfile();
$filedata = implode("", file("incl/zipfile.inc.php"));
$zipfile->add_dir("incl/");
$zipfile->add_file($filedata, "incl/zipfile.inc.php");
// set file to write
$file = 'incl/zipfile.zip';
// open file
$fh = fopen($file, 'w') or die('Could not open file!');
// write to file
fwrite($fh, $zipfile->file()) or die('Could not write to file');
// close file
fclose($fh);

$senderemail = 'You@yourdomain';
$sendername = 'your name';
$receiveremail = 'resipiant@domian';
$receivername = 'resipiant name';
$attachment=($file);
$smtp_username= '';
$smtp_password = '';
$smtp_server = '192.168.2.52';
$mail_subject = 'My Mail Subject';
$mail_html = 'true';
$mail_body = 'My Mail Body';
//do mailing here
$mail = new PHPMailer();
$mail->From = $senderemail;
$mail->FromName = $sendername;
$mail->AddAddress($receiveremail, $receivername);
$mail->AddAttachment($attachment);
// Fill in Username and Password for servers requiring authentication
$mail->Username = $smtp_username;
$mail->Password = $smtp_password;
// SMTP server name
$mail->Host = $smtp_server;
$mail->Mailer = "smtp";
$mail->Subject = $mail_subject;
$mail->IsHTML = $mail_html;
$mail->Body = $mail_body;
if(!$mail->Send()) $results = 'Error message';
else $results = 'Success message';
//Delete the file
Print($results);

unlink($file);

Anyone else having a problem upzipping archives created in any of these examples within OSX?

First, the download of the zip never finishes and safari complains about it not being a valid download.

Then, unzipping the created file with the default unarchive tool within OSX claims "Error 1 - Operation Not Permitted"

Lastly, if you use StuffIT Expander it unzips with no problem.

Downloads, unzips, etc without any problem on my XP box,

Any ideas?

Well done. Cool job. Thanks a lot.

Hi Steven, i had the same issue (Error 1 - Operation Not Permitted), and solved it with a modified version of this library, that you can find in phpMyAdmin (i have v. 2.10.2).

I found something, similar to the file i found, here:
http://www.forosdelweb.com/f18/zip-lib-php-archivo-zip-vacio-431133/

Hope it helps

:)

Hi,

I have to archive some rather large files (80+MB already compressed)
and I was wondering if somebody had a solution where by I could simply add 2 of these files into a zip file.

The main obstacle to using most zipping scripts is that some servers have a memory usage limit. When that limit is too low (as would be the case with most shared hosting), php can't do the job.

I was wondering if adding a file without trying to compress would help.

Any ideas?

Hello,

Is there a limit to the number of files that can be zipped up as I am using this script and I get an error "Allowed memory size of 16777216 bytes exhausted" when trying it with a large number of files.

Thanks
angela

It depends on your PHP settings. Normally you can increase it with
ini_set('memory_limit', '32M');
as the first line of your script. At the moment you have 16M, so 32M should do.

I m using this script and the archive_zip class available from pear.php.net. When i make archives of jpgs and gifs, i get the zip file but if i try with mp3s ,avis ,sis files i always get error in file creation how can i solve this problem? Should i put above script and change the class?

Hi

I have a script named 'SECURELOADS' - it work well except when it is required to zip and download a number of large files together - zipping and downloading a few is now problem, but any attempt to go behond more than a few I get errors like:
Fatal error: Allowed memory size of 524288000 bytes exhausted (tried to allocate 129871054 bytes) in /var/www/vhosts/intentionalnoise.co.uk/httpdocs/sld2/core/hclib/compress/zipfile.php on line 120

the code in the file is below, any ideas as to why it should be doing this?
I have allocated 500mb or ram to it - why is it so hungry?

@Rafael Miranda,

I have spent hours trying to figure out this problem. Your suggestion fixed it! Thanks!
-Ben

I believe it would be wise to disallow files with the extension *.php if using this script in a GET method that is visible.

Using another zip file creator, I was very easily able to download raw php source in a zip package.

It's a wonderful script, but if every used in a get method, would be like an open door to a ......possum?

Leave a comment

Recent Entries

  • Load Balancing with HAProxy

    Load Balancers are some of the most expensive bits of equipment small to medium size sites are likely to buy, even more expensive than database...

  • SixXS IPv6 and CentOS

    I thought its high time I get to spend some time with IPv6 so I signed up for a static tunnel from sixxs.net, apart from...

  • Location aware Bind for RedHat 5.3

    Previously I wrote about RPMs I built to GeoIP enable Bind using the original patches at http://www.caraytech.com/geodns/.I have now refreshed this for the latest CentOS...

  • CentOS 5.3

    CentOS 5.3 was released on the 1st of April, I've since updated a whole lot of my machines to this version and been very happy.There...

  • WIRED UK

    I was quite excited about the new UK edition Wired.  I'm not anymore.I got my first exposure to the Wired while in school in South...

Close