Class Zip

Description

Create simple and compatible ZIP-archives

With this class it is possible to create ZIP-archives that are compatible with the original PKZip 2.04g. This class does not provide a way to read ZIP-archives.

There are three possible options for the output:

  • write the ZIP-archive directly to a file (OpenZipfile())
  • output ('stream') directly to the user's browser, including appropriate headers (OpenZipstream())
  • collect the output in a buffer in memory (OpenZipbuffer()).
There are two different ways to add to the ZIP-archive:
  • add a file from the filesystem (AddFile())
  • add data from memory as if it was a file (AddData())
The ZIP-archive needs to be closed before it is useable (CloseZip()).

Special features:

  • it is not necessary to manually add a directory to the ZIP-archive because all directories that lead to a file will be added automatically
  • both AddFile() and AddData() allow for on-the-fly (re)naming; i.e. the name of the file in the ZIP-archive can be different from the name of the file in the filesystem
  • it is possible to add a comment to an individual file
  • it is possible to add a comment to the ZIP-archive
Limitations

This class might use a lot of memory when creating ZIP-archives, especially the ZIP_TYPE_BUFFER variant which eventually requires the size of the resulting ZIP-archive plus (worst case) the size of the largest file plus the size of the largest compressed file. This might be a problem with large files or many, many smaller files. A workaround could be to either stream the ZIP-archive directly (ZIP_TYPE_STREAM) or write to a file (ZIP_TYPE_FILE) because those variants only require the size of the largest file, the largest compressed file and the size of the central directory.

This class is not able to read ZIP-archives.

This class either stores a file as-is using the PKZIP 'Store' method or compresses the file using the 'Deflate' method. There is no support for other (more advanced) compression algoritms and no encryption is used.

References

I implemented this class using the following references.

[1] The ultimate definition of the ZIP-archive format as published by PKWare, Inc. See: http://www.pkware.com/appnote.txt or http://www.pkware.com/support/zip-application-note. I used version 6.3.2 which was published on 28 September 2007.

[2] RFC1950 - ZLIB Compressed Data Format Specification version 3.3, P. Deutsch, J-L. Gailly (May 1996), http://www.faqs.org/rfcs/rfc1950

[3] RFC1951 - DEFLATE Compressed Data Format Specification version 1.3, P. Deutsch (May 1996), http://www.faqs.org/rfcs/rfc1951

[4] Disk Operating System Technical Reference, IBM Corporation 1985, Chapter 5 (DOS Disk Directory).

[5] Official registration of the application/zip MIME-type: http://www.iana.org/assignments/media-types/application/zip

Examples

Typical usage of this class is as follows.

Example 1 - store 3 existing files in a ZIP-archive

$zip = new Zip;
$zip->OpenFile("/tmp/test.zip");
$zip->AddFile("/tmp/foo.txt");
$zip->AddFile("/tmp/bar.txt");
$zip->AddFile("/tmp/baz.txt");
$zip->CloseZip();

Example 2 - store a chunk of data in a ZIP-archive in memory

$zip_archive = '';
$data = "This is example-data that ends up in file QUUX.TXT";
$zip = new Zip;
$zip->OpenZipbuffer($zip_archive);
$zip->AddData($data,'QUUX.TXT');
$zip->CloseZip();

Example 3 - directly stream a file in a ZIP-archive and rename on the fly

$zip = new Zip;
$zip->OpenStream('htdocs.zip');
$zip->AddFile("/var/www/index.html",'INDEX.HTM');
$zip->CloseZip();

All methods return TRUE on success or FALSE on failure. If the method failed, an (English) error message can be found in $zip->Error.

Located in /program/lib/zip.class.php (line 139)


	
			
Variable Summary
 string $Error
 int $offset
 string $zip_buffer
 string $zip_comment
 null|resource $zip_filehandle
 string $zip_path
 string $zip_type
