2 // @codingStandardsIgnoreFile
3 /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
10 * Copyright (c) 1997-2008,
11 * Vincent Blavet <vincent@phpconcept.net>
12 * All rights reserved.
14 * Redistribution and use in source and binary forms, with or without
15 * modification, are permitted provided that the following conditions are met:
17 * * Redistributions of source code must retain the above copyright notice,
18 * this list of conditions and the following disclaimer.
19 * * Redistributions in binary form must reproduce the above copyright
20 * notice, this list of conditions and the following disclaimer in the
21 * documentation and/or other materials provided with the distribution.
23 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
24 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
26 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
27 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
28 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
29 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
30 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
31 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
32 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
34 * @category File_Formats
35 * @package Archive_Tar
36 * @author Vincent Blavet <vincent@phpconcept.net>
37 * @copyright 1997-2010 The Authors
38 * @license http://www.opensource.org/licenses/bsd-license.php New BSD License
40 * @link http://pear.php.net/package/Archive_Tar
44 * Note on Drupal 8 porting.
45 * This file origin is Tar.php, release 1.4.0 (stable) with some code
46 * from PEAR.php, release 1.9.5 (stable) both at http://pear.php.net.
47 * To simplify future porting from pear of this file, you should not
48 * do cosmetic or other non significant changes to this file.
49 * The following changes have been done:
50 * Added namespace Drupal\Core\Archiver.
51 * Removed require_once 'PEAR.php'.
52 * Added definition of OS_WINDOWS taken from PEAR.php.
53 * Renamed class to ArchiveTar.
54 * Removed extends PEAR from class.
55 * Removed call parent:: __construct().
56 * Changed PEAR::loadExtension($extname) to this->loadExtension($extname).
57 * Added function loadExtension() taken from PEAR.php.
58 * Changed all calls of unlink() to drupal_unlink().
59 * Changed $this->error_object = &$this->raiseError($p_message)
60 * to throw new \Exception($p_message).
67 // Drupal removal require_once 'PEAR.php'.
69 // Drupal addition OS_WINDOWS as defined in PEAR.php.
70 if (substr(PHP_OS, 0, 3) == 'WIN') {
71 define('OS_WINDOWS', true);
73 define('OS_WINDOWS', false);
76 define('ARCHIVE_TAR_ATT_SEPARATOR', 90001);
77 define('ARCHIVE_TAR_END_BLOCK', pack("a512", ''));
79 if (!function_exists('gzopen') && function_exists('gzopen64')) {
80 function gzopen($filename, $mode, $use_include_path = 0)
82 return gzopen64($filename, $mode, $use_include_path);
86 if (!function_exists('gztell') && function_exists('gztell64')) {
93 if (!function_exists('gzseek') && function_exists('gzseek64')) {
94 function gzseek($zp, $offset, $whence = SEEK_SET)
96 return gzseek64($zp, $offset, $whence);
102 namespace Drupal\Core\Archiver {
104 * Creates a (compressed) Tar archive
106 * @package Archive_Tar
107 * @author Vincent Blavet <vincent@phpconcept.net>
108 * @license http://www.opensource.org/licenses/bsd-license.php New BSD License
109 * @version $Revision$
111 // Drupal change class Archive_Tar extends PEAR.
115 * @var string Name of the Tar
117 public $_tarname = '';
120 * @var boolean if true, the Tar file will be gzipped
122 public $_compress = false;
125 * @var string Type of compression : 'none', 'gz', 'bz2' or 'lzma2'
127 public $_compress_type = 'none';
130 * @var string Explode separator
132 public $_separator = ' ';
135 * @var file descriptor
140 * @var string Local Tar name of a remote Tar (http:// or ftp://)
142 public $_temp_tarname = '';
145 * @var string regular expression for ignoring files or directories
147 public $_ignore_regexp = '';
150 * @var object PEAR_Error object
152 public $error_object = null;
155 * Archive_Tar Class constructor. This flavour of the constructor only
156 * declare a new Archive_Tar object, identifying it by the name of the
158 * If the compress argument is set the tar will be read or created as a
159 * gzip or bz2 compressed TAR file.
161 * @param string $p_tarname The name of the tar archive to create
162 * @param string $p_compress can be null, 'gz', 'bz2' or 'lzma2'. This
163 * parameter indicates if gzip, bz2 or lzma2 compression
164 * is required. For compatibility reason the
165 * boolean value 'true' means 'gz'.
169 public function __construct($p_tarname, $p_compress = null)
171 // Drupal removal parent::__construct().
173 $this->_compress = false;
174 $this->_compress_type = 'none';
175 if (($p_compress === null) || ($p_compress == '')) {
176 if (@file_exists($p_tarname)) {
177 if ($fp = @fopen($p_tarname, "rb")) {
178 // look for gzip magic cookie
179 $data = fread($fp, 2);
181 if ($data == "\37\213") {
182 $this->_compress = true;
183 $this->_compress_type = 'gz';
184 // Not sure it's enough for a magic code ....
185 } elseif ($data == "BZ") {
186 $this->_compress = true;
187 $this->_compress_type = 'bz2';
188 } elseif (file_get_contents($p_tarname, false, null, 1, 4) == '7zXZ') {
189 $this->_compress = true;
190 $this->_compress_type = 'lzma2';
194 // probably a remote file or some file accessible
195 // through a stream interface
196 if (substr($p_tarname, -2) == 'gz') {
197 $this->_compress = true;
198 $this->_compress_type = 'gz';
199 } elseif ((substr($p_tarname, -3) == 'bz2') ||
200 (substr($p_tarname, -2) == 'bz')
202 $this->_compress = true;
203 $this->_compress_type = 'bz2';
205 if (substr($p_tarname, -2) == 'xz') {
206 $this->_compress = true;
207 $this->_compress_type = 'lzma2';
212 if (($p_compress === true) || ($p_compress == 'gz')) {
213 $this->_compress = true;
214 $this->_compress_type = 'gz';
216 if ($p_compress == 'bz2') {
217 $this->_compress = true;
218 $this->_compress_type = 'bz2';
220 if ($p_compress == 'lzma2') {
221 $this->_compress = true;
222 $this->_compress_type = 'lzma2';
225 "Unsupported compression type '$p_compress'\n" .
226 "Supported types are 'gz', 'bz2' and 'lzma2'.\n"
233 $this->_tarname = $p_tarname;
234 if ($this->_compress) { // assert zlib or bz2 or xz extension support
235 if ($this->_compress_type == 'gz') {
238 if ($this->_compress_type == 'bz2') {
241 if ($this->_compress_type == 'lzma2') {
247 if (!extension_loaded($extname)) {
248 // Drupal change PEAR::loadExtension($extname).
249 $this->loadExtension($extname);
251 if (!extension_loaded($extname)) {
253 "The extension '$extname' couldn't be found.\n" .
254 "Please make sure your version of PHP was built " .
255 "with '$extname' support.\n"
262 public function __destruct()
265 // ----- Look for a local copy to delete
266 if ($this->_temp_tarname != '') {
267 @drupal_unlink($this->_temp_tarname);
271 // Drupal addition from PEAR.php.
273 * OS independent PHP extension load. Remember to take care
274 * on the correct extension name for case sensitive OSes.
276 * @param string $ext The extension name
277 * @return bool Success or not on the dl() call
279 function loadExtension($ext)
281 if (extension_loaded($ext)) {
285 // if either returns true dl() will produce a FATAL error, stop that
287 function_exists('dl') === false ||
288 ini_get('enable_dl') != 1 ||
289 ini_get('safe_mode') == 1
296 } elseif (PHP_OS == 'HP-UX') {
298 } elseif (PHP_OS == 'AIX') {
300 } elseif (PHP_OS == 'OSX') {
306 return @dl('php_'.$ext.$suffix) || @dl($ext.$suffix);
311 * This method creates the archive file and add the files / directories
312 * that are listed in $p_filelist.
313 * If a file with the same name exist and is writable, it is replaced
315 * The method return false and a PEAR error text.
316 * The $p_filelist parameter can be an array of string, each string
317 * representing a filename or a directory name with their path if
318 * needed. It can also be a single string with names separated by a
320 * For each directory added in the archive, the files and
321 * sub-directories are also added.
322 * See also createModify() method for more details.
324 * @param array $p_filelist An array of filenames and directory names, or a
325 * single string with names separated by a single
328 * @return true on success, false on error.
329 * @see createModify()
331 public function create($p_filelist)
333 return $this->createModify($p_filelist, '', '');
337 * This method add the files / directories that are listed in $p_filelist in
338 * the archive. If the archive does not exist it is created.
339 * The method return false and a PEAR error text.
340 * The files and directories listed are only added at the end of the archive,
341 * even if a file with the same name is already archived.
342 * See also createModify() method for more details.
344 * @param array $p_filelist An array of filenames and directory names, or a
345 * single string with names separated by a single
348 * @return true on success, false on error.
349 * @see createModify()
352 public function add($p_filelist)
354 return $this->addModify($p_filelist, '', '');
358 * @param string $p_path
359 * @param bool $p_preserve
362 public function extract($p_path = '', $p_preserve = false)
364 return $this->extractModify($p_path, '', $p_preserve);
370 public function listContent()
372 $v_list_detail = array();
374 if ($this->_openRead()) {
375 if (!$this->_extractList('', $v_list_detail, "list", '', '')) {
376 unset($v_list_detail);
382 return $v_list_detail;
386 * This method creates the archive file and add the files / directories
387 * that are listed in $p_filelist.
388 * If the file already exists and is writable, it is replaced by the
389 * new tar. It is a create and not an add. If the file exists and is
390 * read-only or is a directory it is not replaced. The method return
391 * false and a PEAR error text.
392 * The $p_filelist parameter can be an array of string, each string
393 * representing a filename or a directory name with their path if
394 * needed. It can also be a single string with names separated by a
396 * The path indicated in $p_remove_dir will be removed from the
397 * memorized path of each file / directory listed when this path
398 * exists. By default nothing is removed (empty path '')
399 * The path indicated in $p_add_dir will be added at the beginning of
400 * the memorized path of each file / directory listed. However it can
401 * be set to empty ''. The adding of a path is done after the removing
403 * The path add/remove ability enables the user to prepare an archive
404 * for extraction in a different path than the origin files are.
405 * See also addModify() method for file adding properties.
407 * @param array $p_filelist An array of filenames and directory names,
408 * or a single string with names separated by
409 * a single blank space.
410 * @param string $p_add_dir A string which contains a path to be added
411 * to the memorized path of each element in
413 * @param string $p_remove_dir A string which contains a path to be
414 * removed from the memorized path of each
415 * element in the list, when relevant.
417 * @return boolean true on success, false on error.
420 public function createModify($p_filelist, $p_add_dir, $p_remove_dir = '')
424 if (!$this->_openWrite()) {
428 if ($p_filelist != '') {
429 if (is_array($p_filelist)) {
430 $v_list = $p_filelist;
431 } elseif (is_string($p_filelist)) {
432 $v_list = explode($this->_separator, $p_filelist);
435 $this->_error('Invalid file list');
439 $v_result = $this->_addList($v_list, $p_add_dir, $p_remove_dir);
443 $this->_writeFooter();
453 * This method add the files / directories listed in $p_filelist at the
454 * end of the existing archive. If the archive does not yet exists it
456 * The $p_filelist parameter can be an array of string, each string
457 * representing a filename or a directory name with their path if
458 * needed. It can also be a single string with names separated by a
460 * The path indicated in $p_remove_dir will be removed from the
461 * memorized path of each file / directory listed when this path
462 * exists. By default nothing is removed (empty path '')
463 * The path indicated in $p_add_dir will be added at the beginning of
464 * the memorized path of each file / directory listed. However it can
465 * be set to empty ''. The adding of a path is done after the removing
467 * The path add/remove ability enables the user to prepare an archive
468 * for extraction in a different path than the origin files are.
469 * If a file/dir is already in the archive it will only be added at the
470 * end of the archive. There is no update of the existing archived
471 * file/dir. However while extracting the archive, the last file will
472 * replace the first one. This results in a none optimization of the
474 * If a file/dir does not exist the file/dir is ignored. However an
475 * error text is send to PEAR error.
476 * If a file/dir is not readable the file/dir is ignored. However an
477 * error text is send to PEAR error.
479 * @param array $p_filelist An array of filenames and directory
480 * names, or a single string with names
481 * separated by a single blank space.
482 * @param string $p_add_dir A string which contains a path to be
483 * added to the memorized path of each
484 * element in the list.
485 * @param string $p_remove_dir A string which contains a path to be
486 * removed from the memorized path of
487 * each element in the list, when
490 * @return true on success, false on error.
492 public function addModify($p_filelist, $p_add_dir, $p_remove_dir = '')
496 if (!$this->_isArchive()) {
497 $v_result = $this->createModify(
503 if (is_array($p_filelist)) {
504 $v_list = $p_filelist;
505 } elseif (is_string($p_filelist)) {
506 $v_list = explode($this->_separator, $p_filelist);
508 $this->_error('Invalid file list');
512 $v_result = $this->_append($v_list, $p_add_dir, $p_remove_dir);
519 * This method add a single string as a file at the
520 * end of the existing archive. If the archive does not yet exists it
523 * @param string $p_filename A string which contains the full
524 * filename path that will be associated
526 * @param string $p_string The content of the file added in
528 * @param bool|int $p_datetime A custom date/time (unix timestamp)
529 * for the file (optional).
530 * @param array $p_params An array of optional params:
531 * stamp => the datetime (replaces
532 * datetime above if it exists)
533 * mode => the permissions on the
534 * file (600 by default)
535 * type => is this a link? See the
536 * tar specification for details.
537 * (default = regular file)
538 * uid => the user ID of the file
539 * (default = 0 = root)
540 * gid => the group ID of the file
541 * (default = 0 = root)
543 * @return true on success, false on error.
545 public function addString($p_filename, $p_string, $p_datetime = false, $p_params = array())
547 $p_stamp = @$p_params["stamp"] ? $p_params["stamp"] : ($p_datetime ? $p_datetime : time());
548 $p_mode = @$p_params["mode"] ? $p_params["mode"] : 0600;
549 $p_type = @$p_params["type"] ? $p_params["type"] : "";
550 $p_uid = @$p_params["uid"] ? $p_params["uid"] : "";
551 $p_gid = @$p_params["gid"] ? $p_params["gid"] : "";
554 if (!$this->_isArchive()) {
555 if (!$this->_openWrite()) {
561 if (!$this->_openAppend()) {
565 // Need to check the get back to the temporary file ? ....
566 $v_result = $this->_addString($p_filename, $p_string, $p_datetime, $p_params);
568 $this->_writeFooter();
576 * This method extract all the content of the archive in the directory
577 * indicated by $p_path. When relevant the memorized path of the
578 * files/dir can be modified by removing the $p_remove_path path at the
579 * beginning of the file/dir path.
580 * While extracting a file, if the directory path does not exist it is
582 * While extracting a file, if the file already exists it is replaced
583 * without looking for last modification date.
584 * While extracting a file, if the file already exists and is write
585 * protected, the extraction is aborted.
586 * While extracting a file, if a directory with the same name already
587 * exists, the extraction is aborted.
588 * While extracting a directory, if a file with the same name already
589 * exists, the extraction is aborted.
590 * While extracting a file/directory if the destination directory exist
591 * and is write protected, or does not exist but can not be created,
592 * the extraction is aborted.
593 * If after extraction an extracted file does not show the correct
594 * stored file size, the extraction is aborted.
595 * When the extraction is aborted, a PEAR error text is set and false
596 * is returned. However the result can be a partial extraction that may
597 * need to be manually cleaned.
599 * @param string $p_path The path of the directory where the
600 * files/dir need to by extracted.
601 * @param string $p_remove_path Part of the memorized path that can be
602 * removed if present at the beginning of
604 * @param boolean $p_preserve Preserve user/group ownership of files
606 * @return boolean true on success, false on error.
609 public function extractModify($p_path, $p_remove_path, $p_preserve = false)
612 $v_list_detail = array();
614 if ($v_result = $this->_openRead()) {
615 $v_result = $this->_extractList(
630 * This method extract from the archive one file identified by $p_filename.
631 * The return value is a string with the file content, or NULL on error.
633 * @param string $p_filename The path of the file to extract in a string.
635 * @return a string with the file content or NULL.
637 public function extractInString($p_filename)
639 if ($this->_openRead()) {
640 $v_result = $this->_extractInString($p_filename);
650 * This method extract from the archive only the files indicated in the
651 * $p_filelist. These files are extracted in the current directory or
652 * in the directory indicated by the optional $p_path parameter.
653 * If indicated the $p_remove_path can be used in the same way as it is
654 * used in extractModify() method.
656 * @param array $p_filelist An array of filenames and directory names,
657 * or a single string with names separated
658 * by a single blank space.
659 * @param string $p_path The path of the directory where the
660 * files/dir need to by extracted.
661 * @param string $p_remove_path Part of the memorized path that can be
662 * removed if present at the beginning of
664 * @param boolean $p_preserve Preserve user/group ownership of files
666 * @return true on success, false on error.
667 * @see extractModify()
669 public function extractList($p_filelist, $p_path = '', $p_remove_path = '', $p_preserve = false)
672 $v_list_detail = array();
674 if (is_array($p_filelist)) {
675 $v_list = $p_filelist;
676 } elseif (is_string($p_filelist)) {
677 $v_list = explode($this->_separator, $p_filelist);
679 $this->_error('Invalid string list');
683 if ($v_result = $this->_openRead()) {
684 $v_result = $this->_extractList(
699 * This method set specific attributes of the archive. It uses a variable
700 * list of parameters, in the format attribute code + attribute values :
701 * $arch->setAttribute(ARCHIVE_TAR_ATT_SEPARATOR, ',');
703 * @return true on success, false on error.
705 public function setAttribute()
709 // ----- Get the number of variable list of arguments
710 if (($v_size = func_num_args()) == 0) {
714 // ----- Get the arguments
715 $v_att_list = & func_get_args();
717 // ----- Read the attributes
719 while ($i < $v_size) {
721 // ----- Look for next option
722 switch ($v_att_list[$i]) {
723 // ----- Look for options that request a string value
724 case ARCHIVE_TAR_ATT_SEPARATOR :
725 // ----- Check the number of parameters
726 if (($i + 1) >= $v_size) {
728 'Invalid number of parameters for '
729 . 'attribute ARCHIVE_TAR_ATT_SEPARATOR'
734 // ----- Get the value
735 $this->_separator = $v_att_list[$i + 1];
740 $this->_error('Unknown attribute code ' . $v_att_list[$i] . '');
744 // ----- Next attribute
752 * This method sets the regular expression for ignoring files and directories
753 * at import, for example:
754 * $arch->setIgnoreRegexp("#CVS|\.svn#");
756 * @param string $regexp regular expression defining which files or directories to ignore
758 public function setIgnoreRegexp($regexp)
760 $this->_ignore_regexp = $regexp;
764 * This method sets the regular expression for ignoring all files and directories
765 * matching the filenames in the array list at import, for example:
766 * $arch->setIgnoreList(array('CVS', '.svn', 'bin/tool'));
768 * @param array $list a list of file or directory names to ignore
772 public function setIgnoreList($list)
774 $regexp = str_replace(array('#', '.', '^', '$'), array('\#', '\.', '\^', '\$'), $list);
775 $regexp = '#/' . join('$|/', $list) . '#';
776 $this->setIgnoreRegexp($regexp);
780 * @param string $p_message
782 public function _error($p_message)
784 // Drupal change $this->error_object = $this->raiseError($p_message).
785 throw new \Exception($p_message);
789 * @param string $p_message
791 public function _warning($p_message)
793 // Drupal change $this->error_object = $this->raiseError($p_message).
794 throw new \Exception($p_message);
798 * @param string $p_filename
801 public function _isArchive($p_filename = null)
803 if ($p_filename == null) {
804 $p_filename = $this->_tarname;
807 return @is_file($p_filename) && !@is_link($p_filename);
813 public function _openWrite()
815 if ($this->_compress_type == 'gz' && function_exists('gzopen')) {
816 $this->_file = @gzopen($this->_tarname, "wb9");
818 if ($this->_compress_type == 'bz2' && function_exists('bzopen')) {
819 $this->_file = @bzopen($this->_tarname, "w");
821 if ($this->_compress_type == 'lzma2' && function_exists('xzopen')) {
822 $this->_file = @xzopen($this->_tarname, 'w');
824 if ($this->_compress_type == 'none') {
825 $this->_file = @fopen($this->_tarname, "wb");
828 'Unknown or missing compression type ('
829 . $this->_compress_type . ')'
837 if ($this->_file == 0) {
839 'Unable to open in write mode \''
840 . $this->_tarname . '\''
851 public function _openRead()
853 if (strtolower(substr($this->_tarname, 0, 7)) == 'http://') {
855 // ----- Look if a local copy need to be done
856 if ($this->_temp_tarname == '') {
857 $this->_temp_tarname = uniqid('tar') . '.tmp';
858 if (!$v_file_from = @fopen($this->_tarname, 'rb')) {
860 'Unable to open in read mode \''
861 . $this->_tarname . '\''
863 $this->_temp_tarname = '';
866 if (!$v_file_to = @fopen($this->_temp_tarname, 'wb')) {
868 'Unable to open in write mode \''
869 . $this->_temp_tarname . '\''
871 $this->_temp_tarname = '';
874 while ($v_data = @fread($v_file_from, 1024)) {
875 @fwrite($v_file_to, $v_data);
877 @fclose($v_file_from);
881 // ----- File to open if the local copy
882 $v_filename = $this->_temp_tarname;
884 // ----- File to open if the normal Tar file
886 $v_filename = $this->_tarname;
889 if ($this->_compress_type == 'gz' && function_exists('gzopen')) {
890 $this->_file = @gzopen($v_filename, "rb");
892 if ($this->_compress_type == 'bz2' && function_exists('bzopen')) {
893 $this->_file = @bzopen($v_filename, "r");
895 if ($this->_compress_type == 'lzma2' && function_exists('xzopen')) {
896 $this->_file = @xzopen($v_filename, "r");
898 if ($this->_compress_type == 'none') {
899 $this->_file = @fopen($v_filename, "rb");
902 'Unknown or missing compression type ('
903 . $this->_compress_type . ')'
911 if ($this->_file == 0) {
912 $this->_error('Unable to open in read mode \'' . $v_filename . '\'');
922 public function _openReadWrite()
924 if ($this->_compress_type == 'gz') {
925 $this->_file = @gzopen($this->_tarname, "r+b");
927 if ($this->_compress_type == 'bz2') {
929 'Unable to open bz2 in read/write mode \''
930 . $this->_tarname . '\' (limitation of bz2 extension)'
934 if ($this->_compress_type == 'lzma2') {
936 'Unable to open lzma2 in read/write mode \''
937 . $this->_tarname . '\' (limitation of lzma2 extension)'
941 if ($this->_compress_type == 'none') {
942 $this->_file = @fopen($this->_tarname, "r+b");
945 'Unknown or missing compression type ('
946 . $this->_compress_type . ')'
954 if ($this->_file == 0) {
956 'Unable to open in read/write mode \''
957 . $this->_tarname . '\''
968 public function _close()
970 //if (isset($this->_file)) {
971 if (is_resource($this->_file)) {
972 if ($this->_compress_type == 'gz') {
973 @gzclose($this->_file);
975 if ($this->_compress_type == 'bz2') {
976 @bzclose($this->_file);
978 if ($this->_compress_type == 'lzma2') {
979 @xzclose($this->_file);
981 if ($this->_compress_type == 'none') {
982 @fclose($this->_file);
985 'Unknown or missing compression type ('
986 . $this->_compress_type . ')'
996 // ----- Look if a local copy need to be erase
997 // Note that it might be interesting to keep the url for a time : ToDo
998 if ($this->_temp_tarname != '') {
999 @drupal_unlink($this->_temp_tarname);
1000 $this->_temp_tarname = '';
1009 public function _cleanFile()
1013 // ----- Look for a local copy
1014 if ($this->_temp_tarname != '') {
1015 // ----- Remove the local copy but not the remote tarname
1016 @drupal_unlink($this->_temp_tarname);
1017 $this->_temp_tarname = '';
1019 // ----- Remove the local tarname file
1020 @drupal_unlink($this->_tarname);
1022 $this->_tarname = '';
1028 * @param mixed $p_binary_data
1029 * @param integer $p_len
1032 public function _writeBlock($p_binary_data, $p_len = null)
1034 if (is_resource($this->_file)) {
1035 if ($p_len === null) {
1036 if ($this->_compress_type == 'gz') {
1037 @gzputs($this->_file, $p_binary_data);
1039 if ($this->_compress_type == 'bz2') {
1040 @bzwrite($this->_file, $p_binary_data);
1042 if ($this->_compress_type == 'lzma2') {
1043 @xzwrite($this->_file, $p_binary_data);
1045 if ($this->_compress_type == 'none') {
1046 @fputs($this->_file, $p_binary_data);
1049 'Unknown or missing compression type ('
1050 . $this->_compress_type . ')'
1057 if ($this->_compress_type == 'gz') {
1058 @gzputs($this->_file, $p_binary_data, $p_len);
1060 if ($this->_compress_type == 'bz2') {
1061 @bzwrite($this->_file, $p_binary_data, $p_len);
1063 if ($this->_compress_type == 'lzma2') {
1064 @xzwrite($this->_file, $p_binary_data, $p_len);
1066 if ($this->_compress_type == 'none') {
1067 @fputs($this->_file, $p_binary_data, $p_len);
1070 'Unknown or missing compression type ('
1071 . $this->_compress_type . ')'
1083 * @return null|string
1085 public function _readBlock()
1088 if (is_resource($this->_file)) {
1089 if ($this->_compress_type == 'gz') {
1090 $v_block = @gzread($this->_file, 512);
1092 if ($this->_compress_type == 'bz2') {
1093 $v_block = @bzread($this->_file, 512);
1095 if ($this->_compress_type == 'lzma2') {
1096 $v_block = @xzread($this->_file, 512);
1098 if ($this->_compress_type == 'none') {
1099 $v_block = @fread($this->_file, 512);
1102 'Unknown or missing compression type ('
1103 . $this->_compress_type . ')'
1114 * @param null $p_len
1117 public function _jumpBlock($p_len = null)
1119 if (is_resource($this->_file)) {
1120 if ($p_len === null) {
1124 if ($this->_compress_type == 'gz') {
1125 @gzseek($this->_file, gztell($this->_file) + ($p_len * 512));
1127 if ($this->_compress_type == 'bz2') {
1128 // ----- Replace missing bztell() and bzseek()
1129 for ($i = 0; $i < $p_len; $i++) {
1130 $this->_readBlock();
1133 if ($this->_compress_type == 'lzma2') {
1134 // ----- Replace missing xztell() and xzseek()
1135 for ($i = 0; $i < $p_len; $i++) {
1136 $this->_readBlock();
1139 if ($this->_compress_type == 'none') {
1140 @fseek($this->_file, $p_len * 512, SEEK_CUR);
1143 'Unknown or missing compression type ('
1144 . $this->_compress_type . ')'
1157 public function _writeFooter()
1159 if (is_resource($this->_file)) {
1160 // ----- Write the last 0 filled block for end of archive
1161 $v_binary_data = pack('a1024', '');
1162 $this->_writeBlock($v_binary_data);
1168 * @param array $p_list
1169 * @param string $p_add_dir
1170 * @param string $p_remove_dir
1173 public function _addList($p_list, $p_add_dir, $p_remove_dir)
1176 $v_header = array();
1178 // ----- Remove potential windows directory separator
1179 $p_add_dir = $this->_translateWinPath($p_add_dir);
1180 $p_remove_dir = $this->_translateWinPath($p_remove_dir, false);
1182 if (!$this->_file) {
1183 $this->_error('Invalid file descriptor');
1187 if (sizeof($p_list) == 0) {
1191 foreach ($p_list as $v_filename) {
1196 // ----- Skip the current tar name
1197 if ($v_filename == $this->_tarname) {
1201 if ($v_filename == '') {
1205 // ----- ignore files and directories matching the ignore regular expression
1206 if ($this->_ignore_regexp && preg_match($this->_ignore_regexp, '/' . $v_filename)) {
1207 $this->_warning("File '$v_filename' ignored");
1211 if (!file_exists($v_filename) && !is_link($v_filename)) {
1212 $this->_warning("File '$v_filename' does not exist");
1216 // ----- Add the file or directory header
1217 if (!$this->_addFile($v_filename, $v_header, $p_add_dir, $p_remove_dir)) {
1221 if (@is_dir($v_filename) && !@is_link($v_filename)) {
1222 if (!($p_hdir = opendir($v_filename))) {
1223 $this->_warning("Directory '$v_filename' can not be read");
1226 while (false !== ($p_hitem = readdir($p_hdir))) {
1227 if (($p_hitem != '.') && ($p_hitem != '..')) {
1228 if ($v_filename != ".") {
1229 $p_temp_list[0] = $v_filename . '/' . $p_hitem;
1231 $p_temp_list[0] = $p_hitem;
1234 $v_result = $this->_addList(
1242 unset($p_temp_list);
1252 * @param string $p_filename
1253 * @param mixed $p_header
1254 * @param string $p_add_dir
1255 * @param string $p_remove_dir
1256 * @param null $v_stored_filename
1259 public function _addFile($p_filename, &$p_header, $p_add_dir, $p_remove_dir, $v_stored_filename = null)
1261 if (!$this->_file) {
1262 $this->_error('Invalid file descriptor');
1266 if ($p_filename == '') {
1267 $this->_error('Invalid file name');
1271 if (is_null($v_stored_filename)) {
1272 // ----- Calculate the stored filename
1273 $p_filename = $this->_translateWinPath($p_filename, false);
1274 $v_stored_filename = $p_filename;
1276 if (strcmp($p_filename, $p_remove_dir) == 0) {
1280 if ($p_remove_dir != '') {
1281 if (substr($p_remove_dir, -1) != '/') {
1282 $p_remove_dir .= '/';
1285 if (substr($p_filename, 0, strlen($p_remove_dir)) == $p_remove_dir) {
1286 $v_stored_filename = substr($p_filename, strlen($p_remove_dir));
1290 $v_stored_filename = $this->_translateWinPath($v_stored_filename);
1291 if ($p_add_dir != '') {
1292 if (substr($p_add_dir, -1) == '/') {
1293 $v_stored_filename = $p_add_dir . $v_stored_filename;
1295 $v_stored_filename = $p_add_dir . '/' . $v_stored_filename;
1299 $v_stored_filename = $this->_pathReduction($v_stored_filename);
1302 if ($this->_isArchive($p_filename)) {
1303 if (($v_file = @fopen($p_filename, "rb")) == 0) {
1305 "Unable to open file '" . $p_filename
1306 . "' in binary read mode"
1311 if (!$this->_writeHeader($p_filename, $v_stored_filename)) {
1315 while (($v_buffer = fread($v_file, 512)) != '') {
1316 $v_binary_data = pack("a512", "$v_buffer");
1317 $this->_writeBlock($v_binary_data);
1322 // ----- Only header for dir
1323 if (!$this->_writeHeader($p_filename, $v_stored_filename)) {
1332 * @param string $p_filename
1333 * @param string $p_string
1334 * @param bool $p_datetime
1335 * @param array $p_params
1338 public function _addString($p_filename, $p_string, $p_datetime = false, $p_params = array())
1340 $p_stamp = @$p_params["stamp"] ? $p_params["stamp"] : ($p_datetime ? $p_datetime : time());
1341 $p_mode = @$p_params["mode"] ? $p_params["mode"] : 0600;
1342 $p_type = @$p_params["type"] ? $p_params["type"] : "";
1343 $p_uid = @$p_params["uid"] ? $p_params["uid"] : 0;
1344 $p_gid = @$p_params["gid"] ? $p_params["gid"] : 0;
1345 if (!$this->_file) {
1346 $this->_error('Invalid file descriptor');
1350 if ($p_filename == '') {
1351 $this->_error('Invalid file name');
1355 // ----- Calculate the stored filename
1356 $p_filename = $this->_translateWinPath($p_filename, false);
1358 // ----- If datetime is not specified, set current time
1359 if ($p_datetime === false) {
1360 $p_datetime = time();
1363 if (!$this->_writeHeaderBlock(
1377 while (($v_buffer = substr($p_string, (($i++) * 512), 512)) != '') {
1378 $v_binary_data = pack("a512", $v_buffer);
1379 $this->_writeBlock($v_binary_data);
1386 * @param string $p_filename
1387 * @param string $p_stored_filename
1390 public function _writeHeader($p_filename, $p_stored_filename)
1392 if ($p_stored_filename == '') {
1393 $p_stored_filename = $p_filename;
1395 $v_reduce_filename = $this->_pathReduction($p_stored_filename);
1397 if (strlen($v_reduce_filename) > 99) {
1398 if (!$this->_writeLongHeader($v_reduce_filename)) {
1403 $v_info = lstat($p_filename);
1404 $v_uid = sprintf("%07s", DecOct($v_info[4]));
1405 $v_gid = sprintf("%07s", DecOct($v_info[5]));
1406 $v_perms = sprintf("%07s", DecOct($v_info['mode'] & 000777));
1408 $v_mtime = sprintf("%011s", DecOct($v_info['mtime']));
1412 if (@is_link($p_filename)) {
1414 $v_linkname = readlink($p_filename);
1415 $v_size = sprintf("%011s", DecOct(0));
1416 } elseif (@is_dir($p_filename)) {
1418 $v_size = sprintf("%011s", DecOct(0));
1422 $v_size = sprintf("%011s", DecOct($v_info['size']));
1425 $v_magic = 'ustar ';
1429 if (function_exists('posix_getpwuid')) {
1430 $userinfo = posix_getpwuid($v_info[4]);
1431 $groupinfo = posix_getgrgid($v_info[5]);
1433 $v_uname = $userinfo['name'];
1434 $v_gname = $groupinfo['name'];
1446 $v_binary_data_first = pack(
1455 $v_binary_data_last = pack(
1456 "a1a100a6a2a32a32a8a8a155a12",
1469 // ----- Calculate the checksum
1471 // ..... First part of the header
1472 for ($i = 0; $i < 148; $i++) {
1473 $v_checksum += ord(substr($v_binary_data_first, $i, 1));
1475 // ..... Ignore the checksum value and replace it by ' ' (space)
1476 for ($i = 148; $i < 156; $i++) {
1477 $v_checksum += ord(' ');
1479 // ..... Last part of the header
1480 for ($i = 156, $j = 0; $i < 512; $i++, $j++) {
1481 $v_checksum += ord(substr($v_binary_data_last, $j, 1));
1484 // ----- Write the first 148 bytes of the header in the archive
1485 $this->_writeBlock($v_binary_data_first, 148);
1487 // ----- Write the calculated checksum
1488 $v_checksum = sprintf("%06s ", DecOct($v_checksum));
1489 $v_binary_data = pack("a8", $v_checksum);
1490 $this->_writeBlock($v_binary_data, 8);
1492 // ----- Write the last 356 bytes of the header in the archive
1493 $this->_writeBlock($v_binary_data_last, 356);
1499 * @param string $p_filename
1500 * @param int $p_size
1501 * @param int $p_mtime
1502 * @param int $p_perms
1503 * @param string $p_type
1508 public function _writeHeaderBlock(
1517 $p_filename = $this->_pathReduction($p_filename);
1519 if (strlen($p_filename) > 99) {
1520 if (!$this->_writeLongHeader($p_filename)) {
1525 if ($p_type == "5") {
1526 $v_size = sprintf("%011s", DecOct(0));
1528 $v_size = sprintf("%011s", DecOct($p_size));
1531 $v_uid = sprintf("%07s", DecOct($p_uid));
1532 $v_gid = sprintf("%07s", DecOct($p_gid));
1533 $v_perms = sprintf("%07s", DecOct($p_perms & 000777));
1535 $v_mtime = sprintf("%11s", DecOct($p_mtime));
1539 $v_magic = 'ustar ';
1543 if (function_exists('posix_getpwuid')) {
1544 $userinfo = posix_getpwuid($p_uid);
1545 $groupinfo = posix_getgrgid($p_gid);
1547 $v_uname = $userinfo['name'];
1548 $v_gname = $groupinfo['name'];
1560 $v_binary_data_first = pack(
1569 $v_binary_data_last = pack(
1570 "a1a100a6a2a32a32a8a8a155a12",
1583 // ----- Calculate the checksum
1585 // ..... First part of the header
1586 for ($i = 0; $i < 148; $i++) {
1587 $v_checksum += ord(substr($v_binary_data_first, $i, 1));
1589 // ..... Ignore the checksum value and replace it by ' ' (space)
1590 for ($i = 148; $i < 156; $i++) {
1591 $v_checksum += ord(' ');
1593 // ..... Last part of the header
1594 for ($i = 156, $j = 0; $i < 512; $i++, $j++) {
1595 $v_checksum += ord(substr($v_binary_data_last, $j, 1));
1598 // ----- Write the first 148 bytes of the header in the archive
1599 $this->_writeBlock($v_binary_data_first, 148);
1601 // ----- Write the calculated checksum
1602 $v_checksum = sprintf("%06s ", DecOct($v_checksum));
1603 $v_binary_data = pack("a8", $v_checksum);
1604 $this->_writeBlock($v_binary_data, 8);
1606 // ----- Write the last 356 bytes of the header in the archive
1607 $this->_writeBlock($v_binary_data_last, 356);
1613 * @param string $p_filename
1616 public function _writeLongHeader($p_filename)
1618 $v_size = sprintf("%11s ", DecOct(strlen($p_filename)));
1638 $v_binary_data_first = pack(
1647 $v_binary_data_last = pack(
1648 "a1a100a6a2a32a32a8a8a155a12",
1661 // ----- Calculate the checksum
1663 // ..... First part of the header
1664 for ($i = 0; $i < 148; $i++) {
1665 $v_checksum += ord(substr($v_binary_data_first, $i, 1));
1667 // ..... Ignore the checksum value and replace it by ' ' (space)
1668 for ($i = 148; $i < 156; $i++) {
1669 $v_checksum += ord(' ');
1671 // ..... Last part of the header
1672 for ($i = 156, $j = 0; $i < 512; $i++, $j++) {
1673 $v_checksum += ord(substr($v_binary_data_last, $j, 1));
1676 // ----- Write the first 148 bytes of the header in the archive
1677 $this->_writeBlock($v_binary_data_first, 148);
1679 // ----- Write the calculated checksum
1680 $v_checksum = sprintf("%06s ", DecOct($v_checksum));
1681 $v_binary_data = pack("a8", $v_checksum);
1682 $this->_writeBlock($v_binary_data, 8);
1684 // ----- Write the last 356 bytes of the header in the archive
1685 $this->_writeBlock($v_binary_data_last, 356);
1687 // ----- Write the filename as content of the block
1689 while (($v_buffer = substr($p_filename, (($i++) * 512), 512)) != '') {
1690 $v_binary_data = pack("a512", "$v_buffer");
1691 $this->_writeBlock($v_binary_data);
1698 * @param mixed $v_binary_data
1699 * @param mixed $v_header
1702 public function _readHeader($v_binary_data, &$v_header)
1704 if (strlen($v_binary_data) == 0) {
1705 $v_header['filename'] = '';
1709 if (strlen($v_binary_data) != 512) {
1710 $v_header['filename'] = '';
1711 $this->_error('Invalid block size : ' . strlen($v_binary_data));
1715 if (!is_array($v_header)) {
1716 $v_header = array();
1718 // ----- Calculate the checksum
1720 // ..... First part of the header
1721 for ($i = 0; $i < 148; $i++) {
1722 $v_checksum += ord(substr($v_binary_data, $i, 1));
1724 // ..... Ignore the checksum value and replace it by ' ' (space)
1725 for ($i = 148; $i < 156; $i++) {
1726 $v_checksum += ord(' ');
1728 // ..... Last part of the header
1729 for ($i = 156; $i < 512; $i++) {
1730 $v_checksum += ord(substr($v_binary_data, $i, 1));
1733 if (version_compare(PHP_VERSION, "5.5.0-dev") < 0) {
1734 $fmt = "a100filename/a8mode/a8uid/a8gid/a12size/a12mtime/" .
1735 "a8checksum/a1typeflag/a100link/a6magic/a2version/" .
1736 "a32uname/a32gname/a8devmajor/a8devminor/a131prefix";
1738 $fmt = "Z100filename/Z8mode/Z8uid/Z8gid/Z12size/Z12mtime/" .
1739 "Z8checksum/Z1typeflag/Z100link/Z6magic/Z2version/" .
1740 "Z32uname/Z32gname/Z8devmajor/Z8devminor/Z131prefix";
1742 $v_data = unpack($fmt, $v_binary_data);
1744 if (strlen($v_data["prefix"]) > 0) {
1745 $v_data["filename"] = "$v_data[prefix]/$v_data[filename]";
1748 // ----- Extract the checksum
1749 $v_header['checksum'] = OctDec(trim($v_data['checksum']));
1750 if ($v_header['checksum'] != $v_checksum) {
1751 $v_header['filename'] = '';
1753 // ----- Look for last block (empty block)
1754 if (($v_checksum == 256) && ($v_header['checksum'] == 0)) {
1759 'Invalid checksum for file "' . $v_data['filename']
1760 . '" : ' . $v_checksum . ' calculated, '
1761 . $v_header['checksum'] . ' expected'
1766 // ----- Extract the properties
1767 $v_header['filename'] = rtrim($v_data['filename'], "\0");
1768 if ($this->_maliciousFilename($v_header['filename'])) {
1770 'Malicious .tar detected, file "' . $v_header['filename'] .
1771 '" will not install in desired directory tree'
1775 $v_header['mode'] = OctDec(trim($v_data['mode']));
1776 $v_header['uid'] = OctDec(trim($v_data['uid']));
1777 $v_header['gid'] = OctDec(trim($v_data['gid']));
1778 $v_header['size'] = OctDec(trim($v_data['size']));
1779 $v_header['mtime'] = OctDec(trim($v_data['mtime']));
1780 if (($v_header['typeflag'] = $v_data['typeflag']) == "5") {
1781 $v_header['size'] = 0;
1783 $v_header['link'] = trim($v_data['link']);
1784 /* ----- All these fields are removed form the header because
1785 they do not carry interesting info
1786 $v_header[magic] = trim($v_data[magic]);
1787 $v_header[version] = trim($v_data[version]);
1788 $v_header[uname] = trim($v_data[uname]);
1789 $v_header[gname] = trim($v_data[gname]);
1790 $v_header[devmajor] = trim($v_data[devmajor]);
1791 $v_header[devminor] = trim($v_data[devminor]);
1798 * Detect and report a malicious file name
1800 * @param string $file
1804 private function _maliciousFilename($file)
1806 if (strpos($file, '/../') !== false) {
1809 if (strpos($file, '../') === 0) {
1819 public function _readLongHeader(&$v_header)
1822 $v_filesize = $v_header['size'];
1823 $n = floor($v_header['size'] / 512);
1824 for ($i = 0; $i < $n; $i++) {
1825 $v_content = $this->_readBlock();
1826 $v_filename .= $v_content;
1828 if (($v_header['size'] % 512) != 0) {
1829 $v_content = $this->_readBlock();
1830 $v_filename .= $v_content;
1833 // ----- Read the next header
1834 $v_binary_data = $this->_readBlock();
1836 if (!$this->_readHeader($v_binary_data, $v_header)) {
1840 $v_filename = rtrim(substr($v_filename, 0, $v_filesize), "\0");
1841 $v_header['filename'] = $v_filename;
1842 if ($this->_maliciousFilename($v_filename)) {
1844 'Malicious .tar detected, file "' . $v_filename .
1845 '" will not install in desired directory tree'
1854 * This method extract from the archive one file identified by $p_filename.
1855 * The return value is a string with the file content, or null on error.
1857 * @param string $p_filename The path of the file to extract in a string.
1859 * @return a string with the file content or null.
1861 private function _extractInString($p_filename)
1865 while (strlen($v_binary_data = $this->_readBlock()) != 0) {
1866 if (!$this->_readHeader($v_binary_data, $v_header)) {
1870 if ($v_header['filename'] == '') {
1874 // ----- Look for long filename
1875 if ($v_header['typeflag'] == 'L') {
1876 if (!$this->_readLongHeader($v_header)) {
1881 if ($v_header['filename'] == $p_filename) {
1882 if ($v_header['typeflag'] == "5") {
1884 'Unable to extract in string a directory '
1885 . 'entry {' . $v_header['filename'] . '}'
1889 $n = floor($v_header['size'] / 512);
1890 for ($i = 0; $i < $n; $i++) {
1891 $v_result_str .= $this->_readBlock();
1893 if (($v_header['size'] % 512) != 0) {
1894 $v_content = $this->_readBlock();
1895 $v_result_str .= substr(
1898 ($v_header['size'] % 512)
1901 return $v_result_str;
1904 $this->_jumpBlock(ceil(($v_header['size'] / 512)));
1912 * @param string $p_path
1913 * @param string $p_list_detail
1914 * @param string $p_mode
1915 * @param string $p_file_list
1916 * @param string $p_remove_path
1917 * @param bool $p_preserve
1920 public function _extractList(
1930 $v_extract_all = true;
1933 $p_path = $this->_translateWinPath($p_path, false);
1934 if ($p_path == '' || (substr($p_path, 0, 1) != '/'
1935 && substr($p_path, 0, 3) != "../" && !strpos($p_path, ':'))
1937 $p_path = "./" . $p_path;
1939 $p_remove_path = $this->_translateWinPath($p_remove_path);
1941 // ----- Look for path to remove format (should end by /)
1942 if (($p_remove_path != '') && (substr($p_remove_path, -1) != '/')) {
1943 $p_remove_path .= '/';
1945 $p_remove_path_size = strlen($p_remove_path);
1949 $v_extract_all = true;
1953 $v_extract_all = false;
1957 $v_extract_all = false;
1961 $this->_error('Invalid extract mode (' . $p_mode . ')');
1967 while (strlen($v_binary_data = $this->_readBlock()) != 0) {
1968 $v_extract_file = false;
1969 $v_extraction_stopped = 0;
1971 if (!$this->_readHeader($v_binary_data, $v_header)) {
1975 if ($v_header['filename'] == '') {
1979 // ----- Look for long filename
1980 if ($v_header['typeflag'] == 'L') {
1981 if (!$this->_readLongHeader($v_header)) {
1986 // ignore extended / pax headers
1987 if ($v_header['typeflag'] == 'x' || $v_header['typeflag'] == 'g') {
1988 $this->_jumpBlock(ceil(($v_header['size'] / 512)));
1992 if ((!$v_extract_all) && (is_array($p_file_list))) {
1993 // ----- By default no unzip if the file is not found
1994 $v_extract_file = false;
1996 for ($i = 0; $i < sizeof($p_file_list); $i++) {
1997 // ----- Look if it is a directory
1998 if (substr($p_file_list[$i], -1) == '/') {
1999 // ----- Look if the directory is in the filename path
2000 if ((strlen($v_header['filename']) > strlen($p_file_list[$i]))
2001 && (substr($v_header['filename'], 0, strlen($p_file_list[$i]))
2002 == $p_file_list[$i])
2004 $v_extract_file = true;
2007 } // ----- It is a file, so compare the file names
2008 elseif ($p_file_list[$i] == $v_header['filename']) {
2009 $v_extract_file = true;
2014 $v_extract_file = true;
2017 // ----- Look if this file need to be extracted
2018 if (($v_extract_file) && (!$v_listing)) {
2019 if (($p_remove_path != '')
2020 && (substr($v_header['filename'] . '/', 0, $p_remove_path_size)
2023 $v_header['filename'] = substr(
2024 $v_header['filename'],
2027 if ($v_header['filename'] == '') {
2031 if (($p_path != './') && ($p_path != '/')) {
2032 while (substr($p_path, -1) == '/') {
2033 $p_path = substr($p_path, 0, strlen($p_path) - 1);
2036 if (substr($v_header['filename'], 0, 1) == '/') {
2037 $v_header['filename'] = $p_path . $v_header['filename'];
2039 $v_header['filename'] = $p_path . '/' . $v_header['filename'];
2042 if (file_exists($v_header['filename'])) {
2043 if ((@is_dir($v_header['filename']))
2044 && ($v_header['typeflag'] == '')
2047 'File ' . $v_header['filename']
2048 . ' already exists as a directory'
2052 if (($this->_isArchive($v_header['filename']))
2053 && ($v_header['typeflag'] == "5")
2056 'Directory ' . $v_header['filename']
2057 . ' already exists as a file'
2061 if (!is_writeable($v_header['filename'])) {
2063 'File ' . $v_header['filename']
2064 . ' already exists and is write protected'
2068 if (filemtime($v_header['filename']) > $v_header['mtime']) {
2069 // To be completed : An error or silent no replace ?
2071 } // ----- Check the directory availability and create it if necessary
2074 ($v_header['typeflag'] == "5"
2075 ? $v_header['filename']
2076 : dirname($v_header['filename']))
2079 $this->_error('Unable to create path for ' . $v_header['filename']);
2083 if ($v_extract_file) {
2084 if ($v_header['typeflag'] == "5") {
2085 if (!@file_exists($v_header['filename'])) {
2086 if (!@mkdir($v_header['filename'], 0777)) {
2088 'Unable to create directory {'
2089 . $v_header['filename'] . '}'
2094 } elseif ($v_header['typeflag'] == "2") {
2095 if (@file_exists($v_header['filename'])) {
2096 @drupal_unlink($v_header['filename']);
2098 if (!@symlink($v_header['link'], $v_header['filename'])) {
2100 'Unable to extract symbolic link {'
2101 . $v_header['filename'] . '}'
2106 if (($v_dest_file = @fopen($v_header['filename'], "wb")) == 0) {
2108 'Error while opening {' . $v_header['filename']
2109 . '} in write binary mode'
2113 $n = floor($v_header['size'] / 512);
2114 for ($i = 0; $i < $n; $i++) {
2115 $v_content = $this->_readBlock();
2116 fwrite($v_dest_file, $v_content, 512);
2118 if (($v_header['size'] % 512) != 0) {
2119 $v_content = $this->_readBlock();
2120 fwrite($v_dest_file, $v_content, ($v_header['size'] % 512));
2123 @fclose($v_dest_file);
2126 @chown($v_header['filename'], $v_header['uid']);
2127 @chgrp($v_header['filename'], $v_header['gid']);
2130 // ----- Change the file mode, mtime
2131 @touch($v_header['filename'], $v_header['mtime']);
2132 if ($v_header['mode'] & 0111) {
2133 // make file executable, obey umask
2134 $mode = fileperms($v_header['filename']) | (~umask() & 0111);
2135 @chmod($v_header['filename'], $mode);
2139 // ----- Check the file size
2141 if (!is_file($v_header['filename'])) {
2143 'Extracted file ' . $v_header['filename']
2144 . 'does not exist. Archive may be corrupted.'
2149 $filesize = filesize($v_header['filename']);
2150 if ($filesize != $v_header['size']) {
2152 'Extracted file ' . $v_header['filename']
2153 . ' does not have the correct file size \''
2155 . '\' (' . $v_header['size']
2156 . ' expected). Archive may be corrupted.'
2162 $this->_jumpBlock(ceil(($v_header['size'] / 512)));
2165 $this->_jumpBlock(ceil(($v_header['size'] / 512)));
2168 /* TBC : Seems to be unused ...
2169 if ($this->_compress)
2170 $v_end_of_file = @gzeof($this->_file);
2172 $v_end_of_file = @feof($this->_file);
2175 if ($v_listing || $v_extract_file || $v_extraction_stopped) {
2176 // ----- Log extracted files
2177 if (($v_file_dir = dirname($v_header['filename']))
2178 == $v_header['filename']
2182 if ((substr($v_header['filename'], 0, 1) == '/') && ($v_file_dir == '')) {
2186 $p_list_detail[$v_nb++] = $v_header;
2187 if (is_array($p_file_list) && (count($p_list_detail) == count($p_file_list))) {
2199 public function _openAppend()
2201 if (filesize($this->_tarname) == 0) {
2202 return $this->_openWrite();
2205 if ($this->_compress) {
2208 if (!@rename($this->_tarname, $this->_tarname . ".tmp")) {
2210 'Error while renaming \'' . $this->_tarname
2211 . '\' to temporary file \'' . $this->_tarname
2217 if ($this->_compress_type == 'gz') {
2218 $v_temp_tar = @gzopen($this->_tarname . ".tmp", "rb");
2219 } elseif ($this->_compress_type == 'bz2') {
2220 $v_temp_tar = @bzopen($this->_tarname . ".tmp", "r");
2221 } elseif ($this->_compress_type == 'lzma2') {
2222 $v_temp_tar = @xzopen($this->_tarname . ".tmp", "r");
2226 if ($v_temp_tar == 0) {
2228 'Unable to open file \'' . $this->_tarname
2229 . '.tmp\' in binary read mode'
2231 @rename($this->_tarname . ".tmp", $this->_tarname);
2235 if (!$this->_openWrite()) {
2236 @rename($this->_tarname . ".tmp", $this->_tarname);
2240 if ($this->_compress_type == 'gz') {
2243 while (!@gzeof($v_temp_tar)) {
2244 $v_buffer = @gzread($v_temp_tar, 512);
2245 if ($v_buffer == ARCHIVE_TAR_END_BLOCK || strlen($v_buffer) == 0) {
2247 // do not copy end blocks, we will re-make them
2250 } elseif ($end_blocks > 0) {
2251 for ($i = 0; $i < $end_blocks; $i++) {
2252 $this->_writeBlock(ARCHIVE_TAR_END_BLOCK);
2256 $v_binary_data = pack("a512", $v_buffer);
2257 $this->_writeBlock($v_binary_data);
2260 @gzclose($v_temp_tar);
2261 } elseif ($this->_compress_type == 'bz2') {
2264 while (strlen($v_buffer = @bzread($v_temp_tar, 512)) > 0) {
2265 if ($v_buffer == ARCHIVE_TAR_END_BLOCK || strlen($v_buffer) == 0) {
2267 // do not copy end blocks, we will re-make them
2270 } elseif ($end_blocks > 0) {
2271 for ($i = 0; $i < $end_blocks; $i++) {
2272 $this->_writeBlock(ARCHIVE_TAR_END_BLOCK);
2276 $v_binary_data = pack("a512", $v_buffer);
2277 $this->_writeBlock($v_binary_data);
2280 @bzclose($v_temp_tar);
2281 } elseif ($this->_compress_type == 'lzma2') {
2284 while (strlen($v_buffer = @xzread($v_temp_tar, 512)) > 0) {
2285 if ($v_buffer == ARCHIVE_TAR_END_BLOCK || strlen($v_buffer) == 0) {
2287 // do not copy end blocks, we will re-make them
2290 } elseif ($end_blocks > 0) {
2291 for ($i = 0; $i < $end_blocks; $i++) {
2292 $this->_writeBlock(ARCHIVE_TAR_END_BLOCK);
2296 $v_binary_data = pack("a512", $v_buffer);
2297 $this->_writeBlock($v_binary_data);
2300 @xzclose($v_temp_tar);
2303 if (!@drupal_unlink($this->_tarname . ".tmp")) {
2305 'Error while deleting temporary file \''
2306 . $this->_tarname . '.tmp\''
2310 // ----- For not compressed tar, just add files before the last
2311 // one or two 512 bytes block
2312 if (!$this->_openReadWrite()) {
2317 $v_size = filesize($this->_tarname);
2319 // We might have zero, one or two end blocks.
2320 // The standard is two, but we should try to handle
2322 fseek($this->_file, $v_size - 1024);
2323 if (fread($this->_file, 512) == ARCHIVE_TAR_END_BLOCK) {
2324 fseek($this->_file, $v_size - 1024);
2325 } elseif (fread($this->_file, 512) == ARCHIVE_TAR_END_BLOCK) {
2326 fseek($this->_file, $v_size - 512);
2334 * @param $p_filelist
2335 * @param string $p_add_dir
2336 * @param string $p_remove_dir
2339 public function _append($p_filelist, $p_add_dir = '', $p_remove_dir = '')
2341 if (!$this->_openAppend()) {
2345 if ($this->_addList($p_filelist, $p_add_dir, $p_remove_dir)) {
2346 $this->_writeFooter();
2355 * Check if a directory exists and create it (including parent
2358 * @param string $p_dir directory to check
2360 * @return bool true if the directory exists or was created
2362 public function _dirCheck($p_dir)
2365 if ((@is_dir($p_dir)) || ($p_dir == '')) {
2369 $p_parent_dir = dirname($p_dir);
2371 if (($p_parent_dir != $p_dir) &&
2372 ($p_parent_dir != '') &&
2373 (!$this->_dirCheck($p_parent_dir))
2378 if (!@mkdir($p_dir, 0777)) {
2379 $this->_error("Unable to create directory '$p_dir'");
2387 * Compress path by changing for example "/dir/foo/../bar" to "/dir/bar",
2388 * and remove double slashes.
2390 * @param string $p_dir path to reduce
2392 * @return string reduced path
2394 private function _pathReduction($p_dir)
2398 // ----- Look for not empty path
2400 // ----- Explode path by directory names
2401 $v_list = explode('/', $p_dir);
2403 // ----- Study directories from last to first
2404 for ($i = sizeof($v_list) - 1; $i >= 0; $i--) {
2405 // ----- Look for current path
2406 if ($v_list[$i] == ".") {
2407 // ----- Ignore this directory
2408 // Should be the first $i=0, but no check is done
2410 if ($v_list[$i] == "..") {
2411 // ----- Ignore it and ignore the $i-1
2414 if (($v_list[$i] == '')
2415 && ($i != (sizeof($v_list) - 1))
2418 // ----- Ignore only the double '//' in path,
2419 // but not the first and last /
2421 $v_result = $v_list[$i] . ($i != (sizeof($v_list) - 1) ? '/'
2429 if (defined('OS_WINDOWS') && OS_WINDOWS) {
2430 $v_result = strtr($v_result, '\\', '/');
2438 * @param bool $p_remove_disk_letter
2441 public function _translateWinPath($p_path, $p_remove_disk_letter = true)
2443 if (defined('OS_WINDOWS') && OS_WINDOWS) {
2444 // ----- Look for potential disk letter
2445 if (($p_remove_disk_letter)
2446 && (($v_position = strpos($p_path, ':')) != false)
2448 $p_path = substr($p_path, $v_position + 1);
2450 // ----- Change potential windows directory separator
2451 if ((strpos($p_path, '\\') > 0) || (substr($p_path, 0, 1) == '\\')) {
2452 $p_path = strtr($p_path, '\\', '/');