diff options
author | pabs <pabs@pablotron.org> | 2007-08-22 21:26:39 -0400 |
---|---|---|
committer | pabs <pabs@pablotron.org> | 2007-08-22 21:26:39 -0400 |
commit | f11b211f154ec56dc860a2b3bfadb17f2a3325dc (patch) | |
tree | 3fa7c90051fb4a24eb9af11f6b8cbc42c7bd4e19 | |
download | zipstream-php-f11b211f154ec56dc860a2b3bfadb17f2a3325dc.tar.bz2 zipstream-php-f11b211f154ec56dc860a2b3bfadb17f2a3325dc.zip |
Initial commit.
-rw-r--r-- | extras/README | 3 | ||||
-rw-r--r-- | extras/zipfile.class.php | 190 | ||||
-rw-r--r-- | zipstream.php | 181 |
3 files changed, 374 insertions, 0 deletions
diff --git a/extras/README b/extras/README new file mode 100644 index 0000000..1023832 --- /dev/null +++ b/extras/README @@ -0,0 +1,3 @@ + +Based on: +http://phpmyadmin.svn.sourceforge.net/viewvc/*checkout*/phpmyadmin/trunk/phpMyAdmin/libraries/zip.lib.php?revision=10240 diff --git a/extras/zipfile.class.php b/extras/zipfile.class.php new file mode 100644 index 0000000..2d377cc --- /dev/null +++ b/extras/zipfile.class.php @@ -0,0 +1,190 @@ +<?php +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + * + * @version $Id$ + */ + +/** + * Zip file creation class. + * Makes zip files. + * + * Based on : + * + * http://www.zend.com/codex.php?id=535&single=1 + * By Eric Mueller <eric@themepark.com> + * + * http://www.zend.com/codex.php?id=470&single=1 + * by Denis125 <webmaster@atlant.ru> + * + * a patch from Peter Listiak <mlady@users.sourceforge.net> for last modified + * date and time of the compressed file + * + * Official ZIP file format: http://www.pkware.com/appnote.txt + * + * @access public + */ +class zipfile +{ + /** + * Array to store compressed data + * + * @var array $datasec + */ + var $datasec = array(); + + /** + * Central directory + * + * @var array $ctrl_dir + */ + var $ctrl_dir = array(); + + /** + * End of central directory record + * + * @var string $eof_ctrl_dir + */ + var $eof_ctrl_dir = "\x50\x4b\x05\x06\x00\x00\x00\x00"; + + /** + * Last offset position + * + * @var integer $old_offset + */ + var $old_offset = 0; + + + /** + * Converts an Unix timestamp to a four byte DOS date and time format (date + * in high two bytes, time in low two bytes allowing magnitude comparison). + * + * @param integer the current Unix timestamp + * + * @return integer the current date in a four byte DOS format + * + * @access private + */ + function unix2DosTime($unixtime = 0) { + $timearray = ($unixtime == 0) ? getdate() : getdate($unixtime); + + if ($timearray['year'] < 1980) { + $timearray['year'] = 1980; + $timearray['mon'] = 1; + $timearray['mday'] = 1; + $timearray['hours'] = 0; + $timearray['minutes'] = 0; + $timearray['seconds'] = 0; + } // end if + + return (($timearray['year'] - 1980) << 25) | ($timearray['mon'] << 21) | ($timearray['mday'] << 16) | + ($timearray['hours'] << 11) | ($timearray['minutes'] << 5) | ($timearray['seconds'] >> 1); + } // end of the 'unix2DosTime()' method + + + /** + * Adds "file" to archive + * + * @param string file contents + * @param string name of the file in the archive (may contains the path) + * @param integer the current timestamp + * + * @access public + */ + function addFile($data, $name, $time = 0) + { + $name = str_replace('\\', '/', $name); + + $dtime = dechex($this->unix2DosTime($time)); + $hexdtime = '\x' . $dtime[6] . $dtime[7] + . '\x' . $dtime[4] . $dtime[5] + . '\x' . $dtime[2] . $dtime[3] + . '\x' . $dtime[0] . $dtime[1]; + eval('$hexdtime = "' . $hexdtime . '";'); + + $fr = "\x50\x4b\x03\x04"; + $fr .= "\x14\x00"; // ver needed to extract + $fr .= "\x00\x00"; // gen purpose bit flag + $fr .= "\x08\x00"; // compression method + $fr .= $hexdtime; // last mod time and date + + // "local file header" segment + $unc_len = strlen($data); + $crc = crc32($data); + $zdata = gzcompress($data); + $zdata = substr(substr($zdata, 0, strlen($zdata) - 4), 2); // fix crc bug + $c_len = strlen($zdata); + $fr .= pack('V', $crc); // crc32 + $fr .= pack('V', $c_len); // compressed filesize + $fr .= pack('V', $unc_len); // uncompressed filesize + $fr .= pack('v', strlen($name)); // length of filename + $fr .= pack('v', 0); // extra field length + $fr .= $name; + + // "file data" segment + $fr .= $zdata; + + // "data descriptor" segment (optional but necessary if archive is not + // served as file) + // nijel(2004-10-19): this seems not to be needed at all and causes + // problems in some cases (bug #1037737) + //$fr .= pack('V', $crc); // crc32 + //$fr .= pack('V', $c_len); // compressed filesize + //$fr .= pack('V', $unc_len); // uncompressed filesize + + // add this entry to array + $this -> datasec[] = $fr; + + // now add to central directory record + $cdrec = "\x50\x4b\x01\x02"; + $cdrec .= "\x00\x00"; // version made by + $cdrec .= "\x14\x00"; // version needed to extract + $cdrec .= "\x00\x00"; // gen purpose bit flag + $cdrec .= "\x08\x00"; // compression method + $cdrec .= $hexdtime; // 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 + $cdrec .= pack('V', 32); // external file attributes - 'archive' bit set + + $cdrec .= pack('V', $this -> old_offset); // relative offset of local header + $this -> old_offset += strlen($fr); + + $cdrec .= $name; + + // optional extra field, file comment goes here + // save to central directory + $this -> ctrl_dir[] = $cdrec; + } // end of the 'addFile()' method + + + /** + * Dumps out file + * + * @return string the zipped file + * + * @access public + */ + function 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 + } // end of the 'file()' method + +} // end of the 'zipfile' class +?> diff --git a/zipstream.php b/zipstream.php new file mode 100644 index 0000000..03fb96b --- /dev/null +++ b/zipstream.php @@ -0,0 +1,181 @@ +<?php + +# +# ZipStream - Streamed, dynamically generated zip archives. +# Paul Duncan <pabs@pablotron.org> +# +# Usage: +# +# # create a new zip stream object +# $zs = new ZipStream(); +# +# # list of local files +# $files = array('foo.txt', 'bar.jpg'); +# +# # read and add each file to the archive +# foreach ($files as $path) +# $zs->add_file($path, file_get_contents($path)); +# +# # write archive footer to stream +# $zs->finish(); +# +class ZipStream { + var $files = array(), + $cdr_ofs = 0, + $ofs = 0; + + # + # add_file - add a file to the archive + # + # Parameters: + # + # $name - path of file in archive (including directory). + # $data - contents of file + # $time - last-modified timestamp of file (optional) + # + # Example: + # + # $data = file_get_contents('foo.txt'); + # $zs->add_file('foo.txt', $data); + # + function add_file($name, $data, $time = 0) { + # compress data + $zdata = substr(gzcompress($data), 2, -4); + + # calculate header attributes + $crc = crc32($data)); + $zlen = strlen($zdata)); + $nlen = strlen($name); + $len = strlen($data)); + + # TODO: create unix timestamp + + # build file header + $fields = array( # (from V.A of APPNOTE.TXT) + array('V', 0x04034b50), # local file header signature + array('v', 0x14), # version needed to extract + array('v', 0x00), # general purpose bit flag + array('v', 0x08), # compresion method (deflate) + array('v', 0x08), # file mod time (dos) FIXME + array('v', 0x08), # file mod date (dos) FIXME + array('V', $crc), # crc32 of data + array('V', $zlen), # compressed data length + array('V', $len), # uncompressed data length + array('v', $nlen), # filename length + array('v', 0), # extra data len + ); + + # pack fields + $ret = $this->pack_fields($fields) . $name . $zdata; + $full_len = strlen($ret); + + # add to central directory record and increment offset + $this->add_to_cdr($name, $time, $crc, $zlen, $len, $full_len); + + # print data + echo $ret; + } + + # + # finish - Write zip footer to stream. + # + # Example: + # + # # add a list of files to the archive + # $files = array('foo.txt', 'bar.jpg'); + # foreach ($files as $path) + # $zs->add_file($path, file_get_contents($path)); + # + # # write footer to stream + # $zs->finish(); + # + function finish() { + $this->add_cdr(); + $this->clear(); + } + + function clear() { + $this->files = array(0; + $this->ofs = 0; + $this->cdr_ofs = 0; + } + + + ################### + # PRIVATE METHODS # + ################### + + function add_to_cdr($name, $time, $crc, $zlen, $len, $full_len) { + $this->files[] = array($this->ofs, $name, $time, $crc32, $zlen, $len); + $this->ofs += $full_len; + } + + function add_cdr_file($args) { + list ($name, $time, $crc32, $zlen, $len, $ofs) = $args; + + $fields = array( # (from V,F of APPNOTE.TXT) + array('V', 0x02014b50), # central file header signature + array('v', 0x14), # version needed to extract + array('v', 0x00), # general purpose bit flag + array('v', 0x08), # compresion method (deflate) + array('v', 0x08), # file mod time (dos) FIXME + array('v', 0x08), # file mod date (dos) FIXME + array('V', $crc), # crc32 of data + array('V', $zlen), # compressed data length + array('V', $len), # uncompressed data length + array('v', $nlen), # filename length + array('v', 0), # extra data len + array('v', 0), # file comment length + array('v', 0), # disk number start + array('v', 0), # internal file attributes + array('V', 0), # external file attributes + array('V', $ofs), # relative offset of local header + ); + + # pack fields and append name + $ret = $this->pack_fields($fields) . $name; + + # increment cdr offset + $this->cdr_ofs += strlen($ret); + } + + function add_cdr_eof() { + $num = count($this->files); + $cdr_len = $this->cdr_ofs; + $cdr_ofs = $this->ofs; + + $fields = array( # (from V,F of APPNOTE.TXT) + array('V', 0x06054b50), # end of central file header signature + array('v', 0x00), # disk number + array('v', 0x00), # number of disk with cdr + array('v', $num), # number of entries in the cdr on this disk + array('v', $num), # number of entries in the cdr + array('V', $cdr_len), # cdr size + array('V', $cdr_ofs), # cdr ofs + array('v', 0x00), # zip file comment length + ); + } + + function add_cdr() { + foreach ($this->files as $file) + $this->add_cdr_file($file); + $this->add_cdr_eof(); + } + + function pack_fields($fields) { + # populate format string and argument list + list ($pack_fmt, $args) = array('', array()); + foreach ($fields as $field) { + $fmt .= $field[0]; + $args[] = $field[1]; + } + + # prepend format string to argument list + array_unshift($args, $fmt); + + # build output string from header and compressed data + return call_user_func('pack', $args)); + } +}; + +?> |