Zip class

Small, short code snippets that other people may find useful. Do you have a good regex that you would like to share? Share it! Even better, the code can be commented on, and improved.

Moderator: General Moderators

redmonkey
Forum Regular
Posts: 836
Joined: Thu Dec 18, 2003 3:58 pm

Zip class

Post by redmonkey »

As title suggests, it's a zip class. Hopefully the included example scripts should give you enough to get it going. Note, there is still much to do, this is the functional initial release.

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;
}
?>
Example scripts....

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);
}
?>
Last edited by redmonkey on Sat Jan 01, 2005 8:54 pm, edited 2 times in total.
Shendemiar
Forum Contributor
Posts: 404
Joined: Thu Jan 08, 2004 8:28 am

Post by Shendemiar »

I'll give it a shot (in a few days time), and let you know how idiotproof it was Image

*******************************************************
ADDED

I must say that it's the most professional script i've ever seen. Not any kind of problems (like in all of the infinite number of Zip classes i've tried) and very good support!

Setting was easy even for a dumb, and the code isn't too tight, even noobs can read it Image

Thank you for it. Image
Last edited by Shendemiar on Sat Jan 01, 2005 10:25 pm, edited 3 times in total.
redmonkey
Forum Regular
Posts: 836
Joined: Thu Dec 18, 2003 3:58 pm

Post by redmonkey »

Shendemiar wrote:I'll give it a shot (in a few days time), and let you know how idiotproof it was [img]ttp://mmuforum.icemark.com/images/smiles/misc/[/img]

*******************************************************
ADDED

I must say that it's the most professional script i've ever seen. Not any kind of problems (like in all of the infinite number of Zip classes i've tried) and very good support!

Setting was easy even for a dumb, and the code isn't too tight, even noobs can read it Image

Thank you for it. Image
Thanks for your very kind words and for bringing to my attention the zero byte length file problem.
mstyle
Forum Newbie
Posts: 1
Joined: Wed May 18, 2005 1:17 pm

Post by mstyle »

Very nice piece of code. Haven't really dived into it, just gave it a spin and it worked flawlessly. *thumbs up*

i'm off to the folluwup on your article...
User avatar
ol4pr0
Forum Regular
Posts: 926
Joined: Thu Jan 08, 2004 11:22 am
Location: ecuador

Post by ol4pr0 »

Very Nice, however zipcreate.ex1.php is already including all sub-dirs in the zip. ej: creating the following in zip /www/domein_dir/dir/to/zipped/files
digitil
Forum Newbie
Posts: 3
Joined: Fri Jul 08, 2005 5:36 pm

nice! now how about backwards: decompressing zip fioles on t

Post by digitil »

anyone seen a script that takes an uploaded zip file and decompresses the contents and writes them into the file system?
User avatar
Chris Corbyn
Breakbeat Nuttzer
Posts: 13098
Joined: Wed Mar 24, 2004 7:57 am
Location: Melbourne, Australia

Re: nice! now how about backwards: decompressing zip fioles

Post by Chris Corbyn »

digitil wrote:anyone seen a script that takes an uploaded zip file and decompresses the contents and writes them into the file system?

*Personal coding - took ten mins, upload script for brother on a tour of Europe to send his photos*
EDIT | Here's the relevent (rushed) snip

Code: Select all

ftp_put($conn, './'.$_FILES['upfile']['name'], $_FILES['upfile']['tmp_name'], FTP_BINARY) or die('d');
echo '<div style="font-size:0.7em">';
if (system('unzip /home/mark/"'.$_FILES['upfile']['name'].'" -d /home/mark/public_html/')) {
	echo 'Files unzipped successully!<br /></div>';
} else {
	die('Unzip failed');
}
Last edited by Chris Corbyn on Fri Jul 08, 2005 6:15 pm, edited 1 time in total.
timvw
DevNet Master
Posts: 4897
Joined: Mon Jan 19, 2004 11:11 pm
Location: Leuven, Belgium

Post by timvw »

Debian has a nice little program "unp". Now you can unpack with fingers in the nose :)
redmonkey
Forum Regular
Posts: 836
Joined: Thu Dec 18, 2003 3:58 pm

Post by redmonkey »

ol4pr0 wrote:Very Nice, however zipcreate.ex1.php is already including all sub-dirs in the zip. ej: creating the following in zip /www/domein_dir/dir/to/zipped/files
Can you show me the code changes you have made to zipcreate.ex1.php ? as I see no apparent reason for the behaviour you describe.
redmonkey
Forum Regular
Posts: 836
Joined: Thu Dec 18, 2003 3:58 pm

Re: nice! now how about backwards: decompressing zip fioles

Post by redmonkey »

digitil wrote:anyone seen a script that takes an uploaded zip file and decompresses the contents and writes them into the file system?
Zip class (Part II)
gnel2000
Forum Newbie
Posts: 16
Joined: Tue Jul 26, 2005 10:41 pm

the zip file was corrupted

Post by gnel2000 »

Hi,

I has tried the class but i only got a corrupted zip file.
What happen to the class?
User avatar
n00b Saibot
DevNet Resident
Posts: 1452
Joined: Fri Dec 24, 2004 2:59 am
Location: Lucknow, UP, India
Contact:

Post by n00b Saibot »

can anyone else see this page contents all messed up or is it just me 8O
User avatar
John Cartwright
Site Admin
Posts: 11470
Joined: Tue Dec 23, 2003 2:10 am
Location: Toronto
Contact:

Post by John Cartwright »

I see it too... :evil:
User avatar
Weirdan
Moderator
Posts: 5978
Joined: Mon Nov 03, 2003 6:13 pm
Location: Odessa, Ukraine

Post by Weirdan »

fixed d11wtq's post

It was a real mess there
User avatar
n00b Saibot
DevNet Resident
Posts: 1452
Joined: Fri Dec 24, 2004 2:59 am
Location: Lucknow, UP, India
Contact:

Post by n00b Saibot »

good job! but why did that post mess up? because of the ol' forum mess :?: or did d11wtq posted same thing 10 times :P
Post Reply