Updated 02/01/05 - to handle files of zero bytes in length, files changed zipcreate.cls.php and zipcreate.ex3.php
Code: Select all
<?php
/***********************************************************
* filename zipcreate.cls.php
* description Create zip files on the fly
* project
* author redmonkey
* version 0.2
* status beta
* support zipclass@redmonkey.com
* license GPL
*
* depends function unix2dostime() (found in supporting
* function library (includes/functions.lib.php))
*
* notes zip file format can be found at
* http://www.pkware.com/company/standards/appnote/
*
* notes the documented zip file format omits to detail
* the required header signature for the data
* descriptor (extended local file header) section
* which is (0x08074b50). while many decompression
* utilities will ignore this error, this signature
* is vital for compatability with Stuffit Expander
* for Mac if you have included the data descriptor
*
* notes while using bzip2 compression offers a reduced
* file size it does come at the expense of higher
* system resources usage. the decompression
* utility will also have to be compatabile with
* at least v4.6 zip file format specification
*
* file history
* ============
* 01/01/2005 v0.1 initial version
* 02/01/2005 v0.2 added checking and handling for files of
* zero bytes in length
************************************************************/
class ZipCreate
{
var $filedata; // file data
var $cntrldir; // central directory record
var $comment; // zip file comment
var $offset; // local header offset tracker
var $entries; // counter for total entries within the zip
var $ztype; // current compression type
/**
* @return
* @param string _ztype compression type to use, currently only supporting
* gzip (deflated), bzip2, and store (no compression)
* @desc constructor, initialise class variables and set compression
* type (defaults to gzip (Deflated)) for files
*/
function ZipCreate($_ztype = 'gzip')
{
$this->filedata = '';
$this->cntrldir = '';
$this->comment = '';
$this->offset = 0;
$this->entries = 0;
switch(strtolower($_ztype))
{
case 'gzip' :
if (!function_exists('gzcompress'))
{
trigger_error('Your PHP installation does not support gzip compression', E_USER_ERROR);
}
$this->ztype = 'gzip';
break;
case 'bzip2':
if (!function_exists('bzcompress'))
{
trigger_error('Your PHP installation does not support bzip2 compression', E_USER_ERROR);
}
$this->ztype = 'bzip2';
break;
case 'stored':
$this->ztype = 'store';
break;
default :
// default to no (Stored) compression type for anything else
$notice_msg = 'Unsupported compression type (' . $_ztype . ') using Stored instead';
$this->ztype = 'store';
trigger_error($notice_msg, E_USER_NOTICE);
}
}
/**
* @return
* @param string _path directory path
* @param string _timestamp unix timestamp for dir last modified date and time
* @desc adds a directory record to the archive
*/
function add_dir($_path, $_timestamp = 0)
{
return $this->add_file(null, $_path, $_timestamp);
}
/**
* @return
* @param string _data file contents
* @param string _name name of the file within the archive including path
* @param int _timestamp unix timestamp for file last modified date and time
* @desc adds a file to the archive
*/
function add_file($_data = null, $_name, $_timestamp = 0)
{
if (is_null($_data)) // assume it's a directory
{
$z_type = 'store'; // set compression to none
$ext_fa = 0x10; // external file attributes
$_data = ''; // initialise $_data
}
elseif ($_data == '') // assume a zero byte length file
{
$z_type = 'store'; // set compression to none
$ext_fa = 0x20; // external file attributes
}
else // assume it's a file
{
$z_type = $this->ztype;
$ext_fa = 0x20; // external file attributes
}
// remove leading and trailing spaces from filename
// and correct any erros with directory seperators
$_name = trim(str_replace('\\\'', '/', $_name));
// remove any invalid path definitions
$_name = preg_replace('/^([A-z]:\/+|\.?\/+)/', '', $_name);
// set last modified time of file in required DOS format
$mod_time = unix2dostime($_timestamp);
switch($z_type)
{
case 'gzip':
$min_ver = 0x14; // minimum version needed to extract (2.0)
$zmethod = 0x08; // compression method
$c_data = gzcompress($_data); // compress file
$c_data = substr($c_data, 2, -4); // fix crc bug
break;
case 'bzip2':
$min_ver = 0x2e; // minimum version needed to extract (4.6)
$zmethod = 0x0c; // compression method
$c_data = bzcompress($_data); // compress file
break;
default : // default to stored (no) compression
$min_ver = 0x0a; // minimum version needed to extract (1.0)
$zmethod = 0x00; // compression method
$c_data = $_data;
break;
}
// file details
$crc32 = crc32($_data); // crc32 checksum of file
$c_len = strlen($c_data); // compressed length of file
$uc_len = strlen($_data); // uncompressed length of file
$fn_len = strlen($_name); // length of filename
// pack file data
$filedata = pack('VvvvVVVVvva' . $fn_len . 'a' . $c_len . 'VVVV',
0x04034b50, // local file header signature (4 bytes)
$min_ver, // version needed to extract (2 bytes)
0x08, // gen purpose bit flag (2 bytes)
$zmethod, // compression method (2 bytes)
$mod_time, // last modified time and date (4 bytes)
0, // crc-32 (4 bytes)
0, // compressed filesize (4 bytes)
0, // uncompressed filesize (4 bytes)
$fn_len, // length of filename (2 bytes)
0, // extra field length (2 bytes)
$_name, // filename (variable length)
$c_data, // compressed data (variable length)
0x08074b50, // extended local header signature (4 bytes)
$crc32, // crc-32 (4 bytes)
$c_len, // compressed filesize (4 bytes)
$uc_len); // uncompressed filesize (4 bytes)
// add to filedata
$this->filedata .= $filedata;
// pack file data and add to central directory
$this->cntrldir .= pack('VvvvvVVVVvvvvvVVa' . $fn_len,
0x02014b50, // central file header signature (4 bytes)
0x14, // version made by (2 bytes)
$min_ver, // version needed to extract (2 bytes)
0x08, // gen purpose bit flag (2 bytes)
$zmethod, // compression method (2 bytes)
$mod_time, // last modified time and date (4 bytes)
$crc32, // crc32 (4 bytes)
$c_len, // compressed filesize (4 bytes)
$uc_len, // uncompressed filesize (4 bytes)
$fn_len, // length of filename (2 bytes)
0, // extra field length (2 bytes)
0, // file comment length (2 bytes)
0, // disk number start (2 bytes)
0, // internal file attributes (2 bytes)
$ext_fa, // external file attributes (4 bytes)
$this->offset, // relative offset of local header (4 bytes)
$_name); // filename (variable length)
// update offset tracker
$this->offset += strlen($filedata);
// increment entry counter
$this->entries++;
// cleanup
unset($c_data, $filedata, $ztype, $min_ver, $zmethod, $mod_time, $c_len, $uc_len, $fn_len);
}
/**
* @return
* @param string _comment zip file comment
* @desc adds a comment to the archive
*/
function add_comment($_comment)
{
$this->comment = $_comment;
}
/**
* @return string the zipped file
* @desc throws everything together and returns it
*/
function build_zip()
{
$com_len = strlen($this->comment); // length of zip file comment
return $this->filedata // .zip file data (variable length)
. $this->cntrldir // .zip central directory record (variable length)
. pack('VvvvvVVva' . $com_len,
0x06054b50, // end of central dir signature (4 bytes)
0, // number of this disk (2 bytes)
0, // number of the disk with start of
// central directory record (2 bytes)
$this->entries, // total # of entries on this disk (2 bytes)
$this->entries, // total # of entries overall (2 bytes)
strlen($this->cntrldir), // size of central dir (4 bytes)
$this->offset, // offset to start of central dir (4 bytes)
$com_len, // .zip file comment length (2 bytes)
$this->comment); // .zip file comment (variable length)
}
}
?>Code: Select all
<?php
/***********************************************************
* filename functions.lib.php
* description Supporting functions for zipcreate.cls.php,
* zipextract.cls.php
* project
* author redmonkey
* version 0.1
* status beta
* support zipclass@redmonkey.com
* license GPL
*
* file history
* ============
* 01/01/2005 v0.1 initial version
************************************************************/
/**
* @return int DOS date and time
* @param int _timestamp Unix timestamp
* @desc returns DOS date and time of the timestamp or
* current local time if no timestamp is given
*/
function unix2dostime($_timestamp = 0)
{
$timebit = ($_timestamp == 0) ? getdate() : getdate($_timestamp);
if ($timebit['year'] < 1980)
{
return (1 << 21 | 1 << 16);
}
$timebit['year'] -= 1980;
return ($timebit['year'] << 25 | $timebit['mon'] << 21 |
$timebit['mday'] << 16 | $timebit['hours'] << 11 |
$timebit['minutes'] << 5 | $timebit['seconds'] >> 1);
}
/**
* @return int Unix timestamp
* @param int _dostime DOS date and time
* @desc converts a DOS date and time integer to a Unix
* timestamp
*/
function dos2unixtime($_dostime)
{
$sec = 2 * ($_dostime & 0x1f);
$min = ($_dostime >> 5) & 0x3f;
$hrs = ($_dostime >> 11) & 0x1f;
$day = ($_dostime >> 16) & 0x1f;
$mon = ($_dostime >> 21) & 0x0f;
$year = (($_dostime >> 25) & 0x7f) + 1980;
return mktime($hrs, $min, $sec, $mon, $day, $year);
}
/**
* @return bool true on sccuess false on failure
* @param string _path path of directories to create
* @param int _modtime timestamp to set last modified time of directory
* @param string _dir base directory to start creating directories in
* @desc loops through the individual directories in $_path
* and attempts to create any that do not exist
*/
function make_dirs($_path, $_modtime = false, $_dir = '.')
{
if ($_path == './')
{
return true;
}
$_dir = $_dir == '/' ? '' : $_dir;
$_modtime = !is_integer($_modtime) ? time() : $_modtime;
$dirs = explode('/', $_path);
for ($i = 0, $n_dirs = count($dirs); $i < $n_dirs; $i++)
{
$_dir = $_dir . '/' . $dirs[$i];
if (!is_dir($_dir))
{
if(!@mkdir($_dir, 0755))
{
return false;
}
}
if ($i == ($n_dirs -1))
{
// supress errors as this does not work on win32 platforms
@touch($_dir, $_modtime);
}
}
return true;
}
/**
* @return int position of last occurrence of _needle in
* _haystack
* @param string _haystack string to be searched
* @param string _needle search string
* @param int _offset position to start search
* @desc find position of last occurrence of a string
* within a string
*/
function _strrpos($_haystack, $_needle, $_offset = 0)
{
if ((int) array_shift(explode('.', phpversion())) > 4)
{
return strrpos($_haystack, $_needle, $_offset);
}
$_haystack = $_offset < 0 ? substr($_haystack, 0, $_offset)
: substr($_haystack, $_offset);
$pos = strpos(strrev($_haystack), strrev($_needle));
if ($pos !== false)
{
$pos = strlen($_haystack) - strlen($_needle) - $pos;
return $_offset > 0 ? $pos + $_offset : $pos;
}
return false;
}
?>Code: Select all
<?php
/***********************************************************
* filename zipcreate.ex1.php
* description Example of reading a few files into the
* archive, then saving the zip archive to file
*
* author redmonkey
************************************************************/
include('./includes/functions.lib.php');
include('./includes/zipcreate.cls.php');
$zipname = 'example.zip';
$ZIP = new ZipCreate();
$infile = './sample_files/appnote.txt';
if ($fp = @fopen($infile, 'rb'))
{
$contents = fread($fp, filesize($infile));
fclose($fp);
$ZIP->add_file($contents, 'appnote.txt', filemtime($infile));
}
$infile = './sample_files/images/diao.jpg';
if ($fp = @fopen($infile, 'rb'))
{
$contents = fread($fp, filesize($infile));
fclose($fp);
$ZIP->add_file($contents, 'images/example.jpg', filemtime($infile));
}
if ($fp = @fopen($zipname, 'wb'))
{
fwrite($fp, $ZIP->build_zip());
fclose($fp);
}
?>Code: Select all
<?php
/***********************************************************
* filename zipcreate.ex2.php
* description Example of reading a few files into the
* archive, then offering the file for immediate
* download
*
* author redmonkey
************************************************************/
include('./includes/functions.lib.php');
include('./includes/zipcreate.cls.php');
$zipname = 'example.zip';
$ZIP = new ZipCreate();
$infile = './sample_files/appnote.txt';
if ($fp = @fopen($infile, 'rb'))
{
$contents = fread($fp, filesize($infile));
fclose($fp);
$ZIP->add_file($contents, 'appnote.txt', filemtime($infile));
}
$infile = './sample_files/images/diao.jpg';
if ($fp = @fopen($infile, 'rb'))
{
$contents = fread($fp, filesize($infile));
fclose($fp);
$ZIP->add_file($contents, 'images/example.jpg', filemtime($infile));
}
$zipfile = $ZIP->build_zip();
header('Content-Type: application/zip');
header('Content-Length: '. strlen($zipfile));
header('Content-Disposition: attachment; filename="' . $zipname . '"');
header('Content-Transfer-Encoding: binary');
echo $zipfile;
?>Code: Select all
<?php
/***********************************************************
* filename zipcreate.ex3.php
* description Example of adding the contents of a directory
* including sub-directories into a zip archive,
* then saving the archive to file
*
* author redmonkey
*
* file history
* ============
* 02/01/2005 revised to properly handle files of zero bytes
* in size
************************************************************/
include('./includes/functions.lib.php');
include('./includes/zipcreate.cls.php');
$zip_dir = 'sample_files';
$zipname = 'example.zip';
$cur_dir = getcwd();
$ZIP = new ZipCreate();
if (chdir($zip_dir))
{
// note parse_dir function can be found at the bottom of this
// script.
list ($files, $directories) = parse_dir();
if (is_array($directories) && count($directories > 0))
{
foreach ($directories as $dir)
{
$ZIP->add_dir($dir, filemtime($dir));
}
}
if (is_array($files) && count($files > 0))
{
foreach ($files as $name)
{
$fsize = filesize($name);
if ($fsize > 0)
{
if ($fp = @fopen($name, 'rb'))
{
$contents = fread($fp, $fsize);
if (substr($name, 0, 2) == './')
{
$name = substr($name, 2);
}
$ZIP->add_file($contents, $name, filemtime($name));
}
}
else
{
$ZIP->add_file('', $name, filemtime($name));
}
}
}
chdir($cur_dir);
}
$comment = 'Example 3 using ZipCreate to zip an' . "\x0a";
$comment .= 'entire directory structure. This will' . "\x0a";
$comment .= 'also add entries for any empty' . "\x0a";
$comment .= 'directories if present.' . "\x0a";
// add a global comment to the zip file
$ZIP->add_comment($comment);
// write the zip file to file
if ($fp = @fopen($zipname, 'wb'))
{
fwrite($fp, $ZIP->build_zip());
fclose($fp);
}
function parse_dir($dir = '.', $recurse = true)
{
$files = array();
$directories = array();
if ($dh = @opendir($dir))
{
//list the files in the dir
while (false !== ($file = readdir($dh)))
{
// ignore entries for current and parent directory, also
// avoid symlinks until we decide if it a good idea to follow
// them or not.
if ($file != '..' && $file != '.' && @filetype($file) != 'link')
{
if (is_dir ($dir . '/' . $file))
{
$directories[] = $dir . '/' . $file;
if ($recurse)
{
// let's go round again
list ($tmp_files, $tmp_dirs) = parse_dir($dir . '/' . $file);
if ($tmp_dirs != false)
{
$directories = array_merge($directories, $tmp_dirs);
}
if ($tmp_files != false)
{
$files = array_merge($files, $tmp_files);
}
}
}
// make sure it is a file before blindly adding it as one
elseif (is_file ($dir . '/' . $file))
{
$files[] = $dir."/".$file;
}
}
}
closedir ($dh);
}
else
{
// failed to open directory to return false
return array(false, false);
}
return array($files, $directories);
}
?>
