From f11b211f154ec56dc860a2b3bfadb17f2a3325dc Mon Sep 17 00:00:00 2001 From: pabs Date: Wed, 22 Aug 2007 21:26:39 -0400 Subject: Initial commit. --- zipstream.php | 181 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 181 insertions(+) create mode 100644 zipstream.php (limited to 'zipstream.php') diff --git a/zipstream.php b/zipstream.php new file mode 100644 index 0000000..03fb96b --- /dev/null +++ b/zipstream.php @@ -0,0 +1,181 @@ + +# +# 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)); + } +}; + +?> -- cgit v1.2.3