Method Summary
 Zip Zip ()
 bool AddData (string $data, [string $filename = ''], [string $comment = ''], [int $timestamp = 0])
 bool AddFile (string $path, [string $filename = ''], [string $comment = ''])
 bool CloseZip ()
 string dos_time_date (int $timestamp)
 string make_suitable_filename (string $filename)
 bool OpenZipbuffer (string &$buffer, [string $comment = ''])
 bool OpenZipfile (string $path, [string $comment = ''])
 bool OpenZipstream (string $name, [string $comment = ''])
 bool zip_add_data (string &$data, string $filename, string $comment, int $timestamp)
 bool zip_add_directories (string $pathname, int $timestamp)
 void zip_error (string $function, string $message)
Variables
array $central_directory = array() (line 145)
  • var:

    $central_directory buffer for the central directory entries

    This array is keyed by relative filename (both files and directories), no leading '/' though directories have a trailing '/'.

string $Error (line 157)
  • var: $Error collects error messages if things go wrong
int $no_name_files (line 169)
  • var: $no_name_files is used to construct names for otherwise unnamed files
int $offset (line 148)
  • var: $offset always points to the next local file header in the ZIP-archive
string $zip_buffer (line 166)
  • var: $zip_buffer reference to output buffer if $zip_type is ZIP_TYPE_BUFFER
string $zip_comment (line 154)
  • var: $zip_comment a file wide comment
null|resource $zip_filehandle = NULL (line 163)
  • var: $zip_filehandle handle on the zipfile output if $zip_type is ZIP_TYPE_FILE
string $zip_path (line 160)
  • var: $zip_path name of the zipfile if $zip_type is ZIP_TYPE_FILE
string $zip_type = ZIP_TYPE_NONE (line 151)
  • var: $zip_type ZIP-archive destination: file, stream or buffer
Methods
Constructor Zip (line 172)

constructor initialises all variables

Zip Zip ()
AddData (line 317)

add data to the current ZIP-archive

This adds the data to the current ZIP-archive.

  • return: TRUE on success, FALSE if an error occurred + message in $this->Error
bool AddData (string $data, [string $filename = ''], [string $comment = ''], [int $timestamp = 0])
  • string $data: the data to add to the ZIP-archive
  • string $filename: the preferred name of the file in the ZIP-archive
  • string $comment: an optional comment for this specific file
  • int $timestamp: the unix timestamp to associate with the file
AddFile (line 276)

add the contents of an existing file to the current ZIP-archive

this reads the file $path into a buffer, and subsequently adds the data to the ZIP-archive.

  • return: TRUE on success, FALSE if an error occurred + message in $this->Error
bool AddFile (string $path, [string $filename = ''], [string $comment = ''])
  • string $path: the full (absolute) name of the file to add to the ZIP-archive
  • string $filename: the preferred name of the file in the ZIP-archive (default is $path)
  • string $comment: an optional comment for this specific file
CloseZip (line 344)

finish the ZIP-archive by outputting the central directory and closing output

this finishes the ZIP-archive by constructing and outputting the Central Directory and subsequently closing the output file (in case of ZIP_TYPE_FILE). The call to CloseZip() is necessary to create a complete ZIP-archive, including the Central Directory.

  • return: TRUE on success, FALSE if an error occurred + message in $this->Error
bool CloseZip ()
dos_time_date (line 675)

construct an MS-DOS time and date based on unix timestamp

this routine constructs a string of 2 x 2 bytes with the time and the date in the following format.

15 14 13 12 11 10  9  8  7  6  5  4  3  2  1  0
 h  h  h  h  h  m  m  m  m  m  m  x  x  x  x  x

hhhhh = hour, from 0 - 23 (5 bits)
mmmmmm = minute, from 0 to 59 (6 bits)
xxxxx = duoseconds, from 0 to 29 (5 bits)

15 14 13 12 11 10  9  8  7  6  5  4  3  2  1  0
 y  y  y  y  y  y  y  m  m  m  m  d  d  d  d  d

yyyyyyy = year offset from 1980, from 0 - 119 (7 bits)
mmmm = month, from 1 to 12 (4 bits)
ddddd = day, from 1 to 31 (5 bits)

Note that the time resolution is 2 seconds whereas the unix timestamp has a 1 second resolution. This means that the seconds are rounded down. Also note that the specification [4] indicates that the maximum value for year offset is 119 which corresponds with 2099 rather than the maximum of 127 which would yield the year 2107.

  • return: packed string with time and date (little endian, 4 bytes)
