Add update hook template
[aur.git] / web / lib / Archive / Tar.php
blob32a2ccc34ea0c8faa953bf743287311d5766be8d
1 <?php
2 /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
4 /**
5 * File::CSV
7 * PHP versions 4 and 5
9 * Copyright (c) 1997-2008,
10 * Vincent Blavet <vincent@phpconcept.net>
11 * All rights reserved.
13 * Redistribution and use in source and binary forms, with or without
14 * modification, are permitted provided that the following conditions are met:
16 * * Redistributions of source code must retain the above copyright notice,
17 * this list of conditions and the following disclaimer.
18 * * Redistributions in binary form must reproduce the above copyright
19 * notice, this list of conditions and the following disclaimer in the
20 * documentation and/or other materials provided with the distribution.
22 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
23 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
24 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
25 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
26 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
27 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
28 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
29 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
30 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
31 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
33 * @category File_Formats
34 * @package Archive_Tar
35 * @author Vincent Blavet <vincent@phpconcept.net>
36 * @copyright 1997-2010 The Authors
37 * @license http://www.opensource.org/licenses/bsd-license.php New BSD License
38 * @version CVS: $Id$
39 * @link http://pear.php.net/package/Archive_Tar
42 require_once 'PEAR.php';
44 define('ARCHIVE_TAR_ATT_SEPARATOR', 90001);
45 define('ARCHIVE_TAR_END_BLOCK', pack("a512", ''));
47 /**
48 * Creates a (compressed) Tar archive
50 * @package Archive_Tar
51 * @author Vincent Blavet <vincent@phpconcept.net>
52 * @license http://www.opensource.org/licenses/bsd-license.php New BSD License
53 * @version $Revision$
55 class Archive_Tar extends PEAR
57 /**
58 * @var string Name of the Tar
60 var $_tarname='';
62 /**
63 * @var boolean if true, the Tar file will be gzipped
65 var $_compress=false;
67 /**
68 * @var string Type of compression : 'none', 'gz' or 'bz2'
70 var $_compress_type='none';
72 /**
73 * @var string Explode separator
75 var $_separator=' ';
77 /**
78 * @var file descriptor
80 var $_file=0;
82 /**
83 * @var string Local Tar name of a remote Tar (http:// or ftp://)
85 var $_temp_tarname='';
87 /**
88 * @var string regular expression for ignoring files or directories
90 var $_ignore_regexp='';
92 /**
93 * @var object PEAR_Error object
95 var $error_object=null;
97 // {{{ constructor
98 /**
99 * Archive_Tar Class constructor. This flavour of the constructor only
100 * declare a new Archive_Tar object, identifying it by the name of the
101 * tar file.
102 * If the compress argument is set the tar will be read or created as a
103 * gzip or bz2 compressed TAR file.
105 * @param string $p_tarname The name of the tar archive to create
106 * @param string $p_compress can be null, 'gz' or 'bz2'. This
107 * parameter indicates if gzip or bz2 compression
108 * is required. For compatibility reason the
109 * boolean value 'true' means 'gz'.
111 * @access public
113 function Archive_Tar($p_tarname, $p_compress = null)
115 $this->PEAR();
116 $this->_compress = false;
117 $this->_compress_type = 'none';
118 if (($p_compress === null) || ($p_compress == '')) {
119 if (@file_exists($p_tarname)) {
120 if ($fp = @fopen($p_tarname, "rb")) {
121 // look for gzip magic cookie
122 $data = fread($fp, 2);
123 fclose($fp);
124 if ($data == "\37\213") {
125 $this->_compress = true;
126 $this->_compress_type = 'gz';
127 // No sure it's enought for a magic code ....
128 } elseif ($data == "BZ") {
129 $this->_compress = true;
130 $this->_compress_type = 'bz2';
133 } else {
134 // probably a remote file or some file accessible
135 // through a stream interface
136 if (substr($p_tarname, -2) == 'gz') {
137 $this->_compress = true;
138 $this->_compress_type = 'gz';
139 } elseif ((substr($p_tarname, -3) == 'bz2') ||
140 (substr($p_tarname, -2) == 'bz')) {
141 $this->_compress = true;
142 $this->_compress_type = 'bz2';
145 } else {
146 if (($p_compress === true) || ($p_compress == 'gz')) {
147 $this->_compress = true;
148 $this->_compress_type = 'gz';
149 } else if ($p_compress == 'bz2') {
150 $this->_compress = true;
151 $this->_compress_type = 'bz2';
152 } else {
153 $this->_error("Unsupported compression type '$p_compress'\n".
154 "Supported types are 'gz' and 'bz2'.\n");
155 return false;
158 $this->_tarname = $p_tarname;
159 if ($this->_compress) { // assert zlib or bz2 extension support
160 if ($this->_compress_type == 'gz')
161 $extname = 'zlib';
162 else if ($this->_compress_type == 'bz2')
163 $extname = 'bz2';
165 if (!extension_loaded($extname)) {
166 PEAR::loadExtension($extname);
168 if (!extension_loaded($extname)) {
169 $this->_error("The extension '$extname' couldn't be found.\n".
170 "Please make sure your version of PHP was built ".
171 "with '$extname' support.\n");
172 return false;
176 // }}}
178 // {{{ destructor
179 function _Archive_Tar()
181 $this->_close();
182 // ----- Look for a local copy to delete
183 if ($this->_temp_tarname != '')
184 @unlink($this->_temp_tarname);
185 $this->_PEAR();
187 // }}}
189 // {{{ create()
191 * This method creates the archive file and add the files / directories
192 * that are listed in $p_filelist.
193 * If a file with the same name exist and is writable, it is replaced
194 * by the new tar.
195 * The method return false and a PEAR error text.
196 * The $p_filelist parameter can be an array of string, each string
197 * representing a filename or a directory name with their path if
198 * needed. It can also be a single string with names separated by a
199 * single blank.
200 * For each directory added in the archive, the files and
201 * sub-directories are also added.
202 * See also createModify() method for more details.
204 * @param array $p_filelist An array of filenames and directory names, or a
205 * single string with names separated by a single
206 * blank space.
208 * @return true on success, false on error.
209 * @see createModify()
210 * @access public
212 function create($p_filelist)
214 return $this->createModify($p_filelist, '', '');
216 // }}}
218 // {{{ add()
220 * This method add the files / directories that are listed in $p_filelist in
221 * the archive. If the archive does not exist it is created.
222 * The method return false and a PEAR error text.
223 * The files and directories listed are only added at the end of the archive,
224 * even if a file with the same name is already archived.
225 * See also createModify() method for more details.
227 * @param array $p_filelist An array of filenames and directory names, or a
228 * single string with names separated by a single
229 * blank space.
231 * @return true on success, false on error.
232 * @see createModify()
233 * @access public
235 function add($p_filelist)
237 return $this->addModify($p_filelist, '', '');
239 // }}}
241 // {{{ extract()
242 function extract($p_path='', $p_preserve=false)
244 return $this->extractModify($p_path, '', $p_preserve);
246 // }}}
248 // {{{ listContent()
249 function listContent()
251 $v_list_detail = array();
253 if ($this->_openRead()) {
254 if (!$this->_extractList('', $v_list_detail, "list", '', '')) {
255 unset($v_list_detail);
256 $v_list_detail = 0;
258 $this->_close();
261 return $v_list_detail;
263 // }}}
265 // {{{ createModify()
267 * This method creates the archive file and add the files / directories
268 * that are listed in $p_filelist.
269 * If the file already exists and is writable, it is replaced by the
270 * new tar. It is a create and not an add. If the file exists and is
271 * read-only or is a directory it is not replaced. The method return
272 * false and a PEAR error text.
273 * The $p_filelist parameter can be an array of string, each string
274 * representing a filename or a directory name with their path if
275 * needed. It can also be a single string with names separated by a
276 * single blank.
277 * The path indicated in $p_remove_dir will be removed from the
278 * memorized path of each file / directory listed when this path
279 * exists. By default nothing is removed (empty path '')
280 * The path indicated in $p_add_dir will be added at the beginning of
281 * the memorized path of each file / directory listed. However it can
282 * be set to empty ''. The adding of a path is done after the removing
283 * of path.
284 * The path add/remove ability enables the user to prepare an archive
285 * for extraction in a different path than the origin files are.
286 * See also addModify() method for file adding properties.
288 * @param array $p_filelist An array of filenames and directory names,
289 * or a single string with names separated by
290 * a single blank space.
291 * @param string $p_add_dir A string which contains a path to be added
292 * to the memorized path of each element in
293 * the list.
294 * @param string $p_remove_dir A string which contains a path to be
295 * removed from the memorized path of each
296 * element in the list, when relevant.
298 * @return boolean true on success, false on error.
299 * @access public
300 * @see addModify()
302 function createModify($p_filelist, $p_add_dir, $p_remove_dir='')
304 $v_result = true;
306 if (!$this->_openWrite())
307 return false;
309 if ($p_filelist != '') {
310 if (is_array($p_filelist))
311 $v_list = $p_filelist;
312 elseif (is_string($p_filelist))
313 $v_list = explode($this->_separator, $p_filelist);
314 else {
315 $this->_cleanFile();
316 $this->_error('Invalid file list');
317 return false;
320 $v_result = $this->_addList($v_list, $p_add_dir, $p_remove_dir);
323 if ($v_result) {
324 $this->_writeFooter();
325 $this->_close();
326 } else
327 $this->_cleanFile();
329 return $v_result;
331 // }}}
333 // {{{ addModify()
335 * This method add the files / directories listed in $p_filelist at the
336 * end of the existing archive. If the archive does not yet exists it
337 * is created.
338 * The $p_filelist parameter can be an array of string, each string
339 * representing a filename or a directory name with their path if
340 * needed. It can also be a single string with names separated by a
341 * single blank.
342 * The path indicated in $p_remove_dir will be removed from the
343 * memorized path of each file / directory listed when this path
344 * exists. By default nothing is removed (empty path '')
345 * The path indicated in $p_add_dir will be added at the beginning of
346 * the memorized path of each file / directory listed. However it can
347 * be set to empty ''. The adding of a path is done after the removing
348 * of path.
349 * The path add/remove ability enables the user to prepare an archive
350 * for extraction in a different path than the origin files are.
351 * If a file/dir is already in the archive it will only be added at the
352 * end of the archive. There is no update of the existing archived
353 * file/dir. However while extracting the archive, the last file will
354 * replace the first one. This results in a none optimization of the
355 * archive size.
356 * If a file/dir does not exist the file/dir is ignored. However an
357 * error text is send to PEAR error.
358 * If a file/dir is not readable the file/dir is ignored. However an
359 * error text is send to PEAR error.
361 * @param array $p_filelist An array of filenames and directory
362 * names, or a single string with names
363 * separated by a single blank space.
364 * @param string $p_add_dir A string which contains a path to be
365 * added to the memorized path of each
366 * element in the list.
367 * @param string $p_remove_dir A string which contains a path to be
368 * removed from the memorized path of
369 * each element in the list, when
370 * relevant.
372 * @return true on success, false on error.
373 * @access public
375 function addModify($p_filelist, $p_add_dir, $p_remove_dir='')
377 $v_result = true;
379 if (!$this->_isArchive())
380 $v_result = $this->createModify($p_filelist, $p_add_dir,
381 $p_remove_dir);
382 else {
383 if (is_array($p_filelist))
384 $v_list = $p_filelist;
385 elseif (is_string($p_filelist))
386 $v_list = explode($this->_separator, $p_filelist);
387 else {
388 $this->_error('Invalid file list');
389 return false;
392 $v_result = $this->_append($v_list, $p_add_dir, $p_remove_dir);
395 return $v_result;
397 // }}}
399 // {{{ addString()
401 * This method add a single string as a file at the
402 * end of the existing archive. If the archive does not yet exists it
403 * is created.
405 * @param string $p_filename A string which contains the full
406 * filename path that will be associated
407 * with the string.
408 * @param string $p_string The content of the file added in
409 * the archive.
410 * @param int $p_datetime A custom date/time (unix timestamp)
411 * for the file (optional).
413 * @return true on success, false on error.
414 * @access public
416 function addString($p_filename, $p_string, $p_datetime = false)
418 $v_result = true;
420 if (!$this->_isArchive()) {
421 if (!$this->_openWrite()) {
422 return false;
424 $this->_close();
427 if (!$this->_openAppend())
428 return false;
430 // Need to check the get back to the temporary file ? ....
431 $v_result = $this->_addString($p_filename, $p_string, $p_datetime);
433 $this->_writeFooter();
435 $this->_close();
437 return $v_result;
439 // }}}
441 // {{{ extractModify()
443 * This method extract all the content of the archive in the directory
444 * indicated by $p_path. When relevant the memorized path of the
445 * files/dir can be modified by removing the $p_remove_path path at the
446 * beginning of the file/dir path.
447 * While extracting a file, if the directory path does not exists it is
448 * created.
449 * While extracting a file, if the file already exists it is replaced
450 * without looking for last modification date.
451 * While extracting a file, if the file already exists and is write
452 * protected, the extraction is aborted.
453 * While extracting a file, if a directory with the same name already
454 * exists, the extraction is aborted.
455 * While extracting a directory, if a file with the same name already
456 * exists, the extraction is aborted.
457 * While extracting a file/directory if the destination directory exist
458 * and is write protected, or does not exist but can not be created,
459 * the extraction is aborted.
460 * If after extraction an extracted file does not show the correct
461 * stored file size, the extraction is aborted.
462 * When the extraction is aborted, a PEAR error text is set and false
463 * is returned. However the result can be a partial extraction that may
464 * need to be manually cleaned.
466 * @param string $p_path The path of the directory where the
467 * files/dir need to by extracted.
468 * @param string $p_remove_path Part of the memorized path that can be
469 * removed if present at the beginning of
470 * the file/dir path.
471 * @param boolean $p_preserve Preserve user/group ownership of files
473 * @return boolean true on success, false on error.
474 * @access public
475 * @see extractList()
477 function extractModify($p_path, $p_remove_path, $p_preserve=false)
479 $v_result = true;
480 $v_list_detail = array();
482 if ($v_result = $this->_openRead()) {
483 $v_result = $this->_extractList($p_path, $v_list_detail,
484 "complete", 0, $p_remove_path, $p_preserve);
485 $this->_close();
488 return $v_result;
490 // }}}
492 // {{{ extractInString()
494 * This method extract from the archive one file identified by $p_filename.
495 * The return value is a string with the file content, or NULL on error.
497 * @param string $p_filename The path of the file to extract in a string.
499 * @return a string with the file content or NULL.
500 * @access public
502 function extractInString($p_filename)
504 if ($this->_openRead()) {
505 $v_result = $this->_extractInString($p_filename);
506 $this->_close();
507 } else {
508 $v_result = null;
511 return $v_result;
513 // }}}
515 // {{{ extractList()
517 * This method extract from the archive only the files indicated in the
518 * $p_filelist. These files are extracted in the current directory or
519 * in the directory indicated by the optional $p_path parameter.
520 * If indicated the $p_remove_path can be used in the same way as it is
521 * used in extractModify() method.
523 * @param array $p_filelist An array of filenames and directory names,
524 * or a single string with names separated
525 * by a single blank space.
526 * @param string $p_path The path of the directory where the
527 * files/dir need to by extracted.
528 * @param string $p_remove_path Part of the memorized path that can be
529 * removed if present at the beginning of
530 * the file/dir path.
531 * @param boolean $p_preserve Preserve user/group ownership of files
533 * @return true on success, false on error.
534 * @access public
535 * @see extractModify()
537 function extractList($p_filelist, $p_path='', $p_remove_path='', $p_preserve=false)
539 $v_result = true;
540 $v_list_detail = array();
542 if (is_array($p_filelist))
543 $v_list = $p_filelist;
544 elseif (is_string($p_filelist))
545 $v_list = explode($this->_separator, $p_filelist);
546 else {
547 $this->_error('Invalid string list');
548 return false;
551 if ($v_result = $this->_openRead()) {
552 $v_result = $this->_extractList($p_path, $v_list_detail, "partial",
553 $v_list, $p_remove_path, $p_preserve);
554 $this->_close();
557 return $v_result;
559 // }}}
561 // {{{ setAttribute()
563 * This method set specific attributes of the archive. It uses a variable
564 * list of parameters, in the format attribute code + attribute values :
565 * $arch->setAttribute(ARCHIVE_TAR_ATT_SEPARATOR, ',');
567 * @param mixed $argv variable list of attributes and values
569 * @return true on success, false on error.
570 * @access public
572 function setAttribute()
574 $v_result = true;
576 // ----- Get the number of variable list of arguments
577 if (($v_size = func_num_args()) == 0) {
578 return true;
581 // ----- Get the arguments
582 $v_att_list = &func_get_args();
584 // ----- Read the attributes
585 $i=0;
586 while ($i<$v_size) {
588 // ----- Look for next option
589 switch ($v_att_list[$i]) {
590 // ----- Look for options that request a string value
591 case ARCHIVE_TAR_ATT_SEPARATOR :
592 // ----- Check the number of parameters
593 if (($i+1) >= $v_size) {
594 $this->_error('Invalid number of parameters for '
595 .'attribute ARCHIVE_TAR_ATT_SEPARATOR');
596 return false;
599 // ----- Get the value
600 $this->_separator = $v_att_list[$i+1];
601 $i++;
602 break;
604 default :
605 $this->_error('Unknow attribute code '.$v_att_list[$i].'');
606 return false;
609 // ----- Next attribute
610 $i++;
613 return $v_result;
615 // }}}
617 // {{{ setIgnoreRegexp()
619 * This method sets the regular expression for ignoring files and directories
620 * at import, for example:
621 * $arch->setIgnoreRegexp("#CVS|\.svn#");
623 * @param string $regexp regular expression defining which files or directories to ignore
625 * @access public
627 function setIgnoreRegexp($regexp)
629 $this->_ignore_regexp = $regexp;
631 // }}}
633 // {{{ setIgnoreList()
635 * This method sets the regular expression for ignoring all files and directories
636 * matching the filenames in the array list at import, for example:
637 * $arch->setIgnoreList(array('CVS', '.svn', 'bin/tool'));
639 * @param array $list a list of file or directory names to ignore
641 * @access public
643 function setIgnoreList($list)
645 $regexp = str_replace(array('#', '.', '^', '$'), array('\#', '\.', '\^', '\$'), $list);
646 $regexp = '#/'.join('$|/', $list).'#';
647 $this->setIgnoreRegexp($regexp);
649 // }}}
651 // {{{ _error()
652 function _error($p_message)
654 $this->error_object = &$this->raiseError($p_message);
656 // }}}
658 // {{{ _warning()
659 function _warning($p_message)
661 $this->error_object = &$this->raiseError($p_message);
663 // }}}
665 // {{{ _isArchive()
666 function _isArchive($p_filename=null)
668 if ($p_filename == null) {
669 $p_filename = $this->_tarname;
671 clearstatcache();
672 return @is_file($p_filename) && !@is_link($p_filename);
674 // }}}
676 // {{{ _openWrite()
677 function _openWrite()
679 if ($this->_compress_type == 'gz' && function_exists('gzopen'))
680 $this->_file = @gzopen($this->_tarname, "wb9");
681 else if ($this->_compress_type == 'bz2' && function_exists('bzopen'))
682 $this->_file = @bzopen($this->_tarname, "w");
683 else if ($this->_compress_type == 'none')
684 $this->_file = @fopen($this->_tarname, "wb");
685 else {
686 $this->_error('Unknown or missing compression type ('
687 .$this->_compress_type.')');
688 return false;
691 if ($this->_file == 0) {
692 $this->_error('Unable to open in write mode \''
693 .$this->_tarname.'\'');
694 return false;
697 return true;
699 // }}}
701 // {{{ _openRead()
702 function _openRead()
704 if (strtolower(substr($this->_tarname, 0, 7)) == 'http://') {
706 // ----- Look if a local copy need to be done
707 if ($this->_temp_tarname == '') {
708 $this->_temp_tarname = uniqid('tar').'.tmp';
709 if (!$v_file_from = @fopen($this->_tarname, 'rb')) {
710 $this->_error('Unable to open in read mode \''
711 .$this->_tarname.'\'');
712 $this->_temp_tarname = '';
713 return false;
715 if (!$v_file_to = @fopen($this->_temp_tarname, 'wb')) {
716 $this->_error('Unable to open in write mode \''
717 .$this->_temp_tarname.'\'');
718 $this->_temp_tarname = '';
719 return false;
721 while ($v_data = @fread($v_file_from, 1024))
722 @fwrite($v_file_to, $v_data);
723 @fclose($v_file_from);
724 @fclose($v_file_to);
727 // ----- File to open if the local copy
728 $v_filename = $this->_temp_tarname;
730 } else
731 // ----- File to open if the normal Tar file
732 $v_filename = $this->_tarname;
734 if ($this->_compress_type == 'gz' && function_exists('gzopen'))
735 $this->_file = @gzopen($v_filename, "rb");
736 else if ($this->_compress_type == 'bz2' && function_exists('bzopen'))
737 $this->_file = @bzopen($v_filename, "r");
738 else if ($this->_compress_type == 'none')
739 $this->_file = @fopen($v_filename, "rb");
740 else {
741 $this->_error('Unknown or missing compression type ('
742 .$this->_compress_type.')');
743 return false;
746 if ($this->_file == 0) {
747 $this->_error('Unable to open in read mode \''.$v_filename.'\'');
748 return false;
751 return true;
753 // }}}
755 // {{{ _openReadWrite()
756 function _openReadWrite()
758 if ($this->_compress_type == 'gz')
759 $this->_file = @gzopen($this->_tarname, "r+b");
760 else if ($this->_compress_type == 'bz2') {
761 $this->_error('Unable to open bz2 in read/write mode \''
762 .$this->_tarname.'\' (limitation of bz2 extension)');
763 return false;
764 } else if ($this->_compress_type == 'none')
765 $this->_file = @fopen($this->_tarname, "r+b");
766 else {
767 $this->_error('Unknown or missing compression type ('
768 .$this->_compress_type.')');
769 return false;
772 if ($this->_file == 0) {
773 $this->_error('Unable to open in read/write mode \''
774 .$this->_tarname.'\'');
775 return false;
778 return true;
780 // }}}
782 // {{{ _close()
783 function _close()
785 //if (isset($this->_file)) {
786 if (is_resource($this->_file)) {
787 if ($this->_compress_type == 'gz')
788 @gzclose($this->_file);
789 else if ($this->_compress_type == 'bz2')
790 @bzclose($this->_file);
791 else if ($this->_compress_type == 'none')
792 @fclose($this->_file);
793 else
794 $this->_error('Unknown or missing compression type ('
795 .$this->_compress_type.')');
797 $this->_file = 0;
800 // ----- Look if a local copy need to be erase
801 // Note that it might be interesting to keep the url for a time : ToDo
802 if ($this->_temp_tarname != '') {
803 @unlink($this->_temp_tarname);
804 $this->_temp_tarname = '';
807 return true;
809 // }}}
811 // {{{ _cleanFile()
812 function _cleanFile()
814 $this->_close();
816 // ----- Look for a local copy
817 if ($this->_temp_tarname != '') {
818 // ----- Remove the local copy but not the remote tarname
819 @unlink($this->_temp_tarname);
820 $this->_temp_tarname = '';
821 } else {
822 // ----- Remove the local tarname file
823 @unlink($this->_tarname);
825 $this->_tarname = '';
827 return true;
829 // }}}
831 // {{{ _writeBlock()
832 function _writeBlock($p_binary_data, $p_len=null)
834 if (is_resource($this->_file)) {
835 if ($p_len === null) {
836 if ($this->_compress_type == 'gz')
837 @gzputs($this->_file, $p_binary_data);
838 else if ($this->_compress_type == 'bz2')
839 @bzwrite($this->_file, $p_binary_data);
840 else if ($this->_compress_type == 'none')
841 @fputs($this->_file, $p_binary_data);
842 else
843 $this->_error('Unknown or missing compression type ('
844 .$this->_compress_type.')');
845 } else {
846 if ($this->_compress_type == 'gz')
847 @gzputs($this->_file, $p_binary_data, $p_len);
848 else if ($this->_compress_type == 'bz2')
849 @bzwrite($this->_file, $p_binary_data, $p_len);
850 else if ($this->_compress_type == 'none')
851 @fputs($this->_file, $p_binary_data, $p_len);
852 else
853 $this->_error('Unknown or missing compression type ('
854 .$this->_compress_type.')');
858 return true;
860 // }}}
862 // {{{ _readBlock()
863 function _readBlock()
865 $v_block = null;
866 if (is_resource($this->_file)) {
867 if ($this->_compress_type == 'gz')
868 $v_block = @gzread($this->_file, 512);
869 else if ($this->_compress_type == 'bz2')
870 $v_block = @bzread($this->_file, 512);
871 else if ($this->_compress_type == 'none')
872 $v_block = @fread($this->_file, 512);
873 else
874 $this->_error('Unknown or missing compression type ('
875 .$this->_compress_type.')');
877 return $v_block;
879 // }}}
881 // {{{ _jumpBlock()
882 function _jumpBlock($p_len=null)
884 if (is_resource($this->_file)) {
885 if ($p_len === null)
886 $p_len = 1;
888 if ($this->_compress_type == 'gz') {
889 @gzseek($this->_file, gztell($this->_file)+($p_len*512));
891 else if ($this->_compress_type == 'bz2') {
892 // ----- Replace missing bztell() and bzseek()
893 for ($i=0; $i<$p_len; $i++)
894 $this->_readBlock();
895 } else if ($this->_compress_type == 'none')
896 @fseek($this->_file, $p_len*512, SEEK_CUR);
897 else
898 $this->_error('Unknown or missing compression type ('
899 .$this->_compress_type.')');
902 return true;
904 // }}}
906 // {{{ _writeFooter()
907 function _writeFooter()
909 if (is_resource($this->_file)) {
910 // ----- Write the last 0 filled block for end of archive
911 $v_binary_data = pack('a1024', '');
912 $this->_writeBlock($v_binary_data);
914 return true;
916 // }}}
918 // {{{ _addList()
919 function _addList($p_list, $p_add_dir, $p_remove_dir)
921 $v_result=true;
922 $v_header = array();
924 // ----- Remove potential windows directory separator
925 $p_add_dir = $this->_translateWinPath($p_add_dir);
926 $p_remove_dir = $this->_translateWinPath($p_remove_dir, false);
928 if (!$this->_file) {
929 $this->_error('Invalid file descriptor');
930 return false;
933 if (sizeof($p_list) == 0)
934 return true;
936 foreach ($p_list as $v_filename) {
937 if (!$v_result) {
938 break;
941 // ----- Skip the current tar name
942 if ($v_filename == $this->_tarname)
943 continue;
945 if ($v_filename == '')
946 continue;
948 // ----- ignore files and directories matching the ignore regular expression
949 if ($this->_ignore_regexp && preg_match($this->_ignore_regexp, '/'.$v_filename)) {
950 $this->_warning("File '$v_filename' ignored");
951 continue;
954 if (!file_exists($v_filename) && !is_link($v_filename)) {
955 $this->_warning("File '$v_filename' does not exist");
956 continue;
959 // ----- Add the file or directory header
960 if (!$this->_addFile($v_filename, $v_header, $p_add_dir, $p_remove_dir))
961 return false;
963 if (@is_dir($v_filename) && !@is_link($v_filename)) {
964 if (!($p_hdir = opendir($v_filename))) {
965 $this->_warning("Directory '$v_filename' can not be read");
966 continue;
968 while (false !== ($p_hitem = readdir($p_hdir))) {
969 if (($p_hitem != '.') && ($p_hitem != '..')) {
970 if ($v_filename != ".")
971 $p_temp_list[0] = $v_filename.'/'.$p_hitem;
972 else
973 $p_temp_list[0] = $p_hitem;
975 $v_result = $this->_addList($p_temp_list,
976 $p_add_dir,
977 $p_remove_dir);
981 unset($p_temp_list);
982 unset($p_hdir);
983 unset($p_hitem);
987 return $v_result;
989 // }}}
991 // {{{ _addFile()
992 function _addFile($p_filename, &$p_header, $p_add_dir, $p_remove_dir)
994 if (!$this->_file) {
995 $this->_error('Invalid file descriptor');
996 return false;
999 if ($p_filename == '') {
1000 $this->_error('Invalid file name');
1001 return false;
1004 // ----- Calculate the stored filename
1005 $p_filename = $this->_translateWinPath($p_filename, false);;
1006 $v_stored_filename = $p_filename;
1007 if (strcmp($p_filename, $p_remove_dir) == 0) {
1008 return true;
1010 if ($p_remove_dir != '') {
1011 if (substr($p_remove_dir, -1) != '/')
1012 $p_remove_dir .= '/';
1014 if (substr($p_filename, 0, strlen($p_remove_dir)) == $p_remove_dir)
1015 $v_stored_filename = substr($p_filename, strlen($p_remove_dir));
1017 $v_stored_filename = $this->_translateWinPath($v_stored_filename);
1018 if ($p_add_dir != '') {
1019 if (substr($p_add_dir, -1) == '/')
1020 $v_stored_filename = $p_add_dir.$v_stored_filename;
1021 else
1022 $v_stored_filename = $p_add_dir.'/'.$v_stored_filename;
1025 $v_stored_filename = $this->_pathReduction($v_stored_filename);
1027 if ($this->_isArchive($p_filename)) {
1028 if (($v_file = @fopen($p_filename, "rb")) == 0) {
1029 $this->_warning("Unable to open file '".$p_filename
1030 ."' in binary read mode");
1031 return true;
1034 if (!$this->_writeHeader($p_filename, $v_stored_filename))
1035 return false;
1037 while (($v_buffer = fread($v_file, 512)) != '') {
1038 $v_binary_data = pack("a512", "$v_buffer");
1039 $this->_writeBlock($v_binary_data);
1042 fclose($v_file);
1044 } else {
1045 // ----- Only header for dir
1046 if (!$this->_writeHeader($p_filename, $v_stored_filename))
1047 return false;
1050 return true;
1052 // }}}
1054 // {{{ _addString()
1055 function _addString($p_filename, $p_string, $p_datetime = false)
1057 if (!$this->_file) {
1058 $this->_error('Invalid file descriptor');
1059 return false;
1062 if ($p_filename == '') {
1063 $this->_error('Invalid file name');
1064 return false;
1067 // ----- Calculate the stored filename
1068 $p_filename = $this->_translateWinPath($p_filename, false);;
1070 // ----- If datetime is not specified, set current time
1071 if ($p_datetime === false) {
1072 $p_datetime = time();
1075 if (!$this->_writeHeaderBlock($p_filename, strlen($p_string),
1076 $p_datetime, 384, "", 0, 0))
1077 return false;
1079 $i=0;
1080 while (($v_buffer = substr($p_string, (($i++)*512), 512)) != '') {
1081 $v_binary_data = pack("a512", $v_buffer);
1082 $this->_writeBlock($v_binary_data);
1085 return true;
1087 // }}}
1089 // {{{ _writeHeader()
1090 function _writeHeader($p_filename, $p_stored_filename)
1092 if ($p_stored_filename == '')
1093 $p_stored_filename = $p_filename;
1094 $v_reduce_filename = $this->_pathReduction($p_stored_filename);
1096 if (strlen($v_reduce_filename) > 99) {
1097 if (!$this->_writeLongHeader($v_reduce_filename))
1098 return false;
1101 $v_info = lstat($p_filename);
1102 $v_uid = sprintf("%07s", DecOct($v_info[4]));
1103 $v_gid = sprintf("%07s", DecOct($v_info[5]));
1104 $v_perms = sprintf("%07s", DecOct($v_info['mode'] & 000777));
1106 $v_mtime = sprintf("%011s", DecOct($v_info['mtime']));
1108 $v_linkname = '';
1110 if (@is_link($p_filename)) {
1111 $v_typeflag = '2';
1112 $v_linkname = readlink($p_filename);
1113 $v_size = sprintf("%011s", DecOct(0));
1114 } elseif (@is_dir($p_filename)) {
1115 $v_typeflag = "5";
1116 $v_size = sprintf("%011s", DecOct(0));
1117 } else {
1118 $v_typeflag = '0';
1119 clearstatcache();
1120 $v_size = sprintf("%011s", DecOct($v_info['size']));
1123 $v_magic = 'ustar ';
1125 $v_version = ' ';
1127 if (function_exists('posix_getpwuid'))
1129 $userinfo = posix_getpwuid($v_info[4]);
1130 $groupinfo = posix_getgrgid($v_info[5]);
1132 $v_uname = $userinfo['name'];
1133 $v_gname = $groupinfo['name'];
1135 else
1137 $v_uname = '';
1138 $v_gname = '';
1141 $v_devmajor = '';
1143 $v_devminor = '';
1145 $v_prefix = '';
1147 $v_binary_data_first = pack("a100a8a8a8a12a12",
1148 $v_reduce_filename, $v_perms, $v_uid,
1149 $v_gid, $v_size, $v_mtime);
1150 $v_binary_data_last = pack("a1a100a6a2a32a32a8a8a155a12",
1151 $v_typeflag, $v_linkname, $v_magic,
1152 $v_version, $v_uname, $v_gname,
1153 $v_devmajor, $v_devminor, $v_prefix, '');
1155 // ----- Calculate the checksum
1156 $v_checksum = 0;
1157 // ..... First part of the header
1158 for ($i=0; $i<148; $i++)
1159 $v_checksum += ord(substr($v_binary_data_first,$i,1));
1160 // ..... Ignore the checksum value and replace it by ' ' (space)
1161 for ($i=148; $i<156; $i++)
1162 $v_checksum += ord(' ');
1163 // ..... Last part of the header
1164 for ($i=156, $j=0; $i<512; $i++, $j++)
1165 $v_checksum += ord(substr($v_binary_data_last,$j,1));
1167 // ----- Write the first 148 bytes of the header in the archive
1168 $this->_writeBlock($v_binary_data_first, 148);
1170 // ----- Write the calculated checksum
1171 $v_checksum = sprintf("%06s ", DecOct($v_checksum));
1172 $v_binary_data = pack("a8", $v_checksum);
1173 $this->_writeBlock($v_binary_data, 8);
1175 // ----- Write the last 356 bytes of the header in the archive
1176 $this->_writeBlock($v_binary_data_last, 356);
1178 return true;
1180 // }}}
1182 // {{{ _writeHeaderBlock()
1183 function _writeHeaderBlock($p_filename, $p_size, $p_mtime=0, $p_perms=0,
1184 $p_type='', $p_uid=0, $p_gid=0)
1186 $p_filename = $this->_pathReduction($p_filename);
1188 if (strlen($p_filename) > 99) {
1189 if (!$this->_writeLongHeader($p_filename))
1190 return false;
1193 if ($p_type == "5") {
1194 $v_size = sprintf("%011s", DecOct(0));
1195 } else {
1196 $v_size = sprintf("%011s", DecOct($p_size));
1199 $v_uid = sprintf("%07s", DecOct($p_uid));
1200 $v_gid = sprintf("%07s", DecOct($p_gid));
1201 $v_perms = sprintf("%07s", DecOct($p_perms & 000777));
1203 $v_mtime = sprintf("%11s", DecOct($p_mtime));
1205 $v_linkname = '';
1207 $v_magic = 'ustar ';
1209 $v_version = ' ';
1211 if (function_exists('posix_getpwuid'))
1213 $userinfo = posix_getpwuid($p_uid);
1214 $groupinfo = posix_getgrgid($p_gid);
1216 $v_uname = $userinfo['name'];
1217 $v_gname = $groupinfo['name'];
1219 else
1221 $v_uname = '';
1222 $v_gname = '';
1225 $v_devmajor = '';
1227 $v_devminor = '';
1229 $v_prefix = '';
1231 $v_binary_data_first = pack("a100a8a8a8a12A12",
1232 $p_filename, $v_perms, $v_uid, $v_gid,
1233 $v_size, $v_mtime);
1234 $v_binary_data_last = pack("a1a100a6a2a32a32a8a8a155a12",
1235 $p_type, $v_linkname, $v_magic,
1236 $v_version, $v_uname, $v_gname,
1237 $v_devmajor, $v_devminor, $v_prefix, '');
1239 // ----- Calculate the checksum
1240 $v_checksum = 0;
1241 // ..... First part of the header
1242 for ($i=0; $i<148; $i++)
1243 $v_checksum += ord(substr($v_binary_data_first,$i,1));
1244 // ..... Ignore the checksum value and replace it by ' ' (space)
1245 for ($i=148; $i<156; $i++)
1246 $v_checksum += ord(' ');
1247 // ..... Last part of the header
1248 for ($i=156, $j=0; $i<512; $i++, $j++)
1249 $v_checksum += ord(substr($v_binary_data_last,$j,1));
1251 // ----- Write the first 148 bytes of the header in the archive
1252 $this->_writeBlock($v_binary_data_first, 148);
1254 // ----- Write the calculated checksum
1255 $v_checksum = sprintf("%06s ", DecOct($v_checksum));
1256 $v_binary_data = pack("a8", $v_checksum);
1257 $this->_writeBlock($v_binary_data, 8);
1259 // ----- Write the last 356 bytes of the header in the archive
1260 $this->_writeBlock($v_binary_data_last, 356);
1262 return true;
1264 // }}}
1266 // {{{ _writeLongHeader()
1267 function _writeLongHeader($p_filename)
1269 $v_size = sprintf("%11s ", DecOct(strlen($p_filename)));
1271 $v_typeflag = 'L';
1273 $v_linkname = '';
1275 $v_magic = '';
1277 $v_version = '';
1279 $v_uname = '';
1281 $v_gname = '';
1283 $v_devmajor = '';
1285 $v_devminor = '';
1287 $v_prefix = '';
1289 $v_binary_data_first = pack("a100a8a8a8a12a12",
1290 '././@LongLink', 0, 0, 0, $v_size, 0);
1291 $v_binary_data_last = pack("a1a100a6a2a32a32a8a8a155a12",
1292 $v_typeflag, $v_linkname, $v_magic,
1293 $v_version, $v_uname, $v_gname,
1294 $v_devmajor, $v_devminor, $v_prefix, '');
1296 // ----- Calculate the checksum
1297 $v_checksum = 0;
1298 // ..... First part of the header
1299 for ($i=0; $i<148; $i++)
1300 $v_checksum += ord(substr($v_binary_data_first,$i,1));
1301 // ..... Ignore the checksum value and replace it by ' ' (space)
1302 for ($i=148; $i<156; $i++)
1303 $v_checksum += ord(' ');
1304 // ..... Last part of the header
1305 for ($i=156, $j=0; $i<512; $i++, $j++)
1306 $v_checksum += ord(substr($v_binary_data_last,$j,1));
1308 // ----- Write the first 148 bytes of the header in the archive
1309 $this->_writeBlock($v_binary_data_first, 148);
1311 // ----- Write the calculated checksum
1312 $v_checksum = sprintf("%06s ", DecOct($v_checksum));
1313 $v_binary_data = pack("a8", $v_checksum);
1314 $this->_writeBlock($v_binary_data, 8);
1316 // ----- Write the last 356 bytes of the header in the archive
1317 $this->_writeBlock($v_binary_data_last, 356);
1319 // ----- Write the filename as content of the block
1320 $i=0;
1321 while (($v_buffer = substr($p_filename, (($i++)*512), 512)) != '') {
1322 $v_binary_data = pack("a512", "$v_buffer");
1323 $this->_writeBlock($v_binary_data);
1326 return true;
1328 // }}}
1330 // {{{ _readHeader()
1331 function _readHeader($v_binary_data, &$v_header)
1333 if (strlen($v_binary_data)==0) {
1334 $v_header['filename'] = '';
1335 return true;
1338 if (strlen($v_binary_data) != 512) {
1339 $v_header['filename'] = '';
1340 $this->_error('Invalid block size : '.strlen($v_binary_data));
1341 return false;
1344 if (!is_array($v_header)) {
1345 $v_header = array();
1347 // ----- Calculate the checksum
1348 $v_checksum = 0;
1349 // ..... First part of the header
1350 for ($i=0; $i<148; $i++)
1351 $v_checksum+=ord(substr($v_binary_data,$i,1));
1352 // ..... Ignore the checksum value and replace it by ' ' (space)
1353 for ($i=148; $i<156; $i++)
1354 $v_checksum += ord(' ');
1355 // ..... Last part of the header
1356 for ($i=156; $i<512; $i++)
1357 $v_checksum+=ord(substr($v_binary_data,$i,1));
1359 if (version_compare(PHP_VERSION,"5.5.0-dev")<0) {
1360 $fmt = "a100filename/a8mode/a8uid/a8gid/a12size/a12mtime/" .
1361 "a8checksum/a1typeflag/a100link/a6magic/a2version/" .
1362 "a32uname/a32gname/a8devmajor/a8devminor/a131prefix";
1363 } else {
1364 $fmt = "Z100filename/Z8mode/Z8uid/Z8gid/Z12size/Z12mtime/" .
1365 "Z8checksum/Z1typeflag/Z100link/Z6magic/Z2version/" .
1366 "Z32uname/Z32gname/Z8devmajor/Z8devminor/Z131prefix";
1368 $v_data = unpack($fmt, $v_binary_data);
1370 if (strlen($v_data["prefix"]) > 0) {
1371 $v_data["filename"] = "$v_data[prefix]/$v_data[filename]";
1374 // ----- Extract the checksum
1375 $v_header['checksum'] = OctDec(trim($v_data['checksum']));
1376 if ($v_header['checksum'] != $v_checksum) {
1377 $v_header['filename'] = '';
1379 // ----- Look for last block (empty block)
1380 if (($v_checksum == 256) && ($v_header['checksum'] == 0))
1381 return true;
1383 $this->_error('Invalid checksum for file "'.$v_data['filename']
1384 .'" : '.$v_checksum.' calculated, '
1385 .$v_header['checksum'].' expected');
1386 return false;
1389 // ----- Extract the properties
1390 $v_header['filename'] = $v_data['filename'];
1391 if ($this->_maliciousFilename($v_header['filename'])) {
1392 $this->_error('Malicious .tar detected, file "' . $v_header['filename'] .
1393 '" will not install in desired directory tree');
1394 return false;
1396 $v_header['mode'] = OctDec(trim($v_data['mode']));
1397 $v_header['uid'] = OctDec(trim($v_data['uid']));
1398 $v_header['gid'] = OctDec(trim($v_data['gid']));
1399 $v_header['size'] = OctDec(trim($v_data['size']));
1400 $v_header['mtime'] = OctDec(trim($v_data['mtime']));
1401 if (($v_header['typeflag'] = $v_data['typeflag']) == "5") {
1402 $v_header['size'] = 0;
1404 $v_header['link'] = trim($v_data['link']);
1405 /* ----- All these fields are removed form the header because
1406 they do not carry interesting info
1407 $v_header[magic] = trim($v_data[magic]);
1408 $v_header[version] = trim($v_data[version]);
1409 $v_header[uname] = trim($v_data[uname]);
1410 $v_header[gname] = trim($v_data[gname]);
1411 $v_header[devmajor] = trim($v_data[devmajor]);
1412 $v_header[devminor] = trim($v_data[devminor]);
1415 return true;
1417 // }}}
1419 // {{{ _maliciousFilename()
1421 * Detect and report a malicious file name
1423 * @param string $file
1425 * @return bool
1426 * @access private
1428 function _maliciousFilename($file)
1430 if (strpos($file, '/../') !== false) {
1431 return true;
1433 if (strpos($file, '../') === 0) {
1434 return true;
1436 return false;
1438 // }}}
1440 // {{{ _readLongHeader()
1441 function _readLongHeader(&$v_header)
1443 $v_filename = '';
1444 $n = floor($v_header['size']/512);
1445 for ($i=0; $i<$n; $i++) {
1446 $v_content = $this->_readBlock();
1447 $v_filename .= $v_content;
1449 if (($v_header['size'] % 512) != 0) {
1450 $v_content = $this->_readBlock();
1451 $v_filename .= trim($v_content);
1454 // ----- Read the next header
1455 $v_binary_data = $this->_readBlock();
1457 if (!$this->_readHeader($v_binary_data, $v_header))
1458 return false;
1460 $v_filename = trim($v_filename);
1461 $v_header['filename'] = $v_filename;
1462 if ($this->_maliciousFilename($v_filename)) {
1463 $this->_error('Malicious .tar detected, file "' . $v_filename .
1464 '" will not install in desired directory tree');
1465 return false;
1468 return true;
1470 // }}}
1472 // {{{ _extractInString()
1474 * This method extract from the archive one file identified by $p_filename.
1475 * The return value is a string with the file content, or null on error.
1477 * @param string $p_filename The path of the file to extract in a string.
1479 * @return a string with the file content or null.
1480 * @access private
1482 function _extractInString($p_filename)
1484 $v_result_str = "";
1486 While (strlen($v_binary_data = $this->_readBlock()) != 0)
1488 if (!$this->_readHeader($v_binary_data, $v_header))
1489 return null;
1491 if ($v_header['filename'] == '')
1492 continue;
1494 // ----- Look for long filename
1495 if ($v_header['typeflag'] == 'L') {
1496 if (!$this->_readLongHeader($v_header))
1497 return null;
1500 if ($v_header['filename'] == $p_filename) {
1501 if ($v_header['typeflag'] == "5") {
1502 $this->_error('Unable to extract in string a directory '
1503 .'entry {'.$v_header['filename'].'}');
1504 return null;
1505 } else {
1506 $n = floor($v_header['size']/512);
1507 for ($i=0; $i<$n; $i++) {
1508 $v_result_str .= $this->_readBlock();
1510 if (($v_header['size'] % 512) != 0) {
1511 $v_content = $this->_readBlock();
1512 $v_result_str .= substr($v_content, 0,
1513 ($v_header['size'] % 512));
1515 return $v_result_str;
1517 } else {
1518 $this->_jumpBlock(ceil(($v_header['size']/512)));
1522 return null;
1524 // }}}
1526 // {{{ _extractList()
1527 function _extractList($p_path, &$p_list_detail, $p_mode,
1528 $p_file_list, $p_remove_path, $p_preserve=false)
1530 $v_result=true;
1531 $v_nb = 0;
1532 $v_extract_all = true;
1533 $v_listing = false;
1535 $p_path = $this->_translateWinPath($p_path, false);
1536 if ($p_path == '' || (substr($p_path, 0, 1) != '/'
1537 && substr($p_path, 0, 3) != "../" && !strpos($p_path, ':'))) {
1538 $p_path = "./".$p_path;
1540 $p_remove_path = $this->_translateWinPath($p_remove_path);
1542 // ----- Look for path to remove format (should end by /)
1543 if (($p_remove_path != '') && (substr($p_remove_path, -1) != '/'))
1544 $p_remove_path .= '/';
1545 $p_remove_path_size = strlen($p_remove_path);
1547 switch ($p_mode) {
1548 case "complete" :
1549 $v_extract_all = true;
1550 $v_listing = false;
1551 break;
1552 case "partial" :
1553 $v_extract_all = false;
1554 $v_listing = false;
1555 break;
1556 case "list" :
1557 $v_extract_all = false;
1558 $v_listing = true;
1559 break;
1560 default :
1561 $this->_error('Invalid extract mode ('.$p_mode.')');
1562 return false;
1565 clearstatcache();
1567 while (strlen($v_binary_data = $this->_readBlock()) != 0)
1569 $v_extract_file = false;
1570 $v_extraction_stopped = 0;
1572 if (!$this->_readHeader($v_binary_data, $v_header))
1573 return false;
1575 if ($v_header['filename'] == '') {
1576 continue;
1579 // ----- Look for long filename
1580 if ($v_header['typeflag'] == 'L') {
1581 if (!$this->_readLongHeader($v_header))
1582 return false;
1585 if ((!$v_extract_all) && (is_array($p_file_list))) {
1586 // ----- By default no unzip if the file is not found
1587 $v_extract_file = false;
1589 for ($i=0; $i<sizeof($p_file_list); $i++) {
1590 // ----- Look if it is a directory
1591 if (substr($p_file_list[$i], -1) == '/') {
1592 // ----- Look if the directory is in the filename path
1593 if ((strlen($v_header['filename']) > strlen($p_file_list[$i]))
1594 && (substr($v_header['filename'], 0, strlen($p_file_list[$i]))
1595 == $p_file_list[$i])) {
1596 $v_extract_file = true;
1597 break;
1601 // ----- It is a file, so compare the file names
1602 elseif ($p_file_list[$i] == $v_header['filename']) {
1603 $v_extract_file = true;
1604 break;
1607 } else {
1608 $v_extract_file = true;
1611 // ----- Look if this file need to be extracted
1612 if (($v_extract_file) && (!$v_listing))
1614 if (($p_remove_path != '')
1615 && (substr($v_header['filename'].'/', 0, $p_remove_path_size)
1616 == $p_remove_path)) {
1617 $v_header['filename'] = substr($v_header['filename'],
1618 $p_remove_path_size);
1619 if( $v_header['filename'] == '' ){
1620 continue;
1623 if (($p_path != './') && ($p_path != '/')) {
1624 while (substr($p_path, -1) == '/')
1625 $p_path = substr($p_path, 0, strlen($p_path)-1);
1627 if (substr($v_header['filename'], 0, 1) == '/')
1628 $v_header['filename'] = $p_path.$v_header['filename'];
1629 else
1630 $v_header['filename'] = $p_path.'/'.$v_header['filename'];
1632 if (file_exists($v_header['filename'])) {
1633 if ( (@is_dir($v_header['filename']))
1634 && ($v_header['typeflag'] == '')) {
1635 $this->_error('File '.$v_header['filename']
1636 .' already exists as a directory');
1637 return false;
1639 if ( ($this->_isArchive($v_header['filename']))
1640 && ($v_header['typeflag'] == "5")) {
1641 $this->_error('Directory '.$v_header['filename']
1642 .' already exists as a file');
1643 return false;
1645 if (!is_writeable($v_header['filename'])) {
1646 $this->_error('File '.$v_header['filename']
1647 .' already exists and is write protected');
1648 return false;
1650 if (filemtime($v_header['filename']) > $v_header['mtime']) {
1651 // To be completed : An error or silent no replace ?
1655 // ----- Check the directory availability and create it if necessary
1656 elseif (($v_result
1657 = $this->_dirCheck(($v_header['typeflag'] == "5"
1658 ?$v_header['filename']
1659 :dirname($v_header['filename'])))) != 1) {
1660 $this->_error('Unable to create path for '.$v_header['filename']);
1661 return false;
1664 if ($v_extract_file) {
1665 if ($v_header['typeflag'] == "5") {
1666 if (!@file_exists($v_header['filename'])) {
1667 if (!@mkdir($v_header['filename'], 0777)) {
1668 $this->_error('Unable to create directory {'
1669 .$v_header['filename'].'}');
1670 return false;
1673 } elseif ($v_header['typeflag'] == "2") {
1674 if (@file_exists($v_header['filename'])) {
1675 @unlink($v_header['filename']);
1677 if (!@symlink($v_header['link'], $v_header['filename'])) {
1678 $this->_error('Unable to extract symbolic link {'
1679 .$v_header['filename'].'}');
1680 return false;
1682 } else {
1683 if (($v_dest_file = @fopen($v_header['filename'], "wb")) == 0) {
1684 $this->_error('Error while opening {'.$v_header['filename']
1685 .'} in write binary mode');
1686 return false;
1687 } else {
1688 $n = floor($v_header['size']/512);
1689 for ($i=0; $i<$n; $i++) {
1690 $v_content = $this->_readBlock();
1691 fwrite($v_dest_file, $v_content, 512);
1693 if (($v_header['size'] % 512) != 0) {
1694 $v_content = $this->_readBlock();
1695 fwrite($v_dest_file, $v_content, ($v_header['size'] % 512));
1698 @fclose($v_dest_file);
1700 if ($p_preserve) {
1701 @chown($v_header['filename'], $v_header['uid']);
1702 @chgrp($v_header['filename'], $v_header['gid']);
1705 // ----- Change the file mode, mtime
1706 @touch($v_header['filename'], $v_header['mtime']);
1707 if ($v_header['mode'] & 0111) {
1708 // make file executable, obey umask
1709 $mode = fileperms($v_header['filename']) | (~umask() & 0111);
1710 @chmod($v_header['filename'], $mode);
1714 // ----- Check the file size
1715 clearstatcache();
1716 if (!is_file($v_header['filename'])) {
1717 $this->_error('Extracted file '.$v_header['filename']
1718 .'does not exist. Archive may be corrupted.');
1719 return false;
1722 $filesize = filesize($v_header['filename']);
1723 if ($filesize != $v_header['size']) {
1724 $this->_error('Extracted file '.$v_header['filename']
1725 .' does not have the correct file size \''
1726 .$filesize
1727 .'\' ('.$v_header['size']
1728 .' expected). Archive may be corrupted.');
1729 return false;
1732 } else {
1733 $this->_jumpBlock(ceil(($v_header['size']/512)));
1735 } else {
1736 $this->_jumpBlock(ceil(($v_header['size']/512)));
1739 /* TBC : Seems to be unused ...
1740 if ($this->_compress)
1741 $v_end_of_file = @gzeof($this->_file);
1742 else
1743 $v_end_of_file = @feof($this->_file);
1746 if ($v_listing || $v_extract_file || $v_extraction_stopped) {
1747 // ----- Log extracted files
1748 if (($v_file_dir = dirname($v_header['filename']))
1749 == $v_header['filename'])
1750 $v_file_dir = '';
1751 if ((substr($v_header['filename'], 0, 1) == '/') && ($v_file_dir == ''))
1752 $v_file_dir = '/';
1754 $p_list_detail[$v_nb++] = $v_header;
1755 if (is_array($p_file_list) && (count($p_list_detail) == count($p_file_list))) {
1756 return true;
1761 return true;
1763 // }}}
1765 // {{{ _openAppend()
1766 function _openAppend()
1768 if (filesize($this->_tarname) == 0)
1769 return $this->_openWrite();
1771 if ($this->_compress) {
1772 $this->_close();
1774 if (!@rename($this->_tarname, $this->_tarname.".tmp")) {
1775 $this->_error('Error while renaming \''.$this->_tarname
1776 .'\' to temporary file \''.$this->_tarname
1777 .'.tmp\'');
1778 return false;
1781 if ($this->_compress_type == 'gz')
1782 $v_temp_tar = @gzopen($this->_tarname.".tmp", "rb");
1783 elseif ($this->_compress_type == 'bz2')
1784 $v_temp_tar = @bzopen($this->_tarname.".tmp", "r");
1786 if ($v_temp_tar == 0) {
1787 $this->_error('Unable to open file \''.$this->_tarname
1788 .'.tmp\' in binary read mode');
1789 @rename($this->_tarname.".tmp", $this->_tarname);
1790 return false;
1793 if (!$this->_openWrite()) {
1794 @rename($this->_tarname.".tmp", $this->_tarname);
1795 return false;
1798 if ($this->_compress_type == 'gz') {
1799 $end_blocks = 0;
1801 while (!@gzeof($v_temp_tar)) {
1802 $v_buffer = @gzread($v_temp_tar, 512);
1803 if ($v_buffer == ARCHIVE_TAR_END_BLOCK || strlen($v_buffer) == 0) {
1804 $end_blocks++;
1805 // do not copy end blocks, we will re-make them
1806 // after appending
1807 continue;
1808 } elseif ($end_blocks > 0) {
1809 for ($i = 0; $i < $end_blocks; $i++) {
1810 $this->_writeBlock(ARCHIVE_TAR_END_BLOCK);
1812 $end_blocks = 0;
1814 $v_binary_data = pack("a512", $v_buffer);
1815 $this->_writeBlock($v_binary_data);
1818 @gzclose($v_temp_tar);
1820 elseif ($this->_compress_type == 'bz2') {
1821 $end_blocks = 0;
1823 while (strlen($v_buffer = @bzread($v_temp_tar, 512)) > 0) {
1824 if ($v_buffer == ARCHIVE_TAR_END_BLOCK || strlen($v_buffer) == 0) {
1825 $end_blocks++;
1826 // do not copy end blocks, we will re-make them
1827 // after appending
1828 continue;
1829 } elseif ($end_blocks > 0) {
1830 for ($i = 0; $i < $end_blocks; $i++) {
1831 $this->_writeBlock(ARCHIVE_TAR_END_BLOCK);
1833 $end_blocks = 0;
1835 $v_binary_data = pack("a512", $v_buffer);
1836 $this->_writeBlock($v_binary_data);
1839 @bzclose($v_temp_tar);
1842 if (!@unlink($this->_tarname.".tmp")) {
1843 $this->_error('Error while deleting temporary file \''
1844 .$this->_tarname.'.tmp\'');
1847 } else {
1848 // ----- For not compressed tar, just add files before the last
1849 // one or two 512 bytes block
1850 if (!$this->_openReadWrite())
1851 return false;
1853 clearstatcache();
1854 $v_size = filesize($this->_tarname);
1856 // We might have zero, one or two end blocks.
1857 // The standard is two, but we should try to handle
1858 // other cases.
1859 fseek($this->_file, $v_size - 1024);
1860 if (fread($this->_file, 512) == ARCHIVE_TAR_END_BLOCK) {
1861 fseek($this->_file, $v_size - 1024);
1863 elseif (fread($this->_file, 512) == ARCHIVE_TAR_END_BLOCK) {
1864 fseek($this->_file, $v_size - 512);
1868 return true;
1870 // }}}
1872 // {{{ _append()
1873 function _append($p_filelist, $p_add_dir='', $p_remove_dir='')
1875 if (!$this->_openAppend())
1876 return false;
1878 if ($this->_addList($p_filelist, $p_add_dir, $p_remove_dir))
1879 $this->_writeFooter();
1881 $this->_close();
1883 return true;
1885 // }}}
1887 // {{{ _dirCheck()
1890 * Check if a directory exists and create it (including parent
1891 * dirs) if not.
1893 * @param string $p_dir directory to check
1895 * @return bool true if the directory exists or was created
1897 function _dirCheck($p_dir)
1899 clearstatcache();
1900 if ((@is_dir($p_dir)) || ($p_dir == ''))
1901 return true;
1903 $p_parent_dir = dirname($p_dir);
1905 if (($p_parent_dir != $p_dir) &&
1906 ($p_parent_dir != '') &&
1907 (!$this->_dirCheck($p_parent_dir)))
1908 return false;
1910 if (!@mkdir($p_dir, 0777)) {
1911 $this->_error("Unable to create directory '$p_dir'");
1912 return false;
1915 return true;
1918 // }}}
1920 // {{{ _pathReduction()
1923 * Compress path by changing for example "/dir/foo/../bar" to "/dir/bar",
1924 * rand emove double slashes.
1926 * @param string $p_dir path to reduce
1928 * @return string reduced path
1930 * @access private
1933 function _pathReduction($p_dir)
1935 $v_result = '';
1937 // ----- Look for not empty path
1938 if ($p_dir != '') {
1939 // ----- Explode path by directory names
1940 $v_list = explode('/', $p_dir);
1942 // ----- Study directories from last to first
1943 for ($i=sizeof($v_list)-1; $i>=0; $i--) {
1944 // ----- Look for current path
1945 if ($v_list[$i] == ".") {
1946 // ----- Ignore this directory
1947 // Should be the first $i=0, but no check is done
1949 else if ($v_list[$i] == "..") {
1950 // ----- Ignore it and ignore the $i-1
1951 $i--;
1953 else if ( ($v_list[$i] == '')
1954 && ($i!=(sizeof($v_list)-1))
1955 && ($i!=0)) {
1956 // ----- Ignore only the double '//' in path,
1957 // but not the first and last /
1958 } else {
1959 $v_result = $v_list[$i].($i!=(sizeof($v_list)-1)?'/'
1960 .$v_result:'');
1965 if (defined('OS_WINDOWS') && OS_WINDOWS) {
1966 $v_result = strtr($v_result, '\\', '/');
1969 return $v_result;
1972 // }}}
1974 // {{{ _translateWinPath()
1975 function _translateWinPath($p_path, $p_remove_disk_letter=true)
1977 if (defined('OS_WINDOWS') && OS_WINDOWS) {
1978 // ----- Look for potential disk letter
1979 if ( ($p_remove_disk_letter)
1980 && (($v_position = strpos($p_path, ':')) != false)) {
1981 $p_path = substr($p_path, $v_position+1);
1983 // ----- Change potential windows directory separator
1984 if ((strpos($p_path, '\\') > 0) || (substr($p_path, 0,1) == '\\')) {
1985 $p_path = strtr($p_path, '\\', '/');
1988 return $p_path;
1990 // }}}