[ Index ] |
PHP Cross Reference of Unnamed Project |
[Summary view] [Print] [Text view]
1 <?php 2 // This file is part of Moodle - http://moodle.org/ 3 // 4 // Moodle is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // Moodle is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU General Public License for more details. 13 // 14 // You should have received a copy of the GNU General Public License 15 // along with Moodle. If not, see <http://www.gnu.org/licenses/>. 16 17 /** 18 * Implementation of zip file archive. 19 * 20 * @package core_files 21 * @copyright 2008 Petr Skoda (http://skodak.org) 22 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 */ 24 25 defined('MOODLE_INTERNAL') || die(); 26 27 require_once("$CFG->libdir/filestorage/file_archive.php"); 28 29 /** 30 * Zip file archive class. 31 * 32 * @package core_files 33 * @category files 34 * @copyright 2008 Petr Skoda (http://skodak.org) 35 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 36 */ 37 class zip_archive extends file_archive { 38 39 /** @var string Pathname of archive */ 40 protected $archivepathname = null; 41 42 /** @var int archive open mode */ 43 protected $mode = null; 44 45 /** @var int Used memory tracking */ 46 protected $usedmem = 0; 47 48 /** @var int Iteration position */ 49 protected $pos = 0; 50 51 /** @var ZipArchive instance */ 52 protected $za; 53 54 /** @var bool was this archive modified? */ 55 protected $modified = false; 56 57 /** @var array unicode decoding array, created by decoding zip file */ 58 protected $namelookup = null; 59 60 /** @var string base64 encoded contents of empty zip file */ 61 protected static $emptyzipcontent = 'UEsFBgAAAAAAAAAAAAAAAAAAAAAAAA=='; 62 63 /** @var bool ugly hack for broken empty zip handling in < PHP 5.3.10 */ 64 protected $emptyziphack = false; 65 66 /** 67 * Create new zip_archive instance. 68 */ 69 public function __construct() { 70 $this->encoding = null; // Autodetects encoding by default. 71 } 72 73 /** 74 * Open or create archive (depending on $mode). 75 * 76 * @todo MDL-31048 return error message 77 * @param string $archivepathname 78 * @param int $mode OPEN, CREATE or OVERWRITE constant 79 * @param string $encoding archive local paths encoding, empty means autodetect 80 * @return bool success 81 */ 82 public function open($archivepathname, $mode=file_archive::CREATE, $encoding=null) { 83 $this->close(); 84 85 $this->usedmem = 0; 86 $this->pos = 0; 87 $this->encoding = $encoding; 88 $this->mode = $mode; 89 90 $this->za = new ZipArchive(); 91 92 switch($mode) { 93 case file_archive::OPEN: $flags = 0; break; 94 case file_archive::OVERWRITE: $flags = ZIPARCHIVE::CREATE | ZIPARCHIVE::OVERWRITE; break; //changed in PHP 5.2.8 95 case file_archive::CREATE: 96 default : $flags = ZIPARCHIVE::CREATE; break; 97 } 98 99 $result = $this->za->open($archivepathname, $flags); 100 101 if ($flags == 0 and $result === ZIPARCHIVE::ER_NOZIP and filesize($archivepathname) === 22) { 102 // Legacy PHP versions < 5.3.10 can not deal with empty zip archives. 103 if (file_get_contents($archivepathname) === base64_decode(self::$emptyzipcontent)) { 104 if ($temp = make_temp_directory('zip')) { 105 $this->emptyziphack = tempnam($temp, 'zip'); 106 $this->za = new ZipArchive(); 107 $result = $this->za->open($this->emptyziphack, ZIPARCHIVE::CREATE); 108 } 109 } 110 } 111 112 if ($result === true) { 113 if (file_exists($archivepathname)) { 114 $this->archivepathname = realpath($archivepathname); 115 } else { 116 $this->archivepathname = $archivepathname; 117 } 118 return true; 119 120 } else { 121 $message = 'Unknown error.'; 122 switch ($result) { 123 case ZIPARCHIVE::ER_EXISTS: $message = 'File already exists.'; break; 124 case ZIPARCHIVE::ER_INCONS: $message = 'Zip archive inconsistent.'; break; 125 case ZIPARCHIVE::ER_INVAL: $message = 'Invalid argument.'; break; 126 case ZIPARCHIVE::ER_MEMORY: $message = 'Malloc failure.'; break; 127 case ZIPARCHIVE::ER_NOENT: $message = 'No such file.'; break; 128 case ZIPARCHIVE::ER_NOZIP: $message = 'Not a zip archive.'; break; 129 case ZIPARCHIVE::ER_OPEN: $message = 'Can\'t open file.'; break; 130 case ZIPARCHIVE::ER_READ: $message = 'Read error.'; break; 131 case ZIPARCHIVE::ER_SEEK: $message = 'Seek error.'; break; 132 } 133 debugging($message.': '.$archivepathname, DEBUG_DEVELOPER); 134 $this->za = null; 135 $this->archivepathname = null; 136 return false; 137 } 138 } 139 140 /** 141 * Normalize $localname, always keep in utf-8 encoding. 142 * 143 * @param string $localname name of file in utf-8 encoding 144 * @return string normalised compressed file or directory name 145 */ 146 protected function mangle_pathname($localname) { 147 $result = str_replace('\\', '/', $localname); // no MS \ separators 148 $result = preg_replace('/\.\.+/', '', $result); // prevent /.../ 149 $result = ltrim($result, '/'); // no leading slash 150 151 if ($result === '.') { 152 $result = ''; 153 } 154 155 return $result; 156 } 157 158 /** 159 * Tries to convert $localname into utf-8 160 * please note that it may fail really badly. 161 * The resulting file name is cleaned. 162 * 163 * @param string $localname name (encoding is read from zip file or guessed) 164 * @return string in utf-8 165 */ 166 protected function unmangle_pathname($localname) { 167 $this->init_namelookup(); 168 169 if (!isset($this->namelookup[$localname])) { 170 $name = $localname; 171 // This should not happen. 172 if (!empty($this->encoding) and $this->encoding !== 'utf-8') { 173 $name = @core_text::convert($name, $this->encoding, 'utf-8'); 174 } 175 $name = str_replace('\\', '/', $name); // no MS \ separators 176 $name = clean_param($name, PARAM_PATH); // only safe chars 177 return ltrim($name, '/'); // no leading slash 178 } 179 180 return $this->namelookup[$localname]; 181 } 182 183 /** 184 * Close archive, write changes to disk. 185 * 186 * @return bool success 187 */ 188 public function close() { 189 if (!isset($this->za)) { 190 return false; 191 } 192 193 if ($this->emptyziphack) { 194 @$this->za->close(); 195 $this->za = null; 196 $this->mode = null; 197 $this->namelookup = null; 198 $this->modified = false; 199 @unlink($this->emptyziphack); 200 $this->emptyziphack = false; 201 return true; 202 203 } else if ($this->za->numFiles == 0) { 204 // PHP can not create empty archives, so let's fake it. 205 @$this->za->close(); 206 $this->za = null; 207 $this->mode = null; 208 $this->namelookup = null; 209 $this->modified = false; 210 @unlink($this->archivepathname); 211 $data = base64_decode(self::$emptyzipcontent); 212 if (!file_put_contents($this->archivepathname, $data)) { 213 return false; 214 } 215 return true; 216 } 217 218 $res = $this->za->close(); 219 $this->za = null; 220 $this->mode = null; 221 $this->namelookup = null; 222 223 if ($this->modified) { 224 $this->fix_utf8_flags(); 225 $this->modified = false; 226 } 227 228 return $res; 229 } 230 231 /** 232 * Returns file stream for reading of content. 233 * 234 * @param int $index index of file 235 * @return resource|bool file handle or false if error 236 */ 237 public function get_stream($index) { 238 if (!isset($this->za)) { 239 return false; 240 } 241 242 $name = $this->za->getNameIndex($index); 243 if ($name === false) { 244 return false; 245 } 246 247 return $this->za->getStream($name); 248 } 249 250 /** 251 * Returns file information. 252 * 253 * @param int $index index of file 254 * @return stdClass|bool info object or false if error 255 */ 256 public function get_info($index) { 257 if (!isset($this->za)) { 258 return false; 259 } 260 261 // Need to use the ZipArchive's numfiles, as $this->count() relies on this function to count actual files (skipping OSX junk). 262 if ($index < 0 or $index >=$this->za->numFiles) { 263 return false; 264 } 265 266 // PHP 5.6 introduced encoding guessing logic, we need to fall back 267 // to raw ZIP_FL_ENC_RAW (== 64) to get consistent results as in PHP 5.5. 268 $result = $this->za->statIndex($index, 64); 269 270 if ($result === false) { 271 return false; 272 } 273 274 $info = new stdClass(); 275 $info->index = $index; 276 $info->original_pathname = $result['name']; 277 $info->pathname = $this->unmangle_pathname($result['name']); 278 $info->mtime = (int)$result['mtime']; 279 280 if ($info->pathname[strlen($info->pathname)-1] === '/') { 281 $info->is_directory = true; 282 $info->size = 0; 283 } else { 284 $info->is_directory = false; 285 $info->size = (int)$result['size']; 286 } 287 288 if ($this->is_system_file($info)) { 289 // Don't return system files. 290 return false; 291 } 292 293 return $info; 294 } 295 296 /** 297 * Returns array of info about all files in archive. 298 * 299 * @return array of file infos 300 */ 301 public function list_files() { 302 if (!isset($this->za)) { 303 return false; 304 } 305 306 $infos = array(); 307 308 foreach ($this as $info) { 309 // Simply iterating over $this will give us info only for files we're interested in. 310 array_push($infos, $info); 311 } 312 313 return $infos; 314 } 315 316 public function is_system_file($fileinfo) { 317 if (substr($fileinfo->pathname, 0, 8) === '__MACOSX' or substr($fileinfo->pathname, -9) === '.DS_Store') { 318 // Mac OSX system files. 319 return true; 320 } 321 if (substr($fileinfo->pathname, -9) === 'Thumbs.db') { 322 $stream = $this->za->getStream($fileinfo->pathname); 323 $info = base64_encode(fread($stream, 8)); 324 fclose($stream); 325 if ($info === '0M8R4KGxGuE=') { 326 // It's an OLE Compound File - so it's almost certainly a Windows thumbnail cache. 327 return true; 328 } 329 } 330 return false; 331 } 332 333 /** 334 * Returns number of files in archive. 335 * 336 * @return int number of files 337 */ 338 public function count() { 339 if (!isset($this->za)) { 340 return false; 341 } 342 343 return count($this->list_files()); 344 } 345 346 /** 347 * Returns approximate number of files in archive. This may be a slight 348 * overestimate. 349 * 350 * @return int|bool Estimated number of files, or false if not opened 351 */ 352 public function estimated_count() { 353 if (!isset($this->za)) { 354 return false; 355 } 356 357 return $this->za->numFiles; 358 } 359 360 /** 361 * Add file into archive. 362 * 363 * @param string $localname name of file in archive 364 * @param string $pathname location of file 365 * @return bool success 366 */ 367 public function add_file_from_pathname($localname, $pathname) { 368 if ($this->emptyziphack) { 369 $this->close(); 370 $this->open($this->archivepathname, file_archive::OVERWRITE, $this->encoding); 371 } 372 373 if (!isset($this->za)) { 374 return false; 375 } 376 377 if ($this->archivepathname === realpath($pathname)) { 378 // Do not add self into archive. 379 return false; 380 } 381 382 if (!is_readable($pathname) or is_dir($pathname)) { 383 return false; 384 } 385 386 if (is_null($localname)) { 387 $localname = clean_param($pathname, PARAM_PATH); 388 } 389 $localname = trim($localname, '/'); // No leading slashes in archives! 390 $localname = $this->mangle_pathname($localname); 391 392 if ($localname === '') { 393 // Sorry - conversion failed badly. 394 return false; 395 } 396 397 if (!$this->za->addFile($pathname, $localname)) { 398 return false; 399 } 400 $this->modified = true; 401 return true; 402 } 403 404 /** 405 * Add content of string into archive. 406 * 407 * @param string $localname name of file in archive 408 * @param string $contents contents 409 * @return bool success 410 */ 411 public function add_file_from_string($localname, $contents) { 412 if ($this->emptyziphack) { 413 $this->close(); 414 $this->open($this->archivepathname, file_archive::OVERWRITE, $this->encoding); 415 } 416 417 if (!isset($this->za)) { 418 return false; 419 } 420 421 $localname = trim($localname, '/'); // No leading slashes in archives! 422 $localname = $this->mangle_pathname($localname); 423 424 if ($localname === '') { 425 // Sorry - conversion failed badly. 426 return false; 427 } 428 429 if ($this->usedmem > 2097151) { 430 // This prevents running out of memory when adding many large files using strings. 431 $this->close(); 432 $res = $this->open($this->archivepathname, file_archive::OPEN, $this->encoding); 433 if ($res !== true) { 434 print_error('cannotopenzip'); 435 } 436 } 437 $this->usedmem += strlen($contents); 438 439 if (!$this->za->addFromString($localname, $contents)) { 440 return false; 441 } 442 $this->modified = true; 443 return true; 444 } 445 446 /** 447 * Add empty directory into archive. 448 * 449 * @param string $localname name of file in archive 450 * @return bool success 451 */ 452 public function add_directory($localname) { 453 if ($this->emptyziphack) { 454 $this->close(); 455 $this->open($this->archivepathname, file_archive::OVERWRITE, $this->encoding); 456 } 457 458 if (!isset($this->za)) { 459 return false; 460 } 461 $localname = trim($localname, '/'). '/'; 462 $localname = $this->mangle_pathname($localname); 463 464 if ($localname === '/') { 465 // Sorry - conversion failed badly. 466 return false; 467 } 468 469 if ($localname !== '') { 470 if (!$this->za->addEmptyDir($localname)) { 471 return false; 472 } 473 $this->modified = true; 474 } 475 return true; 476 } 477 478 /** 479 * Returns current file info. 480 * 481 * @return stdClass 482 */ 483 public function current() { 484 if (!isset($this->za)) { 485 return false; 486 } 487 488 return $this->get_info($this->pos); 489 } 490 491 /** 492 * Returns the index of current file. 493 * 494 * @return int current file index 495 */ 496 public function key() { 497 return $this->pos; 498 } 499 500 /** 501 * Moves forward to next file. 502 */ 503 public function next() { 504 $this->pos++; 505 } 506 507 /** 508 * Rewinds back to the first file. 509 */ 510 public function rewind() { 511 $this->pos = 0; 512 } 513 514 /** 515 * Did we reach the end? 516 * 517 * @return bool 518 */ 519 public function valid() { 520 if (!isset($this->za)) { 521 return false; 522 } 523 524 // Skip over unwanted system files (get_info will return false). 525 while (!$this->get_info($this->pos) && $this->pos < $this->za->numFiles) { 526 $this->next(); 527 } 528 529 // No files left - we're at the end. 530 if ($this->pos >= $this->za->numFiles) { 531 return false; 532 } 533 534 return true; 535 } 536 537 /** 538 * Create a map of file names used in zip archive. 539 * @return void 540 */ 541 protected function init_namelookup() { 542 if ($this->emptyziphack) { 543 $this->namelookup = array(); 544 return; 545 } 546 547 if (!isset($this->za)) { 548 return; 549 } 550 if (isset($this->namelookup)) { 551 return; 552 } 553 554 $this->namelookup = array(); 555 556 if ($this->mode != file_archive::OPEN) { 557 // No need to tweak existing names when creating zip file because there are none yet! 558 return; 559 } 560 561 if (!file_exists($this->archivepathname)) { 562 return; 563 } 564 565 if (!$fp = fopen($this->archivepathname, 'rb')) { 566 return; 567 } 568 if (!$filesize = filesize($this->archivepathname)) { 569 return; 570 } 571 572 $centralend = self::zip_get_central_end($fp, $filesize); 573 574 if ($centralend === false or $centralend['disk'] !== 0 or $centralend['disk_start'] !== 0 or $centralend['offset'] === 0xFFFFFFFF) { 575 // Single disk archives only and o support for ZIP64, sorry. 576 fclose($fp); 577 return; 578 } 579 580 fseek($fp, $centralend['offset']); 581 $data = fread($fp, $centralend['size']); 582 $pos = 0; 583 $files = array(); 584 for($i=0; $i<$centralend['entries']; $i++) { 585 $file = self::zip_parse_file_header($data, $centralend, $pos); 586 if ($file === false) { 587 // Wrong header, sorry. 588 fclose($fp); 589 return; 590 } 591 $files[] = $file; 592 } 593 fclose($fp); 594 595 foreach ($files as $file) { 596 $name = $file['name']; 597 if (preg_match('/^[a-zA-Z0-9_\-\.]*$/', $file['name'])) { 598 // No need to fix ASCII. 599 $name = fix_utf8($name); 600 601 } else if (!($file['general'] & pow(2, 11))) { 602 // First look for unicode name alternatives. 603 $found = false; 604 foreach($file['extra'] as $extra) { 605 if ($extra['id'] === 0x7075) { 606 $data = unpack('cversion/Vcrc', substr($extra['data'], 0, 5)); 607 if ($data['crc'] === crc32($name)) { 608 $found = true; 609 $name = substr($extra['data'], 5); 610 } 611 } 612 } 613 if (!$found and !empty($this->encoding) and $this->encoding !== 'utf-8') { 614 // Try the encoding from open(). 615 $newname = @core_text::convert($name, $this->encoding, 'utf-8'); 616 $original = core_text::convert($newname, 'utf-8', $this->encoding); 617 if ($original === $name) { 618 $found = true; 619 $name = $newname; 620 } 621 } 622 if (!$found and $file['version'] === 0x315) { 623 // This looks like OS X build in zipper. 624 $newname = fix_utf8($name); 625 if ($newname === $name) { 626 $found = true; 627 $name = $newname; 628 } 629 } 630 if (!$found and $file['version'] === 0) { 631 // This looks like our old borked Moodle 2.2 file. 632 $newname = fix_utf8($name); 633 if ($newname === $name) { 634 $found = true; 635 $name = $newname; 636 } 637 } 638 if (!$found and $encoding = get_string('oldcharset', 'langconfig')) { 639 // Last attempt - try the dos/unix encoding from current language. 640 $windows = true; 641 foreach($file['extra'] as $extra) { 642 // In Windows archivers do not usually set any extras with the exception of NTFS flag in WinZip/WinRar. 643 $windows = false; 644 if ($extra['id'] === 0x000a) { 645 $windows = true; 646 break; 647 } 648 } 649 650 if ($windows === true) { 651 switch(strtoupper($encoding)) { 652 case 'ISO-8859-1': $encoding = 'CP850'; break; 653 case 'ISO-8859-2': $encoding = 'CP852'; break; 654 case 'ISO-8859-4': $encoding = 'CP775'; break; 655 case 'ISO-8859-5': $encoding = 'CP866'; break; 656 case 'ISO-8859-6': $encoding = 'CP720'; break; 657 case 'ISO-8859-7': $encoding = 'CP737'; break; 658 case 'ISO-8859-8': $encoding = 'CP862'; break; 659 case 'EUC-JP': 660 case 'UTF-8': 661 if ($winchar = get_string('localewincharset', 'langconfig')) { 662 // Most probably works only for zh_cn, 663 // if there are more problems we could add zipcharset to langconfig files. 664 $encoding = $winchar; 665 } 666 break; 667 } 668 } 669 $newname = @core_text::convert($name, $encoding, 'utf-8'); 670 $original = core_text::convert($newname, 'utf-8', $encoding); 671 672 if ($original === $name) { 673 $name = $newname; 674 } 675 } 676 } 677 $name = str_replace('\\', '/', $name); // no MS \ separators 678 $name = clean_param($name, PARAM_PATH); // only safe chars 679 $name = ltrim($name, '/'); // no leading slash 680 681 if (function_exists('normalizer_normalize')) { 682 $name = normalizer_normalize($name, Normalizer::FORM_C); 683 } 684 685 $this->namelookup[$file['name']] = $name; 686 } 687 } 688 689 /** 690 * Add unicode flag to all files in archive. 691 * 692 * NOTE: single disk archives only, no ZIP64 support. 693 * 694 * @return bool success, modifies the file contents 695 */ 696 protected function fix_utf8_flags() { 697 if ($this->emptyziphack) { 698 return true; 699 } 700 701 if (!file_exists($this->archivepathname)) { 702 return true; 703 } 704 705 // Note: the ZIP structure is described at http://www.pkware.com/documents/casestudies/APPNOTE.TXT 706 if (!$fp = fopen($this->archivepathname, 'rb+')) { 707 return false; 708 } 709 if (!$filesize = filesize($this->archivepathname)) { 710 return false; 711 } 712 713 $centralend = self::zip_get_central_end($fp, $filesize); 714 715 if ($centralend === false or $centralend['disk'] !== 0 or $centralend['disk_start'] !== 0 or $centralend['offset'] === 0xFFFFFFFF) { 716 // Single disk archives only and o support for ZIP64, sorry. 717 fclose($fp); 718 return false; 719 } 720 721 fseek($fp, $centralend['offset']); 722 $data = fread($fp, $centralend['size']); 723 $pos = 0; 724 $files = array(); 725 for($i=0; $i<$centralend['entries']; $i++) { 726 $file = self::zip_parse_file_header($data, $centralend, $pos); 727 if ($file === false) { 728 // Wrong header, sorry. 729 fclose($fp); 730 return false; 731 } 732 733 $newgeneral = $file['general'] | pow(2, 11); 734 if ($newgeneral === $file['general']) { 735 // Nothing to do with this file. 736 continue; 737 } 738 739 if (preg_match('/^[a-zA-Z0-9_\-\.]*$/', $file['name'])) { 740 // ASCII file names are always ok. 741 continue; 742 } 743 if ($file['extra']) { 744 // Most probably not created by php zip ext, better to skip it. 745 continue; 746 } 747 if (fix_utf8($file['name']) !== $file['name']) { 748 // Does not look like a valid utf-8 encoded file name, skip it. 749 continue; 750 } 751 752 // Read local file header. 753 fseek($fp, $file['local_offset']); 754 $localfile = unpack('Vsig/vversion_req/vgeneral/vmethod/vmtime/vmdate/Vcrc/Vsize_compressed/Vsize/vname_length/vextra_length', fread($fp, 30)); 755 if ($localfile['sig'] !== 0x04034b50) { 756 // Borked file! 757 fclose($fp); 758 return false; 759 } 760 761 $file['local'] = $localfile; 762 $files[] = $file; 763 } 764 765 foreach ($files as $file) { 766 $localfile = $file['local']; 767 // Add the unicode flag in central file header. 768 fseek($fp, $file['central_offset'] + 8); 769 if (ftell($fp) === $file['central_offset'] + 8) { 770 $newgeneral = $file['general'] | pow(2, 11); 771 fwrite($fp, pack('v', $newgeneral)); 772 } 773 // Modify local file header too. 774 fseek($fp, $file['local_offset'] + 6); 775 if (ftell($fp) === $file['local_offset'] + 6) { 776 $newgeneral = $localfile['general'] | pow(2, 11); 777 fwrite($fp, pack('v', $newgeneral)); 778 } 779 } 780 781 fclose($fp); 782 return true; 783 } 784 785 /** 786 * Read end of central signature of ZIP file. 787 * @internal 788 * @static 789 * @param resource $fp 790 * @param int $filesize 791 * @return array|bool 792 */ 793 public static function zip_get_central_end($fp, $filesize) { 794 // Find end of central directory record. 795 fseek($fp, $filesize - 22); 796 $info = unpack('Vsig', fread($fp, 4)); 797 if ($info['sig'] === 0x06054b50) { 798 // There is no comment. 799 fseek($fp, $filesize - 22); 800 $data = fread($fp, 22); 801 } else { 802 // There is some comment with 0xFF max size - that is 65557. 803 fseek($fp, $filesize - 65557); 804 $data = fread($fp, 65557); 805 } 806 807 $pos = strpos($data, pack('V', 0x06054b50)); 808 if ($pos === false) { 809 // Borked ZIP structure! 810 return false; 811 } 812 $centralend = unpack('Vsig/vdisk/vdisk_start/vdisk_entries/ventries/Vsize/Voffset/vcomment_length', substr($data, $pos, 22)); 813 if ($centralend['comment_length']) { 814 $centralend['comment'] = substr($data, 22, $centralend['comment_length']); 815 } else { 816 $centralend['comment'] = ''; 817 } 818 819 return $centralend; 820 } 821 822 /** 823 * Parse file header. 824 * @internal 825 * @param string $data 826 * @param array $centralend 827 * @param int $pos (modified) 828 * @return array|bool file info 829 */ 830 public static function zip_parse_file_header($data, $centralend, &$pos) { 831 $file = unpack('Vsig/vversion/vversion_req/vgeneral/vmethod/Vmodified/Vcrc/Vsize_compressed/Vsize/vname_length/vextra_length/vcomment_length/vdisk/vattr/Vattrext/Vlocal_offset', substr($data, $pos, 46)); 832 $file['central_offset'] = $centralend['offset'] + $pos; 833 $pos = $pos + 46; 834 if ($file['sig'] !== 0x02014b50) { 835 // Borked ZIP structure! 836 return false; 837 } 838 $file['name'] = substr($data, $pos, $file['name_length']); 839 $pos = $pos + $file['name_length']; 840 $file['extra'] = array(); 841 $file['extra_data'] = ''; 842 if ($file['extra_length']) { 843 $extradata = substr($data, $pos, $file['extra_length']); 844 $file['extra_data'] = $extradata; 845 while (strlen($extradata) > 4) { 846 $extra = unpack('vid/vsize', substr($extradata, 0, 4)); 847 $extra['data'] = substr($extradata, 4, $extra['size']); 848 $extradata = substr($extradata, 4+$extra['size']); 849 $file['extra'][] = $extra; 850 } 851 $pos = $pos + $file['extra_length']; 852 } 853 if ($file['comment_length']) { 854 $pos = $pos + $file['comment_length']; 855 $file['comment'] = substr($data, $pos, $file['comment_length']); 856 } else { 857 $file['comment'] = ''; 858 } 859 return $file; 860 } 861 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Thu Aug 11 10:00:09 2016 | Cross-referenced by PHPXref 0.7.1 |