1<?php
2/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
3
4/**
5 * File::CSV
6 *
7 * PHP versions 4 and 5
8 *
9 * Copyright (c) 1997-2008,
10 * Vincent Blavet <vincent@phpconcept.net>
11 * All rights reserved.
12 *
13 * Redistribution and use in source and binary forms, with or without
14 * modification, are permitted provided that the following conditions are met:
15 *
16 *     * Redistributions of source code must retain the above copyright notice,
17 *       this list of conditions and the following disclaimer.
18 *     * Redistributions in binary form must reproduce the above copyright
19 *       notice, this list of conditions and the following disclaimer in the
20 *       documentation and/or other materials provided with the distribution.
21 *
22 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
23 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
24 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
25 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
26 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
27 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
28 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
29 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
30 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
31 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32 *
33 * @category  File_Formats
34 * @package   Archive_Tar
35 * @author    Vincent Blavet <vincent@phpconcept.net>
36 * @copyright 1997-2010 The Authors
37 * @license   http://www.opensource.org/licenses/bsd-license.php New BSD License
38 * @version   CVS: $Id$
39 * @link      http://pear.php.net/package/Archive_Tar
40 */
41
42// If the PEAR class cannot be loaded via the autoloader,
43// then try to require_once it from the PHP include path.
44if (!class_exists('PEAR')) {
45    require_once 'PEAR.php';
46}
47
48define('ARCHIVE_TAR_ATT_SEPARATOR', 90001);
49define('ARCHIVE_TAR_END_BLOCK', pack("a512", ''));
50
51if (!function_exists('gzopen') && function_exists('gzopen64')) {
52    function gzopen($filename, $mode, $use_include_path = 0)
53    {
54        return gzopen64($filename, $mode, $use_include_path);
55    }
56}
57
58if (!function_exists('gztell') && function_exists('gztell64')) {
59    function gztell($zp)
60    {
61        return gztell64($zp);
62    }
63}
64
65if (!function_exists('gzseek') && function_exists('gzseek64')) {
66    function gzseek($zp, $offset, $whence = SEEK_SET)
67    {
68        return gzseek64($zp, $offset, $whence);
69    }
70}
71
72/**
73 * Creates a (compressed) Tar archive
74 *
75 * @package Archive_Tar
76 * @author  Vincent Blavet <vincent@phpconcept.net>
77 * @license http://www.opensource.org/licenses/bsd-license.php New BSD License
78 * @version $Revision$
79 */
80class Archive_Tar extends PEAR
81{
82    /**
83     * @var string Name of the Tar
84     */
85    public $_tarname = '';
86
87    /**
88     * @var boolean if true, the Tar file will be gzipped
89     */
90    public $_compress = false;
91
92    /**
93     * @var string Type of compression : 'none', 'gz', 'bz2' or 'lzma2'
94     */
95    public $_compress_type = 'none';
96
97    /**
98     * @var string Explode separator
99     */
100    public $_separator = ' ';
101
102    /**
103     * @var file descriptor
104     */
105    public $_file = 0;
106
107    /**
108     * @var string Local Tar name of a remote Tar (http:// or ftp://)
109     */
110    public $_temp_tarname = '';
111
112    /**
113     * @var string regular expression for ignoring files or directories
114     */
115    public $_ignore_regexp = '';
116
117    /**
118     * @var object PEAR_Error object
119     */
120    public $error_object = null;
121
122    /**
123     * Format for data extraction
124     *
125     * @var string
126     */
127    public $_fmt = '';
128
129    /**
130     * @var int Length of the read buffer in bytes
131     */
132    protected $buffer_length;
133
134    /**
135     * Archive_Tar Class constructor. This flavour of the constructor only
136     * declare a new Archive_Tar object, identifying it by the name of the
137     * tar file.
138     * If the compress argument is set the tar will be read or created as a
139     * gzip or bz2 compressed TAR file.
140     *
141     * @param string $p_tarname The name of the tar archive to create
142     * @param string $p_compress can be null, 'gz', 'bz2' or 'lzma2'. This
143     *               parameter indicates if gzip, bz2 or lzma2 compression
144     *               is required.  For compatibility reason the
145     *               boolean value 'true' means 'gz'.
146     * @param int $buffer_length Length of the read buffer in bytes
147     *
148     * @return bool
149     */
150    public function __construct($p_tarname, $p_compress = null, $buffer_length = 512)
151    {
152        parent::__construct();
153
154        $this->_compress = false;
155        $this->_compress_type = 'none';
156        if (($p_compress === null) || ($p_compress == '')) {
157            if (@file_exists($p_tarname)) {
158                if ($fp = @fopen($p_tarname, "rb")) {
159                    // look for gzip magic cookie
160                    $data = fread($fp, 2);
161                    fclose($fp);
162                    if ($data == "\37\213") {
163                        $this->_compress = true;
164                        $this->_compress_type = 'gz';
165                        // No sure it's enought for a magic code ....
166                    } elseif ($data == "BZ") {
167                        $this->_compress = true;
168                        $this->_compress_type = 'bz2';
169                    } elseif (file_get_contents($p_tarname, false, null, 1, 4) == '7zXZ') {
170                        $this->_compress = true;
171                        $this->_compress_type = 'lzma2';
172                    }
173                }
174            } else {
175                // probably a remote file or some file accessible
176                // through a stream interface
177                if (substr($p_tarname, -2) == 'gz') {
178                    $this->_compress = true;
179                    $this->_compress_type = 'gz';
180                } elseif ((substr($p_tarname, -3) == 'bz2') ||
181                    (substr($p_tarname, -2) == 'bz')
182                ) {
183                    $this->_compress = true;
184                    $this->_compress_type = 'bz2';
185                } else {
186                    if (substr($p_tarname, -2) == 'xz') {
187                        $this->_compress = true;
188                        $this->_compress_type = 'lzma2';
189                    }
190                }
191            }
192        } else {
193            if (($p_compress === true) || ($p_compress == 'gz')) {
194                $this->_compress = true;
195                $this->_compress_type = 'gz';
196            } else {
197                if ($p_compress == 'bz2') {
198                    $this->_compress = true;
199                    $this->_compress_type = 'bz2';
200                } else {
201                    if ($p_compress == 'lzma2') {
202                        $this->_compress = true;
203                        $this->_compress_type = 'lzma2';
204                    } else {
205                        $this->_error(
206                            "Unsupported compression type '$p_compress'\n" .
207                            "Supported types are 'gz', 'bz2' and 'lzma2'.\n"
208                        );
209                        return false;
210                    }
211                }
212            }
213        }
214        $this->_tarname = $p_tarname;
215        if ($this->_compress) { // assert zlib or bz2 or xz extension support
216            if ($this->_compress_type == 'gz') {
217                $extname = 'zlib';
218            } else {
219                if ($this->_compress_type == 'bz2') {
220                    $extname = 'bz2';
221                } else {
222                    if ($this->_compress_type == 'lzma2') {
223                        $extname = 'xz';
224                    }
225                }
226            }
227
228            if (!extension_loaded($extname)) {
229                PEAR::loadExtension($extname);
230            }
231            if (!extension_loaded($extname)) {
232                $this->_error(
233                    "The extension '$extname' couldn't be found.\n" .
234                    "Please make sure your version of PHP was built " .
235                    "with '$extname' support.\n"
236                );
237                return false;
238            }
239        }
240
241
242        if (version_compare(PHP_VERSION, "5.5.0-dev") < 0) {
243            $this->_fmt = "a100filename/a8mode/a8uid/a8gid/a12size/a12mtime/" .
244                "a8checksum/a1typeflag/a100link/a6magic/a2version/" .
245                "a32uname/a32gname/a8devmajor/a8devminor/a131prefix";
246        } else {
247            $this->_fmt = "Z100filename/Z8mode/Z8uid/Z8gid/Z12size/Z12mtime/" .
248                "Z8checksum/Z1typeflag/Z100link/Z6magic/Z2version/" .
249                "Z32uname/Z32gname/Z8devmajor/Z8devminor/Z131prefix";
250        }
251
252
253        $this->buffer_length = $buffer_length;
254    }
255
256    public function __destruct()
257    {
258        $this->_close();
259        // ----- Look for a local copy to delete
260        if ($this->_temp_tarname != '') {
261            @unlink($this->_temp_tarname);
262        }
263    }
264
265    /**
266     * This method creates the archive file and add the files / directories
267     * that are listed in $p_filelist.
268     * If a file with the same name exist and is writable, it is replaced
269     * by the new tar.
270     * The method return false and a PEAR error text.
271     * The $p_filelist parameter can be an array of string, each string
272     * representing a filename or a directory name with their path if
273     * needed. It can also be a single string with names separated by a
274     * single blank.
275     * For each directory added in the archive, the files and
276     * sub-directories are also added.
277     * See also createModify() method for more details.
278     *
279     * @param array $p_filelist An array of filenames and directory names, or a
280     *              single string with names separated by a single
281     *              blank space.
282     *
283     * @return true on success, false on error.
284     * @see    createModify()
285     */
286    public function create($p_filelist)
287    {
288        return $this->createModify($p_filelist, '', '');
289    }
290
291    /**
292     * This method add the files / directories that are listed in $p_filelist in
293     * the archive. If the archive does not exist it is created.
294     * The method return false and a PEAR error text.
295     * The files and directories listed are only added at the end of the archive,
296     * even if a file with the same name is already archived.
297     * See also createModify() method for more details.
298     *
299     * @param array $p_filelist An array of filenames and directory names, or a
300     *              single string with names separated by a single
301     *              blank space.
302     *
303     * @return true on success, false on error.
304     * @see    createModify()
305     * @access public
306     */
307    public function add($p_filelist)
308    {
309        return $this->addModify($p_filelist, '', '');
310    }
311
312    /**
313     * @param string $p_path
314     * @param bool $p_preserve
315     * @param bool $p_symlinks
316     * @return bool
317     */
318    public function extract($p_path = '', $p_preserve = false, $p_symlinks = true)
319    {
320        return $this->extractModify($p_path, '', $p_preserve, $p_symlinks);
321    }
322
323    /**
324     * @return array|int
325     */
326    public function listContent()
327    {
328        $v_list_detail = array();
329
330        if ($this->_openRead()) {
331            if (!$this->_extractList('', $v_list_detail, "list", '', '')) {
332                unset($v_list_detail);
333                $v_list_detail = 0;
334            }
335            $this->_close();
336        }
337
338        return $v_list_detail;
339    }
340
341    /**
342     * This method creates the archive file and add the files / directories
343     * that are listed in $p_filelist.
344     * If the file already exists and is writable, it is replaced by the
345     * new tar. It is a create and not an add. If the file exists and is
346     * read-only or is a directory it is not replaced. The method return
347     * false and a PEAR error text.
348     * The $p_filelist parameter can be an array of string, each string
349     * representing a filename or a directory name with their path if
350     * needed. It can also be a single string with names separated by a
351     * single blank.
352     * The path indicated in $p_remove_dir will be removed from the
353     * memorized path of each file / directory listed when this path
354     * exists. By default nothing is removed (empty path '')
355     * The path indicated in $p_add_dir will be added at the beginning of
356     * the memorized path of each file / directory listed. However it can
357     * be set to empty ''. The adding of a path is done after the removing
358     * of path.
359     * The path add/remove ability enables the user to prepare an archive
360     * for extraction in a different path than the origin files are.
361     * See also addModify() method for file adding properties.
362     *
363     * @param array $p_filelist An array of filenames and directory names,
364     *                             or a single string with names separated by
365     *                             a single blank space.
366     * @param string $p_add_dir A string which contains a path to be added
367     *                             to the memorized path of each element in
368     *                             the list.
369     * @param string $p_remove_dir A string which contains a path to be
370     *                             removed from the memorized path of each
371     *                             element in the list, when relevant.
372     *
373     * @return boolean true on success, false on error.
374     * @see addModify()
375     */
376    public function createModify($p_filelist, $p_add_dir, $p_remove_dir = '')
377    {
378        $v_result = true;
379
380        if (!$this->_openWrite()) {
381            return false;
382        }
383
384        if ($p_filelist != '') {
385            if (is_array($p_filelist)) {
386                $v_list = $p_filelist;
387            } elseif (is_string($p_filelist)) {
388                $v_list = explode($this->_separator, $p_filelist);
389            } else {
390                $this->_cleanFile();
391                $this->_error('Invalid file list');
392                return false;
393            }
394
395            $v_result = $this->_addList($v_list, $p_add_dir, $p_remove_dir);
396        }
397
398        if ($v_result) {
399            $this->_writeFooter();
400            $this->_close();
401        } else {
402            $this->_cleanFile();
403        }
404
405        return $v_result;
406    }
407
408    /**
409     * This method add the files / directories listed in $p_filelist at the
410     * end of the existing archive. If the archive does not yet exists it
411     * is created.
412     * The $p_filelist parameter can be an array of string, each string
413     * representing a filename or a directory name with their path if
414     * needed. It can also be a single string with names separated by a
415     * single blank.
416     * The path indicated in $p_remove_dir will be removed from the
417     * memorized path of each file / directory listed when this path
418     * exists. By default nothing is removed (empty path '')
419     * The path indicated in $p_add_dir will be added at the beginning of
420     * the memorized path of each file / directory listed. However it can
421     * be set to empty ''. The adding of a path is done after the removing
422     * of path.
423     * The path add/remove ability enables the user to prepare an archive
424     * for extraction in a different path than the origin files are.
425     * If a file/dir is already in the archive it will only be added at the
426     * end of the archive. There is no update of the existing archived
427     * file/dir. However while extracting the archive, the last file will
428     * replace the first one. This results in a none optimization of the
429     * archive size.
430     * If a file/dir does not exist the file/dir is ignored. However an
431     * error text is send to PEAR error.
432     * If a file/dir is not readable the file/dir is ignored. However an
433     * error text is send to PEAR error.
434     *
435     * @param array $p_filelist An array of filenames and directory
436     *                             names, or a single string with names
437     *                             separated by a single blank space.
438     * @param string $p_add_dir A string which contains a path to be
439     *                             added to the memorized path of each
440     *                             element in the list.
441     * @param string $p_remove_dir A string which contains a path to be
442     *                             removed from the memorized path of
443     *                             each element in the list, when
444     *                             relevant.
445     *
446     * @return true on success, false on error.
447     */
448    public function addModify($p_filelist, $p_add_dir, $p_remove_dir = '')
449    {
450        $v_result = true;
451
452        if (!$this->_isArchive()) {
453            $v_result = $this->createModify(
454                $p_filelist,
455                $p_add_dir,
456                $p_remove_dir
457            );
458        } else {
459            if (is_array($p_filelist)) {
460                $v_list = $p_filelist;
461            } elseif (is_string($p_filelist)) {
462                $v_list = explode($this->_separator, $p_filelist);
463            } else {
464                $this->_error('Invalid file list');
465                return false;
466            }
467
468            $v_result = $this->_append($v_list, $p_add_dir, $p_remove_dir);
469        }
470
471        return $v_result;
472    }
473
474    /**
475     * This method add a single string as a file at the
476     * end of the existing archive. If the archive does not yet exists it
477     * is created.
478     *
479     * @param string $p_filename A string which contains the full
480     *                           filename path that will be associated
481     *                           with the string.
482     * @param string $p_string The content of the file added in
483     *                           the archive.
484     * @param bool|int $p_datetime A custom date/time (unix timestamp)
485     *                           for the file (optional).
486     * @param array $p_params An array of optional params:
487     *                               stamp => the datetime (replaces
488     *                                   datetime above if it exists)
489     *                               mode => the permissions on the
490     *                                   file (600 by default)
491     *                               type => is this a link?  See the
492     *                                   tar specification for details.
493     *                                   (default = regular file)
494     *                               uid => the user ID of the file
495     *                                   (default = 0 = root)
496     *                               gid => the group ID of the file
497     *                                   (default = 0 = root)
498     *
499     * @return true on success, false on error.
500     */
501    public function addString($p_filename, $p_string, $p_datetime = false, $p_params = array())
502    {
503        $p_stamp = @$p_params["stamp"] ? $p_params["stamp"] : ($p_datetime ? $p_datetime : time());
504        $p_mode = @$p_params["mode"] ? $p_params["mode"] : 0600;
505        $p_type = @$p_params["type"] ? $p_params["type"] : "";
506        $p_uid = @$p_params["uid"] ? $p_params["uid"] : "";
507        $p_gid = @$p_params["gid"] ? $p_params["gid"] : "";
508        $v_result = true;
509
510        if (!$this->_isArchive()) {
511            if (!$this->_openWrite()) {
512                return false;
513            }
514            $this->_close();
515        }
516
517        if (!$this->_openAppend()) {
518            return false;
519        }
520
521        // Need to check the get back to the temporary file ? ....
522        $v_result = $this->_addString($p_filename, $p_string, $p_datetime, $p_params);
523
524        $this->_writeFooter();
525
526        $this->_close();
527
528        return $v_result;
529    }
530
531    /**
532     * This method extract all the content of the archive in the directory
533     * indicated by $p_path. When relevant the memorized path of the
534     * files/dir can be modified by removing the $p_remove_path path at the
535     * beginning of the file/dir path.
536     * While extracting a file, if the directory path does not exists it is
537     * created.
538     * While extracting a file, if the file already exists it is replaced
539     * without looking for last modification date.
540     * While extracting a file, if the file already exists and is write
541     * protected, the extraction is aborted.
542     * While extracting a file, if a directory with the same name already
543     * exists, the extraction is aborted.
544     * While extracting a directory, if a file with the same name already
545     * exists, the extraction is aborted.
546     * While extracting a file/directory if the destination directory exist
547     * and is write protected, or does not exist but can not be created,
548     * the extraction is aborted.
549     * If after extraction an extracted file does not show the correct
550     * stored file size, the extraction is aborted.
551     * When the extraction is aborted, a PEAR error text is set and false
552     * is returned. However the result can be a partial extraction that may
553     * need to be manually cleaned.
554     *
555     * @param string $p_path The path of the directory where the
556     *                               files/dir need to by extracted.
557     * @param string $p_remove_path Part of the memorized path that can be
558     *                               removed if present at the beginning of
559     *                               the file/dir path.
560     * @param boolean $p_preserve Preserve user/group ownership of files
561     * @param boolean $p_symlinks Allow symlinks.
562     *
563     * @return boolean true on success, false on error.
564     * @see    extractList()
565     */
566    public function extractModify($p_path, $p_remove_path, $p_preserve = false, $p_symlinks = true)
567    {
568        $v_result = true;
569        $v_list_detail = array();
570
571        if ($v_result = $this->_openRead()) {
572            $v_result = $this->_extractList(
573                $p_path,
574                $v_list_detail,
575                "complete",
576                0,
577                $p_remove_path,
578                $p_preserve,
579                $p_symlinks
580            );
581            $this->_close();
582        }
583
584        return $v_result;
585    }
586
587    /**
588     * This method extract from the archive one file identified by $p_filename.
589     * The return value is a string with the file content, or NULL on error.
590     *
591     * @param string $p_filename The path of the file to extract in a string.
592     *
593     * @return a string with the file content or NULL.
594     */
595    public function extractInString($p_filename)
596    {
597        if ($this->_openRead()) {
598            $v_result = $this->_extractInString($p_filename);
599            $this->_close();
600        } else {
601            $v_result = null;
602        }
603
604        return $v_result;
605    }
606
607    /**
608     * This method extract from the archive only the files indicated in the
609     * $p_filelist. These files are extracted in the current directory or
610     * in the directory indicated by the optional $p_path parameter.
611     * If indicated the $p_remove_path can be used in the same way as it is
612     * used in extractModify() method.
613     *
614     * @param array $p_filelist An array of filenames and directory names,
615     *                               or a single string with names separated
616     *                               by a single blank space.
617     * @param string $p_path The path of the directory where the
618     *                               files/dir need to by extracted.
619     * @param string $p_remove_path Part of the memorized path that can be
620     *                               removed if present at the beginning of
621     *                               the file/dir path.
622     * @param boolean $p_preserve Preserve user/group ownership of files
623     * @param boolean $p_symlinks Allow symlinks.
624     *
625     * @return true on success, false on error.
626     * @see    extractModify()
627     */
628    public function extractList($p_filelist, $p_path = '', $p_remove_path = '', $p_preserve = false, $p_symlinks = true)
629    {
630        $v_result = true;
631        $v_list_detail = array();
632
633        if (is_array($p_filelist)) {
634            $v_list = $p_filelist;
635        } elseif (is_string($p_filelist)) {
636            $v_list = explode($this->_separator, $p_filelist);
637        } else {
638            $this->_error('Invalid string list');
639            return false;
640        }
641
642        if ($v_result = $this->_openRead()) {
643            $v_result = $this->_extractList(
644                $p_path,
645                $v_list_detail,
646                "partial",
647                $v_list,
648                $p_remove_path,
649                $p_preserve,
650                $p_symlinks
651            );
652            $this->_close();
653        }
654
655        return $v_result;
656    }
657
658    /**
659     * This method set specific attributes of the archive. It uses a variable
660     * list of parameters, in the format attribute code + attribute values :
661     * $arch->setAttribute(ARCHIVE_TAR_ATT_SEPARATOR, ',');
662     *
663     * @return true on success, false on error.
664     */
665    public function setAttribute()
666    {
667        $v_result = true;
668
669        // ----- Get the number of variable list of arguments
670        if (($v_size = func_num_args()) == 0) {
671            return true;
672        }
673
674        // ----- Get the arguments
675        $v_att_list = func_get_args();
676
677        // ----- Read the attributes
678        $i = 0;
679        while ($i < $v_size) {
680
681            // ----- Look for next option
682            switch ($v_att_list[$i]) {
683                // ----- Look for options that request a string value
684                case ARCHIVE_TAR_ATT_SEPARATOR :
685                    // ----- Check the number of parameters
686                    if (($i + 1) >= $v_size) {
687                        $this->_error(
688                            'Invalid number of parameters for '
689                            . 'attribute ARCHIVE_TAR_ATT_SEPARATOR'
690                        );
691                        return false;
692                    }
693
694                    // ----- Get the value
695                    $this->_separator = $v_att_list[$i + 1];
696                    $i++;
697                    break;
698
699                default :
700                    $this->_error('Unknown attribute code ' . $v_att_list[$i] . '');
701                    return false;
702            }
703
704            // ----- Next attribute
705            $i++;
706        }
707
708        return $v_result;
709    }
710
711    /**
712     * This method sets the regular expression for ignoring files and directories
713     * at import, for example:
714     * $arch->setIgnoreRegexp("#CVS|\.svn#");
715     *
716     * @param string $regexp regular expression defining which files or directories to ignore
717     */
718    public function setIgnoreRegexp($regexp)
719    {
720        $this->_ignore_regexp = $regexp;
721    }
722
723    /**
724     * This method sets the regular expression for ignoring all files and directories
725     * matching the filenames in the array list at import, for example:
726     * $arch->setIgnoreList(array('CVS', '.svn', 'bin/tool'));
727     *
728     * @param array $list a list of file or directory names to ignore
729     *
730     * @access public
731     */
732    public function setIgnoreList($list)
733    {
734        $list = str_replace(array('#', '.', '^', '$'), array('\#', '\.', '\^', '\$'), $list);
735        $regexp = '#/' . join('$|/', $list) . '#';
736        $this->setIgnoreRegexp($regexp);
737    }
738
739    /**
740     * @param string $p_message
741     */
742    public function _error($p_message)
743    {
744        $this->error_object = $this->raiseError($p_message);
745    }
746
747    /**
748     * @param string $p_message
749     */
750    public function _warning($p_message)
751    {
752        $this->error_object = $this->raiseError($p_message);
753    }
754
755    /**
756     * @param string $p_filename
757     * @return bool
758     */
759    public function _isArchive($p_filename = null)
760    {
761        if ($p_filename == null) {
762            $p_filename = $this->_tarname;
763        }
764        clearstatcache();
765        return @is_file($p_filename) && !@is_link($p_filename);
766    }
767
768    /**
769     * @return bool
770     */
771    public function _openWrite()
772    {
773        if ($this->_compress_type == 'gz' && function_exists('gzopen')) {
774            $this->_file = @gzopen($this->_tarname, "wb9");
775        } else {
776            if ($this->_compress_type == 'bz2' && function_exists('bzopen')) {
777                $this->_file = @bzopen($this->_tarname, "w");
778            } else {
779                if ($this->_compress_type == 'lzma2' && function_exists('xzopen')) {
780                    $this->_file = @xzopen($this->_tarname, 'w');
781                } else {
782                    if ($this->_compress_type == 'none') {
783                        $this->_file = @fopen($this->_tarname, "wb");
784                    } else {
785                        $this->_error(
786                            'Unknown or missing compression type ('
787                            . $this->_compress_type . ')'
788                        );
789                        return false;
790                    }
791                }
792            }
793        }
794
795        if ($this->_file == 0) {
796            $this->_error(
797                'Unable to open in write mode \''
798                . $this->_tarname . '\''
799            );
800            return false;
801        }
802
803        return true;
804    }
805
806    /**
807     * @return bool
808     */
809    public function _openRead()
810    {
811        if (strtolower(substr($this->_tarname, 0, 7)) == 'http://') {
812
813            // ----- Look if a local copy need to be done
814            if ($this->_temp_tarname == '') {
815                $this->_temp_tarname = uniqid('tar') . '.tmp';
816                if (!$v_file_from = @fopen($this->_tarname, 'rb')) {
817                    $this->_error(
818                        'Unable to open in read mode \''
819                        . $this->_tarname . '\''
820                    );
821                    $this->_temp_tarname = '';
822                    return false;
823                }
824                if (!$v_file_to = @fopen($this->_temp_tarname, 'wb')) {
825                    $this->_error(
826                        'Unable to open in write mode \''
827                        . $this->_temp_tarname . '\''
828                    );
829                    $this->_temp_tarname = '';
830                    return false;
831                }
832                while ($v_data = @fread($v_file_from, 1024)) {
833                    @fwrite($v_file_to, $v_data);
834                }
835                @fclose($v_file_from);
836                @fclose($v_file_to);
837            }
838
839            // ----- File to open if the local copy
840            $v_filename = $this->_temp_tarname;
841        } else {
842            // ----- File to open if the normal Tar file
843
844            $v_filename = $this->_tarname;
845        }
846
847        if ($this->_compress_type == 'gz' && function_exists('gzopen')) {
848            $this->_file = @gzopen($v_filename, "rb");
849        } else {
850            if ($this->_compress_type == 'bz2' && function_exists('bzopen')) {
851                $this->_file = @bzopen($v_filename, "r");
852            } else {
853                if ($this->_compress_type == 'lzma2' && function_exists('xzopen')) {
854                    $this->_file = @xzopen($v_filename, "r");
855                } else {
856                    if ($this->_compress_type == 'none') {
857                        $this->_file = @fopen($v_filename, "rb");
858                    } else {
859                        $this->_error(
860                            'Unknown or missing compression type ('
861                            . $this->_compress_type . ')'
862                        );
863                        return false;
864                    }
865                }
866            }
867        }
868
869        if ($this->_file == 0) {
870            $this->_error('Unable to open in read mode \'' . $v_filename . '\'');
871            return false;
872        }
873
874        return true;
875    }
876
877    /**
878     * @return bool
879     */
880    public function _openReadWrite()
881    {
882        if ($this->_compress_type == 'gz') {
883            $this->_file = @gzopen($this->_tarname, "r+b");
884        } else {
885            if ($this->_compress_type == 'bz2') {
886                $this->_error(
887                    'Unable to open bz2 in read/write mode \''
888                    . $this->_tarname . '\' (limitation of bz2 extension)'
889                );
890                return false;
891            } else {
892                if ($this->_compress_type == 'lzma2') {
893                    $this->_error(
894                        'Unable to open lzma2 in read/write mode \''
895                        . $this->_tarname . '\' (limitation of lzma2 extension)'
896                    );
897                    return false;
898                } else {
899                    if ($this->_compress_type == 'none') {
900                        $this->_file = @fopen($this->_tarname, "r+b");
901                    } else {
902                        $this->_error(
903                            'Unknown or missing compression type ('
904                            . $this->_compress_type . ')'
905                        );
906                        return false;
907                    }
908                }
909            }
910        }
911
912        if ($this->_file == 0) {
913            $this->_error(
914                'Unable to open in read/write mode \''
915                . $this->_tarname . '\''
916            );
917            return false;
918        }
919
920        return true;
921    }
922
923    /**
924     * @return bool
925     */
926    public function _close()
927    {
928        //if (isset($this->_file)) {
929        if (is_resource($this->_file)) {
930            if ($this->_compress_type == 'gz') {
931                @gzclose($this->_file);
932            } else {
933                if ($this->_compress_type == 'bz2') {
934                    @bzclose($this->_file);
935                } else {
936                    if ($this->_compress_type == 'lzma2') {
937                        @xzclose($this->_file);
938                    } else {
939                        if ($this->_compress_type == 'none') {
940                            @fclose($this->_file);
941                        } else {
942                            $this->_error(
943                                'Unknown or missing compression type ('
944                                . $this->_compress_type . ')'
945                            );
946                        }
947                    }
948                }
949            }
950
951            $this->_file = 0;
952        }
953
954        // ----- Look if a local copy need to be erase
955        // Note that it might be interesting to keep the url for a time : ToDo
956        if ($this->_temp_tarname != '') {
957            @unlink($this->_temp_tarname);
958            $this->_temp_tarname = '';
959        }
960
961        return true;
962    }
963
964    /**
965     * @return bool
966     */
967    public function _cleanFile()
968    {
969        $this->_close();
970
971        // ----- Look for a local copy
972        if ($this->_temp_tarname != '') {
973            // ----- Remove the local copy but not the remote tarname
974            @unlink($this->_temp_tarname);
975            $this->_temp_tarname = '';
976        } else {
977            // ----- Remove the local tarname file
978            @unlink($this->_tarname);
979        }
980        $this->_tarname = '';
981
982        return true;
983    }
984
985    /**
986     * @param mixed $p_binary_data
987     * @param integer $p_len
988     * @return bool
989     */
990    public function _writeBlock($p_binary_data, $p_len = null)
991    {
992        if (is_resource($this->_file)) {
993            if ($p_len === null) {
994                if ($this->_compress_type == 'gz') {
995                    @gzputs($this->_file, $p_binary_data);
996                } else {
997                    if ($this->_compress_type == 'bz2') {
998                        @bzwrite($this->_file, $p_binary_data);
999                    } else {
1000                        if ($this->_compress_type == 'lzma2') {
1001                            @xzwrite($this->_file, $p_binary_data);
1002                        } else {
1003                            if ($this->_compress_type == 'none') {
1004                                @fputs($this->_file, $p_binary_data);
1005                            } else {
1006                                $this->_error(
1007                                    'Unknown or missing compression type ('
1008                                    . $this->_compress_type . ')'
1009                                );
1010                            }
1011                        }
1012                    }
1013                }
1014            } else {
1015                if ($this->_compress_type == 'gz') {
1016                    @gzputs($this->_file, $p_binary_data, $p_len);
1017                } else {
1018                    if ($this->_compress_type == 'bz2') {
1019                        @bzwrite($this->_file, $p_binary_data, $p_len);
1020                    } else {
1021                        if ($this->_compress_type == 'lzma2') {
1022                            @xzwrite($this->_file, $p_binary_data, $p_len);
1023                        } else {
1024                            if ($this->_compress_type == 'none') {
1025                                @fputs($this->_file, $p_binary_data, $p_len);
1026                            } else {
1027                                $this->_error(
1028                                    'Unknown or missing compression type ('
1029                                    . $this->_compress_type . ')'
1030                                );
1031                            }
1032                        }
1033                    }
1034                }
1035            }
1036        }
1037        return true;
1038    }
1039
1040    /**
1041     * @return null|string
1042     */
1043    public function _readBlock()
1044    {
1045        $v_block = null;
1046        if (is_resource($this->_file)) {
1047            if ($this->_compress_type == 'gz') {
1048                $v_block = @gzread($this->_file, 512);
1049            } else {
1050                if ($this->_compress_type == 'bz2') {
1051                    $v_block = @bzread($this->_file, 512);
1052                } else {
1053                    if ($this->_compress_type == 'lzma2') {
1054                        $v_block = @xzread($this->_file, 512);
1055                    } else {
1056                        if ($this->_compress_type == 'none') {
1057                            $v_block = @fread($this->_file, 512);
1058                        } else {
1059                            $this->_error(
1060                                'Unknown or missing compression type ('
1061                                . $this->_compress_type . ')'
1062                            );
1063                        }
1064                    }
1065                }
1066            }
1067        }
1068        return $v_block;
1069    }
1070
1071    /**
1072     * @param null $p_len
1073     * @return bool
1074     */
1075    public function _jumpBlock($p_len = null)
1076    {
1077        if (is_resource($this->_file)) {
1078            if ($p_len === null) {
1079                $p_len = 1;
1080            }
1081
1082            if ($this->_compress_type == 'gz') {
1083                @gzseek($this->_file, gztell($this->_file) + ($p_len * 512));
1084            } else {
1085                if ($this->_compress_type == 'bz2') {
1086                    // ----- Replace missing bztell() and bzseek()
1087                    for ($i = 0; $i < $p_len; $i++) {
1088                        $this->_readBlock();
1089                    }
1090                } else {
1091                    if ($this->_compress_type == 'lzma2') {
1092                        // ----- Replace missing xztell() and xzseek()
1093                        for ($i = 0; $i < $p_len; $i++) {
1094                            $this->_readBlock();
1095                        }
1096                    } else {
1097                        if ($this->_compress_type == 'none') {
1098                            @fseek($this->_file, $p_len * 512, SEEK_CUR);
1099                        } else {
1100                            $this->_error(
1101                                'Unknown or missing compression type ('
1102                                . $this->_compress_type . ')'
1103                            );
1104                        }
1105                    }
1106                }
1107            }
1108        }
1109        return true;
1110    }
1111
1112    /**
1113     * @return bool
1114     */
1115    public function _writeFooter()
1116    {
1117        if (is_resource($this->_file)) {
1118            // ----- Write the last 0 filled block for end of archive
1119            $v_binary_data = pack('a1024', '');
1120            $this->_writeBlock($v_binary_data);
1121        }
1122        return true;
1123    }
1124
1125    /**
1126     * @param array $p_list
1127     * @param string $p_add_dir
1128     * @param string $p_remove_dir
1129     * @return bool
1130     */
1131    public function _addList($p_list, $p_add_dir, $p_remove_dir)
1132    {
1133        $v_result = true;
1134        $v_header = array();
1135
1136        // ----- Remove potential windows directory separator
1137        $p_add_dir = $this->_translateWinPath($p_add_dir);
1138        $p_remove_dir = $this->_translateWinPath($p_remove_dir, false);
1139
1140        if (!$this->_file) {
1141            $this->_error('Invalid file descriptor');
1142            return false;
1143        }
1144
1145        if (sizeof($p_list) == 0) {
1146            return true;
1147        }
1148
1149        foreach ($p_list as $v_filename) {
1150            if (!$v_result) {
1151                break;
1152            }
1153
1154            // ----- Skip the current tar name
1155            if ($v_filename == $this->_tarname) {
1156                continue;
1157            }
1158
1159            if ($v_filename == '') {
1160                continue;
1161            }
1162
1163            // ----- ignore files and directories matching the ignore regular expression
1164            if ($this->_ignore_regexp && preg_match($this->_ignore_regexp, '/' . $v_filename)) {
1165                $this->_warning("File '$v_filename' ignored");
1166                continue;
1167            }
1168
1169            if (!file_exists($v_filename) && !is_link($v_filename)) {
1170                $this->_warning("File '$v_filename' does not exist");
1171                continue;
1172            }
1173
1174            // ----- Add the file or directory header
1175            if (!$this->_addFile($v_filename, $v_header, $p_add_dir, $p_remove_dir)) {
1176                return false;
1177            }
1178
1179            if (@is_dir($v_filename) && !@is_link($v_filename)) {
1180                if (!($p_hdir = opendir($v_filename))) {
1181                    $this->_warning("Directory '$v_filename' can not be read");
1182                    continue;
1183                }
1184                while (false !== ($p_hitem = readdir($p_hdir))) {
1185                    if (($p_hitem != '.') && ($p_hitem != '..')) {
1186                        if ($v_filename != ".") {
1187                            $p_temp_list[0] = $v_filename . '/' . $p_hitem;
1188                        } else {
1189                            $p_temp_list[0] = $p_hitem;
1190                        }
1191
1192                        $v_result = $this->_addList(
1193                            $p_temp_list,
1194                            $p_add_dir,
1195                            $p_remove_dir
1196                        );
1197                    }
1198                }
1199
1200                unset($p_temp_list);
1201                unset($p_hdir);
1202                unset($p_hitem);
1203            }
1204        }
1205
1206        return $v_result;
1207    }
1208
1209    /**
1210     * @param string $p_filename
1211     * @param mixed $p_header
1212     * @param string $p_add_dir
1213     * @param string $p_remove_dir
1214     * @param null $v_stored_filename
1215     * @return bool
1216     */
1217    public function _addFile($p_filename, &$p_header, $p_add_dir, $p_remove_dir, $v_stored_filename = null)
1218    {
1219        if (!$this->_file) {
1220            $this->_error('Invalid file descriptor');
1221            return false;
1222        }
1223
1224        if ($p_filename == '') {
1225            $this->_error('Invalid file name');
1226            return false;
1227        }
1228
1229        if (is_null($v_stored_filename)) {
1230            // ----- Calculate the stored filename
1231            $p_filename = $this->_translateWinPath($p_filename, false);
1232            $v_stored_filename = $p_filename;
1233
1234            if (strcmp($p_filename, $p_remove_dir) == 0) {
1235                return true;
1236            }
1237
1238            if ($p_remove_dir != '') {
1239                if (substr($p_remove_dir, -1) != '/') {
1240                    $p_remove_dir .= '/';
1241                }
1242
1243                if (substr($p_filename, 0, strlen($p_remove_dir)) == $p_remove_dir) {
1244                    $v_stored_filename = substr($p_filename, strlen($p_remove_dir));
1245                }
1246            }
1247
1248            $v_stored_filename = $this->_translateWinPath($v_stored_filename);
1249            if ($p_add_dir != '') {
1250                if (substr($p_add_dir, -1) == '/') {
1251                    $v_stored_filename = $p_add_dir . $v_stored_filename;
1252                } else {
1253                    $v_stored_filename = $p_add_dir . '/' . $v_stored_filename;
1254                }
1255            }
1256
1257            $v_stored_filename = $this->_pathReduction($v_stored_filename);
1258        }
1259
1260        if ($this->_isArchive($p_filename)) {
1261            if (($v_file = @fopen($p_filename, "rb")) == 0) {
1262                $this->_warning(
1263                    "Unable to open file '" . $p_filename
1264                    . "' in binary read mode"
1265                );
1266                return true;
1267            }
1268
1269            if (!$this->_writeHeader($p_filename, $v_stored_filename)) {
1270                return false;
1271            }
1272
1273            while (($v_buffer = fread($v_file, $this->buffer_length)) != '') {
1274                $buffer_length = strlen("$v_buffer");
1275                if ($buffer_length != $this->buffer_length) {
1276                    $pack_size = ((int)($buffer_length / 512) + ($buffer_length % 512 !== 0 ? 1 : 0)) * 512;
1277                    $pack_format = sprintf('a%d', $pack_size);
1278                } else {
1279                    $pack_format = sprintf('a%d', $this->buffer_length);
1280                }
1281                $v_binary_data = pack($pack_format, "$v_buffer");
1282                $this->_writeBlock($v_binary_data);
1283            }
1284
1285            fclose($v_file);
1286        } else {
1287            // ----- Only header for dir
1288            if (!$this->_writeHeader($p_filename, $v_stored_filename)) {
1289                return false;
1290            }
1291        }
1292
1293        return true;
1294    }
1295
1296    /**
1297     * @param string $p_filename
1298     * @param string $p_string
1299     * @param bool $p_datetime
1300     * @param array $p_params
1301     * @return bool
1302     */
1303    public function _addString($p_filename, $p_string, $p_datetime = false, $p_params = array())
1304    {
1305        $p_stamp = @$p_params["stamp"] ? $p_params["stamp"] : ($p_datetime ? $p_datetime : time());
1306        $p_mode = @$p_params["mode"] ? $p_params["mode"] : 0600;
1307        $p_type = @$p_params["type"] ? $p_params["type"] : "";
1308        $p_uid = @$p_params["uid"] ? $p_params["uid"] : 0;
1309        $p_gid = @$p_params["gid"] ? $p_params["gid"] : 0;
1310        if (!$this->_file) {
1311            $this->_error('Invalid file descriptor');
1312            return false;
1313        }
1314
1315        if ($p_filename == '') {
1316            $this->_error('Invalid file name');
1317            return false;
1318        }
1319
1320        // ----- Calculate the stored filename
1321        $p_filename = $this->_translateWinPath($p_filename, false);
1322
1323        // ----- If datetime is not specified, set current time
1324        if ($p_datetime === false) {
1325            $p_datetime = time();
1326        }
1327
1328        if (!$this->_writeHeaderBlock(
1329            $p_filename,
1330            strlen($p_string),
1331            $p_stamp,
1332            $p_mode,
1333            $p_type,
1334            $p_uid,
1335            $p_gid
1336        )
1337        ) {
1338            return false;
1339        }
1340
1341        $i = 0;
1342        while (($v_buffer = substr($p_string, (($i++) * 512), 512)) != '') {
1343            $v_binary_data = pack("a512", $v_buffer);
1344            $this->_writeBlock($v_binary_data);
1345        }
1346
1347        return true;
1348    }
1349
1350    /**
1351     * @param string $p_filename
1352     * @param string $p_stored_filename
1353     * @return bool
1354     */
1355    public function _writeHeader($p_filename, $p_stored_filename)
1356    {
1357        if ($p_stored_filename == '') {
1358            $p_stored_filename = $p_filename;
1359        }
1360
1361        $v_reduced_filename = $this->_pathReduction($p_stored_filename);
1362
1363        if (strlen($v_reduced_filename) > 99) {
1364            if (!$this->_writeLongHeader($v_reduced_filename, false)) {
1365                return false;
1366            }
1367        }
1368
1369        $v_linkname = '';
1370        if (@is_link($p_filename)) {
1371            $v_linkname = readlink($p_filename);
1372        }
1373
1374        if (strlen($v_linkname) > 99) {
1375            if (!$this->_writeLongHeader($v_linkname, true)) {
1376                return false;
1377            }
1378        }
1379
1380        $v_info = lstat($p_filename);
1381        $v_uid = sprintf("%07s", DecOct($v_info[4]));
1382        $v_gid = sprintf("%07s", DecOct($v_info[5]));
1383        $v_perms = sprintf("%07s", DecOct($v_info['mode'] & 000777));
1384        $v_mtime = sprintf("%011s", DecOct($v_info['mtime']));
1385
1386        if (@is_link($p_filename)) {
1387            $v_typeflag = '2';
1388            $v_size = sprintf("%011s", DecOct(0));
1389        } elseif (@is_dir($p_filename)) {
1390            $v_typeflag = "5";
1391            $v_size = sprintf("%011s", DecOct(0));
1392        } else {
1393            $v_typeflag = '0';
1394            clearstatcache();
1395            $v_size = sprintf("%011s", DecOct($v_info['size']));
1396        }
1397
1398        $v_magic = 'ustar ';
1399        $v_version = ' ';
1400
1401        if (function_exists('posix_getpwuid')) {
1402            $userinfo = posix_getpwuid($v_info[4]);
1403            $groupinfo = posix_getgrgid($v_info[5]);
1404
1405            $v_uname = $userinfo['name'];
1406            $v_gname = $groupinfo['name'];
1407        } else {
1408            $v_uname = '';
1409            $v_gname = '';
1410        }
1411
1412        $v_devmajor = '';
1413        $v_devminor = '';
1414        $v_prefix = '';
1415
1416        $v_binary_data_first = pack(
1417            "a100a8a8a8a12a12",
1418            $v_reduced_filename,
1419            $v_perms,
1420            $v_uid,
1421            $v_gid,
1422            $v_size,
1423            $v_mtime
1424        );
1425        $v_binary_data_last = pack(
1426            "a1a100a6a2a32a32a8a8a155a12",
1427            $v_typeflag,
1428            $v_linkname,
1429            $v_magic,
1430            $v_version,
1431            $v_uname,
1432            $v_gname,
1433            $v_devmajor,
1434            $v_devminor,
1435            $v_prefix,
1436            ''
1437        );
1438
1439        // ----- Calculate the checksum
1440        $v_checksum = 0;
1441        // ..... First part of the header
1442        for ($i = 0; $i < 148; $i++) {
1443            $v_checksum += ord(substr($v_binary_data_first, $i, 1));
1444        }
1445        // ..... Ignore the checksum value and replace it by ' ' (space)
1446        for ($i = 148; $i < 156; $i++) {
1447            $v_checksum += ord(' ');
1448        }
1449        // ..... Last part of the header
1450        for ($i = 156, $j = 0; $i < 512; $i++, $j++) {
1451            $v_checksum += ord(substr($v_binary_data_last, $j, 1));
1452        }
1453
1454        // ----- Write the first 148 bytes of the header in the archive
1455        $this->_writeBlock($v_binary_data_first, 148);
1456
1457        // ----- Write the calculated checksum
1458        $v_checksum = sprintf("%06s\0 ", DecOct($v_checksum));
1459        $v_binary_data = pack("a8", $v_checksum);
1460        $this->_writeBlock($v_binary_data, 8);
1461
1462        // ----- Write the last 356 bytes of the header in the archive
1463        $this->_writeBlock($v_binary_data_last, 356);
1464
1465        return true;
1466    }
1467
1468    /**
1469     * @param string $p_filename
1470     * @param int $p_size
1471     * @param int $p_mtime
1472     * @param int $p_perms
1473     * @param string $p_type
1474     * @param int $p_uid
1475     * @param int $p_gid
1476     * @return bool
1477     */
1478    public function _writeHeaderBlock(
1479        $p_filename,
1480        $p_size,
1481        $p_mtime = 0,
1482        $p_perms = 0,
1483        $p_type = '',
1484        $p_uid = 0,
1485        $p_gid = 0
1486    )
1487    {
1488        $p_filename = $this->_pathReduction($p_filename);
1489
1490        if (strlen($p_filename) > 99) {
1491            if (!$this->_writeLongHeader($p_filename, false)) {
1492                return false;
1493            }
1494        }
1495
1496        if ($p_type == "5") {
1497            $v_size = sprintf("%011s", DecOct(0));
1498        } else {
1499            $v_size = sprintf("%011s", DecOct($p_size));
1500        }
1501
1502        $v_uid = sprintf("%07s", DecOct($p_uid));
1503        $v_gid = sprintf("%07s", DecOct($p_gid));
1504        $v_perms = sprintf("%07s", DecOct($p_perms & 000777));
1505
1506        $v_mtime = sprintf("%11s", DecOct($p_mtime));
1507
1508        $v_linkname = '';
1509
1510        $v_magic = 'ustar ';
1511
1512        $v_version = ' ';
1513
1514        if (function_exists('posix_getpwuid')) {
1515            $userinfo = posix_getpwuid($p_uid);
1516            $groupinfo = posix_getgrgid($p_gid);
1517
1518            if ($userinfo === false || $groupinfo === false) {
1519                $v_uname = '';
1520                $v_gname = '';
1521            } else {
1522                $v_uname = $userinfo['name'];
1523                $v_gname = $groupinfo['name'];
1524            }
1525        } else {
1526            $v_uname = '';
1527            $v_gname = '';
1528        }
1529
1530        $v_devmajor = '';
1531
1532        $v_devminor = '';
1533
1534        $v_prefix = '';
1535
1536        $v_binary_data_first = pack(
1537            "a100a8a8a8a12A12",
1538            $p_filename,
1539            $v_perms,
1540            $v_uid,
1541            $v_gid,
1542            $v_size,
1543            $v_mtime
1544        );
1545        $v_binary_data_last = pack(
1546            "a1a100a6a2a32a32a8a8a155a12",
1547            $p_type,
1548            $v_linkname,
1549            $v_magic,
1550            $v_version,
1551            $v_uname,
1552            $v_gname,
1553            $v_devmajor,
1554            $v_devminor,
1555            $v_prefix,
1556            ''
1557        );
1558
1559        // ----- Calculate the checksum
1560        $v_checksum = 0;
1561        // ..... First part of the header
1562        for ($i = 0; $i < 148; $i++) {
1563            $v_checksum += ord(substr($v_binary_data_first, $i, 1));
1564        }
1565        // ..... Ignore the checksum value and replace it by ' ' (space)
1566        for ($i = 148; $i < 156; $i++) {
1567            $v_checksum += ord(' ');
1568        }
1569        // ..... Last part of the header
1570        for ($i = 156, $j = 0; $i < 512; $i++, $j++) {
1571            $v_checksum += ord(substr($v_binary_data_last, $j, 1));
1572        }
1573
1574        // ----- Write the first 148 bytes of the header in the archive
1575        $this->_writeBlock($v_binary_data_first, 148);
1576
1577        // ----- Write the calculated checksum
1578        $v_checksum = sprintf("%06s ", DecOct($v_checksum));
1579        $v_binary_data = pack("a8", $v_checksum);
1580        $this->_writeBlock($v_binary_data, 8);
1581
1582        // ----- Write the last 356 bytes of the header in the archive
1583        $this->_writeBlock($v_binary_data_last, 356);
1584
1585        return true;
1586    }
1587
1588    /**
1589     * @param string $p_filename
1590     * @return bool
1591     */
1592    public function _writeLongHeader($p_filename, $is_link = false)
1593    {
1594        $v_uid = sprintf("%07s", 0);
1595        $v_gid = sprintf("%07s", 0);
1596        $v_perms = sprintf("%07s", 0);
1597        $v_size = sprintf("%'011s", DecOct(strlen($p_filename)));
1598        $v_mtime = sprintf("%011s", 0);
1599        $v_typeflag = ($is_link ? 'K' : 'L');
1600        $v_linkname = '';
1601        $v_magic = 'ustar ';
1602        $v_version = ' ';
1603        $v_uname = '';
1604        $v_gname = '';
1605        $v_devmajor = '';
1606        $v_devminor = '';
1607        $v_prefix = '';
1608
1609        $v_binary_data_first = pack(
1610            "a100a8a8a8a12a12",
1611            '././@LongLink',
1612            $v_perms,
1613            $v_uid,
1614            $v_gid,
1615            $v_size,
1616            $v_mtime
1617        );
1618        $v_binary_data_last = pack(
1619            "a1a100a6a2a32a32a8a8a155a12",
1620            $v_typeflag,
1621            $v_linkname,
1622            $v_magic,
1623            $v_version,
1624            $v_uname,
1625            $v_gname,
1626            $v_devmajor,
1627            $v_devminor,
1628            $v_prefix,
1629            ''
1630        );
1631
1632        // ----- Calculate the checksum
1633        $v_checksum = 0;
1634        // ..... First part of the header
1635        for ($i = 0; $i < 148; $i++) {
1636            $v_checksum += ord(substr($v_binary_data_first, $i, 1));
1637        }
1638        // ..... Ignore the checksum value and replace it by ' ' (space)
1639        for ($i = 148; $i < 156; $i++) {
1640            $v_checksum += ord(' ');
1641        }
1642        // ..... Last part of the header
1643        for ($i = 156, $j = 0; $i < 512; $i++, $j++) {
1644            $v_checksum += ord(substr($v_binary_data_last, $j, 1));
1645        }
1646
1647        // ----- Write the first 148 bytes of the header in the archive
1648        $this->_writeBlock($v_binary_data_first, 148);
1649
1650        // ----- Write the calculated checksum
1651        $v_checksum = sprintf("%06s\0 ", DecOct($v_checksum));
1652        $v_binary_data = pack("a8", $v_checksum);
1653        $this->_writeBlock($v_binary_data, 8);
1654
1655        // ----- Write the last 356 bytes of the header in the archive
1656        $this->_writeBlock($v_binary_data_last, 356);
1657
1658        // ----- Write the filename as content of the block
1659        $i = 0;
1660        while (($v_buffer = substr($p_filename, (($i++) * 512), 512)) != '') {
1661            $v_binary_data = pack("a512", "$v_buffer");
1662            $this->_writeBlock($v_binary_data);
1663        }
1664
1665        return true;
1666    }
1667
1668    /**
1669     * @param mixed $v_binary_data
1670     * @param mixed $v_header
1671     * @return bool
1672     */
1673    public function _readHeader($v_binary_data, &$v_header)
1674    {
1675        if (strlen($v_binary_data) == 0) {
1676            $v_header['filename'] = '';
1677            return true;
1678        }
1679
1680        if (strlen($v_binary_data) != 512) {
1681            $v_header['filename'] = '';
1682            $this->_error('Invalid block size : ' . strlen($v_binary_data));
1683            return false;
1684        }
1685
1686        if (!is_array($v_header)) {
1687            $v_header = array();
1688        }
1689        // ----- Calculate the checksum
1690        $v_checksum = 0;
1691        // ..... First part of the header
1692        $v_binary_split = str_split($v_binary_data);
1693        $v_checksum += array_sum(array_map('ord', array_slice($v_binary_split, 0, 148)));
1694        $v_checksum += array_sum(array_map('ord', array(' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ',)));
1695        $v_checksum += array_sum(array_map('ord', array_slice($v_binary_split, 156, 512)));
1696
1697
1698        $v_data = unpack($this->_fmt, $v_binary_data);
1699
1700        if (strlen($v_data["prefix"]) > 0) {
1701            $v_data["filename"] = "$v_data[prefix]/$v_data[filename]";
1702        }
1703
1704        // ----- Extract the checksum
1705        $v_data_checksum = trim($v_data['checksum']);
1706        if (!preg_match('/^[0-7]*$/', $v_data_checksum)) {
1707            $this->_error(
1708                'Invalid checksum for file "' . $v_data['filename']
1709                . '" : ' . $v_data_checksum . ' extracted'
1710            );
1711            return false;
1712        }
1713
1714        $v_header['checksum'] = OctDec($v_data_checksum);
1715        if ($v_header['checksum'] != $v_checksum) {
1716            $v_header['filename'] = '';
1717
1718            // ----- Look for last block (empty block)
1719            if (($v_checksum == 256) && ($v_header['checksum'] == 0)) {
1720                return true;
1721            }
1722
1723            $this->_error(
1724                'Invalid checksum for file "' . $v_data['filename']
1725                . '" : ' . $v_checksum . ' calculated, '
1726                . $v_header['checksum'] . ' expected'
1727            );
1728            return false;
1729        }
1730
1731        // ----- Extract the properties
1732        $v_header['filename'] = rtrim($v_data['filename'], "\0");
1733        if ($this->_maliciousFilename($v_header['filename'])) {
1734            $this->_error(
1735                'Malicious .tar detected, file "' . $v_header['filename'] .
1736                '" will not install in desired directory tree'
1737            );
1738            return false;
1739        }
1740        $v_header['mode'] = OctDec(trim($v_data['mode']));
1741        $v_header['uid'] = OctDec(trim($v_data['uid']));
1742        $v_header['gid'] = OctDec(trim($v_data['gid']));
1743        $v_header['size'] = $this->_tarRecToSize($v_data['size']);
1744        $v_header['mtime'] = OctDec(trim($v_data['mtime']));
1745        if (($v_header['typeflag'] = $v_data['typeflag']) == "5") {
1746            $v_header['size'] = 0;
1747        }
1748        $v_header['link'] = trim($v_data['link']);
1749        /* ----- All these fields are removed form the header because
1750        they do not carry interesting info
1751        $v_header[magic] = trim($v_data[magic]);
1752        $v_header[version] = trim($v_data[version]);
1753        $v_header[uname] = trim($v_data[uname]);
1754        $v_header[gname] = trim($v_data[gname]);
1755        $v_header[devmajor] = trim($v_data[devmajor]);
1756        $v_header[devminor] = trim($v_data[devminor]);
1757        */
1758
1759        return true;
1760    }
1761
1762    /**
1763     * Convert Tar record size to actual size
1764     *
1765     * @param string $tar_size
1766     * @return size of tar record in bytes
1767     */
1768    private function _tarRecToSize($tar_size)
1769    {
1770        /*
1771         * First byte of size has a special meaning if bit 7 is set.
1772         *
1773         * Bit 7 indicates base-256 encoding if set.
1774         * Bit 6 is the sign bit.
1775         * Bits 5:0 are most significant value bits.
1776         */
1777        $ch = ord($tar_size[0]);
1778        if ($ch & 0x80) {
1779            // Full 12-bytes record is required.
1780            $rec_str = $tar_size . "\x00";
1781
1782            $size = ($ch & 0x40) ? -1 : 0;
1783            $size = ($size << 6) | ($ch & 0x3f);
1784
1785            for ($num_ch = 1; $num_ch < 12; ++$num_ch) {
1786                $size = ($size * 256) + ord($rec_str[$num_ch]);
1787            }
1788
1789            return $size;
1790
1791        } else {
1792            return OctDec(trim($tar_size));
1793        }
1794    }
1795
1796    /**
1797     * Detect and report a malicious file name
1798     *
1799     * @param string $file
1800     *
1801     * @return bool
1802     */
1803    private function _maliciousFilename($file)
1804    {
1805        if (strpos($file, 'phar://') === 0) {
1806            return true;
1807        }
1808        if (strpos($file, '../') !== false || strpos($file, '..\\') !== false) {
1809            return true;
1810        }
1811        return false;
1812    }
1813
1814    /**
1815     * @param $v_header
1816     * @return bool
1817     */
1818    public function _readLongHeader(&$v_header)
1819    {
1820        $v_filename = '';
1821        $v_filesize = $v_header['size'];
1822        $n = floor($v_header['size'] / 512);
1823        for ($i = 0; $i < $n; $i++) {
1824            $v_content = $this->_readBlock();
1825            $v_filename .= $v_content;
1826        }
1827        if (($v_header['size'] % 512) != 0) {
1828            $v_content = $this->_readBlock();
1829            $v_filename .= $v_content;
1830        }
1831
1832        // ----- Read the next header
1833        $v_binary_data = $this->_readBlock();
1834
1835        if (!$this->_readHeader($v_binary_data, $v_header)) {
1836            return false;
1837        }
1838
1839        $v_filename = rtrim(substr($v_filename, 0, $v_filesize), "\0");
1840        $v_header['filename'] = $v_filename;
1841        if ($this->_maliciousFilename($v_filename)) {
1842            $this->_error(
1843                'Malicious .tar detected, file "' . $v_filename .
1844                '" will not install in desired directory tree'
1845            );
1846            return false;
1847        }
1848
1849        return true;
1850    }
1851
1852    /**
1853     * This method extract from the archive one file identified by $p_filename.
1854     * The return value is a string with the file content, or null on error.
1855     *
1856     * @param string $p_filename The path of the file to extract in a string.
1857     *
1858     * @return a string with the file content or null.
1859     */
1860    private function _extractInString($p_filename)
1861    {
1862        $v_result_str = "";
1863
1864        while (strlen($v_binary_data = $this->_readBlock()) != 0) {
1865            if (!$this->_readHeader($v_binary_data, $v_header)) {
1866                return null;
1867            }
1868
1869            if ($v_header['filename'] == '') {
1870                continue;
1871            }
1872
1873            switch ($v_header['typeflag']) {
1874                case 'L':
1875                    {
1876                        if (!$this->_readLongHeader($v_header)) {
1877                            return null;
1878                        }
1879                    }
1880                    break;
1881
1882                case 'K':
1883                    {
1884                        $v_link_header = $v_header;
1885                        if (!$this->_readLongHeader($v_link_header)) {
1886                            return null;
1887                        }
1888                        $v_header['link'] = $v_link_header['filename'];
1889                    }
1890                    break;
1891            }
1892
1893            if ($v_header['filename'] == $p_filename) {
1894                if ($v_header['typeflag'] == "5") {
1895                    $this->_error(
1896                        'Unable to extract in string a directory '
1897                        . 'entry {' . $v_header['filename'] . '}'
1898                    );
1899                    return null;
1900                } else {
1901                    $n = floor($v_header['size'] / 512);
1902                    for ($i = 0; $i < $n; $i++) {
1903                        $v_result_str .= $this->_readBlock();
1904                    }
1905                    if (($v_header['size'] % 512) != 0) {
1906                        $v_content = $this->_readBlock();
1907                        $v_result_str .= substr(
1908                            $v_content,
1909                            0,
1910                            ($v_header['size'] % 512)
1911                        );
1912                    }
1913                    return $v_result_str;
1914                }
1915            } else {
1916                $this->_jumpBlock(ceil(($v_header['size'] / 512)));
1917            }
1918        }
1919
1920        return null;
1921    }
1922
1923    /**
1924     * @param string $p_path
1925     * @param string $p_list_detail
1926     * @param string $p_mode
1927     * @param string $p_file_list
1928     * @param string $p_remove_path
1929     * @param bool $p_preserve
1930     * @param bool $p_symlinks
1931     * @return bool
1932     */
1933    public function _extractList(
1934        $p_path,
1935        &$p_list_detail,
1936        $p_mode,
1937        $p_file_list,
1938        $p_remove_path,
1939        $p_preserve = false,
1940        $p_symlinks = true
1941    )
1942    {
1943        $v_result = true;
1944        $v_nb = 0;
1945        $v_extract_all = true;
1946        $v_listing = false;
1947
1948        $p_path = $this->_translateWinPath($p_path, false);
1949        if ($p_path == '' || (substr($p_path, 0, 1) != '/'
1950                && substr($p_path, 0, 3) != "../" && !strpos($p_path, ':'))
1951        ) {
1952            $p_path = "./" . $p_path;
1953        }
1954        $p_remove_path = $this->_translateWinPath($p_remove_path);
1955
1956        // ----- Look for path to remove format (should end by /)
1957        if (($p_remove_path != '') && (substr($p_remove_path, -1) != '/')) {
1958            $p_remove_path .= '/';
1959        }
1960        $p_remove_path_size = strlen($p_remove_path);
1961
1962        switch ($p_mode) {
1963            case "complete" :
1964                $v_extract_all = true;
1965                $v_listing = false;
1966                break;
1967            case "partial" :
1968                $v_extract_all = false;
1969                $v_listing = false;
1970                break;
1971            case "list" :
1972                $v_extract_all = false;
1973                $v_listing = true;
1974                break;
1975            default :
1976                $this->_error('Invalid extract mode (' . $p_mode . ')');
1977                return false;
1978        }
1979
1980        clearstatcache();
1981
1982        while (strlen($v_binary_data = $this->_readBlock()) != 0) {
1983            $v_extract_file = false;
1984            $v_extraction_stopped = 0;
1985
1986            if (!$this->_readHeader($v_binary_data, $v_header)) {
1987                return false;
1988            }
1989
1990            if ($v_header['filename'] == '') {
1991                continue;
1992            }
1993
1994            switch ($v_header['typeflag']) {
1995                case 'L':
1996                    {
1997                        if (!$this->_readLongHeader($v_header)) {
1998                            return null;
1999                        }
2000                    }
2001                    break;
2002
2003                case 'K':
2004                    {
2005                        $v_link_header = $v_header;
2006                        if (!$this->_readLongHeader($v_link_header)) {
2007                            return null;
2008                        }
2009                        $v_header['link'] = $v_link_header['filename'];
2010                    }
2011                    break;
2012            }
2013
2014            // ignore extended / pax headers
2015            if ($v_header['typeflag'] == 'x' || $v_header['typeflag'] == 'g') {
2016                $this->_jumpBlock(ceil(($v_header['size'] / 512)));
2017                continue;
2018            }
2019
2020            if ((!$v_extract_all) && (is_array($p_file_list))) {
2021                // ----- By default no unzip if the file is not found
2022                $v_extract_file = false;
2023
2024                for ($i = 0; $i < sizeof($p_file_list); $i++) {
2025                    // ----- Look if it is a directory
2026                    if (substr($p_file_list[$i], -1) == '/') {
2027                        // ----- Look if the directory is in the filename path
2028                        if ((strlen($v_header['filename']) > strlen($p_file_list[$i]))
2029                            && (substr($v_header['filename'], 0, strlen($p_file_list[$i]))
2030                                == $p_file_list[$i])
2031                        ) {
2032                            $v_extract_file = true;
2033                            break;
2034                        }
2035                    } // ----- It is a file, so compare the file names
2036                    elseif ($p_file_list[$i] == $v_header['filename']) {
2037                        $v_extract_file = true;
2038                        break;
2039                    }
2040                }
2041            } else {
2042                $v_extract_file = true;
2043            }
2044
2045            // ----- Look if this file need to be extracted
2046            if (($v_extract_file) && (!$v_listing)) {
2047                if (($p_remove_path != '')
2048                    && (substr($v_header['filename'] . '/', 0, $p_remove_path_size)
2049                        == $p_remove_path)
2050                ) {
2051                    $v_header['filename'] = substr(
2052                        $v_header['filename'],
2053                        $p_remove_path_size
2054                    );
2055                    if ($v_header['filename'] == '') {
2056                        continue;
2057                    }
2058                }
2059                if (($p_path != './') && ($p_path != '/')) {
2060                    while (substr($p_path, -1) == '/') {
2061                        $p_path = substr($p_path, 0, strlen($p_path) - 1);
2062                    }
2063
2064                    if (substr($v_header['filename'], 0, 1) == '/') {
2065                        $v_header['filename'] = $p_path . $v_header['filename'];
2066                    } else {
2067                        $v_header['filename'] = $p_path . '/' . $v_header['filename'];
2068                    }
2069                }
2070                if (file_exists($v_header['filename'])) {
2071                    if ((@is_dir($v_header['filename']))
2072                        && ($v_header['typeflag'] == '')
2073                    ) {
2074                        $this->_error(
2075                            'File ' . $v_header['filename']
2076                            . ' already exists as a directory'
2077                        );
2078                        return false;
2079                    }
2080                    if (($this->_isArchive($v_header['filename']))
2081                        && ($v_header['typeflag'] == "5")
2082                    ) {
2083                        $this->_error(
2084                            'Directory ' . $v_header['filename']
2085                            . ' already exists as a file'
2086                        );
2087                        return false;
2088                    }
2089                    if (!is_writeable($v_header['filename'])) {
2090                        $this->_error(
2091                            'File ' . $v_header['filename']
2092                            . ' already exists and is write protected'
2093                        );
2094                        return false;
2095                    }
2096                    if (filemtime($v_header['filename']) > $v_header['mtime']) {
2097                        // To be completed : An error or silent no replace ?
2098                    }
2099                } // ----- Check the directory availability and create it if necessary
2100                elseif (($v_result
2101                        = $this->_dirCheck(
2102                        ($v_header['typeflag'] == "5"
2103                            ? $v_header['filename']
2104                            : dirname($v_header['filename']))
2105                    )) != 1
2106                ) {
2107                    $this->_error('Unable to create path for ' . $v_header['filename']);
2108                    return false;
2109                }
2110
2111                if ($v_extract_file) {
2112                    if ($v_header['typeflag'] == "5") {
2113                        if (!@file_exists($v_header['filename'])) {
2114                            if (!@mkdir($v_header['filename'], 0777)) {
2115                                $this->_error(
2116                                    'Unable to create directory {'
2117                                    . $v_header['filename'] . '}'
2118                                );
2119                                return false;
2120                            }
2121                        }
2122                    } elseif ($v_header['typeflag'] == "2") {
2123                        if (!$p_symlinks) {
2124                            $this->_warning('Symbolic links are not allowed. '
2125                                . 'Unable to extract {'
2126                                . $v_header['filename'] . '}'
2127                            );
2128                            return false;
2129                        }
2130                        if (@file_exists($v_header['filename'])) {
2131                            @unlink($v_header['filename']);
2132                        }
2133                        if (!@symlink($v_header['link'], $v_header['filename'])) {
2134                            $this->_error(
2135                                'Unable to extract symbolic link {'
2136                                . $v_header['filename'] . '}'
2137                            );
2138                            return false;
2139                        }
2140                    } else {
2141                        if (($v_dest_file = @fopen($v_header['filename'], "wb")) == 0) {
2142                            $this->_error(
2143                                'Error while opening {' . $v_header['filename']
2144                                . '} in write binary mode'
2145                            );
2146                            return false;
2147                        } else {
2148                            $n = floor($v_header['size'] / 512);
2149                            for ($i = 0; $i < $n; $i++) {
2150                                $v_content = $this->_readBlock();
2151                                fwrite($v_dest_file, $v_content, 512);
2152                            }
2153                            if (($v_header['size'] % 512) != 0) {
2154                                $v_content = $this->_readBlock();
2155                                fwrite($v_dest_file, $v_content, ($v_header['size'] % 512));
2156                            }
2157
2158                            @fclose($v_dest_file);
2159
2160                            if ($p_preserve) {
2161                                @chown($v_header['filename'], $v_header['uid']);
2162                                @chgrp($v_header['filename'], $v_header['gid']);
2163                            }
2164
2165                            // ----- Change the file mode, mtime
2166                            @touch($v_header['filename'], $v_header['mtime']);
2167                            if ($v_header['mode'] & 0111) {
2168                                // make file executable, obey umask
2169                                $mode = fileperms($v_header['filename']) | (~umask() & 0111);
2170                                @chmod($v_header['filename'], $mode);
2171                            }
2172                        }
2173
2174                        // ----- Check the file size
2175                        clearstatcache();
2176                        if (!is_file($v_header['filename'])) {
2177                            $this->_error(
2178                                'Extracted file ' . $v_header['filename']
2179                                . 'does not exist. Archive may be corrupted.'
2180                            );
2181                            return false;
2182                        }
2183
2184                        $filesize = filesize($v_header['filename']);
2185                        if ($filesize != $v_header['size']) {
2186                            $this->_error(
2187                                'Extracted file ' . $v_header['filename']
2188                                . ' does not have the correct file size \''
2189                                . $filesize
2190                                . '\' (' . $v_header['size']
2191                                . ' expected). Archive may be corrupted.'
2192                            );
2193                            return false;
2194                        }
2195                    }
2196                } else {
2197                    $this->_jumpBlock(ceil(($v_header['size'] / 512)));
2198                }
2199            } else {
2200                $this->_jumpBlock(ceil(($v_header['size'] / 512)));
2201            }
2202
2203            /* TBC : Seems to be unused ...
2204            if ($this->_compress)
2205              $v_end_of_file = @gzeof($this->_file);
2206            else
2207              $v_end_of_file = @feof($this->_file);
2208              */
2209
2210            if ($v_listing || $v_extract_file || $v_extraction_stopped) {
2211                // ----- Log extracted files
2212                if (($v_file_dir = dirname($v_header['filename']))
2213                    == $v_header['filename']
2214                ) {
2215                    $v_file_dir = '';
2216                }
2217                if ((substr($v_header['filename'], 0, 1) == '/') && ($v_file_dir == '')) {
2218                    $v_file_dir = '/';
2219                }
2220
2221                $p_list_detail[$v_nb++] = $v_header;
2222                if (is_array($p_file_list) && (count($p_list_detail) == count($p_file_list))) {
2223                    return true;
2224                }
2225            }
2226        }
2227
2228        return true;
2229    }
2230
2231    /**
2232     * @return bool
2233     */
2234    public function _openAppend()
2235    {
2236        if (filesize($this->_tarname) == 0) {
2237            return $this->_openWrite();
2238        }
2239
2240        if ($this->_compress) {
2241            $this->_close();
2242
2243            if (!@rename($this->_tarname, $this->_tarname . ".tmp")) {
2244                $this->_error(
2245                    'Error while renaming \'' . $this->_tarname
2246                    . '\' to temporary file \'' . $this->_tarname
2247                    . '.tmp\''
2248                );
2249                return false;
2250            }
2251
2252            if ($this->_compress_type == 'gz') {
2253                $v_temp_tar = @gzopen($this->_tarname . ".tmp", "rb");
2254            } elseif ($this->_compress_type == 'bz2') {
2255                $v_temp_tar = @bzopen($this->_tarname . ".tmp", "r");
2256            } elseif ($this->_compress_type == 'lzma2') {
2257                $v_temp_tar = @xzopen($this->_tarname . ".tmp", "r");
2258            }
2259
2260
2261            if ($v_temp_tar == 0) {
2262                $this->_error(
2263                    'Unable to open file \'' . $this->_tarname
2264                    . '.tmp\' in binary read mode'
2265                );
2266                @rename($this->_tarname . ".tmp", $this->_tarname);
2267                return false;
2268            }
2269
2270            if (!$this->_openWrite()) {
2271                @rename($this->_tarname . ".tmp", $this->_tarname);
2272                return false;
2273            }
2274
2275            if ($this->_compress_type == 'gz') {
2276                $end_blocks = 0;
2277
2278                while (!@gzeof($v_temp_tar)) {
2279                    $v_buffer = @gzread($v_temp_tar, 512);
2280                    if ($v_buffer == ARCHIVE_TAR_END_BLOCK || strlen($v_buffer) == 0) {
2281                        $end_blocks++;
2282                        // do not copy end blocks, we will re-make them
2283                        // after appending
2284                        continue;
2285                    } elseif ($end_blocks > 0) {
2286                        for ($i = 0; $i < $end_blocks; $i++) {
2287                            $this->_writeBlock(ARCHIVE_TAR_END_BLOCK);
2288                        }
2289                        $end_blocks = 0;
2290                    }
2291                    $v_binary_data = pack("a512", $v_buffer);
2292                    $this->_writeBlock($v_binary_data);
2293                }
2294
2295                @gzclose($v_temp_tar);
2296            } elseif ($this->_compress_type == 'bz2') {
2297                $end_blocks = 0;
2298
2299                while (strlen($v_buffer = @bzread($v_temp_tar, 512)) > 0) {
2300                    if ($v_buffer == ARCHIVE_TAR_END_BLOCK || strlen($v_buffer) == 0) {
2301                        $end_blocks++;
2302                        // do not copy end blocks, we will re-make them
2303                        // after appending
2304                        continue;
2305                    } elseif ($end_blocks > 0) {
2306                        for ($i = 0; $i < $end_blocks; $i++) {
2307                            $this->_writeBlock(ARCHIVE_TAR_END_BLOCK);
2308                        }
2309                        $end_blocks = 0;
2310                    }
2311                    $v_binary_data = pack("a512", $v_buffer);
2312                    $this->_writeBlock($v_binary_data);
2313                }
2314
2315                @bzclose($v_temp_tar);
2316            } elseif ($this->_compress_type == 'lzma2') {
2317                $end_blocks = 0;
2318
2319                while (strlen($v_buffer = @xzread($v_temp_tar, 512)) > 0) {
2320                    if ($v_buffer == ARCHIVE_TAR_END_BLOCK || strlen($v_buffer) == 0) {
2321                        $end_blocks++;
2322                        // do not copy end blocks, we will re-make them
2323                        // after appending
2324                        continue;
2325                    } elseif ($end_blocks > 0) {
2326                        for ($i = 0; $i < $end_blocks; $i++) {
2327                            $this->_writeBlock(ARCHIVE_TAR_END_BLOCK);
2328                        }
2329                        $end_blocks = 0;
2330                    }
2331                    $v_binary_data = pack("a512", $v_buffer);
2332                    $this->_writeBlock($v_binary_data);
2333                }
2334
2335                @xzclose($v_temp_tar);
2336            }
2337
2338            if (!@unlink($this->_tarname . ".tmp")) {
2339                $this->_error(
2340                    'Error while deleting temporary file \''
2341                    . $this->_tarname . '.tmp\''
2342                );
2343            }
2344        } else {
2345            // ----- For not compressed tar, just add files before the last
2346            //       one or two 512 bytes block
2347            if (!$this->_openReadWrite()) {
2348                return false;
2349            }
2350
2351            clearstatcache();
2352            $v_size = filesize($this->_tarname);
2353
2354            // We might have zero, one or two end blocks.
2355            // The standard is two, but we should try to handle
2356            // other cases.
2357            fseek($this->_file, $v_size - 1024);
2358            if (fread($this->_file, 512) == ARCHIVE_TAR_END_BLOCK) {
2359                fseek($this->_file, $v_size - 1024);
2360            } elseif (fread($this->_file, 512) == ARCHIVE_TAR_END_BLOCK) {
2361                fseek($this->_file, $v_size - 512);
2362            }
2363        }
2364
2365        return true;
2366    }
2367
2368    /**
2369     * @param $p_filelist
2370     * @param string $p_add_dir
2371     * @param string $p_remove_dir
2372     * @return bool
2373     */
2374    public function _append($p_filelist, $p_add_dir = '', $p_remove_dir = '')
2375    {
2376        if (!$this->_openAppend()) {
2377            return false;
2378        }
2379
2380        if ($this->_addList($p_filelist, $p_add_dir, $p_remove_dir)) {
2381            $this->_writeFooter();
2382        }
2383
2384        $this->_close();
2385
2386        return true;
2387    }
2388
2389    /**
2390     * Check if a directory exists and create it (including parent
2391     * dirs) if not.
2392     *
2393     * @param string $p_dir directory to check
2394     *
2395     * @return bool true if the directory exists or was created
2396     */
2397    public function _dirCheck($p_dir)
2398    {
2399        clearstatcache();
2400        if ((@is_dir($p_dir)) || ($p_dir == '')) {
2401            return true;
2402        }
2403
2404        $p_parent_dir = dirname($p_dir);
2405
2406        if (($p_parent_dir != $p_dir) &&
2407            ($p_parent_dir != '') &&
2408            (!$this->_dirCheck($p_parent_dir))
2409        ) {
2410            return false;
2411        }
2412
2413        if (!@mkdir($p_dir, 0777)) {
2414            $this->_error("Unable to create directory '$p_dir'");
2415            return false;
2416        }
2417
2418        return true;
2419    }
2420
2421    /**
2422     * Compress path by changing for example "/dir/foo/../bar" to "/dir/bar",
2423     * rand emove double slashes.
2424     *
2425     * @param string $p_dir path to reduce
2426     *
2427     * @return string reduced path
2428     */
2429    private function _pathReduction($p_dir)
2430    {
2431        $v_result = '';
2432
2433        // ----- Look for not empty path
2434        if ($p_dir != '') {
2435            // ----- Explode path by directory names
2436            $v_list = explode('/', $p_dir);
2437
2438            // ----- Study directories from last to first
2439            for ($i = sizeof($v_list) - 1; $i >= 0; $i--) {
2440                // ----- Look for current path
2441                if ($v_list[$i] == ".") {
2442                    // ----- Ignore this directory
2443                    // Should be the first $i=0, but no check is done
2444                } else {
2445                    if ($v_list[$i] == "..") {
2446                        // ----- Ignore it and ignore the $i-1
2447                        $i--;
2448                    } else {
2449                        if (($v_list[$i] == '')
2450                            && ($i != (sizeof($v_list) - 1))
2451                            && ($i != 0)
2452                        ) {
2453                            // ----- Ignore only the double '//' in path,
2454                            // but not the first and last /
2455                        } else {
2456                            $v_result = $v_list[$i] . ($i != (sizeof($v_list) - 1) ? '/'
2457                                    . $v_result : '');
2458                        }
2459                    }
2460                }
2461            }
2462        }
2463
2464        if (defined('OS_WINDOWS') && OS_WINDOWS) {
2465            $v_result = strtr($v_result, '\\', '/');
2466        }
2467
2468        return $v_result;
2469    }
2470
2471    /**
2472     * @param $p_path
2473     * @param bool $p_remove_disk_letter
2474     * @return string
2475     */
2476    public function _translateWinPath($p_path, $p_remove_disk_letter = true)
2477    {
2478        if (defined('OS_WINDOWS') && OS_WINDOWS) {
2479            // ----- Look for potential disk letter
2480            if (($p_remove_disk_letter)
2481                && (($v_position = strpos($p_path, ':')) != false)
2482            ) {
2483                $p_path = substr($p_path, $v_position + 1);
2484            }
2485            // ----- Change potential windows directory separator
2486            if ((strpos($p_path, '\\') > 0) || (substr($p_path, 0, 1) == '\\')) {
2487                $p_path = strtr($p_path, '\\', '/');
2488            }
2489        }
2490        return $p_path;
2491    }
2492}
2493