Updated to Drupal 8.6.4, which is PHP 7.3 friendly. Also updated HTMLaw library....
[yaffs-website] / web / core / lib / Drupal / Core / Archiver / ArchiveTar.php
1 <?php
2 // @codingStandardsIgnoreFile
3 /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
4
5 /**
6  * File::CSV
7  *
8  * PHP versions 4 and 5
9  *
10  * Copyright (c) 1997-2008,
11  * Vincent Blavet <vincent@phpconcept.net>
12  * All rights reserved.
13  *
14  * Redistribution and use in source and binary forms, with or without
15  * modification, are permitted provided that the following conditions are met:
16  *
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.
22  *
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.
33  *
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
39  * @version   CVS: $Id$
40  * @link      http://pear.php.net/package/Archive_Tar
41  */
42
43  /**
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).
61  */
62
63
64 // Drupal addition.
65 namespace {
66
67 // Drupal removal require_once 'PEAR.php'.
68
69 // Drupal addition OS_WINDOWS as defined in PEAR.php.
70 if (substr(PHP_OS, 0, 3) == 'WIN') {
71     define('OS_WINDOWS', true);
72 } else {
73     define('OS_WINDOWS', false);
74 }
75
76 define('ARCHIVE_TAR_ATT_SEPARATOR', 90001);
77 define('ARCHIVE_TAR_END_BLOCK', pack("a512", ''));
78
79 if (!function_exists('gzopen') && function_exists('gzopen64')) {
80     function gzopen($filename, $mode, $use_include_path = 0)
81     {
82         return gzopen64($filename, $mode, $use_include_path);
83     }
84 }
85
86 if (!function_exists('gztell') && function_exists('gztell64')) {
87     function gztell($zp)
88     {
89         return gztell64($zp);
90     }
91 }
92
93 if (!function_exists('gzseek') && function_exists('gzseek64')) {
94     function gzseek($zp, $offset, $whence = SEEK_SET)
95     {
96         return gzseek64($zp, $offset, $whence);
97     }
98 }
99 }
100
101 // Drupal addition.
102 namespace Drupal\Core\Archiver {
103 /**
104  * Creates a (compressed) Tar archive
105  *
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$
110  */
111 // Drupal change class Archive_Tar extends PEAR.
112 class ArchiveTar
113 {
114     /**
115      * @var string Name of the Tar
116      */
117     public $_tarname = '';
118
119     /**
120      * @var boolean if true, the Tar file will be gzipped
121      */
122     public $_compress = false;
123
124     /**
125      * @var string Type of compression : 'none', 'gz', 'bz2' or 'lzma2'
126      */
127     public $_compress_type = 'none';
128
129     /**
130      * @var string Explode separator
131      */
132     public $_separator = ' ';
133
134     /**
135      * @var file descriptor
136      */
137     public $_file = 0;
138
139     /**
140      * @var string Local Tar name of a remote Tar (http:// or ftp://)
141      */
142     public $_temp_tarname = '';
143
144     /**
145      * @var string regular expression for ignoring files or directories
146      */
147     public $_ignore_regexp = '';
148
149     /**
150      * @var object PEAR_Error object
151      */
152     public $error_object = null;
153
154     /**
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
157      * tar file.
158      * If the compress argument is set the tar will be read or created as a
159      * gzip or bz2 compressed TAR file.
160      *
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'.
166      *
167      * @return bool
168      */
169     public function __construct($p_tarname, $p_compress = null)
170     {
171         // Drupal removal parent::__construct().
172
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);
180                     fclose($fp);
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';
191                     }
192                 }
193             } else {
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')
201                 ) {
202                     $this->_compress = true;
203                     $this->_compress_type = 'bz2';
204                 } else {
205                     if (substr($p_tarname, -2) == 'xz') {
206                         $this->_compress = true;
207                         $this->_compress_type = 'lzma2';
208                     }
209                 }
210             }
211         } else {
212             if (($p_compress === true) || ($p_compress == 'gz')) {
213                 $this->_compress = true;
214                 $this->_compress_type = 'gz';
215             } else {
216                 if ($p_compress == 'bz2') {
217                     $this->_compress = true;
218                     $this->_compress_type = 'bz2';
219                 } else {
220                     if ($p_compress == 'lzma2') {
221                         $this->_compress = true;
222                         $this->_compress_type = 'lzma2';
223                     } else {
224                         $this->_error(
225                             "Unsupported compression type '$p_compress'\n" .
226                             "Supported types are 'gz', 'bz2' and 'lzma2'.\n"
227                         );
228                         return false;
229                     }
230                 }
231             }
232         }
233         $this->_tarname = $p_tarname;
234         if ($this->_compress) { // assert zlib or bz2 or xz extension support
235             if ($this->_compress_type == 'gz') {
236                 $extname = 'zlib';
237             } else {
238                 if ($this->_compress_type == 'bz2') {
239                     $extname = 'bz2';
240                 } else {
241                     if ($this->_compress_type == 'lzma2') {
242                         $extname = 'xz';
243                     }
244                 }
245             }
246
247             if (!extension_loaded($extname)) {
248                 // Drupal change PEAR::loadExtension($extname).
249                 $this->loadExtension($extname);
250             }
251             if (!extension_loaded($extname)) {
252                 $this->_error(
253                     "The extension '$extname' couldn't be found.\n" .
254                     "Please make sure your version of PHP was built " .
255                     "with '$extname' support.\n"
256                 );
257                 return false;
258             }
259         }
260     }
261
262     public function __destruct()
263     {
264         $this->_close();
265         // ----- Look for a local copy to delete
266         if ($this->_temp_tarname != '') {
267             @drupal_unlink($this->_temp_tarname);
268         }
269     }
270
271     // Drupal addition from PEAR.php.
272     /**
273     * OS independent PHP extension load. Remember to take care
274     * on the correct extension name for case sensitive OSes.
275     *
276     * @param string $ext The extension name
277     * @return bool Success or not on the dl() call
278     */
279     function loadExtension($ext)
280     {
281         if (extension_loaded($ext)) {
282             return true;
283         }
284
285         // if either returns true dl() will produce a FATAL error, stop that
286         if (
287             function_exists('dl') === false ||
288             ini_get('enable_dl') != 1 ||
289             ini_get('safe_mode') == 1
290         ) {
291             return false;
292         }
293
294         if (OS_WINDOWS) {
295             $suffix = '.dll';
296         } elseif (PHP_OS == 'HP-UX') {
297             $suffix = '.sl';
298         } elseif (PHP_OS == 'AIX') {
299             $suffix = '.a';
300         } elseif (PHP_OS == 'OSX') {
301             $suffix = '.bundle';
302         } else {
303             $suffix = '.so';
304         }
305
306         return @dl('php_'.$ext.$suffix) || @dl($ext.$suffix);
307     }
308
309
310     /**
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
314      * by the new tar.
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
319      * single blank.
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.
323      *
324      * @param array $p_filelist An array of filenames and directory names, or a
325      *              single string with names separated by a single
326      *              blank space.
327      *
328      * @return true on success, false on error.
329      * @see    createModify()
330      */
331     public function create($p_filelist)
332     {
333         return $this->createModify($p_filelist, '', '');
334     }
335
336     /**
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.
343      *
344      * @param array $p_filelist An array of filenames and directory names, or a
345      *              single string with names separated by a single
346      *              blank space.
347      *
348      * @return true on success, false on error.
349      * @see    createModify()
350      * @access public
351      */
352     public function add($p_filelist)
353     {
354         return $this->addModify($p_filelist, '', '');
355     }
356
357     /**
358      * @param string $p_path
359      * @param bool $p_preserve
360      * @return bool
361      */
362     public function extract($p_path = '', $p_preserve = false)
363     {
364         return $this->extractModify($p_path, '', $p_preserve);
365     }
366
367     /**
368      * @return array|int
369      */
370     public function listContent()
371     {
372         $v_list_detail = array();
373
374         if ($this->_openRead()) {
375             if (!$this->_extractList('', $v_list_detail, "list", '', '')) {
376                 unset($v_list_detail);
377                 $v_list_detail = 0;
378             }
379             $this->_close();
380         }
381
382         return $v_list_detail;
383     }
384
385     /**
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
395      * single blank.
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
402      * of path.
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.
406      *
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
412      *                             the list.
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.
416      *
417      * @return boolean true on success, false on error.
418      * @see addModify()
419      */
420     public function createModify($p_filelist, $p_add_dir, $p_remove_dir = '')
421     {
422         $v_result = true;
423
424         if (!$this->_openWrite()) {
425             return false;
426         }
427
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);
433             } else {
434                 $this->_cleanFile();
435                 $this->_error('Invalid file list');
436                 return false;
437             }
438
439             $v_result = $this->_addList($v_list, $p_add_dir, $p_remove_dir);
440         }
441
442         if ($v_result) {
443             $this->_writeFooter();
444             $this->_close();
445         } else {
446             $this->_cleanFile();
447         }
448
449         return $v_result;
450     }
451
452     /**
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
455      * is created.
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
459      * single blank.
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
466      * of path.
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
473      * archive size.
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.
478      *
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
488      *                             relevant.
489      *
490      * @return true on success, false on error.
491      */
492     public function addModify($p_filelist, $p_add_dir, $p_remove_dir = '')
493     {
494         $v_result = true;
495
496         if (!$this->_isArchive()) {
497             $v_result = $this->createModify(
498                 $p_filelist,
499                 $p_add_dir,
500                 $p_remove_dir
501             );
502         } else {
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);
507             } else {
508                 $this->_error('Invalid file list');
509                 return false;
510             }
511
512             $v_result = $this->_append($v_list, $p_add_dir, $p_remove_dir);
513         }
514
515         return $v_result;
516     }
517
518     /**
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
521      * is created.
522      *
523      * @param string $p_filename A string which contains the full
524      *                           filename path that will be associated
525      *                           with the string.
526      * @param string $p_string The content of the file added in
527      *                           the archive.
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)
542      *
543      * @return true on success, false on error.
544      */
545     public function addString($p_filename, $p_string, $p_datetime = false, $p_params = array())
546     {
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"] : "";
552         $v_result = true;
553
554         if (!$this->_isArchive()) {
555             if (!$this->_openWrite()) {
556                 return false;
557             }
558             $this->_close();
559         }
560
561         if (!$this->_openAppend()) {
562             return false;
563         }
564
565         // Need to check the get back to the temporary file ? ....
566         $v_result = $this->_addString($p_filename, $p_string, $p_datetime, $p_params);
567
568         $this->_writeFooter();
569
570         $this->_close();
571
572         return $v_result;
573     }
574
575     /**
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
581      * created.
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.
598      *
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
603      *                               the file/dir path.
604      * @param boolean $p_preserve Preserve user/group ownership of files
605      *
606      * @return boolean true on success, false on error.
607      * @see    extractList()
608      */
609     public function extractModify($p_path, $p_remove_path, $p_preserve = false)
610     {
611         $v_result = true;
612         $v_list_detail = array();
613
614         if ($v_result = $this->_openRead()) {
615             $v_result = $this->_extractList(
616                 $p_path,
617                 $v_list_detail,
618                 "complete",
619                 0,
620                 $p_remove_path,
621                 $p_preserve
622             );
623             $this->_close();
624         }
625
626         return $v_result;
627     }
628
629     /**
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.
632      *
633      * @param string $p_filename The path of the file to extract in a string.
634      *
635      * @return a string with the file content or NULL.
636      */
637     public function extractInString($p_filename)
638     {
639         if ($this->_openRead()) {
640             $v_result = $this->_extractInString($p_filename);
641             $this->_close();
642         } else {
643             $v_result = null;
644         }
645
646         return $v_result;
647     }
648
649     /**
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.
655      *
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
663      *                               the file/dir path.
664      * @param boolean $p_preserve Preserve user/group ownership of files
665      *
666      * @return true on success, false on error.
667      * @see    extractModify()
668      */
669     public function extractList($p_filelist, $p_path = '', $p_remove_path = '', $p_preserve = false)
670     {
671         $v_result = true;
672         $v_list_detail = array();
673
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);
678         } else {
679             $this->_error('Invalid string list');
680             return false;
681         }
682
683         if ($v_result = $this->_openRead()) {
684             $v_result = $this->_extractList(
685                 $p_path,
686                 $v_list_detail,
687                 "partial",
688                 $v_list,
689                 $p_remove_path,
690                 $p_preserve
691             );
692             $this->_close();
693         }
694
695         return $v_result;
696     }
697
698     /**
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, ',');
702      *
703      * @return true on success, false on error.
704      */
705     public function setAttribute()
706     {
707         $v_result = true;
708
709         // ----- Get the number of variable list of arguments
710         if (($v_size = func_num_args()) == 0) {
711             return true;
712         }
713
714         // ----- Get the arguments
715         $v_att_list = & func_get_args();
716
717         // ----- Read the attributes
718         $i = 0;
719         while ($i < $v_size) {
720
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) {
727                         $this->_error(
728                             'Invalid number of parameters for '
729                             . 'attribute ARCHIVE_TAR_ATT_SEPARATOR'
730                         );
731                         return false;
732                     }
733
734                     // ----- Get the value
735                     $this->_separator = $v_att_list[$i + 1];
736                     $i++;
737                     break;
738
739                 default :
740                     $this->_error('Unknown attribute code ' . $v_att_list[$i] . '');
741                     return false;
742             }
743
744             // ----- Next attribute
745             $i++;
746         }
747
748         return $v_result;
749     }
750
751     /**
752      * This method sets the regular expression for ignoring files and directories
753      * at import, for example:
754      * $arch->setIgnoreRegexp("#CVS|\.svn#");
755      *
756      * @param string $regexp regular expression defining which files or directories to ignore
757      */
758     public function setIgnoreRegexp($regexp)
759     {
760         $this->_ignore_regexp = $regexp;
761     }
762
763     /**
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'));
767      *
768      * @param array $list a list of file or directory names to ignore
769      *
770      * @access public
771      */
772     public function setIgnoreList($list)
773     {
774         $regexp = str_replace(array('#', '.', '^', '$'), array('\#', '\.', '\^', '\$'), $list);
775         $regexp = '#/' . join('$|/', $list) . '#';
776         $this->setIgnoreRegexp($regexp);
777     }
778
779     /**
780      * @param string $p_message
781      */
782     public function _error($p_message)
783     {
784         // Drupal change $this->error_object = $this->raiseError($p_message).
785         throw new \Exception($p_message);
786     }
787
788     /**
789      * @param string $p_message
790      */
791     public function _warning($p_message)
792     {
793         // Drupal change $this->error_object = $this->raiseError($p_message).
794         throw new \Exception($p_message);
795     }
796
797     /**
798      * @param string $p_filename
799      * @return bool
800      */
801     public function _isArchive($p_filename = null)
802     {
803         if ($p_filename == null) {
804             $p_filename = $this->_tarname;
805         }
806         clearstatcache();
807         return @is_file($p_filename) && !@is_link($p_filename);
808     }
809
810     /**
811      * @return bool
812      */
813     public function _openWrite()
814     {
815         if ($this->_compress_type == 'gz' && function_exists('gzopen')) {
816             $this->_file = @gzopen($this->_tarname, "wb9");
817         } else {
818             if ($this->_compress_type == 'bz2' && function_exists('bzopen')) {
819                 $this->_file = @bzopen($this->_tarname, "w");
820             } else {
821                 if ($this->_compress_type == 'lzma2' && function_exists('xzopen')) {
822                     $this->_file = @xzopen($this->_tarname, 'w');
823                 } else {
824                     if ($this->_compress_type == 'none') {
825                         $this->_file = @fopen($this->_tarname, "wb");
826                     } else {
827                         $this->_error(
828                             'Unknown or missing compression type ('
829                             . $this->_compress_type . ')'
830                         );
831                         return false;
832                     }
833                 }
834             }
835         }
836
837         if ($this->_file == 0) {
838             $this->_error(
839                 'Unable to open in write mode \''
840                 . $this->_tarname . '\''
841             );
842             return false;
843         }
844
845         return true;
846     }
847
848     /**
849      * @return bool
850      */
851     public function _openRead()
852     {
853         if (strtolower(substr($this->_tarname, 0, 7)) == 'http://') {
854
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')) {
859                     $this->_error(
860                         'Unable to open in read mode \''
861                         . $this->_tarname . '\''
862                     );
863                     $this->_temp_tarname = '';
864                     return false;
865                 }
866                 if (!$v_file_to = @fopen($this->_temp_tarname, 'wb')) {
867                     $this->_error(
868                         'Unable to open in write mode \''
869                         . $this->_temp_tarname . '\''
870                     );
871                     $this->_temp_tarname = '';
872                     return false;
873                 }
874                 while ($v_data = @fread($v_file_from, 1024)) {
875                     @fwrite($v_file_to, $v_data);
876                 }
877                 @fclose($v_file_from);
878                 @fclose($v_file_to);
879             }
880
881             // ----- File to open if the local copy
882             $v_filename = $this->_temp_tarname;
883         } else {
884             // ----- File to open if the normal Tar file
885
886             $v_filename = $this->_tarname;
887         }
888
889         if ($this->_compress_type == 'gz' && function_exists('gzopen')) {
890             $this->_file = @gzopen($v_filename, "rb");
891         } else {
892             if ($this->_compress_type == 'bz2' && function_exists('bzopen')) {
893                 $this->_file = @bzopen($v_filename, "r");
894             } else {
895                 if ($this->_compress_type == 'lzma2' && function_exists('xzopen')) {
896                     $this->_file = @xzopen($v_filename, "r");
897                 } else {
898                     if ($this->_compress_type == 'none') {
899                         $this->_file = @fopen($v_filename, "rb");
900                     } else {
901                         $this->_error(
902                             'Unknown or missing compression type ('
903                             . $this->_compress_type . ')'
904                         );
905                         return false;
906                     }
907                 }
908             }
909         }
910
911         if ($this->_file == 0) {
912             $this->_error('Unable to open in read mode \'' . $v_filename . '\'');
913             return false;
914         }
915
916         return true;
917     }
918
919     /**
920      * @return bool
921      */
922     public function _openReadWrite()
923     {
924         if ($this->_compress_type == 'gz') {
925             $this->_file = @gzopen($this->_tarname, "r+b");
926         } else {
927             if ($this->_compress_type == 'bz2') {
928                 $this->_error(
929                     'Unable to open bz2 in read/write mode \''
930                     . $this->_tarname . '\' (limitation of bz2 extension)'
931                 );
932                 return false;
933             } else {
934                 if ($this->_compress_type == 'lzma2') {
935                     $this->_error(
936                         'Unable to open lzma2 in read/write mode \''
937                         . $this->_tarname . '\' (limitation of lzma2 extension)'
938                     );
939                     return false;
940                 } else {
941                     if ($this->_compress_type == 'none') {
942                         $this->_file = @fopen($this->_tarname, "r+b");
943                     } else {
944                         $this->_error(
945                             'Unknown or missing compression type ('
946                             . $this->_compress_type . ')'
947                         );
948                         return false;
949                     }
950                 }
951             }
952         }
953
954         if ($this->_file == 0) {
955             $this->_error(
956                 'Unable to open in read/write mode \''
957                 . $this->_tarname . '\''
958             );
959             return false;
960         }
961
962         return true;
963     }
964
965     /**
966      * @return bool
967      */
968     public function _close()
969     {
970         //if (isset($this->_file)) {
971         if (is_resource($this->_file)) {
972             if ($this->_compress_type == 'gz') {
973                 @gzclose($this->_file);
974             } else {
975                 if ($this->_compress_type == 'bz2') {
976                     @bzclose($this->_file);
977                 } else {
978                     if ($this->_compress_type == 'lzma2') {
979                         @xzclose($this->_file);
980                     } else {
981                         if ($this->_compress_type == 'none') {
982                             @fclose($this->_file);
983                         } else {
984                             $this->_error(
985                                 'Unknown or missing compression type ('
986                                 . $this->_compress_type . ')'
987                             );
988                         }
989                     }
990                 }
991             }
992
993             $this->_file = 0;
994         }
995
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 = '';
1001         }
1002
1003         return true;
1004     }
1005
1006     /**
1007      * @return bool
1008      */
1009     public function _cleanFile()
1010     {
1011         $this->_close();
1012
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 = '';
1018         } else {
1019             // ----- Remove the local tarname file
1020             @drupal_unlink($this->_tarname);
1021         }
1022         $this->_tarname = '';
1023
1024         return true;
1025     }
1026
1027     /**
1028      * @param mixed $p_binary_data
1029      * @param integer $p_len
1030      * @return bool
1031      */
1032     public function _writeBlock($p_binary_data, $p_len = null)
1033     {
1034         if (is_resource($this->_file)) {
1035             if ($p_len === null) {
1036                 if ($this->_compress_type == 'gz') {
1037                     @gzputs($this->_file, $p_binary_data);
1038                 } else {
1039                     if ($this->_compress_type == 'bz2') {
1040                         @bzwrite($this->_file, $p_binary_data);
1041                     } else {
1042                         if ($this->_compress_type == 'lzma2') {
1043                             @xzwrite($this->_file, $p_binary_data);
1044                         } else {
1045                             if ($this->_compress_type == 'none') {
1046                                 @fputs($this->_file, $p_binary_data);
1047                             } else {
1048                                 $this->_error(
1049                                     'Unknown or missing compression type ('
1050                                     . $this->_compress_type . ')'
1051                                 );
1052                             }
1053                         }
1054                     }
1055                 }
1056             } else {
1057                 if ($this->_compress_type == 'gz') {
1058                     @gzputs($this->_file, $p_binary_data, $p_len);
1059                 } else {
1060                     if ($this->_compress_type == 'bz2') {
1061                         @bzwrite($this->_file, $p_binary_data, $p_len);
1062                     } else {
1063                         if ($this->_compress_type == 'lzma2') {
1064                             @xzwrite($this->_file, $p_binary_data, $p_len);
1065                         } else {
1066                             if ($this->_compress_type == 'none') {
1067                                 @fputs($this->_file, $p_binary_data, $p_len);
1068                             } else {
1069                                 $this->_error(
1070                                     'Unknown or missing compression type ('
1071                                     . $this->_compress_type . ')'
1072                                 );
1073                             }
1074                         }
1075                     }
1076                 }
1077             }
1078         }
1079         return true;
1080     }
1081
1082     /**
1083      * @return null|string
1084      */
1085     public function _readBlock()
1086     {
1087         $v_block = null;
1088         if (is_resource($this->_file)) {
1089             if ($this->_compress_type == 'gz') {
1090                 $v_block = @gzread($this->_file, 512);
1091             } else {
1092                 if ($this->_compress_type == 'bz2') {
1093                     $v_block = @bzread($this->_file, 512);
1094                 } else {
1095                     if ($this->_compress_type == 'lzma2') {
1096                         $v_block = @xzread($this->_file, 512);
1097                     } else {
1098                         if ($this->_compress_type == 'none') {
1099                             $v_block = @fread($this->_file, 512);
1100                         } else {
1101                             $this->_error(
1102                                 'Unknown or missing compression type ('
1103                                 . $this->_compress_type . ')'
1104                             );
1105                         }
1106                     }
1107                 }
1108             }
1109         }
1110         return $v_block;
1111     }
1112
1113     /**
1114      * @param null $p_len
1115      * @return bool
1116      */
1117     public function _jumpBlock($p_len = null)
1118     {
1119         if (is_resource($this->_file)) {
1120             if ($p_len === null) {
1121                 $p_len = 1;
1122             }
1123
1124             if ($this->_compress_type == 'gz') {
1125                 @gzseek($this->_file, gztell($this->_file) + ($p_len * 512));
1126             } else {
1127                 if ($this->_compress_type == 'bz2') {
1128                     // ----- Replace missing bztell() and bzseek()
1129                     for ($i = 0; $i < $p_len; $i++) {
1130                         $this->_readBlock();
1131                     }
1132                 } else {
1133                     if ($this->_compress_type == 'lzma2') {
1134                         // ----- Replace missing xztell() and xzseek()
1135                         for ($i = 0; $i < $p_len; $i++) {
1136                             $this->_readBlock();
1137                         }
1138                     } else {
1139                         if ($this->_compress_type == 'none') {
1140                             @fseek($this->_file, $p_len * 512, SEEK_CUR);
1141                         } else {
1142                             $this->_error(
1143                                 'Unknown or missing compression type ('
1144                                 . $this->_compress_type . ')'
1145                             );
1146                         }
1147                     }
1148                 }
1149             }
1150         }
1151         return true;
1152     }
1153
1154     /**
1155      * @return bool
1156      */
1157     public function _writeFooter()
1158     {
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);
1163         }
1164         return true;
1165     }
1166
1167     /**
1168      * @param array $p_list
1169      * @param string $p_add_dir
1170      * @param string $p_remove_dir
1171      * @return bool
1172      */
1173     public function _addList($p_list, $p_add_dir, $p_remove_dir)
1174     {
1175         $v_result = true;
1176         $v_header = array();
1177
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);
1181
1182         if (!$this->_file) {
1183             $this->_error('Invalid file descriptor');
1184             return false;
1185         }
1186
1187         if (sizeof($p_list) == 0) {
1188             return true;
1189         }
1190
1191         foreach ($p_list as $v_filename) {
1192             if (!$v_result) {
1193                 break;
1194             }
1195
1196             // ----- Skip the current tar name
1197             if ($v_filename == $this->_tarname) {
1198                 continue;
1199             }
1200
1201             if ($v_filename == '') {
1202                 continue;
1203             }
1204
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");
1208                 continue;
1209             }
1210
1211             if (!file_exists($v_filename) && !is_link($v_filename)) {
1212                 $this->_warning("File '$v_filename' does not exist");
1213                 continue;
1214             }
1215
1216             // ----- Add the file or directory header
1217             if (!$this->_addFile($v_filename, $v_header, $p_add_dir, $p_remove_dir)) {
1218                 return false;
1219             }
1220
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");
1224                     continue;
1225                 }
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;
1230                         } else {
1231                             $p_temp_list[0] = $p_hitem;
1232                         }
1233
1234                         $v_result = $this->_addList(
1235                             $p_temp_list,
1236                             $p_add_dir,
1237                             $p_remove_dir
1238                         );
1239                     }
1240                 }
1241
1242                 unset($p_temp_list);
1243                 unset($p_hdir);
1244                 unset($p_hitem);
1245             }
1246         }
1247
1248         return $v_result;
1249     }
1250
1251     /**
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
1257      * @return bool
1258      */
1259     public function _addFile($p_filename, &$p_header, $p_add_dir, $p_remove_dir, $v_stored_filename = null)
1260     {
1261         if (!$this->_file) {
1262             $this->_error('Invalid file descriptor');
1263             return false;
1264         }
1265
1266         if ($p_filename == '') {
1267             $this->_error('Invalid file name');
1268             return false;
1269         }
1270
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;
1275
1276             if (strcmp($p_filename, $p_remove_dir) == 0) {
1277                 return true;
1278             }
1279
1280             if ($p_remove_dir != '') {
1281                 if (substr($p_remove_dir, -1) != '/') {
1282                     $p_remove_dir .= '/';
1283                 }
1284
1285                 if (substr($p_filename, 0, strlen($p_remove_dir)) == $p_remove_dir) {
1286                     $v_stored_filename = substr($p_filename, strlen($p_remove_dir));
1287                 }
1288             }
1289
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;
1294                 } else {
1295                     $v_stored_filename = $p_add_dir . '/' . $v_stored_filename;
1296                 }
1297             }
1298
1299             $v_stored_filename = $this->_pathReduction($v_stored_filename);
1300         }
1301
1302         if ($this->_isArchive($p_filename)) {
1303             if (($v_file = @fopen($p_filename, "rb")) == 0) {
1304                 $this->_warning(
1305                     "Unable to open file '" . $p_filename
1306                     . "' in binary read mode"
1307                 );
1308                 return true;
1309             }
1310
1311             if (!$this->_writeHeader($p_filename, $v_stored_filename)) {
1312                 return false;
1313             }
1314
1315             while (($v_buffer = fread($v_file, 512)) != '') {
1316                 $v_binary_data = pack("a512", "$v_buffer");
1317                 $this->_writeBlock($v_binary_data);
1318             }
1319
1320             fclose($v_file);
1321         } else {
1322             // ----- Only header for dir
1323             if (!$this->_writeHeader($p_filename, $v_stored_filename)) {
1324                 return false;
1325             }
1326         }
1327
1328         return true;
1329     }
1330
1331     /**
1332      * @param string $p_filename
1333      * @param string $p_string
1334      * @param bool $p_datetime
1335      * @param array $p_params
1336      * @return bool
1337      */
1338     public function _addString($p_filename, $p_string, $p_datetime = false, $p_params = array())
1339     {
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');
1347             return false;
1348         }
1349
1350         if ($p_filename == '') {
1351             $this->_error('Invalid file name');
1352             return false;
1353         }
1354
1355         // ----- Calculate the stored filename
1356         $p_filename = $this->_translateWinPath($p_filename, false);
1357
1358         // ----- If datetime is not specified, set current time
1359         if ($p_datetime === false) {
1360             $p_datetime = time();
1361         }
1362
1363         if (!$this->_writeHeaderBlock(
1364             $p_filename,
1365             strlen($p_string),
1366             $p_stamp,
1367             $p_mode,
1368             $p_type,
1369             $p_uid,
1370             $p_gid
1371         )
1372         ) {
1373             return false;
1374         }
1375
1376         $i = 0;
1377         while (($v_buffer = substr($p_string, (($i++) * 512), 512)) != '') {
1378             $v_binary_data = pack("a512", $v_buffer);
1379             $this->_writeBlock($v_binary_data);
1380         }
1381
1382         return true;
1383     }
1384
1385     /**
1386      * @param string $p_filename
1387      * @param string $p_stored_filename
1388      * @return bool
1389      */
1390     public function _writeHeader($p_filename, $p_stored_filename)
1391     {
1392         if ($p_stored_filename == '') {
1393             $p_stored_filename = $p_filename;
1394         }
1395         $v_reduce_filename = $this->_pathReduction($p_stored_filename);
1396
1397         if (strlen($v_reduce_filename) > 99) {
1398             if (!$this->_writeLongHeader($v_reduce_filename)) {
1399                 return false;
1400             }
1401         }
1402
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));
1407
1408         $v_mtime = sprintf("%011s", DecOct($v_info['mtime']));
1409
1410         $v_linkname = '';
1411
1412         if (@is_link($p_filename)) {
1413             $v_typeflag = '2';
1414             $v_linkname = readlink($p_filename);
1415             $v_size = sprintf("%011s", DecOct(0));
1416         } elseif (@is_dir($p_filename)) {
1417             $v_typeflag = "5";
1418             $v_size = sprintf("%011s", DecOct(0));
1419         } else {
1420             $v_typeflag = '0';
1421             clearstatcache();
1422             $v_size = sprintf("%011s", DecOct($v_info['size']));
1423         }
1424
1425         $v_magic = 'ustar ';
1426
1427         $v_version = ' ';
1428
1429         if (function_exists('posix_getpwuid')) {
1430             $userinfo = posix_getpwuid($v_info[4]);
1431             $groupinfo = posix_getgrgid($v_info[5]);
1432
1433             $v_uname = $userinfo['name'];
1434             $v_gname = $groupinfo['name'];
1435         } else {
1436             $v_uname = '';
1437             $v_gname = '';
1438         }
1439
1440         $v_devmajor = '';
1441
1442         $v_devminor = '';
1443
1444         $v_prefix = '';
1445
1446         $v_binary_data_first = pack(
1447             "a100a8a8a8a12a12",
1448             $v_reduce_filename,
1449             $v_perms,
1450             $v_uid,
1451             $v_gid,
1452             $v_size,
1453             $v_mtime
1454         );
1455         $v_binary_data_last = pack(
1456             "a1a100a6a2a32a32a8a8a155a12",
1457             $v_typeflag,
1458             $v_linkname,
1459             $v_magic,
1460             $v_version,
1461             $v_uname,
1462             $v_gname,
1463             $v_devmajor,
1464             $v_devminor,
1465             $v_prefix,
1466             ''
1467         );
1468
1469         // ----- Calculate the checksum
1470         $v_checksum = 0;
1471         // ..... First part of the header
1472         for ($i = 0; $i < 148; $i++) {
1473             $v_checksum += ord(substr($v_binary_data_first, $i, 1));
1474         }
1475         // ..... Ignore the checksum value and replace it by ' ' (space)
1476         for ($i = 148; $i < 156; $i++) {
1477             $v_checksum += ord(' ');
1478         }
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));
1482         }
1483
1484         // ----- Write the first 148 bytes of the header in the archive
1485         $this->_writeBlock($v_binary_data_first, 148);
1486
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);
1491
1492         // ----- Write the last 356 bytes of the header in the archive
1493         $this->_writeBlock($v_binary_data_last, 356);
1494
1495         return true;
1496     }
1497
1498     /**
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
1504      * @param int $p_uid
1505      * @param int $p_gid
1506      * @return bool
1507      */
1508     public function _writeHeaderBlock(
1509         $p_filename,
1510         $p_size,
1511         $p_mtime = 0,
1512         $p_perms = 0,
1513         $p_type = '',
1514         $p_uid = 0,
1515         $p_gid = 0
1516     ) {
1517         $p_filename = $this->_pathReduction($p_filename);
1518
1519         if (strlen($p_filename) > 99) {
1520             if (!$this->_writeLongHeader($p_filename)) {
1521                 return false;
1522             }
1523         }
1524
1525         if ($p_type == "5") {
1526             $v_size = sprintf("%011s", DecOct(0));
1527         } else {
1528             $v_size = sprintf("%011s", DecOct($p_size));
1529         }
1530
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));
1534
1535         $v_mtime = sprintf("%11s", DecOct($p_mtime));
1536
1537         $v_linkname = '';
1538
1539         $v_magic = 'ustar ';
1540
1541         $v_version = ' ';
1542
1543         if (function_exists('posix_getpwuid')) {
1544             $userinfo = posix_getpwuid($p_uid);
1545             $groupinfo = posix_getgrgid($p_gid);
1546
1547             $v_uname = $userinfo['name'];
1548             $v_gname = $groupinfo['name'];
1549         } else {
1550             $v_uname = '';
1551             $v_gname = '';
1552         }
1553
1554         $v_devmajor = '';
1555
1556         $v_devminor = '';
1557
1558         $v_prefix = '';
1559
1560         $v_binary_data_first = pack(
1561             "a100a8a8a8a12A12",
1562             $p_filename,
1563             $v_perms,
1564             $v_uid,
1565             $v_gid,
1566             $v_size,
1567             $v_mtime
1568         );
1569         $v_binary_data_last = pack(
1570             "a1a100a6a2a32a32a8a8a155a12",
1571             $p_type,
1572             $v_linkname,
1573             $v_magic,
1574             $v_version,
1575             $v_uname,
1576             $v_gname,
1577             $v_devmajor,
1578             $v_devminor,
1579             $v_prefix,
1580             ''
1581         );
1582
1583         // ----- Calculate the checksum
1584         $v_checksum = 0;
1585         // ..... First part of the header
1586         for ($i = 0; $i < 148; $i++) {
1587             $v_checksum += ord(substr($v_binary_data_first, $i, 1));
1588         }
1589         // ..... Ignore the checksum value and replace it by ' ' (space)
1590         for ($i = 148; $i < 156; $i++) {
1591             $v_checksum += ord(' ');
1592         }
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));
1596         }
1597
1598         // ----- Write the first 148 bytes of the header in the archive
1599         $this->_writeBlock($v_binary_data_first, 148);
1600
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);
1605
1606         // ----- Write the last 356 bytes of the header in the archive
1607         $this->_writeBlock($v_binary_data_last, 356);
1608
1609         return true;
1610     }
1611
1612     /**
1613      * @param string $p_filename
1614      * @return bool
1615      */
1616     public function _writeLongHeader($p_filename)
1617     {
1618         $v_size = sprintf("%11s ", DecOct(strlen($p_filename)));
1619
1620         $v_typeflag = 'L';
1621
1622         $v_linkname = '';
1623
1624         $v_magic = '';
1625
1626         $v_version = '';
1627
1628         $v_uname = '';
1629
1630         $v_gname = '';
1631
1632         $v_devmajor = '';
1633
1634         $v_devminor = '';
1635
1636         $v_prefix = '';
1637
1638         $v_binary_data_first = pack(
1639             "a100a8a8a8a12a12",
1640             '././@LongLink',
1641             0,
1642             0,
1643             0,
1644             $v_size,
1645             0
1646         );
1647         $v_binary_data_last = pack(
1648             "a1a100a6a2a32a32a8a8a155a12",
1649             $v_typeflag,
1650             $v_linkname,
1651             $v_magic,
1652             $v_version,
1653             $v_uname,
1654             $v_gname,
1655             $v_devmajor,
1656             $v_devminor,
1657             $v_prefix,
1658             ''
1659         );
1660
1661         // ----- Calculate the checksum
1662         $v_checksum = 0;
1663         // ..... First part of the header
1664         for ($i = 0; $i < 148; $i++) {
1665             $v_checksum += ord(substr($v_binary_data_first, $i, 1));
1666         }
1667         // ..... Ignore the checksum value and replace it by ' ' (space)
1668         for ($i = 148; $i < 156; $i++) {
1669             $v_checksum += ord(' ');
1670         }
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));
1674         }
1675
1676         // ----- Write the first 148 bytes of the header in the archive
1677         $this->_writeBlock($v_binary_data_first, 148);
1678
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);
1683
1684         // ----- Write the last 356 bytes of the header in the archive
1685         $this->_writeBlock($v_binary_data_last, 356);
1686
1687         // ----- Write the filename as content of the block
1688         $i = 0;
1689         while (($v_buffer = substr($p_filename, (($i++) * 512), 512)) != '') {
1690             $v_binary_data = pack("a512", "$v_buffer");
1691             $this->_writeBlock($v_binary_data);
1692         }
1693
1694         return true;
1695     }
1696
1697     /**
1698      * @param mixed $v_binary_data
1699      * @param mixed $v_header
1700      * @return bool
1701      */
1702     public function _readHeader($v_binary_data, &$v_header)
1703     {
1704         if (strlen($v_binary_data) == 0) {
1705             $v_header['filename'] = '';
1706             return true;
1707         }
1708
1709         if (strlen($v_binary_data) != 512) {
1710             $v_header['filename'] = '';
1711             $this->_error('Invalid block size : ' . strlen($v_binary_data));
1712             return false;
1713         }
1714
1715         if (!is_array($v_header)) {
1716             $v_header = array();
1717         }
1718         // ----- Calculate the checksum
1719         $v_checksum = 0;
1720         // ..... First part of the header
1721         for ($i = 0; $i < 148; $i++) {
1722             $v_checksum += ord(substr($v_binary_data, $i, 1));
1723         }
1724         // ..... Ignore the checksum value and replace it by ' ' (space)
1725         for ($i = 148; $i < 156; $i++) {
1726             $v_checksum += ord(' ');
1727         }
1728         // ..... Last part of the header
1729         for ($i = 156; $i < 512; $i++) {
1730             $v_checksum += ord(substr($v_binary_data, $i, 1));
1731         }
1732
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";
1737         } else {
1738             $fmt = "Z100filename/Z8mode/Z8uid/Z8gid/Z12size/Z12mtime/" .
1739                 "Z8checksum/Z1typeflag/Z100link/Z6magic/Z2version/" .
1740                 "Z32uname/Z32gname/Z8devmajor/Z8devminor/Z131prefix";
1741         }
1742         $v_data = unpack($fmt, $v_binary_data);
1743
1744         if (strlen($v_data["prefix"]) > 0) {
1745             $v_data["filename"] = "$v_data[prefix]/$v_data[filename]";
1746         }
1747
1748         // ----- Extract the checksum
1749         $v_header['checksum'] = OctDec(trim($v_data['checksum']));
1750         if ($v_header['checksum'] != $v_checksum) {
1751             $v_header['filename'] = '';
1752
1753             // ----- Look for last block (empty block)
1754             if (($v_checksum == 256) && ($v_header['checksum'] == 0)) {
1755                 return true;
1756             }
1757
1758             $this->_error(
1759                 'Invalid checksum for file "' . $v_data['filename']
1760                 . '" : ' . $v_checksum . ' calculated, '
1761                 . $v_header['checksum'] . ' expected'
1762             );
1763             return false;
1764         }
1765
1766         // ----- Extract the properties
1767         $v_header['filename'] = rtrim($v_data['filename'], "\0");
1768         if ($this->_maliciousFilename($v_header['filename'])) {
1769             $this->_error(
1770                 'Malicious .tar detected, file "' . $v_header['filename'] .
1771                 '" will not install in desired directory tree'
1772             );
1773             return false;
1774         }
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;
1782         }
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]);
1792         */
1793
1794         return true;
1795     }
1796
1797     /**
1798      * Detect and report a malicious file name
1799      *
1800      * @param string $file
1801      *
1802      * @return bool
1803      */
1804     private function _maliciousFilename($file)
1805     {
1806         if (strpos($file, '/../') !== false) {
1807             return true;
1808         }
1809         if (strpos($file, '../') === 0) {
1810             return true;
1811         }
1812         return false;
1813     }
1814
1815     /**
1816      * @param $v_header
1817      * @return bool
1818      */
1819     public function _readLongHeader(&$v_header)
1820     {
1821         $v_filename = '';
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;
1827         }
1828         if (($v_header['size'] % 512) != 0) {
1829             $v_content = $this->_readBlock();
1830             $v_filename .= $v_content;
1831         }
1832
1833         // ----- Read the next header
1834         $v_binary_data = $this->_readBlock();
1835
1836         if (!$this->_readHeader($v_binary_data, $v_header)) {
1837             return false;
1838         }
1839
1840         $v_filename = rtrim(substr($v_filename, 0, $v_filesize), "\0");
1841         $v_header['filename'] = $v_filename;
1842         if ($this->_maliciousFilename($v_filename)) {
1843             $this->_error(
1844                 'Malicious .tar detected, file "' . $v_filename .
1845                 '" will not install in desired directory tree'
1846             );
1847             return false;
1848         }
1849
1850         return true;
1851     }
1852
1853     /**
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.
1856      *
1857      * @param string $p_filename The path of the file to extract in a string.
1858      *
1859      * @return a string with the file content or null.
1860      */
1861     private function _extractInString($p_filename)
1862     {
1863         $v_result_str = "";
1864
1865         while (strlen($v_binary_data = $this->_readBlock()) != 0) {
1866             if (!$this->_readHeader($v_binary_data, $v_header)) {
1867                 return null;
1868             }
1869
1870             if ($v_header['filename'] == '') {
1871                 continue;
1872             }
1873
1874             // ----- Look for long filename
1875             if ($v_header['typeflag'] == 'L') {
1876                 if (!$this->_readLongHeader($v_header)) {
1877                     return null;
1878                 }
1879             }
1880
1881             if ($v_header['filename'] == $p_filename) {
1882                 if ($v_header['typeflag'] == "5") {
1883                     $this->_error(
1884                         'Unable to extract in string a directory '
1885                         . 'entry {' . $v_header['filename'] . '}'
1886                     );
1887                     return null;
1888                 } else {
1889                     $n = floor($v_header['size'] / 512);
1890                     for ($i = 0; $i < $n; $i++) {
1891                         $v_result_str .= $this->_readBlock();
1892                     }
1893                     if (($v_header['size'] % 512) != 0) {
1894                         $v_content = $this->_readBlock();
1895                         $v_result_str .= substr(
1896                             $v_content,
1897                             0,
1898                             ($v_header['size'] % 512)
1899                         );
1900                     }
1901                     return $v_result_str;
1902                 }
1903             } else {
1904                 $this->_jumpBlock(ceil(($v_header['size'] / 512)));
1905             }
1906         }
1907
1908         return null;
1909     }
1910
1911     /**
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
1918      * @return bool
1919      */
1920     public function _extractList(
1921         $p_path,
1922         &$p_list_detail,
1923         $p_mode,
1924         $p_file_list,
1925         $p_remove_path,
1926         $p_preserve = false
1927     ) {
1928         $v_result = true;
1929         $v_nb = 0;
1930         $v_extract_all = true;
1931         $v_listing = false;
1932
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, ':'))
1936         ) {
1937             $p_path = "./" . $p_path;
1938         }
1939         $p_remove_path = $this->_translateWinPath($p_remove_path);
1940
1941         // ----- Look for path to remove format (should end by /)
1942         if (($p_remove_path != '') && (substr($p_remove_path, -1) != '/')) {
1943             $p_remove_path .= '/';
1944         }
1945         $p_remove_path_size = strlen($p_remove_path);
1946
1947         switch ($p_mode) {
1948             case "complete" :
1949                 $v_extract_all = true;
1950                 $v_listing = false;
1951                 break;
1952             case "partial" :
1953                 $v_extract_all = false;
1954                 $v_listing = false;
1955                 break;
1956             case "list" :
1957                 $v_extract_all = false;
1958                 $v_listing = true;
1959                 break;
1960             default :
1961                 $this->_error('Invalid extract mode (' . $p_mode . ')');
1962                 return false;
1963         }
1964
1965         clearstatcache();
1966
1967         while (strlen($v_binary_data = $this->_readBlock()) != 0) {
1968             $v_extract_file = false;
1969             $v_extraction_stopped = 0;
1970
1971             if (!$this->_readHeader($v_binary_data, $v_header)) {
1972                 return false;
1973             }
1974
1975             if ($v_header['filename'] == '') {
1976                 continue;
1977             }
1978
1979             // ----- Look for long filename
1980             if ($v_header['typeflag'] == 'L') {
1981                 if (!$this->_readLongHeader($v_header)) {
1982                     return false;
1983                 }
1984             }
1985
1986             // ignore extended / pax headers
1987             if ($v_header['typeflag'] == 'x' || $v_header['typeflag'] == 'g') {
1988                 $this->_jumpBlock(ceil(($v_header['size'] / 512)));
1989                 continue;
1990             }
1991
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;
1995
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])
2003                         ) {
2004                             $v_extract_file = true;
2005                             break;
2006                         }
2007                     } // ----- It is a file, so compare the file names
2008                     elseif ($p_file_list[$i] == $v_header['filename']) {
2009                         $v_extract_file = true;
2010                         break;
2011                     }
2012                 }
2013             } else {
2014                 $v_extract_file = true;
2015             }
2016
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)
2021                         == $p_remove_path)
2022                 ) {
2023                     $v_header['filename'] = substr(
2024                         $v_header['filename'],
2025                         $p_remove_path_size
2026                     );
2027                     if ($v_header['filename'] == '') {
2028                         continue;
2029                     }
2030                 }
2031                 if (($p_path != './') && ($p_path != '/')) {
2032                     while (substr($p_path, -1) == '/') {
2033                         $p_path = substr($p_path, 0, strlen($p_path) - 1);
2034                     }
2035
2036                     if (substr($v_header['filename'], 0, 1) == '/') {
2037                         $v_header['filename'] = $p_path . $v_header['filename'];
2038                     } else {
2039                         $v_header['filename'] = $p_path . '/' . $v_header['filename'];
2040                     }
2041                 }
2042                 if (file_exists($v_header['filename'])) {
2043                     if ((@is_dir($v_header['filename']))
2044                         && ($v_header['typeflag'] == '')
2045                     ) {
2046                         $this->_error(
2047                             'File ' . $v_header['filename']
2048                             . ' already exists as a directory'
2049                         );
2050                         return false;
2051                     }
2052                     if (($this->_isArchive($v_header['filename']))
2053                         && ($v_header['typeflag'] == "5")
2054                     ) {
2055                         $this->_error(
2056                             'Directory ' . $v_header['filename']
2057                             . ' already exists as a file'
2058                         );
2059                         return false;
2060                     }
2061                     if (!is_writeable($v_header['filename'])) {
2062                         $this->_error(
2063                             'File ' . $v_header['filename']
2064                             . ' already exists and is write protected'
2065                         );
2066                         return false;
2067                     }
2068                     if (filemtime($v_header['filename']) > $v_header['mtime']) {
2069                         // To be completed : An error or silent no replace ?
2070                     }
2071                 } // ----- Check the directory availability and create it if necessary
2072                 elseif (($v_result
2073                         = $this->_dirCheck(
2074                         ($v_header['typeflag'] == "5"
2075                             ? $v_header['filename']
2076                             : dirname($v_header['filename']))
2077                     )) != 1
2078                 ) {
2079                     $this->_error('Unable to create path for ' . $v_header['filename']);
2080                     return false;
2081                 }
2082
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)) {
2087                                 $this->_error(
2088                                     'Unable to create directory {'
2089                                     . $v_header['filename'] . '}'
2090                                 );
2091                                 return false;
2092                             }
2093                         }
2094                     } elseif ($v_header['typeflag'] == "2") {
2095                         if (@file_exists($v_header['filename'])) {
2096                             @drupal_unlink($v_header['filename']);
2097                         }
2098                         if (!@symlink($v_header['link'], $v_header['filename'])) {
2099                             $this->_error(
2100                                 'Unable to extract symbolic link {'
2101                                 . $v_header['filename'] . '}'
2102                             );
2103                             return false;
2104                         }
2105                     } else {
2106                         if (($v_dest_file = @fopen($v_header['filename'], "wb")) == 0) {
2107                             $this->_error(
2108                                 'Error while opening {' . $v_header['filename']
2109                                 . '} in write binary mode'
2110                             );
2111                             return false;
2112                         } else {
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);
2117                             }
2118                             if (($v_header['size'] % 512) != 0) {
2119                                 $v_content = $this->_readBlock();
2120                                 fwrite($v_dest_file, $v_content, ($v_header['size'] % 512));
2121                             }
2122
2123                             @fclose($v_dest_file);
2124
2125                             if ($p_preserve) {
2126                                 @chown($v_header['filename'], $v_header['uid']);
2127                                 @chgrp($v_header['filename'], $v_header['gid']);
2128                             }
2129
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);
2136                             }
2137                         }
2138
2139                         // ----- Check the file size
2140                         clearstatcache();
2141                         if (!is_file($v_header['filename'])) {
2142                             $this->_error(
2143                                 'Extracted file ' . $v_header['filename']
2144                                 . 'does not exist. Archive may be corrupted.'
2145                             );
2146                             return false;
2147                         }
2148
2149                         $filesize = filesize($v_header['filename']);
2150                         if ($filesize != $v_header['size']) {
2151                             $this->_error(
2152                                 'Extracted file ' . $v_header['filename']
2153                                 . ' does not have the correct file size \''
2154                                 . $filesize
2155                                 . '\' (' . $v_header['size']
2156                                 . ' expected). Archive may be corrupted.'
2157                             );
2158                             return false;
2159                         }
2160                     }
2161                 } else {
2162                     $this->_jumpBlock(ceil(($v_header['size'] / 512)));
2163                 }
2164             } else {
2165                 $this->_jumpBlock(ceil(($v_header['size'] / 512)));
2166             }
2167
2168             /* TBC : Seems to be unused ...
2169             if ($this->_compress)
2170               $v_end_of_file = @gzeof($this->_file);
2171             else
2172               $v_end_of_file = @feof($this->_file);
2173               */
2174
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']
2179                 ) {
2180                     $v_file_dir = '';
2181                 }
2182                 if ((substr($v_header['filename'], 0, 1) == '/') && ($v_file_dir == '')) {
2183                     $v_file_dir = '/';
2184                 }
2185
2186                 $p_list_detail[$v_nb++] = $v_header;
2187                 if (is_array($p_file_list) && (count($p_list_detail) == count($p_file_list))) {
2188                     return true;
2189                 }
2190             }
2191         }
2192
2193         return true;
2194     }
2195
2196     /**
2197      * @return bool
2198      */
2199     public function _openAppend()
2200     {
2201         if (filesize($this->_tarname) == 0) {
2202             return $this->_openWrite();
2203         }
2204
2205         if ($this->_compress) {
2206             $this->_close();
2207
2208             if (!@rename($this->_tarname, $this->_tarname . ".tmp")) {
2209                 $this->_error(
2210                     'Error while renaming \'' . $this->_tarname
2211                     . '\' to temporary file \'' . $this->_tarname
2212                     . '.tmp\''
2213                 );
2214                 return false;
2215             }
2216
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");
2223             }
2224
2225
2226             if ($v_temp_tar == 0) {
2227                 $this->_error(
2228                     'Unable to open file \'' . $this->_tarname
2229                     . '.tmp\' in binary read mode'
2230                 );
2231                 @rename($this->_tarname . ".tmp", $this->_tarname);
2232                 return false;
2233             }
2234
2235             if (!$this->_openWrite()) {
2236                 @rename($this->_tarname . ".tmp", $this->_tarname);
2237                 return false;
2238             }
2239
2240             if ($this->_compress_type == 'gz') {
2241                 $end_blocks = 0;
2242
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) {
2246                         $end_blocks++;
2247                         // do not copy end blocks, we will re-make them
2248                         // after appending
2249                         continue;
2250                     } elseif ($end_blocks > 0) {
2251                         for ($i = 0; $i < $end_blocks; $i++) {
2252                             $this->_writeBlock(ARCHIVE_TAR_END_BLOCK);
2253                         }
2254                         $end_blocks = 0;
2255                     }
2256                     $v_binary_data = pack("a512", $v_buffer);
2257                     $this->_writeBlock($v_binary_data);
2258                 }
2259
2260                 @gzclose($v_temp_tar);
2261             } elseif ($this->_compress_type == 'bz2') {
2262                 $end_blocks = 0;
2263
2264                 while (strlen($v_buffer = @bzread($v_temp_tar, 512)) > 0) {
2265                     if ($v_buffer == ARCHIVE_TAR_END_BLOCK || strlen($v_buffer) == 0) {
2266                         $end_blocks++;
2267                         // do not copy end blocks, we will re-make them
2268                         // after appending
2269                         continue;
2270                     } elseif ($end_blocks > 0) {
2271                         for ($i = 0; $i < $end_blocks; $i++) {
2272                             $this->_writeBlock(ARCHIVE_TAR_END_BLOCK);
2273                         }
2274                         $end_blocks = 0;
2275                     }
2276                     $v_binary_data = pack("a512", $v_buffer);
2277                     $this->_writeBlock($v_binary_data);
2278                 }
2279
2280                 @bzclose($v_temp_tar);
2281             } elseif ($this->_compress_type == 'lzma2') {
2282                 $end_blocks = 0;
2283
2284                 while (strlen($v_buffer = @xzread($v_temp_tar, 512)) > 0) {
2285                     if ($v_buffer == ARCHIVE_TAR_END_BLOCK || strlen($v_buffer) == 0) {
2286                         $end_blocks++;
2287                         // do not copy end blocks, we will re-make them
2288                         // after appending
2289                         continue;
2290                     } elseif ($end_blocks > 0) {
2291                         for ($i = 0; $i < $end_blocks; $i++) {
2292                             $this->_writeBlock(ARCHIVE_TAR_END_BLOCK);
2293                         }
2294                         $end_blocks = 0;
2295                     }
2296                     $v_binary_data = pack("a512", $v_buffer);
2297                     $this->_writeBlock($v_binary_data);
2298                 }
2299
2300                 @xzclose($v_temp_tar);
2301             }
2302
2303             if (!@drupal_unlink($this->_tarname . ".tmp")) {
2304                 $this->_error(
2305                     'Error while deleting temporary file \''
2306                     . $this->_tarname . '.tmp\''
2307                 );
2308             }
2309         } else {
2310             // ----- For not compressed tar, just add files before the last
2311             //       one or two 512 bytes block
2312             if (!$this->_openReadWrite()) {
2313                 return false;
2314             }
2315
2316             clearstatcache();
2317             $v_size = filesize($this->_tarname);
2318
2319             // We might have zero, one or two end blocks.
2320             // The standard is two, but we should try to handle
2321             // other cases.
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);
2327             }
2328         }
2329
2330         return true;
2331     }
2332
2333     /**
2334      * @param $p_filelist
2335      * @param string $p_add_dir
2336      * @param string $p_remove_dir
2337      * @return bool
2338      */
2339     public function _append($p_filelist, $p_add_dir = '', $p_remove_dir = '')
2340     {
2341         if (!$this->_openAppend()) {
2342             return false;
2343         }
2344
2345         if ($this->_addList($p_filelist, $p_add_dir, $p_remove_dir)) {
2346             $this->_writeFooter();
2347         }
2348
2349         $this->_close();
2350
2351         return true;
2352     }
2353
2354     /**
2355      * Check if a directory exists and create it (including parent
2356      * dirs) if not.
2357      *
2358      * @param string $p_dir directory to check
2359      *
2360      * @return bool true if the directory exists or was created
2361      */
2362     public function _dirCheck($p_dir)
2363     {
2364         clearstatcache();
2365         if ((@is_dir($p_dir)) || ($p_dir == '')) {
2366             return true;
2367         }
2368
2369         $p_parent_dir = dirname($p_dir);
2370
2371         if (($p_parent_dir != $p_dir) &&
2372             ($p_parent_dir != '') &&
2373             (!$this->_dirCheck($p_parent_dir))
2374         ) {
2375             return false;
2376         }
2377
2378         if (!@mkdir($p_dir, 0777)) {
2379             $this->_error("Unable to create directory '$p_dir'");
2380             return false;
2381         }
2382
2383         return true;
2384     }
2385
2386     /**
2387      * Compress path by changing for example "/dir/foo/../bar" to "/dir/bar",
2388      * and remove double slashes.
2389      *
2390      * @param string $p_dir path to reduce
2391      *
2392      * @return string reduced path
2393      */
2394     private function _pathReduction($p_dir)
2395     {
2396         $v_result = '';
2397
2398         // ----- Look for not empty path
2399         if ($p_dir != '') {
2400             // ----- Explode path by directory names
2401             $v_list = explode('/', $p_dir);
2402
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
2409                 } else {
2410                     if ($v_list[$i] == "..") {
2411                         // ----- Ignore it and ignore the $i-1
2412                         $i--;
2413                     } else {
2414                         if (($v_list[$i] == '')
2415                             && ($i != (sizeof($v_list) - 1))
2416                             && ($i != 0)
2417                         ) {
2418                             // ----- Ignore only the double '//' in path,
2419                             // but not the first and last /
2420                         } else {
2421                             $v_result = $v_list[$i] . ($i != (sizeof($v_list) - 1) ? '/'
2422                                     . $v_result : '');
2423                         }
2424                     }
2425                 }
2426             }
2427         }
2428
2429         if (defined('OS_WINDOWS') && OS_WINDOWS) {
2430             $v_result = strtr($v_result, '\\', '/');
2431         }
2432
2433         return $v_result;
2434     }
2435
2436     /**
2437      * @param $p_path
2438      * @param bool $p_remove_disk_letter
2439      * @return string
2440      */
2441     public function _translateWinPath($p_path, $p_remove_disk_letter = true)
2442     {
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)
2447             ) {
2448                 $p_path = substr($p_path, $v_position + 1);
2449             }
2450             // ----- Change potential windows directory separator
2451             if ((strpos($p_path, '\\') > 0) || (substr($p_path, 0, 1) == '\\')) {
2452                 $p_path = strtr($p_path, '\\', '/');
2453             }
2454         }
2455         return $p_path;
2456     }
2457 }
2458 }