| [ Index ] |
PHP Cross Reference of Joomla 1.5.26 DE |
[Summary view] [Print] [Text view]
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 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
| Generated: Wed Mar 28 15:54:07 2012 | Cross-referenced by PHPXref 0.7.1 |