7ffa7af1b7b1437ebfe41d74366d26b55766740d
[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-2009 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 $v_header = '';
511 if (($v_result = $this->_readFileHeader($v_header)) != 1)
512 {
513 // Return
514 return $v_result;
515 }
516
517
518 // Check that the file header is coherent with $p_entry info
519 // TBC
520
521 // Look for all path to remove
522 if ($p_remove_all_path == true) {
523 // Get the basename of the path
524 $p_entry['filename'] = basename($p_entry['filename']);
525 }
526
527 // Look for path to remove
528 else if ($p_remove_path != "")
529 {
530 //if (strcmp($p_remove_path, $p_entry['filename'])==0)
531 if ($this->_tool_PathInclusion($p_remove_path, $p_entry['filename']) == 2)
532 {
533
534 // Change the file status
535 $p_entry['status'] = "filtered";
536
537 // Return
538 return $v_result;
539 }
540
541 $p_remove_path_size = strlen($p_remove_path);
542 if (substr($p_entry['filename'], 0, $p_remove_path_size) == $p_remove_path)
543 {
544
545 // Remove the path
546 $p_entry['filename'] = substr($p_entry['filename'], $p_remove_path_size);
547
548 }
549 }
550
551 // Add the path
552 if ($p_path != '')
553 {
554 $p_entry['filename'] = $p_path."/".$p_entry['filename'];
555 }
556
557 // Look for pre-extract callback
558 if ( (isset($p_params[ARCHIVE_ZIP_PARAM_PRE_EXTRACT]))
559 && ($p_params[ARCHIVE_ZIP_PARAM_PRE_EXTRACT] != '')) {
560
561 // Generate a local information
562 $v_local_header = array();
563 $this->_convertHeader2FileInfo($p_entry, $v_local_header);
564
565 // Call the callback
566 // Here I do not use call_user_func() because I need to send a reference to the
567 // header.
568 eval('$v_result = '.$p_params[ARCHIVE_ZIP_PARAM_PRE_EXTRACT].'(ARCHIVE_ZIP_PARAM_PRE_EXTRACT, $v_local_header);');
569 if ($v_result == 0) {
570 // Change the file status
571 $p_entry['status'] = "skipped";
572 $v_result = 1;
573 }
574
575 // Update the informations
576 // Only some fields can be modified
577 $p_entry['filename'] = $v_local_header['filename'];
578 }
579
580 // Trace
581
582 // Look if extraction should be done
583 if ($p_entry['status'] == 'ok') {
584
585 // Look for specific actions while the file exist
586 if (file_exists($p_entry['filename'])) {
587 // Look if file is a directory
588 if (is_dir($p_entry['filename'])) {
589 // Change the file status
590 $p_entry['status'] = "already_a_directory";
591
592 // Return
593 //return $v_result;
594 }
595 // Look if file is write protected
596 else if (!is_writeable($p_entry['filename'])) {
597 // Change the file status
598 $p_entry['status'] = "write_protected";
599
600 // Return
601 //return $v_result;
602 }
603
604 // Look if the extracted file is older
605 else if (filemtime($p_entry['filename']) > $p_entry['mtime']) {
606 // Change the file status
607 $p_entry['status'] = "newer_exist";
608
609 // Return
610 //return $v_result;
611 }
612 }
613
614 // Check the directory availability and create it if necessary
615 else {
616 if ((($p_entry['external']&0x00000010)==0x00000010) || (substr($p_entry['filename'], -1) == '/'))
617 $v_dir_to_check = $p_entry['filename'];
618 else if (!strstr($p_entry['filename'], "/"))
619 $v_dir_to_check = "";
620 else
621 $v_dir_to_check = dirname($p_entry['filename']);
622
623 if (($v_result = $this->_dirCheck($v_dir_to_check, (($p_entry['external']&0x00000010)==0x00000010))) != 1) {
624 // Change the file status
625 $p_entry['status'] = "path_creation_fail";
626
627 // Return
628 //return $v_result;
629 $v_result = 1;
630 }
631 }
632 }
633
634 // Look if extraction should be done
635 if ($p_entry['status'] == 'ok') {
636 // Do the extraction (if not a folder)
637 if (!(($p_entry['external']&0x00000010)==0x00000010)) {
638 // Look for not compressed file
639 if ($p_entry['compressed_size'] == $p_entry['size']) {
640 // Opening destination file
641 if (($v_dest_file = @fopen($p_entry['filename'], 'wb')) == 0) {
642 // Change the file status
643 $p_entry['status'] = "write_error";
644
645 // Return
646 return $v_result;
647 }
648
649
650 // Read the file by ARCHIVE_ZIP_READ_BLOCK_SIZE octets blocks
651 $v_size = $p_entry['compressed_size'];
652 while ($v_size != 0) {
653 $v_read_size = ($v_size < ARCHIVE_ZIP_READ_BLOCK_SIZE ? $v_size : ARCHIVE_ZIP_READ_BLOCK_SIZE);
654 $v_buffer = fread($this->_zip_fd, $v_read_size);
655 $v_binary_data = pack('a'.$v_read_size, $v_buffer);
656 @fwrite($v_dest_file, $v_binary_data, $v_read_size);
657 $v_size -= $v_read_size;
658 }
659
660 // Closing the destination file
661 fclose($v_dest_file);
662
663 // Change the file mtime
664 touch($p_entry['filename'], $p_entry['mtime']);
665 } else {
666 // Trace
667
668 // Opening destination file
669 if (($v_dest_file = @fopen($p_entry['filename'], 'wb')) == 0) {
670
671 // Change the file status
672 $p_entry['status'] = "write_error";
673
674 return $v_result;
675 }
676
677
678 // Read the compressed file in a buffer (one shot)
679 $v_buffer = @fread($this->_zip_fd, $p_entry['compressed_size']);
680
681 // Decompress the file
682 $v_file_content = gzinflate($v_buffer);
683 unset($v_buffer);
684
685 // Write the uncompressed data
686 @fwrite($v_dest_file, $v_file_content, $p_entry['size']);
687 unset($v_file_content);
688
689 // Closing the destination file
690 @fclose($v_dest_file);
691
692 // Change the file mtime
693 @touch($p_entry['filename'], $p_entry['mtime']);
694 }
695
696 // Look for chmod option
697 if ( (isset($p_params[ARCHIVE_ZIP_PARAM_SET_CHMOD]))
698 && ($p_params[ARCHIVE_ZIP_PARAM_SET_CHMOD] != 0)) {
699
700 // Change the mode of the file
701 chmod($p_entry['filename'], $p_params[ARCHIVE_ZIP_PARAM_SET_CHMOD]);
702 }
703
704 }
705 }
706
707 // Look for post-extract callback
708 if ( (isset($p_params[ARCHIVE_ZIP_PARAM_POST_EXTRACT]))
709 && ($p_params[ARCHIVE_ZIP_PARAM_POST_EXTRACT] != '')) {
710
711 // Generate a local information
712 $v_local_header = array();
713 $this->_convertHeader2FileInfo($p_entry, $v_local_header);
714
715 // Call the callback
716 // Here I do not use call_user_func() because I need to send a reference to the
717 // header.
718 eval('$v_result = '.$p_params[ARCHIVE_ZIP_PARAM_POST_EXTRACT].'(ARCHIVE_ZIP_PARAM_POST_EXTRACT, $v_local_header);');
719 }
720
721 // Return
722 return $v_result;
723 }
724
725 /**
726 * em_unzip::_readFileHeader()
727 *
728 * { Description }
729 *
730 */
731 function _readFileHeader(&$p_header) {
732 $v_result=1;
733
734 // Read the 4 bytes signature
735 $v_binary_data = @fread($this->_zip_fd, 4);
736 $v_data = unpack('Vid', $v_binary_data);
737
738 // Check signature
739 if ($v_data['id'] != 0x04034b50) {
740
741 // Error log
742 $this->_errorLog(ARCHIVE_ZIP_ERR_BAD_FORMAT, 'Invalid archive structure');
743
744 // Return
745 return em_unzip::errorCode();
746 }
747
748 // Read the first 42 bytes of the header
749 $v_binary_data = fread($this->_zip_fd, 26);
750
751 // Look for invalid block size
752 if (strlen($v_binary_data) != 26) {
753 $p_header['filename'] = "";
754 $p_header['status'] = "invalid_header";
755
756 // Error log
757 $this->_errorLog(ARCHIVE_ZIP_ERR_BAD_FORMAT, "Invalid block size : ".strlen($v_binary_data));
758
759 // Return
760 return em_unzip::errorCode();
761 }
762
763 // Extract the values
764 $v_data = unpack('vversion/vflag/vcompression/vmtime/vmdate/Vcrc/Vcompressed_size/Vsize/vfilename_len/vextra_len', $v_binary_data);
765
766 // Get filename
767 $p_header['filename'] = fread($this->_zip_fd, $v_data['filename_len']);
768
769 // Get extra_fields
770 if ($v_data['extra_len'] != 0) {
771 $p_header['extra'] = fread($this->_zip_fd, $v_data['extra_len']);
772 }
773 else {
774 $p_header['extra'] = '';
775 }
776
777 // Extract properties
778 $p_header['compression'] = $v_data['compression'];
779 $p_header['size'] = $v_data['size'];
780 $p_header['compressed_size'] = $v_data['compressed_size'];
781 $p_header['crc'] = $v_data['crc'];
782 $p_header['flag'] = $v_data['flag'];
783
784 // Recuperate date in UNIX format
785 $p_header['mdate'] = $v_data['mdate'];
786 $p_header['mtime'] = $v_data['mtime'];
787 if ($p_header['mdate'] && $p_header['mtime']) {
788 // Extract time
789 $v_hour = ($p_header['mtime'] & 0xF800) >> 11;
790 $v_minute = ($p_header['mtime'] & 0x07E0) >> 5;
791 $v_seconde = ($p_header['mtime'] & 0x001F)*2;
792
793 // Extract date
794 $v_year = (($p_header['mdate'] & 0xFE00) >> 9) + 1980;
795 $v_month = ($p_header['mdate'] & 0x01E0) >> 5;
796 $v_day = $p_header['mdate'] & 0x001F;
797
798 // Get UNIX date format
799 $p_header['mtime'] = mktime($v_hour, $v_minute, $v_seconde, $v_month, $v_day, $v_year);
800
801 } else {
802 $p_header['mtime'] = time();
803 }
804
805 // Other informations
806
807 // TBC
808 //for(reset($v_data); $key = key($v_data); next($v_data)) {
809 //}
810
811 // Set the stored filename
812 $p_header['stored_filename'] = $p_header['filename'];
813
814 // Set the status field
815 $p_header['status'] = "ok";
816
817 // Return
818 return $v_result;
819 }
820
821 /**
822 * em_unzip::_readCentralFileHeader()
823 *
824 * { Description }
825 *
826 */
827 function _readCentralFileHeader(&$p_header) {
828 $v_result=1;
829
830 // Read the 4 bytes signature
831 $v_binary_data = @fread($this->_zip_fd, 4);
832 $v_data = unpack('Vid', $v_binary_data);
833
834 // Check signature
835 if ($v_data['id'] != 0x02014b50) {
836
837 // Error log
838 $this->_errorLog(ARCHIVE_ZIP_ERR_BAD_FORMAT, 'Invalid archive structure');
839
840 // Return
841 return em_unzip::errorCode();
842 }
843
844 // Read the first 42 bytes of the header
845 $v_binary_data = fread($this->_zip_fd, 42);
846
847 // Look for invalid block size
848 if (strlen($v_binary_data) != 42) {
849 $p_header['filename'] = "";
850 $p_header['status'] = "invalid_header";
851
852 // Error log
853 $this->_errorLog(ARCHIVE_ZIP_ERR_BAD_FORMAT, "Invalid block size : ".strlen($v_binary_data));
854
855 // Return
856 return em_unzip::errorCode();
857 }
858
859 // Extract the values
860 $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);
861
862 // Get filename
863 if ($p_header['filename_len'] != 0)
864 $p_header['filename'] = fread($this->_zip_fd, $p_header['filename_len']);
865 else
866 $p_header['filename'] = '';
867
868 // Get extra
869 if ($p_header['extra_len'] != 0)
870 $p_header['extra'] = fread($this->_zip_fd, $p_header['extra_len']);
871 else
872 $p_header['extra'] = '';
873
874 // Get comment
875 if ($p_header['comment_len'] != 0)
876 $p_header['comment'] = fread($this->_zip_fd, $p_header['comment_len']);
877 else
878 $p_header['comment'] = '';
879
880 // Extract properties
881
882 // Recuperate date in UNIX format
883 if ($p_header['mdate'] && $p_header['mtime']) {
884 // Extract time
885 $v_hour = ($p_header['mtime'] & 0xF800) >> 11;
886 $v_minute = ($p_header['mtime'] & 0x07E0) >> 5;
887 $v_seconde = ($p_header['mtime'] & 0x001F)*2;
888
889 // Extract date
890 $v_year = (($p_header['mdate'] & 0xFE00) >> 9) + 1980;
891 $v_month = ($p_header['mdate'] & 0x01E0) >> 5;
892 $v_day = $p_header['mdate'] & 0x001F;
893
894 // Get UNIX date format
895 $p_header['mtime'] = mktime($v_hour, $v_minute, $v_seconde, $v_month, $v_day, $v_year);
896
897 } else {
898 $p_header['mtime'] = time();
899 }
900
901 // Set the stored filename
902 $p_header['stored_filename'] = $p_header['filename'];
903
904 // Set default status to ok
905 $p_header['status'] = 'ok';
906
907 // Look if it is a directory
908 if (substr($p_header['filename'], -1) == '/') {
909 $p_header['external'] = 0x41FF0010;
910 }
911
912
913 // Return
914 return $v_result;
915 }
916
917 /**
918 * em_unzip::_readEndCentralDir()
919 *
920 * { Description }
921 *
922 */
923 function _readEndCentralDir(&$p_central_dir) {
924 $v_result=1;
925
926 // Go to the end of the zip file
927 $v_size = filesize($this->_zipname);
928 @fseek($this->_zip_fd, $v_size);
929 if (@ftell($this->_zip_fd) != $v_size) {
930 $this->_errorLog(ARCHIVE_ZIP_ERR_BAD_FORMAT,
931 'Unable to go to the end of the archive \''
932 .$this->_zipname.'\'');
933 return em_unzip::errorCode();
934 }
935
936 // First try : look if this is an archive with no commentaries
937 // (most of the time)
938 // in this case the end of central dir is at 22 bytes of the file end
939 $v_found = 0;
940 if ($v_size > 26) {
941 @fseek($this->_zip_fd, $v_size-22);
942 if (($v_pos = @ftell($this->_zip_fd)) != ($v_size-22)) {
943 $this->_errorLog(ARCHIVE_ZIP_ERR_BAD_FORMAT,
944 'Unable to seek back to the middle of the archive \''
945 .$this->_zipname.'\'');
946 return em_unzip::errorCode();
947 }
948
949 // Read for bytes
950 $v_binary_data = @fread($this->_zip_fd, 4);
951 $v_data = unpack('Vid', $v_binary_data);
952
953 // Check signature
954 if ($v_data['id'] == 0x06054b50) {
955 $v_found = 1;
956 }
957
958 $v_pos = ftell($this->_zip_fd);
959 }
960
961 // Go back to the maximum possible size of the Central Dir End Record
962 if (!$v_found) {
963 $v_maximum_size = 65557; // 0xFFFF + 22;
964 if ($v_maximum_size > $v_size)
965 $v_maximum_size = $v_size;
966 @fseek($this->_zip_fd, $v_size-$v_maximum_size);
967 if (@ftell($this->_zip_fd) != ($v_size-$v_maximum_size)) {
968 $this->_errorLog(ARCHIVE_ZIP_ERR_BAD_FORMAT,
969 'Unable to seek back to the middle of the archive \''
970 .$this->_zipname.'\'');
971 return em_unzip::errorCode();
972 }
973
974 // Read byte per byte in order to find the signature
975 $v_pos = ftell($this->_zip_fd);
976 $v_bytes = 0x00000000;
977 while ($v_pos < $v_size) {
978 // Read a byte
979 $v_byte = @fread($this->_zip_fd, 1);
980
981 // Add the byte
982 $v_bytes = ($v_bytes << 8) | Ord($v_byte);
983
984 // Compare the bytes
985 if ($v_bytes == 0x504b0506) {
986 $v_pos++;
987 break;
988 }
989
990 $v_pos++;
991 }
992
993 // Look if not found end of central dir
994 if ($v_pos == $v_size) {
995 $this->_errorLog(ARCHIVE_ZIP_ERR_BAD_FORMAT,
996 "Unable to find End of Central Dir Record signature");
997 return em_unzip::errorCode();
998 }
999 }
1000
1001 // Read the first 18 bytes of the header
1002 $v_binary_data = fread($this->_zip_fd, 18);
1003
1004 // Look for invalid block size
1005 if (strlen($v_binary_data) != 18) {
1006 $this->_errorLog(ARCHIVE_ZIP_ERR_BAD_FORMAT,
1007 "Invalid End of Central Dir Record size : "
1008 .strlen($v_binary_data));
1009 return em_unzip::errorCode();
1010 }
1011
1012 // Extract the values
1013 $v_data = unpack('vdisk/vdisk_start/vdisk_entries/ventries/Vsize/Voffset/vcomment_size', $v_binary_data);
1014
1015 // Check the global size
1016 if (($v_pos + $v_data['comment_size'] + 18) != $v_size) {
1017 $this->_errorLog(ARCHIVE_ZIP_ERR_BAD_FORMAT,
1018 "Fail to find the right signature");
1019 return em_unzip::errorCode();
1020 }
1021
1022 // Get comment
1023 if ($v_data['comment_size'] != 0)
1024 $p_central_dir['comment'] = fread($this->_zip_fd, $v_data['comment_size']);
1025 else
1026 $p_central_dir['comment'] = '';
1027
1028 $p_central_dir['entries'] = $v_data['entries'];
1029 $p_central_dir['disk_entries'] = $v_data['disk_entries'];
1030 $p_central_dir['offset'] = $v_data['offset'];
1031 $p_central_dir['size'] = $v_data['size'];
1032 $p_central_dir['disk'] = $v_data['disk'];
1033 $p_central_dir['disk_start'] = $v_data['disk_start'];
1034
1035 // Return
1036 return $v_result;
1037 }
1038
1039 /**
1040 * em_unzip::_dirCheck()
1041 *
1042 * { Description }
1043 *
1044 * @param [type] $p_is_dir
1045 */
1046 function _dirCheck($p_dir, $p_is_dir=false) {
1047 $v_result = 1;
1048
1049 // Remove the final '/'
1050 if (($p_is_dir) && (substr($p_dir, -1)=='/')) {
1051 $p_dir = substr($p_dir, 0, strlen($p_dir)-1);
1052 }
1053
1054 // Check the directory availability
1055 if ((is_dir($p_dir)) || ($p_dir == "")) {
1056 return 1;
1057 }
1058
1059 // Extract parent directory
1060 $p_parent_dir = dirname($p_dir);
1061
1062 // Just a check
1063 if ($p_parent_dir != $p_dir) {
1064 // Look for parent directory
1065 if ($p_parent_dir != "") {
1066 if (($v_result = $this->_dirCheck($p_parent_dir)) != 1) {
1067 return $v_result;
1068 }
1069 }
1070 }
1071
1072 // Create the directory
1073 if (!@mkdir($p_dir, 0777)) {
1074 $this->_errorLog(ARCHIVE_ZIP_ERR_DIR_CREATE_FAIL,
1075 "Unable to create directory '$p_dir'");
1076 return em_unzip::errorCode();
1077 }
1078
1079 // Return
1080 return $v_result;
1081 }
1082
1083 /**
1084 * em_unzip::_check_parameters()
1085 *
1086 * { Description }
1087 *
1088 * @param integer $p_error_code
1089 * @param string $p_error_string
1090 */
1091 function _check_parameters(&$p_params, $p_default) {
1092
1093 // Check that param is an array
1094 if (!is_array($p_params)) {
1095 $this->_errorLog(ARCHIVE_ZIP_ERR_INVALID_PARAMETER,
1096 'Unsupported parameter, waiting for an array');
1097 return em_unzip::errorCode();
1098 }
1099
1100 // Check that all the params are valid
1101 for (reset($p_params); list($v_key, $v_value) = each($p_params); ) {
1102 if (!isset($p_default[$v_key])) {
1103 $this->_errorLog(ARCHIVE_ZIP_ERR_INVALID_PARAMETER,
1104 'Unsupported parameter with key \''.$v_key.'\'');
1105
1106 return em_unzip::errorCode();
1107 }
1108 }
1109
1110 // Set the default values
1111 for (reset($p_default); list($v_key, $v_value) = each($p_default); ) {
1112 if (!isset($p_params[$v_key])) {
1113 $p_params[$v_key] = $p_default[$v_key];
1114 }
1115 }
1116
1117 // Check specific parameters
1118 $v_callback_list = array ('callback_pre_add','callback_post_add',
1119 'callback_pre_extract','callback_post_extract');
1120 for ($i=0; $i<sizeof($v_callback_list); $i++) {
1121 $v_key=$v_callback_list[$i];
1122 if ( (isset($p_params[$v_key])) && ($p_params[$v_key] != '')) {
1123 if (!function_exists($p_params[$v_key])) {
1124 $this->_errorLog(ARCHIVE_ZIP_ERR_INVALID_PARAM_VALUE,
1125 "Callback '".$p_params[$v_key]
1126 ."()' is not an existing function for "
1127 ."parameter '".$v_key."'");
1128 return em_unzip::errorCode();
1129 }
1130 }
1131 }
1132
1133 return(1);
1134 }
1135
1136 /**
1137 * em_unzip::_errorLog()
1138 *
1139 * { Description }
1140 *
1141 * @param integer $p_error_code
1142 * @param string $p_error_string
1143 */
1144 function _errorLog($p_error_code=0, $p_error_string='') {
1145 $this->_error_code = $p_error_code;
1146 $this->_error_string = $p_error_string;
1147 }
1148
1149 /**
1150 * em_unzip::_errorReset()
1151 *
1152 * { Description }
1153 *
1154 */
1155 function _errorReset() {
1156 $this->_error_code = 1;
1157 $this->_error_string = '';
1158 }
1159
1160 /**
1161 * _tool_PathReduction()
1162 *
1163 * { Description }
1164 *
1165 */
1166 function _tool_PathReduction($p_dir) {
1167 $v_result = "";
1168
1169 // Look for not empty path
1170 if ($p_dir != "") {
1171 // Explode path by directory names
1172 $v_list = explode("/", $p_dir);
1173
1174 // Study directories from last to first
1175 for ($i=sizeof($v_list)-1; $i>=0; $i--) {
1176 // Look for current path
1177 if ($v_list[$i] == ".") {
1178 // Ignore this directory
1179 // Should be the first $i=0, but no check is done
1180 } else if ($v_list[$i] == "..") {
1181 // Ignore it and ignore the $i-1
1182 $i--;
1183 } else if (($v_list[$i] == "") && ($i!=(sizeof($v_list)-1)) && ($i!=0)) {
1184 // Ignore only the double '//' in path,
1185 // but not the first and last '/'
1186 } else {
1187 $v_result = $v_list[$i].($i!=(sizeof($v_list)-1)?"/".$v_result:"");
1188 }
1189 }
1190 }
1191
1192 // Return
1193 return $v_result;
1194 }
1195
1196 /**
1197 * _tool_PathInclusion()
1198 *
1199 * { Description }
1200 *
1201 */
1202 function _tool_PathInclusion($p_dir, $p_path) {
1203 $v_result = 1;
1204
1205 // Explode dir and path by directory separator
1206 $v_list_dir = explode("/", $p_dir);
1207 $v_list_dir_size = sizeof($v_list_dir);
1208 $v_list_path = explode("/", $p_path);
1209 $v_list_path_size = sizeof($v_list_path);
1210
1211 // Study directories paths
1212 $i = 0;
1213 $j = 0;
1214 while (($i < $v_list_dir_size) && ($j < $v_list_path_size) && ($v_result)) {
1215 // Look for empty dir (path reduction)
1216 if ($v_list_dir[$i] == '') {
1217 $i++;
1218 continue;
1219 }
1220 if ($v_list_path[$j] == '') {
1221 $j++;
1222 continue;
1223 }
1224
1225 // Compare the items
1226 if ( ($v_list_dir[$i] != $v_list_path[$j]) && ($v_list_dir[$i] != '') && ( $v_list_path[$j] != '')) {
1227 $v_result = 0;
1228 }
1229
1230 // Next items
1231 $i++;
1232 $j++;
1233 }
1234
1235 // Look if everything seems to be the same
1236 if ($v_result) {
1237 // Skip all the empty items
1238 while (($j < $v_list_path_size) && ($v_list_path[$j] == '')) $j++;
1239 while (($i < $v_list_dir_size) && ($v_list_dir[$i] == '')) $i++;
1240
1241 if (($i >= $v_list_dir_size) && ($j >= $v_list_path_size)) {
1242 // There are exactly the same
1243 $v_result = 2;
1244 } else if ($i < $v_list_dir_size) {
1245 // The path is shorter than the dir
1246 $v_result = 0;
1247 }
1248 }
1249
1250 // Return
1251 return $v_result;
1252 }
1253
1254 /**
1255 * _tool_CopyBlock()
1256 *
1257 * { Description }
1258 *
1259 * @param integer $p_mode
1260 */
1261 function _tool_CopyBlock($p_src, $p_dest, $p_size, $p_mode=0) {
1262 $v_result = 1;
1263
1264 if ($p_mode==0) {
1265 while ($p_size != 0) {
1266 $v_read_size = ($p_size < ARCHIVE_ZIP_READ_BLOCK_SIZE
1267 ? $p_size : ARCHIVE_ZIP_READ_BLOCK_SIZE);
1268 $v_buffer = @fread($p_src, $v_read_size);
1269 @fwrite($p_dest, $v_buffer, $v_read_size);
1270 $p_size -= $v_read_size;
1271 }
1272 } else if ($p_mode==1) {
1273 while ($p_size != 0) {
1274 $v_read_size = ($p_size < ARCHIVE_ZIP_READ_BLOCK_SIZE
1275 ? $p_size : ARCHIVE_ZIP_READ_BLOCK_SIZE);
1276 $v_buffer = @gzread($p_src, $v_read_size);
1277 @fwrite($p_dest, $v_buffer, $v_read_size);
1278 $p_size -= $v_read_size;
1279 }
1280 } else if ($p_mode==2) {
1281 while ($p_size != 0) {
1282 $v_read_size = ($p_size < ARCHIVE_ZIP_READ_BLOCK_SIZE
1283 ? $p_size : ARCHIVE_ZIP_READ_BLOCK_SIZE);
1284 $v_buffer = @fread($p_src, $v_read_size);
1285 @gzwrite($p_dest, $v_buffer, $v_read_size);
1286 $p_size -= $v_read_size;
1287 }
1288 }
1289 else if ($p_mode==3) {
1290 while ($p_size != 0) {
1291 $v_read_size = ($p_size < ARCHIVE_ZIP_READ_BLOCK_SIZE
1292 ? $p_size : ARCHIVE_ZIP_READ_BLOCK_SIZE);
1293 $v_buffer = @gzread($p_src, $v_read_size);
1294 @gzwrite($p_dest, $v_buffer, $v_read_size);
1295 $p_size -= $v_read_size;
1296 }
1297 }
1298
1299 // Return
1300 return $v_result;
1301 }
1302
1303 /**
1304 * _tool_Rename()
1305 *
1306 * { Description }
1307 *
1308 */
1309 function _tool_Rename($p_src, $p_dest) {
1310 $v_result = 1;
1311
1312 // Try to rename the files
1313 if (!@rename($p_src, $p_dest)) {
1314
1315 // Try to copy & unlink the src
1316 if (!@copy($p_src, $p_dest)) {
1317 $v_result = 0;
1318 } else if (!@unlink($p_src)) {
1319 $v_result = 0;
1320 }
1321 }
1322
1323 // Return
1324 return $v_result;
1325 }
1326
1327 /**
1328 * _tool_TranslateWinPath()
1329 *
1330 * { Description }
1331 *
1332 * @param [type] $p_remove_disk_letter
1333 */
1334 function _tool_TranslateWinPath($p_path, $p_remove_disk_letter=true) {
1335 if (stristr(php_uname(), 'windows')) {
1336 // Look for potential disk letter
1337 if ( ($p_remove_disk_letter)
1338 && (($v_position = strpos($p_path, ':')) != false)) {
1339 $p_path = substr($p_path, $v_position+1);
1340 }
1341 // Change potential windows directory separator
1342 if ((strpos($p_path, '\\') > 0) || (substr($p_path, 0,1) == '\\')) {
1343 $p_path = strtr($p_path, '\\', '/');
1344 }
1345 }
1346 return $p_path;
1347 }
1348
1349 }
1350
1351 ?>