├── .hgtags ├── README.markdown └── libraries └── Unzip.php /.hgtags: -------------------------------------------------------------------------------- 1 | f325f42c25843aedf26d74382815e9ea78f1a99d 1.0 2 | -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | CodeIgniter-Unzip 2 | ============ 3 | 4 | Extract ZIP files in CodeIgniter without installing any PECL extensions for PHP. 5 | 6 | 7 | Requirements 8 | ------------ 9 | 10 | 1. PHP 5.1+ 11 | 2. CodeIgniter 1.6.x - 2.0-dev 12 | 3. ZLib extension enabled 13 | 14 | 15 | Usage 16 | ----- 17 | 18 | $this->load->library('unzip'); 19 | 20 | // Optional: Only take out these files, anything else is ignored 21 | $this->unzip->allow(array('css', 'js', 'png', 'gif', 'jpeg', 'jpg', 'tpl', 'html', 'swf')); 22 | 23 | // Give it one parameter and it will extract to the same folder 24 | $this->unzip->extract('uploads/my_archive.zip'); 25 | 26 | // or specify a destination directory 27 | $this->unzip->extract('uploads/my_archive.zip', '/path/to/directory/); 28 | 29 | Simple as that! 30 | 31 | 32 | To-do 33 | ----- 34 | 35 | - Add extract_file() to extract a specific file from your ZIP. -------------------------------------------------------------------------------- /libraries/Unzip.php: -------------------------------------------------------------------------------- 1 | compressed_list = array(); 78 | $this->central_dir_list = array(); 79 | $this->end_of_central = array(); 80 | $this->info = array(); 81 | $this->error = array(); 82 | } 83 | 84 | /** 85 | * Unzip all files in archive. 86 | * 87 | * @access Public 88 | * @param none 89 | * @return none 90 | */ 91 | public function extract($zip_file, $target_dir = NULL, $preserve_filepath = TRUE) 92 | { 93 | $this->_reinit(); 94 | $this->_zip_file = $zip_file; 95 | $this->_target_dir = $target_dir ? $target_dir : dirname($this->_zip_file); 96 | 97 | if ( ! $files = $this->_list_files()) 98 | { 99 | $this->set_error('ZIP folder was empty.'); 100 | return FALSE; 101 | } 102 | 103 | $file_locations = array(); 104 | foreach ($files as $file => $trash) 105 | { 106 | $dirname = pathinfo($file, PATHINFO_DIRNAME); 107 | $extension = pathinfo($file, PATHINFO_EXTENSION); 108 | 109 | $folders = explode('/', $dirname); 110 | $out_dn = $this->_target_dir . '/' . $dirname; 111 | 112 | // Skip stuff in stupid folders 113 | if (in_array(current($folders), $this->_skip_dirs)) 114 | { 115 | continue; 116 | } 117 | 118 | // Skip any files that are not allowed 119 | if (is_array($this->_allow_extensions) AND $extension AND ! in_array($extension, $this->_allow_extensions)) 120 | { 121 | continue; 122 | } 123 | 124 | if ( ! is_dir($out_dn) AND $preserve_filepath) 125 | { 126 | $str = ""; 127 | foreach ($folders as $folder) 128 | { 129 | $str = $str ? $str . '/' . $folder : $folder; 130 | if ( ! is_dir($this->_target_dir . '/' . $str)) 131 | { 132 | $this->set_debug('Creating folder: ' . $this->_target_dir . '/' . $str); 133 | 134 | if ( ! @mkdir($this->_target_dir . '/' . $str)) 135 | { 136 | $this->set_error('Desitnation path is not writable.'); 137 | return FALSE; 138 | } 139 | 140 | // Apply chmod if configured to do so 141 | $this->apply_chmod AND chmod($this->_target_dir . '/' . $str, $this->apply_chmod); 142 | } 143 | } 144 | } 145 | 146 | if (substr($file, -1, 1) == '/') continue; 147 | 148 | $file_locations[] = $file_location = $this->_target_dir . '/' . ($preserve_filepath ? $file : basename($file)); 149 | 150 | $this->_extract_file($file, $file_location); 151 | } 152 | 153 | $this->compressed_list = array(); 154 | 155 | return $file_locations; 156 | } 157 | 158 | // -------------------------------------------------------------------- 159 | 160 | /** 161 | * What extensions do we want out of this ZIP 162 | * 163 | * @access Public 164 | * @param none 165 | * @return none 166 | */ 167 | public function allow($ext = NULL) 168 | { 169 | $this->_allow_extensions = $ext; 170 | } 171 | 172 | // -------------------------------------------------------------------- 173 | 174 | /** 175 | * Show error messages 176 | * 177 | * @access public 178 | * @param string 179 | * @return string 180 | */ 181 | public function error_string($open = '
', $close = '
') 182 | { 183 | return $open . implode($close . $open, $this->error) . $close; 184 | } 185 | 186 | // -------------------------------------------------------------------- 187 | 188 | /** 189 | * Show debug messages 190 | * 191 | * @access public 192 | * @param string 193 | * @return string 194 | */ 195 | public function debug_string($open = '', $close = '
') 196 | { 197 | return $open . implode($close . $open, $this->info) . $close; 198 | } 199 | 200 | // -------------------------------------------------------------------- 201 | 202 | /** 203 | * Save errors 204 | * 205 | * @access Private 206 | * @param string 207 | * @return none 208 | */ 209 | function set_error($string) 210 | { 211 | $this->error[] = $string; 212 | } 213 | 214 | // -------------------------------------------------------------------- 215 | 216 | /** 217 | * Save debug data 218 | * 219 | * @access Private 220 | * @param string 221 | * @return none 222 | */ 223 | function set_debug($string) 224 | { 225 | $this->info[] = $string; 226 | } 227 | 228 | // -------------------------------------------------------------------- 229 | 230 | /** 231 | * List all files in archive. 232 | * 233 | * @access Public 234 | * @param boolean 235 | * @return mixed 236 | */ 237 | private function _list_files($stop_on_file = FALSE) 238 | { 239 | if (sizeof($this->compressed_list)) 240 | { 241 | $this->set_debug('Returning already loaded file list.'); 242 | return $this->compressed_list; 243 | } 244 | 245 | // Open file, and set file handler 246 | $fh = fopen($this->_zip_file, 'r'); 247 | $this->fh = &$fh; 248 | 249 | if ( ! $fh) 250 | { 251 | $this->set_error('Failed to load file: ' . $this->_zip_file); 252 | return FALSE; 253 | } 254 | 255 | $this->set_debug('Loading list from "End of Central Dir" index list...'); 256 | 257 | if ( ! $this->_load_file_list_by_eof($fh, $stop_on_file)) 258 | { 259 | $this->set_debug('Failed! Trying to load list looking for signatures...'); 260 | 261 | if ( ! $this->_load_files_by_signatures($fh, $stop_on_file)) 262 | { 263 | $this->set_debug('Failed! Could not find any valid header.'); 264 | $this->set_error('ZIP File is corrupted or empty'); 265 | 266 | return FALSE; 267 | } 268 | } 269 | 270 | return $this->compressed_list; 271 | } 272 | 273 | // -------------------------------------------------------------------- 274 | 275 | /** 276 | * Unzip file in archive. 277 | * 278 | * @access Public 279 | * @param string, boolean 280 | * @return Unziped file. 281 | */ 282 | private function _extract_file($compressed_file_name, $target_file_name = FALSE) 283 | { 284 | if ( ! sizeof($this->compressed_list)) 285 | { 286 | $this->set_debug('Trying to unzip before loading file list... Loading it!'); 287 | $this->_list_files(FALSE, $compressed_file_name); 288 | } 289 | 290 | $fdetails = &$this->compressed_list[$compressed_file_name]; 291 | 292 | if ( ! isset($this->compressed_list[$compressed_file_name])) 293 | { 294 | $this->set_error('File "$compressed_file_name" is not compressed in the zip.'); 295 | return FALSE; 296 | } 297 | 298 | if (substr($compressed_file_name, -1) == '/') 299 | { 300 | $this->set_error('Trying to unzip a folder name "$compressed_file_name".'); 301 | return FALSE; 302 | } 303 | 304 | if ( ! $fdetails['uncompressed_size']) 305 | { 306 | $this->set_debug('File "$compressed_file_name" is empty.'); 307 | 308 | return $target_file_name ? file_put_contents($target_file_name, '') : ''; 309 | } 310 | 311 | fseek($this->fh, $fdetails['contents_start_offset']); 312 | $ret = $this->_uncompress( 313 | fread($this->fh, $fdetails['compressed_size']), 314 | $fdetails['compression_method'], 315 | $fdetails['uncompressed_size'], 316 | $target_file_name 317 | ); 318 | 319 | if ($this->apply_chmod AND $target_file_name) 320 | { 321 | chmod($target_file_name, FILE_READ_MODE); 322 | } 323 | 324 | return $ret; 325 | } 326 | 327 | // -------------------------------------------------------------------- 328 | 329 | /** 330 | * Free the file resource. 331 | * 332 | * @access Public 333 | * @param none 334 | * @return none 335 | */ 336 | public function close() 337 | { 338 | // Free the file resource 339 | if ($this->fh) 340 | { 341 | fclose($this->fh); 342 | } 343 | } 344 | 345 | // -------------------------------------------------------------------- 346 | 347 | /** 348 | * Free the file resource Automatic destroy. 349 | * 350 | * @access Public 351 | * @param none 352 | * @return none 353 | */ 354 | public function __destroy() 355 | { 356 | $this->close(); 357 | } 358 | 359 | // -------------------------------------------------------------------- 360 | 361 | /** 362 | * Uncompress file. And save it to the targetFile. 363 | * 364 | * @access Private 365 | * @param Filecontent, int, int, boolean 366 | * @return none 367 | */ 368 | private function _uncompress($content, $mode, $uncompressed_size, $target_file_name = FALSE) 369 | { 370 | switch ($mode) 371 | { 372 | case 0: 373 | return $target_file_name ? file_put_contents($target_file_name, $content) : $content; 374 | case 1: 375 | $this->set_error('Shrunk mode is not supported... yet?'); 376 | return FALSE; 377 | case 2: 378 | case 3: 379 | case 4: 380 | case 5: 381 | $this->set_error('Compression factor ' . ($mode - 1) . ' is not supported... yet?'); 382 | return FALSE; 383 | case 6: 384 | $this->set_error('Implode is not supported... yet?'); 385 | return FALSE; 386 | case 7: 387 | $this->set_error('Tokenizing compression algorithm is not supported... yet?'); 388 | return FALSE; 389 | case 8: 390 | // Deflate 391 | return $target_file_name ? 392 | file_put_contents($target_file_name, gzinflate($content, $uncompressed_size)) : 393 | gzinflate($content, $uncompressed_size); 394 | case 9: 395 | $this->set_error('Enhanced Deflating is not supported... yet?'); 396 | return FALSE; 397 | case 10: 398 | $this->set_error('PKWARE Date Compression Library Impoloding is not supported... yet?'); 399 | return FALSE; 400 | case 12: 401 | // Bzip2 402 | return $target_file_name ? 403 | file_put_contents($target_file_name, bzdecompress($content)) : 404 | bzdecompress($content); 405 | case 18: 406 | $this->set_error('IBM TERSE is not supported... yet?'); 407 | return FALSE; 408 | default: 409 | $this->set_error('Unknown uncompress method: $mode'); 410 | return FALSE; 411 | } 412 | } 413 | 414 | private function _load_file_list_by_eof(&$fh, $stop_on_file = FALSE) 415 | { 416 | // Check if there's a valid Central Dir signature. 417 | // Let's consider a file comment smaller than 1024 characters... 418 | // Actually, it length can be 65536.. But we're not going to support it. 419 | 420 | for ($x = 0; $x < 1024; $x++) 421 | { 422 | fseek($fh, -22 - $x, SEEK_END); 423 | 424 | $signature = fread($fh, 4); 425 | 426 | if ($signature == $this->central_signature_end) 427 | { 428 | // If found EOF Central Dir 429 | $eodir['disk_number_this'] = unpack("v", fread($fh, 2)); // number of this disk 430 | $eodir['disk_number'] = unpack("v", fread($fh, 2)); // number of the disk with the start of the central directory 431 | $eodir['total_entries_this'] = unpack("v", fread($fh, 2)); // total number of entries in the central dir on this disk 432 | $eodir['total_entries'] = unpack("v", fread($fh, 2)); // total number of entries in 433 | $eodir['size_of_cd'] = unpack("V", fread($fh, 4)); // size of the central directory 434 | $eodir['offset_start_cd'] = unpack("V", fread($fh, 4)); // offset of start of central directory with respect to the starting disk number 435 | $zip_comment_lenght = unpack("v", fread($fh, 2)); // zipfile comment length 436 | $eodir['zipfile_comment'] = $zip_comment_lenght[1] ? fread($fh, $zip_comment_lenght[1]) : ''; // zipfile comment 437 | 438 | $this->end_of_central = array( 439 | 'disk_number_this' => $eodir['disk_number_this'][1], 440 | 'disk_number' => $eodir['disk_number'][1], 441 | 'total_entries_this' => $eodir['total_entries_this'][1], 442 | 'total_entries' => $eodir['total_entries'][1], 443 | 'size_of_cd' => $eodir['size_of_cd'][1], 444 | 'offset_start_cd' => $eodir['offset_start_cd'][1], 445 | 'zipfile_comment' => $eodir['zipfile_comment'], 446 | ); 447 | 448 | // Then, load file list 449 | fseek($fh, $this->end_of_central['offset_start_cd']); 450 | $signature = fread($fh, 4); 451 | 452 | while ($signature == $this->dir_signature) 453 | { 454 | $dir['version_madeby'] = unpack("v", fread($fh, 2)); // version made by 455 | $dir['version_needed'] = unpack("v", fread($fh, 2)); // version needed to extract 456 | $dir['general_bit_flag'] = unpack("v", fread($fh, 2)); // general purpose bit flag 457 | $dir['compression_method'] = unpack("v", fread($fh, 2)); // compression method 458 | $dir['lastmod_time'] = unpack("v", fread($fh, 2)); // last mod file time 459 | $dir['lastmod_date'] = unpack("v", fread($fh, 2)); // last mod file date 460 | $dir['crc-32'] = fread($fh, 4); // crc-32 461 | $dir['compressed_size'] = unpack("V", fread($fh, 4)); // compressed size 462 | $dir['uncompressed_size'] = unpack("V", fread($fh, 4)); // uncompressed size 463 | $zip_file_length = unpack("v", fread($fh, 2)); // filename length 464 | $extra_field_length = unpack("v", fread($fh, 2)); // extra field length 465 | $fileCommentLength = unpack("v", fread($fh, 2)); // file comment length 466 | $dir['disk_number_start'] = unpack("v", fread($fh, 2)); // disk number start 467 | $dir['internal_attributes'] = unpack("v", fread($fh, 2)); // internal file attributes-byte1 468 | $dir['external_attributes1'] = unpack("v", fread($fh, 2)); // external file attributes-byte2 469 | $dir['external_attributes2'] = unpack("v", fread($fh, 2)); // external file attributes 470 | $dir['relative_offset'] = unpack("V", fread($fh, 4)); // relative offset of local header 471 | $dir['file_name'] = fread($fh, $zip_file_length[1]); // filename 472 | $dir['extra_field'] = $extra_field_length[1] ? fread($fh, $extra_field_length[1]) : ''; // extra field 473 | $dir['file_comment'] = $fileCommentLength[1] ? fread($fh, $fileCommentLength[1]) : ''; // file comment 474 | 475 | // Convert the date and time, from MS-DOS format to UNIX Timestamp 476 | $binary_mod_date = str_pad(decbin($dir['lastmod_date'][1]), 16, '0', STR_PAD_LEFT); 477 | $binary_mod_time = str_pad(decbin($dir['lastmod_time'][1]), 16, '0', STR_PAD_LEFT); 478 | $last_mod_year = bindec(substr($binary_mod_date, 0, 7)) + 1980; 479 | $last_mod_month = bindec(substr($binary_mod_date, 7, 4)); 480 | $last_mod_day = bindec(substr($binary_mod_date, 11, 5)); 481 | $last_mod_hour = bindec(substr($binary_mod_time, 0, 5)); 482 | $last_mod_minute = bindec(substr($binary_mod_time, 5, 6)); 483 | $last_mod_second = bindec(substr($binary_mod_time, 11, 5)); 484 | 485 | $this->central_dir_list[$dir['file_name']] = array( 486 | 'version_madeby' => $dir['version_madeby'][1], 487 | 'version_needed' => $dir['version_needed'][1], 488 | 'general_bit_flag' => str_pad(decbin($dir['general_bit_flag'][1]), 8, '0', STR_PAD_LEFT), 489 | 'compression_method' => $dir['compression_method'][1], 490 | 'lastmod_datetime' => mktime($last_mod_hour, $last_mod_minute, $last_mod_second, $last_mod_month, $last_mod_day, $last_mod_year), 491 | 'crc-32' => str_pad(dechex(ord($dir['crc-32'][3])), 2, '0', STR_PAD_LEFT) . 492 | str_pad(dechex(ord($dir['crc-32'][2])), 2, '0', STR_PAD_LEFT) . 493 | str_pad(dechex(ord($dir['crc-32'][1])), 2, '0', STR_PAD_LEFT) . 494 | str_pad(dechex(ord($dir['crc-32'][0])), 2, '0', STR_PAD_LEFT), 495 | 'compressed_size' => $dir['compressed_size'][1], 496 | 'uncompressed_size' => $dir['uncompressed_size'][1], 497 | 'disk_number_start' => $dir['disk_number_start'][1], 498 | 'internal_attributes' => $dir['internal_attributes'][1], 499 | 'external_attributes1' => $dir['external_attributes1'][1], 500 | 'external_attributes2' => $dir['external_attributes2'][1], 501 | 'relative_offset' => $dir['relative_offset'][1], 502 | 'file_name' => $dir['file_name'], 503 | 'extra_field' => $dir['extra_field'], 504 | 'file_comment' => $dir['file_comment'], 505 | ); 506 | 507 | $signature = fread($fh, 4); 508 | } 509 | 510 | // If loaded centralDirs, then try to identify the offsetPosition of the compressed data. 511 | if ($this->central_dir_list) 512 | { 513 | foreach ($this->central_dir_list as $filename => $details) 514 | { 515 | $i = $this->_get_file_header($fh, $details['relative_offset']); 516 | 517 | $this->compressed_list[$filename]['file_name'] = $filename; 518 | $this->compressed_list[$filename]['compression_method'] = $details['compression_method']; 519 | $this->compressed_list[$filename]['version_needed'] = $details['version_needed']; 520 | $this->compressed_list[$filename]['lastmod_datetime'] = $details['lastmod_datetime']; 521 | $this->compressed_list[$filename]['crc-32'] = $details['crc-32']; 522 | $this->compressed_list[$filename]['compressed_size'] = $details['compressed_size']; 523 | $this->compressed_list[$filename]['uncompressed_size'] = $details['uncompressed_size']; 524 | $this->compressed_list[$filename]['lastmod_datetime'] = $details['lastmod_datetime']; 525 | $this->compressed_list[$filename]['extra_field'] = $i['extra_field']; 526 | $this->compressed_list[$filename]['contents_start_offset'] = $i['contents_start_offset']; 527 | 528 | if (strtolower($stop_on_file) == strtolower($filename)) 529 | { 530 | break; 531 | } 532 | } 533 | } 534 | 535 | return TRUE; 536 | } 537 | } 538 | return FALSE; 539 | } 540 | 541 | private function _load_files_by_signatures(&$fh, $stop_on_file = FALSE) 542 | { 543 | fseek($fh, 0); 544 | 545 | $return = FALSE; 546 | for (;;) 547 | { 548 | $details = $this->_get_file_header($fh); 549 | 550 | if ( ! $details) 551 | { 552 | $this->set_debug('Invalid signature. Trying to verify if is old style Data Descriptor...'); 553 | fseek($fh, 12 - 4, SEEK_CUR); // 12: Data descriptor - 4: Signature (that will be read again) 554 | $details = $this->_get_file_header($fh); 555 | } 556 | 557 | if ( ! $details) 558 | { 559 | $this->set_debug('Still invalid signature. Probably reached the end of the file.'); 560 | break; 561 | } 562 | 563 | $filename = $details['file_name']; 564 | $this->compressed_list[$filename] = $details; 565 | $return = true; 566 | 567 | if (strtolower($stop_on_file) == strtolower($filename)) 568 | { 569 | break; 570 | } 571 | } 572 | 573 | return $return; 574 | } 575 | 576 | private function _get_file_header(&$fh, $start_offset = FALSE) 577 | { 578 | if ($start_offset !== FALSE) 579 | { 580 | fseek($fh, $start_offset); 581 | } 582 | 583 | $signature = fread($fh, 4); 584 | 585 | if ($signature == $this->zip_signature) 586 | { 587 | // Get information about the zipped file 588 | $file['version_needed'] = unpack("v", fread($fh, 2)); // version needed to extract 589 | $file['general_bit_flag'] = unpack("v", fread($fh, 2)); // general purpose bit flag 590 | $file['compression_method'] = unpack("v", fread($fh, 2)); // compression method 591 | $file['lastmod_time'] = unpack("v", fread($fh, 2)); // last mod file time 592 | $file['lastmod_date'] = unpack("v", fread($fh, 2)); // last mod file date 593 | $file['crc-32'] = fread($fh, 4); // crc-32 594 | $file['compressed_size'] = unpack("V", fread($fh, 4)); // compressed size 595 | $file['uncompressed_size'] = unpack("V", fread($fh, 4)); // uncompressed size 596 | $zip_file_length = unpack("v", fread($fh, 2)); // filename length 597 | $extra_field_length = unpack("v", fread($fh, 2)); // extra field length 598 | $file['file_name'] = fread($fh, $zip_file_length[1]); // filename 599 | $file['extra_field'] = $extra_field_length[1] ? fread($fh, $extra_field_length[1]) : ''; // extra field 600 | $file['contents_start_offset'] = ftell($fh); 601 | 602 | // Bypass the whole compressed contents, and look for the next file 603 | fseek($fh, $file['compressed_size'][1], SEEK_CUR); 604 | 605 | // Convert the date and time, from MS-DOS format to UNIX Timestamp 606 | $binary_mod_date = str_pad(decbin($file['lastmod_date'][1]), 16, '0', STR_PAD_LEFT); 607 | $binary_mod_time = str_pad(decbin($file['lastmod_time'][1]), 16, '0', STR_PAD_LEFT); 608 | 609 | $last_mod_year = bindec(substr($binary_mod_date, 0, 7)) + 1980; 610 | $last_mod_month = bindec(substr($binary_mod_date, 7, 4)); 611 | $last_mod_day = bindec(substr($binary_mod_date, 11, 5)); 612 | $last_mod_hour = bindec(substr($binary_mod_time, 0, 5)); 613 | $last_mod_minute = bindec(substr($binary_mod_time, 5, 6)); 614 | $last_mod_second = bindec(substr($binary_mod_time, 11, 5)); 615 | 616 | // Mount file table 617 | $i = array( 618 | 'file_name' => $file['file_name'], 619 | 'compression_method' => $file['compression_method'][1], 620 | 'version_needed' => $file['version_needed'][1], 621 | 'lastmod_datetime' => mktime($last_mod_hour, $last_mod_minute, $last_mod_second, $last_mod_month, $last_mod_day, $last_mod_year), 622 | 'crc-32' => str_pad(dechex(ord($file['crc-32'][3])), 2, '0', STR_PAD_LEFT) . 623 | str_pad(dechex(ord($file['crc-32'][2])), 2, '0', STR_PAD_LEFT) . 624 | str_pad(dechex(ord($file['crc-32'][1])), 2, '0', STR_PAD_LEFT) . 625 | str_pad(dechex(ord($file['crc-32'][0])), 2, '0', STR_PAD_LEFT), 626 | 'compressed_size' => $file['compressed_size'][1], 627 | 'uncompressed_size' => $file['uncompressed_size'][1], 628 | 'extra_field' => $file['extra_field'], 629 | 'general_bit_flag' => str_pad(decbin($file['general_bit_flag'][1]), 8, '0', STR_PAD_LEFT), 630 | 'contents_start_offset' => $file['contents_start_offset'] 631 | ); 632 | 633 | return $i; 634 | } 635 | 636 | return FALSE; 637 | } 638 | } 639 | 640 | /* End of file Unzip.php */ 641 | /* Location: ./system/libraries/Unzip.php */ --------------------------------------------------------------------------------