2c03cb92da60bdd78be0a93e7dd427d7f45ae5de
[Packages/TYPO3.CMS.git] / typo3 / mod / tools / em / class.em_unzip.php
1 <?php
2 /***************************************************************
3 * Copyright notice
4 *
5 * (c) Vincent Blavet <vincent@phpconcept.net>
6 * (c) 2005-2006 Karsten Dambekalns <karsten@typo3.org>
7 * All rights reserved
8 *
9 * This library is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU Lesser General Public
11 * License as published by the Free Software Foundation; either
12 * version 2.1 of the License, or (at your option) any later version.
13 *
14 * This library is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 * Lesser General Public License for more details.
18 *
19 * You should have received a copy of the GNU Lesser General Public
20 * License along with this library; if not, write to the Free Software
21 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
22 * MA 02110-1301 USA
23 *
24 * This copyright notice MUST APPEAR in all copies of the script!
25 ***************************************************************/
26 /**
27 * Module: Extension manager
28 *
29 * $Id$
30 *
31 * @author Vincent Blavet <vincent@phpconcept.net>
32 * @author Karsten Dambekalns <karsten@typo3.org>
33 */
34
35
36 // Constants
37 define( 'ARCHIVE_ZIP_READ_BLOCK_SIZE', 2048 );
38
39 // File list separator
40 define( 'ARCHIVE_ZIP_SEPARATOR', ',' );
41
42 define( 'ARCHIVE_ZIP_TEMPORARY_DIR', '' );
43
44 // Error codes
45 define( 'ARCHIVE_ZIP_ERR_NO_ERROR', 0 );
46 define( 'ARCHIVE_ZIP_ERR_WRITE_OPEN_FAIL', -1 );
47 define( 'ARCHIVE_ZIP_ERR_READ_OPEN_FAIL', -2 );
48 define( 'ARCHIVE_ZIP_ERR_INVALID_PARAMETER', -3 );
49 define( 'ARCHIVE_ZIP_ERR_MISSING_FILE', -4 );
50 define( 'ARCHIVE_ZIP_ERR_FILENAME_TOO_LONG', -5 );
51 define( 'ARCHIVE_ZIP_ERR_INVALID_ZIP', -6 );
52 define( 'ARCHIVE_ZIP_ERR_BAD_EXTRACTED_FILE', -7 );
53 define( 'ARCHIVE_ZIP_ERR_DIR_CREATE_FAIL', -8 );
54 define( 'ARCHIVE_ZIP_ERR_BAD_EXTENSION', -9 );
55 define( 'ARCHIVE_ZIP_ERR_BAD_FORMAT', -10 );
56 define( 'ARCHIVE_ZIP_ERR_DELETE_FILE_FAIL', -11 );
57 define( 'ARCHIVE_ZIP_ERR_RENAME_FILE_FAIL', -12 );
58 define( 'ARCHIVE_ZIP_ERR_BAD_CHECKSUM', -13 );
59 define( 'ARCHIVE_ZIP_ERR_INVALID_ARCHIVE_ZIP', -14 );
60 define( 'ARCHIVE_ZIP_ERR_MISSING_OPTION_VALUE', -15 );
61 define( 'ARCHIVE_ZIP_ERR_INVALID_PARAM_VALUE', -16 );
62
63 // Warning codes
64 define( 'ARCHIVE_ZIP_WARN_NO_WARNING', 0 );
65 define( 'ARCHIVE_ZIP_WARN_FILE_EXIST', 1 );
66
67 // Methods parameters
68 define( 'ARCHIVE_ZIP_PARAM_PATH', 'path' );
69 define( 'ARCHIVE_ZIP_PARAM_ADD_PATH', 'add_path' );
70 define( 'ARCHIVE_ZIP_PARAM_REMOVE_PATH', 'remove_path' );
71 define( 'ARCHIVE_ZIP_PARAM_REMOVE_ALL_PATH', 'remove_all_path' );
72 define( 'ARCHIVE_ZIP_PARAM_SET_CHMOD', 'set_chmod' );
73 define( 'ARCHIVE_ZIP_PARAM_EXTRACT_AS_STRING', 'extract_as_string' );
74 define( 'ARCHIVE_ZIP_PARAM_NO_COMPRESSION', 'no_compression' );
75
76 define( 'ARCHIVE_ZIP_PARAM_PRE_EXTRACT', 'callback_pre_extract' );
77 define( 'ARCHIVE_ZIP_PARAM_POST_EXTRACT', 'callback_post_extract' );
78 define( 'ARCHIVE_ZIP_PARAM_PRE_ADD', 'callback_pre_add' );
79 define( 'ARCHIVE_ZIP_PARAM_POST_ADD', 'callback_post_add' );
80
81
82
83 /**
84 * Class for unpacking zip archive files
85 *
86 * @author Vincent Blavet <vincent@blavet.net>
87 * @author Karsten Dambekalns <karsten@typo3.org>
88 */
89 class em_unzip {
90 /**
91 * The filename of the zip archive.
92 *
93 * @var string Name of the Zip file
94 */
95 var $_zipname='';
96
97 /**
98 * File descriptor of the opened Zip file.
99 *
100 * @var int Internal zip file descriptor
101 */
102 var $_zip_fd=0;
103
104 /**
105 * @var int last error code
106 */
107 var $_error_code=1;
108
109 /**
110 * @var string Last error description
111 */
112 var $_error_string='';
113
114 /**
115 * em_unzip Class constructor. This flavour of the constructor only
116 * declare a new em_unzip object, identifying it by the name of the
117 * zip file.
118 *
119 * @param string $p_zipname The name of the zip archive to create
120 * @access public
121 */
122 function em_unzip($p_zipname) {
123
124 // Check the zlib
125 if (!extension_loaded('zlib')) {
126 die("The extension 'zlib' couldn't be found.\n".
127 "Please make sure your version of PHP was built ".
128 "with 'zlib' support.\n");
129 }
130
131 // Set the attributes
132 $this->_zipname = $p_zipname;
133 $this->_zip_fd = 0;
134
135 return;
136 }
137
138
139
140 /**
141 * This method extract the files and folders which are in the zip archive.
142 * It can extract all the archive or a part of the archive by using filter
143 * feature (extract by name, by index, by ereg, by preg). The extraction
144 * can occur in the current path or an other path.
145 * All the advanced features are activated by the use of variable
146 * parameters.
147 * The return value is an array of entry descriptions which gives
148 * information on extracted files (See listContent()).
149 * The method may return a success value (an array) even if some files
150 * are not correctly extracted (see the file status in listContent()).
151 * The supported variable parameters for this method are :
152 * 'add_path' : Path where the files and directories are to be extracted
153 *
154 * @access public
155 * @param mixed $p_params An array of variable parameters and values.
156 * @return mixed An array of file description on success,
157 * 0 on an unrecoverable failure, an error code is logged.
158 */
159 function extract($p_params=0) {
160 $this->_errorReset();
161
162 // Check archive
163 if (!$this->_checkFormat()) {
164 return(0);
165 }
166
167 // Set default values
168 if ($p_params === 0) {
169 $p_params = array();
170 }
171 if ($this->_check_parameters($p_params,
172 array ('extract_as_string' => false,
173 'add_path' => '',
174 'remove_path' => '',
175 'remove_all_path' => false,
176 'callback_pre_extract' => '',
177 'callback_post_extract' => '',
178 'set_chmod' => 0) ) != 1) {
179 return 0;
180 }
181
182 // Call the extracting fct
183 $v_list = array();
184 if ($this->_extractByRule($v_list, $p_params) != 1) {
185 unset($v_list);
186 return(0);
187 }
188
189 return $v_list;
190 }
191
192 /**
193 * Method that gives the lastest error code.
194 *
195 * @access public
196 * @return integer The error code value.
197 */
198 function errorCode() {
199 return($this->_error_code);
200 }
201
202 /**
203 * This method gives the latest error code name.
204 *
205 * @access public
206 * @param boolean $p_with_code If true, gives the name and the int value.
207 * @return string The error name.
208 */
209 function errorName($p_with_code=false) {
210 $v_const_list = get_defined_constants();
211
212 // Extract error constants from all const.
213 for (reset($v_const_list);
214 list($v_key, $v_value) = each($v_const_list);) {
215 if (substr($v_key, 0, strlen('ARCHIVE_ZIP_ERR_')) =='ARCHIVE_ZIP_ERR_') {
216 $v_error_list[$v_key] = $v_value;
217 }
218 }
219
220 // Search the name form the code value
221 $v_key=array_search($this->_error_code, $v_error_list, true);
222 if ($v_key!=false) {
223 $v_value = $v_key;
224 } else {
225 $v_value = 'NoName';
226 }
227
228 if ($p_with_code) {
229 return($v_value.' ('.$this->_error_code.')');
230 } else {
231 return($v_value);
232 }
233 }
234
235 /**
236 * This method returns the description associated with the latest error.
237 *
238 * @access public
239 * @param boolean $p_full If set to true gives the description with the
240 * error code, the name and the description.
241 * If set to false gives only the description
242 * and the error code.
243 * @return string The error description.
244 */
245 function errorInfo($p_full=false) {
246 if ($p_full) {
247 return($this->errorName(true)." : ".$this->_error_string);
248 } else {
249 return($this->_error_string." [code ".$this->_error_code."]");
250 }
251 }
252
253
254 /**
255 * em_unzip::_checkFormat()
256 *
257 * { Description }
258 *
259 * @param integer $p_level
260 */
261 function _checkFormat($p_level=0) {
262 $v_result = true;
263
264 // Reset the error handler
265 $this->_errorReset();
266
267 // Look if the file exits
268 if (!is_file($this->_zipname)) {
269 // Error log
270 $this->_errorLog(ARCHIVE_ZIP_ERR_MISSING_FILE,
271 "Missing archive file '".$this->_zipname."'");
272 return(false);
273 }
274
275 // Check that the file is readeable
276 if (!is_readable($this->_zipname)) {
277 // Error log
278 $this->_errorLog(ARCHIVE_ZIP_ERR_READ_OPEN_FAIL,
279 "Unable to read archive '".$this->_zipname."'");
280 return(false);
281 }
282
283 // Check the magic code
284 // TBC
285
286 // Check the central header
287 // TBC
288
289 // Check each file header
290 // TBC
291
292 // Return
293 return $v_result;
294 }
295
296
297 /**
298 * em_unzip::_openFd()
299 *
300 * { Description }
301 *
302 */
303 function _openFd($p_mode) {
304 $v_result=1;
305
306 // Look if already open
307 if ($this->_zip_fd != 0) {
308 $this->_errorLog(ARCHIVE_ZIP_ERR_READ_OPEN_FAIL,
309 'Zip file \''.$this->_zipname.'\' already open');
310 return em_unzip::errorCode();
311 }
312
313 // Open the zip file
314 if (($this->_zip_fd = @fopen($this->_zipname, $p_mode)) == 0) {
315 $this->_errorLog(ARCHIVE_ZIP_ERR_READ_OPEN_FAIL,
316 'Unable to open archive \''.$this->_zipname
317 .'\' in '.$p_mode.' mode');
318 return em_unzip::errorCode();
319 }
320
321 // Return
322 return $v_result;
323 }
324
325 /**
326 * em_unzip::_closeFd()
327 *
328 * { Description }
329 *
330 */
331 function _closeFd() {
332 $v_result=1;
333
334 if ($this->_zip_fd != 0)
335 @fclose($this->_zip_fd);
336 $this->_zip_fd = 0;
337
338 // Return
339 return $v_result;
340 }
341
342
343
344 /**
345 * em_unzip::_convertHeader2FileInfo()
346 *
347 * { Description }
348 *
349 */
350 function _convertHeader2FileInfo($p_header, &$p_info) {
351 $v_result=1;
352
353 // Get the interesting attributes
354 $p_info['filename'] = $p_header['filename'];
355 $p_info['stored_filename'] = $p_header['stored_filename'];
356 $p_info['size'] = $p_header['size'];
357 $p_info['compressed_size'] = $p_header['compressed_size'];
358 $p_info['mtime'] = $p_header['mtime'];
359 $p_info['comment'] = $p_header['comment'];
360 $p_info['folder'] = (($p_header['external']&0x00000010)==0x00000010);
361 $p_info['index'] = $p_header['index'];
362 $p_info['status'] = $p_header['status'];
363
364 // Return
365 return $v_result;
366 }
367
368
369 // Function : _extractByRule()
370 // Description :
371 // Extract a file or directory depending of rules (by index, by name, ...)
372 // Parameters :
373 // $p_file_list : An array where will be placed the properties of each
374 // extracted file
375 // $p_path : Path to add while writing the extracted files
376 // $p_remove_path : Path to remove (from the file memorized path) while writing the
377 // extracted files. If the path does not match the file path,
378 // the file is extracted with its memorized path.
379 // $p_remove_path does not apply to 'list' mode.
380 // $p_path and $p_remove_path are commulative.
381 // Return Values :
382 // 1 on success,0 or less on error (see error code list)
383
384 /**
385 * em_unzip::_extractByRule()
386 *
387 * { Description }
388 *
389 */
390 function _extractByRule(&$p_file_list, &$p_params)
391 {
392 $v_result=1;
393
394 $p_path = $p_params['add_path'];
395 $p_remove_path = $p_params['remove_path'];
396 $p_remove_all_path = $p_params['remove_all_path'];
397
398 // Check the path
399 if (($p_path == "")
400 || ((substr($p_path, 0, 1) != "/")
401 && (substr($p_path, 0, 3) != "../") && (substr($p_path,1,2)!=":/")))
402 $p_path = "./".$p_path;
403
404 // Reduce the path last (and duplicated) '/'
405 if (($p_path != "./") && ($p_path != "/")) {
406 // Look for the path end '/'
407 while (substr($p_path, -1) == "/") {
408 $p_path = substr($p_path, 0, strlen($p_path)-1);
409 }
410 }
411
412 // Open the zip file
413 if (($v_result = $this->_openFd('rb')) != 1)
414 {
415 return $v_result;
416 }
417
418 // Read the central directory informations
419 $v_central_dir = array();
420 if (($v_result = $this->_readEndCentralDir($v_central_dir)) != 1)
421 {
422 // Close the zip file
423 $this->_closeFd();
424
425 return $v_result;
426 }
427
428 // Start at beginning of Central Dir
429 $v_pos_entry = $v_central_dir['offset'];
430
431 // Read each entry
432 $j_start = 0;
433 for ($i=0, $v_nb_extracted=0; $i<$v_central_dir['entries']; $i++) {
434 // Read next Central dir entry
435 @rewind($this->_zip_fd);
436 if (@fseek($this->_zip_fd, $v_pos_entry)) {
437 $this->_closeFd();
438
439 $this->_errorLog(ARCHIVE_ZIP_ERR_INVALID_ARCHIVE_ZIP,
440 'Invalid archive size');
441
442 return em_unzip::errorCode();
443 }
444
445 // Read the file header
446 $v_header = array();
447 if (($v_result = $this->_readCentralFileHeader($v_header)) != 1) {
448 $this->_closeFd();
449
450 return $v_result;
451 }
452
453 // Store the index
454 $v_header['index'] = $i;
455
456 // Store the file position
457 $v_pos_entry = ftell($this->_zip_fd);
458
459
460 // Go to the file position
461 @rewind($this->_zip_fd);
462 if (@fseek($this->_zip_fd, $v_header['offset']))
463 {
464 // Close the zip file
465 $this->_closeFd();
466
467 // Error log
468 $this->_errorLog(ARCHIVE_ZIP_ERR_INVALID_ARCHIVE_ZIP, 'Invalid archive size');
469
470 // Return
471 return em_unzip::errorCode();
472 }
473
474 // Extracting the file
475 if (($v_result = $this->_extractFile($v_header, $p_path, $p_remove_path, $p_remove_all_path, $p_params)) != 1)
476 {
477 // Close the zip file
478 $this->_closeFd();
479
480 return $v_result;
481 }
482
483 // Get the only interesting attributes
484 if (($v_result = $this->_convertHeader2FileInfo($v_header, $p_file_list[$v_nb_extracted++])) != 1)
485 {
486 // Close the zip file
487 $this->_closeFd();
488
489 return $v_result;
490 }
491 }
492
493 // Close the zip file
494 $this->_closeFd();
495
496 // Return
497 return $v_result;
498 }
499
500 /**
501 * em_unzip::_extractFile()
502 *
503 * { Description }
504 *
505 */
506 function _extractFile(&$p_entry, $p_path, $p_remove_path, $p_remove_all_path, &$p_params) {
507 $v_result=1;
508
509 // Read the file header
510 if (($v_result = $this->_readFileHeader($v_header)) != 1)
511 {
512 // Return
513 return $v_result;
514 }
515
516
517 // Check that the file header is coherent with $p_entry info
518 // TBC
519
520 // Look for all path to remove
521 if ($p_remove_all_path == true) {
522 // Get the basename of the path
523 $p_entry['filename'] = basename($p_entry['filename']);
524 }
525
526 // Look for path to remove
527 else if ($p_remove_path != "")
528 {
529 //if (strcmp($p_remove_path, $p_entry['filename'])==0)
530 if ($this->_tool_PathInclusion($p_remove_path, $p_entry['filename']) == 2)
531 {
532
533 // Change the file status
534 $p_entry['status'] = "filtered";
535
536 // Return
537 return $v_result;
538 }
539
540 $p_remove_path_size = strlen($p_remove_path);
541 if (substr($p_entry['filename'], 0, $p_remove_path_size) == $p_remove_path)
542 {
543
544 // Remove the path
545 $p_entry['filename'] = substr($p_entry['filename'], $p_remove_path_size);
546
547 }
548 }
549
550 // Add the path
551 if ($p_path != '')
552 {
553 $p_entry['filename'] = $p_path."/".$p_entry['filename'];
554 }
555
556 // Look for pre-extract callback
557 if ( (isset($p_params[ARCHIVE_ZIP_PARAM_PRE_EXTRACT]))
558 && ($p_params[ARCHIVE_ZIP_PARAM_PRE_EXTRACT] != '')) {
559
560 // Generate a local information
561 $v_local_header = array();
562 $this->_convertHeader2FileInfo($p_entry, $v_local_header);
563
564 // Call the callback
565 // Here I do not use call_user_func() because I need to send a reference to the
566 // header.
567 eval('$v_result = '.$p_params[ARCHIVE_ZIP_PARAM_PRE_EXTRACT].'(ARCHIVE_ZIP_PARAM_PRE_EXTRACT, $v_local_header);');
568 if ($v_result == 0) {
569 // Change the file status
570 $p_entry['status'] = "skipped";
571 $v_result = 1;
572 }
573
574 // Update the informations
575 // Only some fields can be modified
576 $p_entry['filename'] = $v_local_header['filename'];
577 }
578
579 // Trace
580
581 // Look if extraction should be done
582 if ($p_entry['status'] == 'ok') {
583
584 // Look for specific actions while the file exist
585 if (file_exists($p_entry['filename'])) {
586 // Look if file is a directory
587 if (is_dir($p_entry['filename'])) {
588 // Change the file status
589 $p_entry['status'] = "already_a_directory";
590
591 // Return
592 //return $v_result;
593 }
594 // Look if file is write protected
595 else if (!is_writeable($p_entry['filename'])) {
596 // Change the file status
597 $p_entry['status'] = "write_protected";
598
599 // Return
600 //return $v_result;
601 }
602
603 // Look if the extracted file is older
604 else if (filemtime($p_entry['filename']) > $p_entry['mtime']) {
605 // Change the file status
606 $p_entry['status'] = "newer_exist";
607
608 // Return
609 //return $v_result;
610 }
611 }
612
613 // Check the directory availability and create it if necessary
614 else {
615 if ((($p_entry['external']&0x00000010)==0x00000010) || (substr($p_entry['filename'], -1) == '/'))
616 $v_dir_to_check = $p_entry['filename'];
617 else if (!strstr($p_entry['filename'], "/"))
618 $v_dir_to_check = "";
619 else
620 $v_dir_to_check = dirname($p_entry['filename']);
621
622 if (($v_result = $this->_dirCheck($v_dir_to_check, (($p_entry['external']&0x00000010)==0x00000010))) != 1) {
623 // Change the file status
624 $p_entry['status'] = "path_creation_fail";
625
626 // Return
627 //return $v_result;
628 $v_result = 1;
629 }
630 }
631 }
632
633 // Look if extraction should be done
634 if ($p_entry['status'] == 'ok') {
635 // Do the extraction (if not a folder)
636 if (!(($p_entry['external']&0x00000010)==0x00000010)) {
637 // Look for not compressed file
638 if ($p_entry['compressed_size'] == $p_entry['size']) {
639 // Opening destination file
640 if (($v_dest_file = @fopen($p_entry['filename'], 'wb')) == 0) {
641 // Change the file status
642 $p_entry['status'] = "write_error";
643
644 // Return
645 return $v_result;
646 }
647
648
649 // Read the file by ARCHIVE_ZIP_READ_BLOCK_SIZE octets blocks
650 $v_size = $p_entry['compressed_size'];
651 while ($v_size != 0) {
652 $v_read_size = ($v_size < ARCHIVE_ZIP_READ_BLOCK_SIZE ? $v_size : ARCHIVE_ZIP_READ_BLOCK_SIZE);
653 $v_buffer = fread($this->_zip_fd, $v_read_size);
654 $v_binary_data = pack('a'.$v_read_size, $v_buffer);
655 @fwrite($v_dest_file, $v_binary_data, $v_read_size);
656 $v_size -= $v_read_size;
657 }
658
659 // Closing the destination file
660 fclose($v_dest_file);
661
662 // Change the file mtime
663 touch($p_entry['filename'], $p_entry['mtime']);
664 } else {
665 // Trace
666
667 // Opening destination file
668 if (($v_dest_file = @fopen($p_entry['filename'], 'wb')) == 0) {
669
670 // Change the file status
671 $p_entry['status'] = "write_error";
672
673 return $v_result;
674 }
675
676
677 // Read the compressed file in a buffer (one shot)
678 $v_buffer = @fread($this->_zip_fd, $p_entry['compressed_size']);
679
680 // Decompress the file
681 $v_file_content = gzinflate($v_buffer);
682 unset($v_buffer);
683
684 // Write the uncompressed data
685 @fwrite($v_dest_file, $v_file_content, $p_entry['size']);
686 unset($v_file_content);
687
688 // Closing the destination file
689 @fclose($v_dest_file);
690
691 // Change the file mtime
692 @touch($p_entry['filename'], $p_entry['mtime']);
693 }
694
695 // Look for chmod option
696 if ( (isset($p_params[ARCHIVE_ZIP_PARAM_SET_CHMOD]))
697 && ($p_params[ARCHIVE_ZIP_PARAM_SET_CHMOD] != 0)) {
698
699 // Change the mode of the file
700 chmod($p_entry['filename'], $p_params[ARCHIVE_ZIP_PARAM_SET_CHMOD]);
701 }
702
703 }
704 }
705
706 // Look for post-extract callback
707 if ( (isset($p_params[ARCHIVE_ZIP_PARAM_POST_EXTRACT]))
708 && ($p_params[ARCHIVE_ZIP_PARAM_POST_EXTRACT] != '')) {
709
710 // Generate a local information
711 $v_local_header = array();
712 $this->_convertHeader2FileInfo($p_entry, $v_local_header);
713
714 // Call the callback
715 // Here I do not use call_user_func() because I need to send a reference to the
716 // header.
717 eval('$v_result = '.$p_params[ARCHIVE_ZIP_PARAM_POST_EXTRACT].'(ARCHIVE_ZIP_PARAM_POST_EXTRACT, $v_local_header);');
718 }
719
720 // Return
721 return $v_result;
722 }
723
724 /**
725 * em_unzip::_readFileHeader()
726 *
727 * { Description }
728 *
729 */
730 function _readFileHeader(&$p_header) {
731 $v_result=1;
732
733 // Read the 4 bytes signature
734 $v_binary_data = @fread($this->_zip_fd, 4);
735 $v_data = unpack('Vid', $v_binary_data);
736
737 // Check signature
738 if ($v_data['id'] != 0x04034b50) {
739
740 // Error log
741 $this->_errorLog(ARCHIVE_ZIP_ERR_BAD_FORMAT, 'Invalid archive structure');
742
743 // Return
744 return em_unzip::errorCode();
745 }
746
747 // Read the first 42 bytes of the header
748 $v_binary_data = fread($this->_zip_fd, 26);
749
750 // Look for invalid block size
751 if (strlen($v_binary_data) != 26) {
752 $p_header['filename'] = "";
753 $p_header['status'] = "invalid_header";
754
755 // Error log
756 $this->_errorLog(ARCHIVE_ZIP_ERR_BAD_FORMAT, "Invalid block size : ".strlen($v_binary_data));
757
758 // Return
759 return em_unzip::errorCode();
760 }
761
762 // Extract the values
763 $v_data = unpack('vversion/vflag/vcompression/vmtime/vmdate/Vcrc/Vcompressed_size/Vsize/vfilename_len/vextra_len', $v_binary_data);
764
765 // Get filename
766 $p_header['filename'] = fread($this->_zip_fd, $v_data['filename_len']);
767
768 // Get extra_fields
769 if ($v_data['extra_len'] != 0) {
770 $p_header['extra'] = fread($this->_zip_fd, $v_data['extra_len']);
771 }
772 else {
773 $p_header['extra'] = '';
774 }
775
776 // Extract properties
777 $p_header['compression'] = $v_data['compression'];
778 $p_header['size'] = $v_data['size'];
779 $p_header['compressed_size'] = $v_data['compressed_size'];
780 $p_header['crc'] = $v_data['crc'];
781 $p_header['flag'] = $v_data['flag'];
782
783 // Recuperate date in UNIX format
784 $p_header['mdate'] = $v_data['mdate'];
785 $p_header['mtime'] = $v_data['mtime'];
786 if ($p_header['mdate'] && $p_header['mtime']) {
787 // Extract time
788 $v_hour = ($p_header['mtime'] & 0xF800) >> 11;
789 $v_minute = ($p_header['mtime'] & 0x07E0) >> 5;
790 $v_seconde = ($p_header['mtime'] & 0x001F)*2;
791
792 // Extract date
793 $v_year = (($p_header['mdate'] & 0xFE00) >> 9) + 1980;
794 $v_month = ($p_header['mdate'] & 0x01E0) >> 5;
795 $v_day = $p_header['mdate'] & 0x001F;
796
797 // Get UNIX date format
798 $p_header['mtime'] = mktime($v_hour, $v_minute, $v_seconde, $v_month, $v_day, $v_year);
799
800 } else {
801 $p_header['mtime'] = time();
802 }
803
804 // Other informations
805
806 // TBC
807 //for(reset($v_data); $key = key($v_data); next($v_data)) {
808 //}
809
810 // Set the stored filename
811 $p_header['stored_filename'] = $p_header['filename'];
812
813 // Set the status field
814 $p_header['status'] = "ok";
815
816 // Return
817 return $v_result;
818 }
819
820 /**
821 * em_unzip::_readCentralFileHeader()
822 *
823 * { Description }
824 *
825 */
826 function _readCentralFileHeader(&$p_header) {
827 $v_result=1;
828
829 // Read the 4 bytes signature
830 $v_binary_data = @fread($this->_zip_fd, 4);
831 $v_data = unpack('Vid', $v_binary_data);
832
833 // Check signature
834 if ($v_data['id'] != 0x02014b50) {
835
836 // Error log
837 $this->_errorLog(ARCHIVE_ZIP_ERR_BAD_FORMAT, 'Invalid archive structure');
838
839 // Return
840 return em_unzip::errorCode();
841 }
842
843 // Read the first 42 bytes of the header
844 $v_binary_data = fread($this->_zip_fd, 42);
845
846 // Look for invalid block size
847 if (strlen($v_binary_data) != 42) {
848 $p_header['filename'] = "";
849 $p_header['status'] = "invalid_header";
850
851 // Error log
852 $this->_errorLog(ARCHIVE_ZIP_ERR_BAD_FORMAT, "Invalid block size : ".strlen($v_binary_data));
853
854 // Return
855 return em_unzip::errorCode();
856 }
857
858 // Extract the values
859 $p_header = unpack('vversion/vversion_extracted/vflag/vcompression/vmtime/vmdate/Vcrc/Vcompressed_size/Vsize/vfilename_len/vextra_len/vcomment_len/vdisk/vinternal/Vexternal/Voffset', $v_binary_data);
860
861 // Get filename
862 if ($p_header['filename_len'] != 0)
863 $p_header['filename'] = fread($this->_zip_fd, $p_header['filename_len']);
864 else
865 $p_header['filename'] = '';
866
867 // Get extra
868 if ($p_header['extra_len'] != 0)
869 $p_header['extra'] = fread($this->_zip_fd, $p_header['extra_len']);
870 else
871 $p_header['extra'] = '';
872
873 // Get comment
874 if ($p_header['comment_len'] != 0)
875 $p_header['comment'] = fread($this->_zip_fd, $p_header['comment_len']);
876 else
877 $p_header['comment'] = '';
878
879 // Extract properties
880
881 // Recuperate date in UNIX format
882 if ($p_header['mdate'] && $p_header['mtime']) {
883 // Extract time
884 $v_hour = ($p_header['mtime'] & 0xF800) >> 11;
885 $v_minute = ($p_header['mtime'] & 0x07E0) >> 5;
886 $v_seconde = ($p_header['mtime'] & 0x001F)*2;
887
888 // Extract date
889 $v_year = (($p_header['mdate'] & 0xFE00) >> 9) + 1980;
890 $v_month = ($p_header['mdate'] & 0x01E0) >> 5;
891 $v_day = $p_header['mdate'] & 0x001F;
892
893 // Get UNIX date format
894 $p_header['mtime'] = mktime($v_hour, $v_minute, $v_seconde, $v_month, $v_day, $v_year);
895
896 } else {
897 $p_header['mtime'] = time();
898 }
899
900 // Set the stored filename
901 $p_header['stored_filename'] = $p_header['filename'];
902
903 // Set default status to ok
904 $p_header['status'] = 'ok';
905
906 // Look if it is a directory
907 if (substr($p_header['filename'], -1) == '/') {
908 $p_header['external'] = 0x41FF0010;
909 }
910
911
912 // Return
913 return $v_result;
914 }
915
916 /**
917 * em_unzip::_readEndCentralDir()
918 *
919 * { Description }
920 *
921 */
922 function _readEndCentralDir(&$p_central_dir) {
923 $v_result=1;
924
925 // Go to the end of the zip file
926 $v_size = filesize($this->_zipname);
927 @fseek($this->_zip_fd, $v_size);
928 if (@ftell($this->_zip_fd) != $v_size) {
929 $this->_errorLog(ARCHIVE_ZIP_ERR_BAD_FORMAT,
930 'Unable to go to the end of the archive \''
931 .$this->_zipname.'\'');
932 return em_unzip::errorCode();
933 }
934
935 // First try : look if this is an archive with no commentaries
936 // (most of the time)
937 // in this case the end of central dir is at 22 bytes of the file end
938 $v_found = 0;
939 if ($v_size > 26) {
940 @fseek($this->_zip_fd, $v_size-22);
941 if (($v_pos = @ftell($this->_zip_fd)) != ($v_size-22)) {
942 $this->_errorLog(ARCHIVE_ZIP_ERR_BAD_FORMAT,
943 'Unable to seek back to the middle of the archive \''
944 .$this->_zipname.'\'');
945 return em_unzip::errorCode();
946 }
947
948 // Read for bytes
949 $v_binary_data = @fread($this->_zip_fd, 4);
950 $v_data = unpack('Vid', $v_binary_data);
951
952 // Check signature
953 if ($v_data['id'] == 0x06054b50) {
954 $v_found = 1;
955 }
956
957 $v_pos = ftell($this->_zip_fd);
958 }
959
960 // Go back to the maximum possible size of the Central Dir End Record
961 if (!$v_found) {
962 $v_maximum_size = 65557; // 0xFFFF + 22;
963 if ($v_maximum_size > $v_size)
964 $v_maximum_size = $v_size;
965 @fseek($this->_zip_fd, $v_size-$v_maximum_size);
966 if (@ftell($this->_zip_fd) != ($v_size-$v_maximum_size)) {
967 $this->_errorLog(ARCHIVE_ZIP_ERR_BAD_FORMAT,
968 'Unable to seek back to the middle of the archive \''
969 .$this->_zipname.'\'');
970 return em_unzip::errorCode();
971 }
972
973 // Read byte per byte in order to find the signature
974 $v_pos = ftell($this->_zip_fd);
975 $v_bytes = 0x00000000;
976 while ($v_pos < $v_size) {
977 // Read a byte
978 $v_byte = @fread($this->_zip_fd, 1);
979
980 // Add the byte
981 $v_bytes = ($v_bytes << 8) | Ord($v_byte);
982
983 // Compare the bytes
984 if ($v_bytes == 0x504b0506) {
985 $v_pos++;
986 break;
987 }
988
989 $v_pos++;
990 }
991
992 // Look if not found end of central dir
993 if ($v_pos == $v_size) {
994 $this->_errorLog(ARCHIVE_ZIP_ERR_BAD_FORMAT,
995 "Unable to find End of Central Dir Record signature");
996 return em_unzip::errorCode();
997 }
998 }
999
1000 // Read the first 18 bytes of the header
1001 $v_binary_data = fread($this->_zip_fd, 18);
1002
1003 // Look for invalid block size
1004 if (strlen($v_binary_data) != 18) {
1005 $this->_errorLog(ARCHIVE_ZIP_ERR_BAD_FORMAT,
1006 "Invalid End of Central Dir Record size : "
1007 .strlen($v_binary_data));
1008 return em_unzip::errorCode();
1009 }
1010
1011 // Extract the values
1012 $v_data = unpack('vdisk/vdisk_start/vdisk_entries/ventries/Vsize/Voffset/vcomment_size', $v_binary_data);
1013
1014 // Check the global size
1015 if (($v_pos + $v_data['comment_size'] + 18) != $v_size) {
1016 $this->_errorLog(ARCHIVE_ZIP_ERR_BAD_FORMAT,
1017 "Fail to find the right signature");
1018 return em_unzip::errorCode();
1019 }
1020
1021 // Get comment
1022 if ($v_data['comment_size'] != 0)
1023 $p_central_dir['comment'] = fread($this->_zip_fd, $v_data['comment_size']);
1024 else
1025 $p_central_dir['comment'] = '';
1026
1027 $p_central_dir['entries'] = $v_data['entries'];
1028 $p_central_dir['disk_entries'] = $v_data['disk_entries'];
1029 $p_central_dir['offset'] = $v_data['offset'];
1030 $p_central_dir['size'] = $v_data['size'];
1031 $p_central_dir['disk'] = $v_data['disk'];
1032 $p_central_dir['disk_start'] = $v_data['disk_start'];
1033
1034 // Return
1035 return $v_result;
1036 }
1037
1038 /**
1039 * em_unzip::_dirCheck()
1040 *
1041 * { Description }
1042 *
1043 * @param [type] $p_is_dir
1044 */
1045 function _dirCheck($p_dir, $p_is_dir=false) {
1046 $v_result = 1;
1047
1048 // Remove the final '/'
1049 if (($p_is_dir) && (substr($p_dir, -1)=='/')) {
1050 $p_dir = substr($p_dir, 0, strlen($p_dir)-1);
1051 }
1052
1053 // Check the directory availability
1054 if ((is_dir($p_dir)) || ($p_dir == "")) {
1055 return 1;
1056 }
1057
1058 // Extract parent directory
1059 $p_parent_dir = dirname($p_dir);
1060
1061 // Just a check
1062 if ($p_parent_dir != $p_dir) {
1063 // Look for parent directory
1064 if ($p_parent_dir != "") {
1065 if (($v_result = $this->_dirCheck($p_parent_dir)) != 1) {
1066 return $v_result;
1067 }
1068 }
1069 }
1070
1071 // Create the directory
1072 if (!@mkdir($p_dir, 0777)) {
1073 $this->_errorLog(ARCHIVE_ZIP_ERR_DIR_CREATE_FAIL,
1074 "Unable to create directory '$p_dir'");
1075 return em_unzip::errorCode();
1076 }
1077
1078 // Return
1079 return $v_result;
1080 }
1081
1082 /**
1083 * em_unzip::_check_parameters()
1084 *
1085 * { Description }
1086 *
1087 * @param integer $p_error_code
1088 * @param string $p_error_string
1089 */
1090 function _check_parameters(&$p_params, $p_default) {
1091
1092 // Check that param is an array
1093 if (!is_array($p_params)) {
1094 $this->_errorLog(ARCHIVE_ZIP_ERR_INVALID_PARAMETER,
1095 'Unsupported parameter, waiting for an array');
1096 return em_unzip::errorCode();
1097 }
1098
1099 // Check that all the params are valid
1100 for (reset($p_params); list($v_key, $v_value) = each($p_params); ) {
1101 if (!isset($p_default[$v_key])) {
1102 $this->_errorLog(ARCHIVE_ZIP_ERR_INVALID_PARAMETER,
1103 'Unsupported parameter with key \''.$v_key.'\'');
1104
1105 return em_unzip::errorCode();
1106 }
1107 }
1108
1109 // Set the default values
1110 for (reset($p_default); list($v_key, $v_value) = each($p_default); ) {
1111 if (!isset($p_params[$v_key])) {
1112 $p_params[$v_key] = $p_default[$v_key];
1113 }
1114 }
1115
1116 // Check specific parameters
1117 $v_callback_list = array ('callback_pre_add','callback_post_add',
1118 'callback_pre_extract','callback_post_extract');
1119 for ($i=0; $i<sizeof($v_callback_list); $i++) {
1120 $v_key=$v_callback_list[$i];
1121 if ( (isset($p_params[$v_key])) && ($p_params[$v_key] != '')) {
1122 if (!function_exists($p_params[$v_key])) {
1123 $this->_errorLog(ARCHIVE_ZIP_ERR_INVALID_PARAM_VALUE,
1124 "Callback '".$p_params[$v_key]
1125 ."()' is not an existing function for "
1126 ."parameter '".$v_key."'");
1127 return em_unzip::errorCode();
1128 }
1129 }
1130 }
1131
1132 return(1);
1133 }
1134
1135 /**
1136 * em_unzip::_errorLog()
1137 *
1138 * { Description }
1139 *
1140 * @param integer $p_error_code
1141 * @param string $p_error_string
1142 */
1143 function _errorLog($p_error_code=0, $p_error_string='') {
1144 $this->_error_code = $p_error_code;
1145 $this->_error_string = $p_error_string;
1146 }
1147
1148 /**
1149 * em_unzip::_errorReset()
1150 *
1151 * { Description }
1152 *
1153 */
1154 function _errorReset() {
1155 $this->_error_code = 1;
1156 $this->_error_string = '';
1157 }
1158
1159 /**
1160 * _tool_PathReduction()
1161 *
1162 * { Description }
1163 *
1164 */
1165 function _tool_PathReduction($p_dir) {
1166 $v_result = "";
1167
1168 // Look for not empty path
1169 if ($p_dir != "") {
1170 // Explode path by directory names
1171 $v_list = explode("/", $p_dir);
1172
1173 // Study directories from last to first
1174 for ($i=sizeof($v_list)-1; $i>=0; $i--) {
1175 // Look for current path
1176 if ($v_list[$i] == ".") {
1177 // Ignore this directory
1178 // Should be the first $i=0, but no check is done
1179 } else if ($v_list[$i] == "..") {
1180 // Ignore it and ignore the $i-1
1181 $i--;
1182 } else if (($v_list[$i] == "") && ($i!=(sizeof($v_list)-1)) && ($i!=0)) {
1183 // Ignore only the double '//' in path,
1184 // but not the first and last '/'
1185 } else {
1186 $v_result = $v_list[$i].($i!=(sizeof($v_list)-1)?"/".$v_result:"");
1187 }
1188 }
1189 }
1190
1191 // Return
1192 return $v_result;
1193 }
1194
1195 /**
1196 * _tool_PathInclusion()
1197 *
1198 * { Description }
1199 *
1200 */
1201 function _tool_PathInclusion($p_dir, $p_path) {
1202 $v_result = 1;
1203
1204 // Explode dir and path by directory separator
1205 $v_list_dir = explode("/", $p_dir);
1206 $v_list_dir_size = sizeof($v_list_dir);
1207 $v_list_path = explode("/", $p_path);
1208 $v_list_path_size = sizeof($v_list_path);
1209
1210 // Study directories paths
1211 $i = 0;
1212 $j = 0;
1213 while (($i < $v_list_dir_size) && ($j < $v_list_path_size) && ($v_result)) {
1214 // Look for empty dir (path reduction)
1215 if ($v_list_dir[$i] == '') {
1216 $i++;
1217 continue;
1218 }
1219 if ($v_list_path[$j] == '') {
1220 $j++;
1221 continue;
1222 }
1223
1224 // Compare the items
1225 if ( ($v_list_dir[$i] != $v_list_path[$j]) && ($v_list_dir[$i] != '') && ( $v_list_path[$j] != '')) {
1226 $v_result = 0;
1227 }
1228
1229 // Next items
1230 $i++;
1231 $j++;
1232 }
1233
1234 // Look if everything seems to be the same
1235 if ($v_result) {
1236 // Skip all the empty items
1237 while (($j < $v_list_path_size) && ($v_list_path[$j] == '')) $j++;
1238 while (($i < $v_list_dir_size) && ($v_list_dir[$i] == '')) $i++;
1239
1240 if (($i >= $v_list_dir_size) && ($j >= $v_list_path_size)) {
1241 // There are exactly the same
1242 $v_result = 2;
1243 } else if ($i < $v_list_dir_size) {
1244 // The path is shorter than the dir
1245 $v_result = 0;
1246 }
1247 }
1248
1249 // Return
1250 return $v_result;
1251 }
1252
1253 /**
1254 * _tool_CopyBlock()
1255 *
1256 * { Description }
1257 *
1258 * @param integer $p_mode
1259 */
1260 function _tool_CopyBlock($p_src, $p_dest, $p_size, $p_mode=0) {
1261 $v_result = 1;
1262
1263 if ($p_mode==0) {
1264 while ($p_size != 0) {
1265 $v_read_size = ($p_size < ARCHIVE_ZIP_READ_BLOCK_SIZE
1266 ? $p_size : ARCHIVE_ZIP_READ_BLOCK_SIZE);
1267 $v_buffer = @fread($p_src, $v_read_size);
1268 @fwrite($p_dest, $v_buffer, $v_read_size);
1269 $p_size -= $v_read_size;
1270 }
1271 } else if ($p_mode==1) {
1272 while ($p_size != 0) {
1273 $v_read_size = ($p_size < ARCHIVE_ZIP_READ_BLOCK_SIZE
1274 ? $p_size : ARCHIVE_ZIP_READ_BLOCK_SIZE);
1275 $v_buffer = @gzread($p_src, $v_read_size);
1276 @fwrite($p_dest, $v_buffer, $v_read_size);
1277 $p_size -= $v_read_size;
1278 }
1279 } else if ($p_mode==2) {
1280 while ($p_size != 0) {
1281 $v_read_size = ($p_size < ARCHIVE_ZIP_READ_BLOCK_SIZE
1282 ? $p_size : ARCHIVE_ZIP_READ_BLOCK_SIZE);
1283 $v_buffer = @fread($p_src, $v_read_size);
1284 @gzwrite($p_dest, $v_buffer, $v_read_size);
1285 $p_size -= $v_read_size;
1286 }
1287 }
1288 else if ($p_mode==3) {
1289 while ($p_size != 0) {
1290 $v_read_size = ($p_size < ARCHIVE_ZIP_READ_BLOCK_SIZE
1291 ? $p_size : ARCHIVE_ZIP_READ_BLOCK_SIZE);
1292 $v_buffer = @gzread($p_src, $v_read_size);
1293 @gzwrite($p_dest, $v_buffer, $v_read_size);
1294 $p_size -= $v_read_size;
1295 }
1296 }
1297
1298 // Return
1299 return $v_result;
1300 }
1301
1302 /**
1303 * _tool_Rename()
1304 *
1305 * { Description }
1306 *
1307 */
1308 function _tool_Rename($p_src, $p_dest) {
1309 $v_result = 1;
1310
1311 // Try to rename the files
1312 if (!@rename($p_src, $p_dest)) {
1313
1314 // Try to copy & unlink the src
1315 if (!@copy($p_src, $p_dest)) {
1316 $v_result = 0;
1317 } else if (!@unlink($p_src)) {
1318 $v_result = 0;
1319 }
1320 }
1321
1322 // Return
1323 return $v_result;
1324 }
1325
1326 /**
1327 * _tool_TranslateWinPath()
1328 *
1329 * { Description }
1330 *
1331 * @param [type] $p_remove_disk_letter
1332 */
1333 function _tool_TranslateWinPath($p_path, $p_remove_disk_letter=true) {
1334 if (stristr(php_uname(), 'windows')) {
1335 // Look for potential disk letter
1336 if ( ($p_remove_disk_letter)
1337 && (($v_position = strpos($p_path, ':')) != false)) {
1338 $p_path = substr($p_path, $v_position+1);
1339 }
1340 // Change potential windows directory separator
1341 if ((strpos($p_path, '\\') > 0) || (substr($p_path, 0,1) == '\\')) {
1342 $p_path = strtr($p_path, '\\', '/');
1343 }
1344 }
1345 return $p_path;
1346 }
1347
1348 }
1349
1350 ?>