string dos_time_date (int $timestamp)
  • int $timestamp: unix timestamp (seconds since 1970-01-01 00:00:00)
make_suitable_filename (line 622)

construct a suitable filename for use in ZIP-archive

this analyses and edits the string $filename in such a way that a suitable name for use in a ZIP-archive remains. This means that:

  • MS-DOS driverletters are removed from the path
  • backslashes are replaced with slashes
  • leading './' if any is removed
  • a leading slash is removed

  • return: suitable filename with or without a path
string make_suitable_filename (string $filename)
  • string $filename: name to analyse/massage
OpenZipbuffer (line 252)

prepare the user supplied buffer for subsequent ZIP-archive data

  • return: TRUE on success, FALSE if an error occurred + message in $this->Error
bool OpenZipbuffer (string &$buffer, [string $comment = ''])
  • string &$buffer: a pointer to the buffer where we can write the ZIP-archive
  • string $comment: an optional comment to include in the ZIP-archive
OpenZipfile (line 195)

open a file for subsequent output of ZIP-archive

this opens the file $path for writing and also sets the zip_type to ZIP_TYPE_FILE. The optional $comment is stored for future reference. The file must be closed afterwards via CloseZip().

  • return: TRUE on success, FALSE if an error occurred + message in $this->Error
bool OpenZipfile (string $path, [string $comment = ''])
  • string $path: the (absolute) pathname of the destination file
  • string $comment: an optional comment to include in the ZIP-archive
OpenZipstream (line 223)

start with a stream (direct output) indicating an application/zip type of content

this starts the output of the ZIP-archive directly to the browser. The Content-Type and the Content-Disposition are set by sending headers. The stream must be closed afterwards via CloseZip().

  • return: TRUE on success, FALSE if an error occurred + message in $this->Error
bool OpenZipstream (string $name, [string $comment = ''])
  • string $name: the name of the ZIP-archive that is suggested to the browser
  • string $comment: an optional comment to include in the ZIP-archive
zip_add_data (line 415)

workhorse function to add data to the current ZIP-archive

This actually adds the data to the current ZIP-archive.

Note that we try to make a wise decision about compressed data: the compressed data should be smaller than the uncompressed data. If not, we don't bother and simply store the data as-is.

We also try to keep the number of copies of the data down to a minimum by not copying the $data but selecting between $data and $zdata only when we are really ready to write output the data.

  • return: TRUE on success, FALSE if an error occurred + message in $this->Error
  • todo: should we handle the possibility of an additional 4 bytes for DICTID (RFC1950, reference [2])?
  • todo: should we handle the option of a better compression level (eg. level 9) in gzcompress()? we could check to see if CMF equals 0x78 and FLG is either 0x01, 0x5E, 0x9C or 0xDA the latter 4 values might have an effect on general purpose bit flag bits 2 and 3. for now we'll just keep it simple, but there might be a little something to improve here.
bool zip_add_data (string &$data, string $filename, string $comment, int $timestamp)
  • string &$data: a pointer to a buffer with data to add to the ZIP-archive
  • string $filename: the preferred name of the file in the ZIP-archive
  • string $comment: an optional comment for this specific file (could be '')
  • int $timestamp: the unix timestamp to associate with the file
zip_add_directories (line 519)

workhorse function to add 0, 1 or more directories to the current ZIP-archive

this routine works from top to bottom through the specified path, adding directories to the archive. If a particular directory was already added before, it is not added again. This information is based on the existence of the corresponding key in the central_directory array.

  • return: TRUE on success, FALSE if an error occurred + message in $this->Error
bool zip_add_directories (string $pathname, int $timestamp)
  • string $pathname: contains 0, 1 or more directories that lead to the file that needs to be added
  • int $timestamp: unix timestamp to associate with the directory
zip_error (line 600)

add an error message to the list of error messages

  • return: message added to list
void zip_error (string $function, string $message)
  • string $function: name of the function/method where the error occurred
  • string $message: the error message to add

Documentation generated on Tue, 28 Jun 2016 19:12:49 +0200 by phpDocumentor 1.4.0