[ Index ]

PHP Cross Reference of Joomla 1.5.26 DE

title

Body

[close]

/libraries/joomla/filesystem/archive/ -> zip.php (source)

   1  <?php
   2  /**
   3   * @version        $Id:zip.php 6961 2007-03-15 16:06:53Z tcp $
   4   * @package        Joomla.Framework
   5   * @subpackage    FileSystem
   6   * @copyright    Copyright (C) 2005 - 2010 Open Source Matters. All rights reserved.
   7   * @license        GNU/GPL, see LICENSE.php
   8   * Joomla! is free software. This version may have been modified pursuant
   9   * to the GNU General Public License, and as distributed it includes or
  10   * is derivative of works licensed under the GNU General Public License or
  11   * other free or open source software licenses.
  12   * See COPYRIGHT.php for copyright notices and details.
  13   */
  14  
  15  // Check to ensure this file is within the rest of the framework
  16  defined('JPATH_BASE') or die();
  17  
  18  /**
  19   * ZIP format adapter for the JArchive class
  20   *
  21   * The ZIP compression code is partially based on code from:
  22   *   Eric Mueller <eric@themepark.com>
  23   *   http://www.zend.com/codex.php?id=535&single=1
  24   *
  25   *   Deins125 <webmaster@atlant.ru>
  26   *   http://www.zend.com/codex.php?id=470&single=1
  27   *
  28   * The ZIP compression date code is partially based on code from
  29   *   Peter Listiak <mlady@users.sourceforge.net>
  30   *
  31   * This class is inspired from and draws heavily in code and concept from the Compress package of
  32   * The Horde Project <http://www.horde.org>
  33   *
  34   * @contributor  Chuck Hagenbuch <chuck@horde.org>
  35   * @contributor  Michael Slusarz <slusarz@horde.org>
  36   * @contributor  Michael Cochrane <mike@graftonhall.co.nz>
  37   *
  38   * @package     Joomla.Framework
  39   * @subpackage    FileSystem
  40   * @since        1.5
  41   */
  42  class JArchiveZip extends JObject
  43  {
  44      /**
  45       * ZIP compression methods.
  46       * @var array
  47       */
  48      var $_methods = array (
  49          0x0 => 'None',
  50          0x1 => 'Shrunk',
  51          0x2 => 'Super Fast',
  52          0x3 => 'Fast',
  53          0x4 => 'Normal',
  54          0x5 => 'Maximum',
  55          0x6 => 'Imploded',
  56          0x8 => 'Deflated'
  57      );
  58  
  59      /**
  60       * Beginning of central directory record.
  61       * @var string
  62       */
  63      var $_ctrlDirHeader = "\x50\x4b\x01\x02";
  64  
  65      /**
  66       * End of central directory record.
  67       * @var string
  68       */
  69      var $_ctrlDirEnd = "\x50\x4b\x05\x06\x00\x00\x00\x00";
  70  
  71      /**
  72       * Beginning of file contents.
  73       * @var string
  74       */
  75      var $_fileHeader = "\x50\x4b\x03\x04";
  76  
  77      /**
  78       * ZIP file data buffer
  79       * @var string
  80       */
  81      var $_data = null;
  82  
  83      /**
  84       * ZIP file metadata array
  85       * @var array
  86       */
  87      var $_metadata = null;
  88  
  89      /**
  90       * Create a ZIP compressed file from an array of file data.
  91       *
  92       * @todo    Finish Implementation
  93       *
  94       * @access    public
  95       * @param    string    $archive    Path to save archive
  96       * @param    array    $files        Array of files to add to archive
  97       * @param    array    $options    Compression options [unused]
  98       * @return    boolean    True if successful
  99       * @since    1.5
 100       */
 101  	function create($archive, $files, $options = array ())
 102      {
 103          // Initialize variables
 104          $contents = array();
 105          $ctrldir  = array();
 106  
 107          foreach ($files as $file)
 108          {
 109              $this->_addToZIPFile($file, $contents, $ctrldir);
 110          }
 111          return $this->_createZIPFile($contents, $ctrldir, $archive);
 112      }
 113  
 114      /**
 115       * Extract a ZIP compressed file to a given path
 116       *
 117       * @access    public
 118       * @param    string    $archive        Path to ZIP archive to extract
 119       * @param    string    $destination    Path to extract archive into
 120       * @param    array    $options        Extraction options [unused]
 121       * @return    boolean    True if successful
 122       * @since    1.5
 123       */
 124  	function extract($archive, $destination, $options = array ())
 125      {
 126          if ( ! is_file($archive) )
 127          {
 128              $this->set('error.message', 'Archive does not exist');
 129              return false;
 130          }
 131  
 132          if ($this->hasNativeSupport()) {
 133              return ($this->_extractNative($archive, $destination, $options))? true : JError::raiseWarning(100, $this->get('error.message'));
 134          } else {
 135              return ($this->_extract($archive, $destination, $options))? true : JError::raiseWarning(100, $this->get('error.message'));
 136          }
 137      }
 138  
 139      /**
 140       * Method to determine if the server has native zip support for faster handling
 141       *
 142       * @access    public
 143       * @return    boolean    True if php has native ZIP support
 144       * @since    1.5
 145       */
 146  	function hasNativeSupport()
 147      {
 148          return (function_exists('zip_open') && function_exists('zip_read'));
 149      }
 150  
 151      /**
 152       * Checks to see if the data is a valid ZIP file.
 153       *
 154       * @access    public
 155       * @param    string    $data    ZIP archive data buffer
 156       * @return    boolean    True if valid, false if invalid.
 157       * @since    1.5
 158       */
 159  	function checkZipData(& $data) {
 160          if (strpos($data, $this->_fileHeader) === false) {
 161              return false;
 162          } else {
 163              return true;
 164          }
 165      }
 166  
 167      /**
 168       * Extract a ZIP compressed file to a given path using a php based algorithm that only requires zlib support
 169       *
 170       * @access    private
 171       * @param    string    $archive        Path to ZIP archive to extract
 172       * @param    string    $destination    Path to extract archive into
 173       * @param    array    $options        Extraction options [unused]
 174       * @return    boolean    True if successful
 175       * @since    1.5
 176       */
 177  	function _extract($archive, $destination, $options)
 178      {
 179          // Initialize variables
 180          $this->_data = null;
 181          $this->_metadata = null;
 182  
 183          if (!extension_loaded('zlib')) {
 184              $this->set('error.message', 'Zlib Not Supported');
 185              return false;
 186          }
 187  
 188          if (!$this->_data = JFile::read($archive)) {
 189              $this->set('error.message', 'Unable to read archive');
 190              return false;
 191          }
 192          if (!$this->_getZipInfo($this->_data)) {
 193              return false;
 194          }
 195  
 196          for ($i=0,$n=count($this->_metadata);$i<$n;$i++) {
 197              if (substr($this->_metadata[$i]['name'], -1, 1) != '/' && substr($this->_metadata[$i]['name'], -1, 1) != '\\') {
 198                  $buffer = $this->_getFileData($i);
 199                  $path = JPath::clean($destination.DS.$this->_metadata[$i]['name']);
 200                  // Make sure the destination folder exists
 201                  if (!JFolder::create(dirname($path))) {
 202                      $this->set('error.message', 'Unable to create destination');
 203                      return false;
 204                  }
 205                  if (JFile::write($path, $buffer) === false) {
 206                      $this->set('error.message', 'Unable to write entry');
 207                      return false;
 208                  }
 209              }
 210          }
 211          return true;
 212      }
 213  
 214      /**
 215       * Extract a ZIP compressed file to a given path using native php api calls for speed
 216       *
 217       * @access    private
 218       * @param    string    $archive        Path to ZIP archive to extract
 219       * @param    string    $destination    Path to extract archive into
 220       * @param    array    $options        Extraction options [unused]
 221       * @return    boolean    True if successful
 222       * @since    1.5
 223       */
 224  	function _extractNative($archive, $destination, $options)
 225      {
 226          if ($zip = zip_open($archive)) {
 227              if (is_resource($zip)) {
 228                  // Make sure the destination folder exists
 229                  if (!JFolder::create($destination)) {
 230                      $this->set('error.message', 'Unable to create destination');
 231                      return false;
 232                  }
 233                  // Read files in the archive
 234                  while ($file = @zip_read($zip))
 235                  {
 236                      if (zip_entry_open($zip, $file, "r")) {
 237                          if (substr(zip_entry_name($file), strlen(zip_entry_name($file)) - 1) != "/") {
 238                              $buffer = zip_entry_read($file, zip_entry_filesize($file));
 239                              if (JFile::write($destination.DS.zip_entry_name($file), $buffer) === false) {
 240                                  $this->set('error.message', 'Unable to write entry');
 241                                  return false;
 242                              }
 243                              zip_entry_close($file);
 244                          }
 245                      } else {
 246                          $this->set('error.message', 'Unable to read entry');
 247                          return false;
 248                      }
 249                  }
 250                  @zip_close($zip);
 251              }
 252          } else {
 253              $this->set('error.message', 'Unable to open archive');
 254              return false;
 255          }
 256          return true;
 257      }
 258  
 259      /**
 260       * Get the list of files/data from a ZIP archive buffer.
 261       *
 262       * @access    private
 263       * @param     string    $data    The ZIP archive buffer.
 264       * @return    array    Archive metadata array
 265       * <pre>
 266       * KEY: Position in zipfile
 267       * VALUES: 'attr'    --  File attributes
 268       *         'crc'     --  CRC checksum
 269       *         'csize'   --  Compressed file size
 270       *         'date'    --  File modification time
 271       *         'name'    --  Filename
 272       *         'method'  --  Compression method
 273       *         'size'    --  Original file size
 274       *         'type'    --  File type
 275       * </pre>
 276       * @since    1.5
 277       */
 278  	function _getZipInfo(& $data)
 279      {
 280          // Initialize variables
 281          $entries = array ();
 282  
 283          // Find the last central directory header entry
 284          $fhLast = strpos($data, $this->_ctrlDirEnd);
 285          do {
 286              $last = $fhLast;        
 287          } while(($fhLast = strpos($data, $this->_ctrlDirEnd, $fhLast+1)) !== false);
 288          
 289          
 290          // Find the central directory offset
 291          $offset = 0;
 292          if($last) {
 293              $endOfCentralDirectory = unpack('vNumberOfDisk/vNoOfDiskWithStartOfCentralDirectory/vNoOfCentralDirectoryEntriesOnDisk/vTotalCentralDirectoryEntries/VSizeOfCentralDirectory/VCentralDirectoryOffset/vCommentLength', substr($data, $last+4));
 294              $offset    = $endOfCentralDirectory['CentralDirectoryOffset'];
 295          }
 296          
 297          // Get details from Central directory structure.
 298          $fhStart = strpos($data, $this->_ctrlDirHeader, $offset);
 299          do {
 300              if (strlen($data) < $fhStart +31) {
 301                  $this->set('error.message', 'Invalid ZIP data');
 302                  return false;
 303              }
 304              $info = unpack('vMethod/VTime/VCRC32/VCompressed/VUncompressed/vLength', substr($data, $fhStart +10, 20));
 305              $name = substr($data, $fhStart +46, $info['Length']);
 306  
 307              $entries[$name] = array('attr' => null, 'crc' => sprintf("%08s", dechex($info['CRC32'] )), 'csize' => $info['Compressed'], 'date' => null, '_dataStart' => null, 'name' => $name, 'method' => $this->_methods[$info['Method']], '_method' => $info['Method'], 'size' => $info['Uncompressed'], 'type' => null);
 308              $entries[$name]['date'] = mktime((($info['Time'] >> 11) & 0x1f), (($info['Time'] >> 5) & 0x3f), (($info['Time'] << 1) & 0x3e), (($info['Time'] >> 21) & 0x07), (($info['Time'] >> 16) & 0x1f), ((($info['Time'] >> 25) & 0x7f) + 1980));
 309  
 310              if (strlen($data) < $fhStart +43) {
 311                  $this->set('error.message', 'Invalid ZIP data');
 312                  return false;
 313              }
 314              $info = unpack('vInternal/VExternal/VOffset', substr($data, $fhStart +36, 10));
 315  
 316              $entries[$name]['type'] = ($info['Internal'] & 0x01) ? 'text' : 'binary';
 317              $entries[$name]['attr'] = (($info['External'] & 0x10) ? 'D' : '-') .
 318                                        (($info['External'] & 0x20) ? 'A' : '-') .
 319                                        (($info['External'] & 0x03) ? 'S' : '-') .
 320                                        (($info['External'] & 0x02) ? 'H' : '-') .
 321                                        (($info['External'] & 0x01) ? 'R' : '-');
 322              $entries[$name]['offset'] = $info['Offset'];
 323  
 324              // Get details from local file header since we have the offset
 325              $lfhStart = strpos($data, $this->_fileHeader, $entries[$name]['offset']);
 326              if (strlen($data) < $lfhStart +34) {
 327                  $this->set('error.message', 'Invalid ZIP data');
 328                  return false;
 329              }
 330              $info = unpack('vMethod/VTime/VCRC32/VCompressed/VUncompressed/vLength/vExtraLength', substr($data, $lfhStart +8, 25));
 331              $name = substr($data, $lfhStart +30, $info['Length']);
 332              $entries[$name]['_dataStart'] = $lfhStart +30 + $info['Length'] + $info['ExtraLength'];
 333          } while ((($fhStart = strpos($data, $this->_ctrlDirHeader, $fhStart +46)) !== false));    
 334  
 335          $this->_metadata = array_values($entries);
 336          return true;
 337      }
 338  
 339      /**
 340       * Returns the file data for a file by offsest in the ZIP archive
 341       *
 342       * @access    private
 343       * @param    int        $key    The position of the file in the archive.
 344       * @return    string    Uncompresed file data buffer
 345       * @since    1.5
 346       */
 347  	function _getFileData($key) {
 348          if ($this->_metadata[$key]['_method'] == 0x8) {
 349              // If zlib extention is loaded use it
 350              if (extension_loaded('zlib')) {
 351                  return @ gzinflate(substr($this->_data, $this->_metadata[$key]['_dataStart'], $this->_metadata[$key]['csize']));
 352              }
 353          }
 354          elseif ($this->_metadata[$key]['_method'] == 0x0) {
 355              /* Files that aren't compressed. */
 356              return substr($this->_data, $this->_metadata[$key]['_dataStart'], $this->_metadata[$key]['csize']);
 357          } elseif ($this->_metadata[$key]['_method'] == 0x12) {
 358              // Is bz2 extension loaded?  If not try to load it
 359              if (!extension_loaded('bz2')) {
 360                  if (JPATH_ISWIN) {
 361                      @ dl('php_bz2.dll');
 362                  } else {
 363                      @ dl('bz2.so');
 364                  }
 365              }
 366              // If bz2 extention is sucessfully loaded use it
 367              if (extension_loaded('bz2')) {
 368                  return bzdecompress(substr($this->_data, $this->_metadata[$key]['_dataStart'], $this->_metadata[$key]['csize']));
 369              }
 370          }
 371          return '';
 372      }
 373  
 374      /**
 375       * Converts a UNIX timestamp to a 4-byte DOS date and time format
 376       * (date in high 2-bytes, time in low 2-bytes allowing magnitude
 377       * comparison).
 378       *
 379       * @access    private
 380       * @param    int    $unixtime    The current UNIX timestamp.
 381       * @return    int    The current date in a 4-byte DOS format.
 382       * @since    1.5
 383       */
 384  	function _unix2DOSTime($unixtime = null) {
 385          $timearray = (is_null($unixtime)) ? getdate() : getdate($unixtime);
 386  
 387          if ($timearray['year'] < 1980) {
 388              $timearray['year'] = 1980;
 389              $timearray['mon'] = 1;
 390              $timearray['mday'] = 1;
 391              $timearray['hours'] = 0;
 392              $timearray['minutes'] = 0;
 393              $timearray['seconds'] = 0;
 394          }
 395          return (($timearray['year'] - 1980) << 25) | ($timearray['mon'] << 21) | ($timearray['mday'] << 16) | ($timearray['hours'] << 11) | ($timearray['minutes'] << 5) | ($timearray['seconds'] >> 1);
 396      }
 397  
 398      /**
 399       * Adds a "file" to the ZIP archive.
 400       *
 401       * @todo Review and finish implementation
 402       *
 403       * @access    private
 404       * @param    array    $file        File data array to add
 405       * @param    array    $contents    An array of existing zipped files.
 406       * @param    array    $ctrldir    An array of central directory information.
 407       * @return    void
 408       * @since    1.5
 409       */
 410  	function _addToZIPFile(& $file, & $contents, & $ctrldir) {
 411          $data = & $file['data'];
 412          $name = str_replace('\\', '/', $file['name']);
 413  
 414          /* See if time/date information has been provided. */
 415          $ftime = null;
 416          if (isset ($file['time'])) {
 417              $ftime = $file['time'];
 418          }
 419  
 420          /* Get the hex time. */
 421          $dtime = dechex($this->_unix2DosTime($ftime));
 422          $hexdtime = chr(hexdec($dtime[6] . $dtime[7])) .
 423          chr(hexdec($dtime[4] . $dtime[5])) .
 424          chr(hexdec($dtime[2] . $dtime[3])) .
 425          chr(hexdec($dtime[0] . $dtime[1]));
 426  
 427          $fr = $this->_fileHeader; /* Begin creating the ZIP data. */
 428          $fr .= "\x14\x00"; /* Version needed to extract. */
 429          $fr .= "\x00\x00"; /* General purpose bit flag. */
 430          $fr .= "\x08\x00"; /* Compression method. */
 431          $fr .= $hexdtime; /* Last modification time/date. */
 432  
 433          /* "Local file header" segment. */
 434          $unc_len = strlen($data);
 435          $crc = crc32($data);
 436          $zdata = gzcompress($data);
 437          $zdata = substr(substr($zdata, 0, strlen($zdata) - 4), 2);
 438          $c_len = strlen($zdata);
 439  
 440          $fr .= pack('V', $crc); /* CRC 32 information. */
 441          $fr .= pack('V', $c_len); /* Compressed filesize. */
 442          $fr .= pack('V', $unc_len); /* Uncompressed filesize. */
 443          $fr .= pack('v', strlen($name)); /* Length of filename. */
 444          $fr .= pack('v', 0); /* Extra field length. */
 445          $fr .= $name; /* File name. */
 446  
 447          /* "File data" segment. */
 448          $fr .= $zdata;
 449  
 450          /* Add this entry to array. */
 451          $old_offset = strlen(implode('', $contents));
 452          $contents[] = & $fr;
 453  
 454          /* Add to central directory record. */
 455          $cdrec = $this->_ctrlDirHeader;
 456          $cdrec .= "\x00\x00"; /* Version made by. */
 457          $cdrec .= "\x14\x00"; /* Version needed to extract */
 458          $cdrec .= "\x00\x00"; /* General purpose bit flag */
 459          $cdrec .= "\x08\x00"; /* Compression method */
 460          $cdrec .= $hexdtime; /* Last mod time/date. */
 461          $cdrec .= pack('V', $crc); /* CRC 32 information. */
 462          $cdrec .= pack('V', $c_len); /* Compressed filesize. */
 463          $cdrec .= pack('V', $unc_len); /* Uncompressed filesize. */
 464          $cdrec .= pack('v', strlen($name)); /* Length of filename. */
 465          $cdrec .= pack('v', 0); /* Extra field length. */
 466          $cdrec .= pack('v', 0); /* File comment length. */
 467          $cdrec .= pack('v', 0); /* Disk number start. */
 468          $cdrec .= pack('v', 0); /* Internal file attributes. */
 469          $cdrec .= pack('V', 32); /* External file attributes -
 470                                             'archive' bit set. */
 471          $cdrec .= pack('V', $old_offset); /* Relative offset of local
 472                                                      header. */
 473          $cdrec .= $name; /* File name. */
 474          /* Optional extra field, file comment goes here. */
 475  
 476          // Save to central directory array. */
 477          $ctrldir[] = & $cdrec;
 478      }
 479  
 480      /**
 481       * Creates the ZIP file.
 482       * Official ZIP file format: http://www.pkware.com/appnote.txt
 483       *
 484       * @todo Review and finish implementation
 485       *
 486       * @access    private
 487       * @param    array    $contents    An array of existing zipped files.
 488       * @param    array    $ctrldir    An array of central directory information.
 489       * @param    string    $path        The path to store the archive.
 490       * @return    boolean    True if successful
 491       * @since    1.5
 492       */
 493  	function _createZIPFile(& $contents, & $ctrlDir, $path)
 494      {
 495          $data = implode('', $contents);
 496          $dir = implode('', $ctrlDir);
 497  
 498          $buffer = $data . $dir . $this->_ctrlDirEnd .
 499          /* Total # of entries "on this disk". */
 500          pack('v', count($ctrlDir)) .
 501          /* Total # of entries overall. */
 502          pack('v', count($ctrlDir)) .
 503          /* Size of central directory. */
 504          pack('V', strlen($dir)) .
 505          /* Offset to start of central dir. */
 506          pack('V', strlen($data)) .
 507          /* ZIP file comment length. */
 508          "\x00\x00";
 509  
 510          if (JFile::write($path, $buffer) === false) {
 511              return false;
 512          } else {
 513              return true;
 514          }
 515      }
 516  }


Generated: Wed Mar 28 15:54:07 2012 Cross-referenced by PHPXref 0.7.1