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        $v_uname = '';
1401        $v_gname = '';
1402
1403        if (function_exists('posix_getpwuid')) {
1404            $userinfo = posix_getpwuid($v_info[4]);
1405            $groupinfo = posix_getgrgid($v_info[5]);
1406
1407            if (isset($userinfo['name'])) {
1408                $v_uname = $userinfo['name'];
1409            }
1410
1411            if (isset($groupinfo['name'])) {
1412                $v_gname = $groupinfo['name'];
1413            }
1414        }
1415
1416        $v_devmajor = '';
1417        $v_devminor = '';
1418        $v_prefix = '';
1419
1420        $v_binary_data_first = pack(
1421            "a100a8a8a8a12a12",
1422            $v_reduced_filename,
1423            $v_perms,
1424            $v_uid,
1425            $v_gid,
1426            $v_size,
1427            $v_mtime
1428        );
1429        $v_binary_data_last = pack(
1430            "a1a100a6a2a32a32a8a8a155a12",
1431            $v_typeflag,
1432            $v_linkname,
1433            $v_magic,
1434            $v_version,
1435            $v_uname,
1436            $v_gname,
1437            $v_devmajor,
1438            $v_devminor,
1439            $v_prefix,
1440            ''
1441        );
1442
1443        // ----- Calculate the checksum
1444        $v_checksum = 0;
1445        // ..... First part of the header
1446        for ($i = 0; $i < 148; $i++) {
1447            $v_checksum += ord(substr($v_binary_data_first, $i, 1));
1448        }
1449        // ..... Ignore the checksum value and replace it by ' ' (space)
1450        for ($i = 148; $i < 156; $i++) {
1451            $v_checksum += ord(' ');
1452        }
1453        // ..... Last part of the header
1454        for ($i = 156, $j = 0; $i < 512; $i++, $j++) {
1455            $v_checksum += ord(substr($v_binary_data_last, $j, 1));
1456        }
1457
1458        // ----- Write the first 148 bytes of the header in the archive
1459        $this->_writeBlock($v_binary_data_first, 148);
1460
1461        // ----- Write the calculated checksum
1462        $v_checksum = sprintf("%06s\0 ", DecOct($v_checksum));
1463        $v_binary_data = pack("a8", $v_checksum);
1464        $this->_writeBlock($v_binary_data, 8);
1465
1466        // ----- Write the last 356 bytes of the header in the archive
1467        $this->_writeBlock($v_binary_data_last, 356);
1468
1469        return true;
1470    }
1471
1472    /**
1473     * @param string $p_filename
1474     * @param int $p_size
1475     * @param int $p_mtime
1476     * @param int $p_perms
1477     * @param string $p_type
1478     * @param int $p_uid
1479     * @param int $p_gid
1480     * @return bool
1481     */
1482    public function _writeHeaderBlock(
1483        $p_filename,
1484        $p_size,
1485        $p_mtime = 0,
1486        $p_perms = 0,
1487        $p_type = '',
1488        $p_uid = 0,
1489        $p_gid = 0
1490    )
1491    {
1492        $p_filename = $this->_pathReduction($p_filename);
1493
1494        if (strlen($p_filename) > 99) {
1495            if (!$this->_writeLongHeader($p_filename, false)) {
1496                return false;
1497            }
1498        }
1499
1500        if ($p_type == "5") {
1501            $v_size = sprintf("%011s", DecOct(0));
1502        } else {
1503            $v_size = sprintf("%011s", DecOct($p_size));
1504        }
1505
1506        $v_uid = sprintf("%07s", DecOct($p_uid));
1507        $v_gid = sprintf("%07s", DecOct($p_gid));
1508        $v_perms = sprintf("%07s", DecOct($p_perms & 000777));
1509
1510        $v_mtime = sprintf("%11s", DecOct($p_mtime));
1511
1512        $v_linkname = '';
1513
1514        $v_magic = 'ustar ';
1515
1516        $v_version = ' ';
1517
1518        if (function_exists('posix_getpwuid')) {
1519            $userinfo = posix_getpwuid($p_uid);
1520            $groupinfo = posix_getgrgid($p_gid);
1521
1522            if ($userinfo === false || $groupinfo === false) {
1523                $v_uname = '';
1524                $v_gname = '';
1525            } else {
1526                $v_uname = $userinfo['name'];
1527                $v_gname = $groupinfo['name'];
1528            }
1529        } else {
1530            $v_uname = '';
1531            $v_gname = '';
1532        }
1533
1534        $v_devmajor = '';
1535
1536        $v_devminor = '';
1537
1538        $v_prefix = '';
1539
1540        $v_binary_data_first = pack(
1541            "a100a8a8a8a12A12",
1542            $p_filename,
1543            $v_perms,
1544            $v_uid,
1545            $v_gid,
1546            $v_size,
1547            $v_mtime
1548        );
1549        $v_binary_data_last = pack(
1550            "a1a100a6a2a32a32a8a8a155a12",
1551            $p_type,
1552            $v_linkname,
1553            $v_magic,
1554            $v_version,
1555            $v_uname,
1556            $v_gname,
1557            $v_devmajor,
1558            $v_devminor,
1559            $v_prefix,
1560            ''
1561        );
1562
1563        // ----- Calculate the checksum
1564        $v_checksum = 0;
1565        // ..... First part of the header
1566        for ($i = 0; $i < 148; $i++) {
1567            $v_checksum += ord(substr($v_binary_data_first, $i, 1));
1568        }
1569        // ..... Ignore the checksum value and replace it by ' ' (space)
1570        for ($i = 148; $i < 156; $i++) {
1571            $v_checksum += ord(' ');
1572        }
1573        // ..... Last part of the header
1574        for ($i = 156, $j = 0; $i < 512; $i++, $j++) {
1575            $v_checksum += ord(substr($v_binary_data_last, $j, 1));
1576        }
1577
1578        // ----- Write the first 148 bytes of the header in the archive
1579        $this->_writeBlock($v_binary_data_first, 148);
1580
1581        // ----- Write the calculated checksum
1582        $v_checksum = sprintf("%06s ", DecOct($v_checksum));
1583        $v_binary_data = pack("a8", $v_checksum);
1584        $this->_writeBlock($v_binary_data, 8);
1585
1586        // ----- Write the last 356 bytes of the header in the archive
1587        $this->_writeBlock($v_binary_data_last, 356);
1588
1589        return true;
1590    }
1591
1592    /**
1593     * @param string $p_filename
1594     * @return bool
1595     */
1596    public function _writeLongHeader($p_filename, $is_link = false)
1597    {
1598        $v_uid = sprintf("%07s", 0);
1599        $v_gid = sprintf("%07s", 0);
1600        $v_perms = sprintf("%07s", 0);
1601        $v_size = sprintf("%'011s", DecOct(strlen($p_filename)));
1602        $v_mtime = sprintf("%011s", 0);
1603        $v_typeflag = ($is_link ? 'K' : 'L');
1604        $v_linkname = '';
1605        $v_magic = 'ustar ';
1606        $v_version = ' ';
1607        $v_uname = '';
1608        $v_gname = '';
1609        $v_devmajor = '';
1610        $v_devminor = '';
1611        $v_prefix = '';
1612
1613        $v_binary_data_first = pack(
1614            "a100a8a8a8a12a12",
1615            '././@LongLink',
1616            $v_perms,
1617            $v_uid,
1618            $v_gid,
1619            $v_size,
1620            $v_mtime
1621        );
1622        $v_binary_data_last = pack(
1623            "a1a100a6a2a32a32a8a8a155a12",
1624            $v_typeflag,
1625            $v_linkname,
1626            $v_magic,
1627            $v_version,
1628            $v_uname,
1629            $v_gname,
1630            $v_devmajor,
1631            $v_devminor,
1632            $v_prefix,
1633            ''
1634        );
1635
1636        // ----- Calculate the checksum
1637        $v_checksum = 0;
1638        // ..... First part of the header
1639        for ($i = 0; $i < 148; $i++) {
1640            $v_checksum += ord(substr($v_binary_data_first, $i, 1));
1641        }
1642        // ..... Ignore the checksum value and replace it by ' ' (space)
1643        for ($i = 148; $i < 156; $i++) {
1644            $v_checksum += ord(' ');
1645        }
1646        // ..... Last part of the header
1647        for ($i = 156, $j = 0; $i < 512; $i++, $j++) {
1648            $v_checksum += ord(substr($v_binary_data_last, $j, 1));
1649        }
1650
1651        // ----- Write the first 148 bytes of the header in the archive
1652        $this->_writeBlock($v_binary_data_first, 148);
1653
1654        // ----- Write the calculated checksum
1655        $v_checksum = sprintf("%06s\0 ", DecOct($v_checksum));
1656        $v_binary_data = pack("a8", $v_checksum);
1657        $this->_writeBlock($v_binary_data, 8);
1658
1659        // ----- Write the last 356 bytes of the header in the archive
1660        $this->_writeBlock($v_binary_data_last, 356);
1661
1662        // ----- Write the filename as content of the block
1663        $i = 0;
1664        while (($v_buffer = substr($p_filename, (($i++) * 512), 512)) != '') {
1665            $v_binary_data = pack("a512", "$v_buffer");
1666            $this->_writeBlock($v_binary_data);
1667        }
1668
1669        return true;
1670    }
1671
1672    /**
1673     * @param mixed $v_binary_data
1674     * @param mixed $v_header
1675     * @return bool
1676     */
1677    public function _readHeader($v_binary_data, &$v_header)
1678    {
1679        if (strlen($v_binary_data) == 0) {
1680            $v_header['filename'] = '';
1681            return true;
1682        }
1683
1684        if (strlen($v_binary_data) != 512) {
1685            $v_header['filename'] = '';
1686            $this->_error('Invalid block size : ' . strlen($v_binary_data));
1687            return false;
1688        }
1689
1690        if (!is_array($v_header)) {
1691            $v_header = array();
1692        }
1693        // ----- Calculate the checksum
1694        $v_checksum = 0;
1695        // ..... First part of the header
1696        $v_binary_split = str_split($v_binary_data);
1697        $v_checksum += array_sum(array_map('ord', array_slice($v_binary_split, 0, 148)));
1698        $v_checksum += array_sum(array_map('ord', array(' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ',)));
1699        $v_checksum += array_sum(array_map('ord', array_slice($v_binary_split, 156, 512)));
1700
1701
1702        $v_data = unpack($this->_fmt, $v_binary_data);
1703
1704        if (strlen($v_data["prefix"]) > 0) {
1705            $v_data["filename"] = "$v_data[prefix]/$v_data[filename]";
1706        }
1707
1708        // ----- Extract the checksum
1709        $v_data_checksum = trim($v_data['checksum']);
1710        if (!preg_match('/^[0-7]*$/', $v_data_checksum)) {
1711            $this->_error(
1712                'Invalid checksum for file "' . $v_data['filename']
1713                . '" : ' . $v_data_checksum . ' extracted'
1714            );
1715            return false;
1716        }
1717
1718        $v_header['checksum'] = OctDec($v_data_checksum);
1719        if ($v_header['checksum'] != $v_checksum) {
1720            $v_header['filename'] = '';
1721
1722            // ----- Look for last block (empty block)
1723            if (($v_checksum == 256) && ($v_header['checksum'] == 0)) {
1724                return true;
1725            }
1726
1727            $this->_error(
1728                'Invalid checksum for file "' . $v_data['filename']
1729                . '" : ' . $v_checksum . ' calculated, '
1730                . $v_header['checksum'] . ' expected'
1731            );
1732            return false;
1733        }
1734
1735        // ----- Extract the properties
1736        $v_header['filename'] = rtrim($v_data['filename'], "\0");
1737        if ($this->_isMaliciousFilename($v_header['filename'])) {
1738            $this->_error(
1739                'Malicious .tar detected, file "' . $v_header['filename'] .
1740                '" will not install in desired directory tree'
1741            );
1742            return false;
1743        }
1744        $v_header['mode'] = OctDec(trim($v_data['mode']));
1745        $v_header['uid'] = OctDec(trim($v_data['uid']));
1746        $v_header['gid'] = OctDec(trim($v_data['gid']));
1747        $v_header['size'] = $this->_tarRecToSize($v_data['size']);
1748        $v_header['mtime'] = OctDec(trim($v_data['mtime']));
1749        if (($v_header['typeflag'] = $v_data['typeflag']) == "5") {
1750            $v_header['size'] = 0;
1751        }
1752        $v_header['link'] = trim($v_data['link']);
1753        /* ----- All these fields are removed form the header because
1754        they do not carry interesting info
1755        $v_header[magic] = trim($v_data[magic]);
1756        $v_header[version] = trim($v_data[version]);
1757        $v_header[uname] = trim($v_data[uname]);
1758        $v_header[gname] = trim($v_data[gname]);
1759        $v_header[devmajor] = trim($v_data[devmajor]);
1760        $v_header[devminor] = trim($v_data[devminor]);
1761        */
1762
1763        return true;
1764    }
1765
1766    /**
1767     * Convert Tar record size to actual size
1768     *
1769     * @param string $tar_size
1770     * @return size of tar record in bytes
1771     */
1772    private function _tarRecToSize($tar_size)
1773    {
1774        /*
1775         * First byte of size has a special meaning if bit 7 is set.
1776         *
1777         * Bit 7 indicates base-256 encoding if set.
1778         * Bit 6 is the sign bit.
1779         * Bits 5:0 are most significant value bits.
1780         */
1781        $ch = ord($tar_size[0]);
1782        if ($ch & 0x80) {
1783            // Full 12-bytes record is required.
1784            $rec_str = $tar_size . "\x00";
1785
1786            $size = ($ch & 0x40) ? -1 : 0;
1787            $size = ($size << 6) | ($ch & 0x3f);
1788
1789            for ($num_ch = 1; $num_ch < 12; ++$num_ch) {
1790                $size = ($size * 256) + ord($rec_str[$num_ch]);
1791            }
1792
1793            return $size;
1794
1795        } else {
1796            return OctDec(trim($tar_size));
1797        }
1798    }
1799
1800    /**
1801     * Detect and report a malicious file name
1802     *
1803     * @param string $file
1804     *
1805     * @return bool
1806     */
1807    private function _isMaliciousFilename($file)
1808    {
1809        if (strpos($file, '://') !== false) {
1810            return true;
1811        }
1812        if (strpos($file, '../') !== false || strpos($file, '..\\') !== false) {
1813            return true;
1814        }
1815        return false;
1816    }
1817
1818    /**
1819     * @param $v_header
1820     * @return bool
1821     */
1822    public function _readLongHeader(&$v_header)
1823    {
1824        $v_filename = '';
1825        $v_filesize = $v_header['size'];
1826        $n = floor($v_header['size'] / 512);
1827        for ($i = 0; $i < $n; $i++) {
1828            $v_content = $this->_readBlock();
1829            $v_filename .= $v_content;
1830        }
1831        if (($v_header['size'] % 512) != 0) {
1832            $v_content = $this->_readBlock();
1833            $v_filename .= $v_content;
1834        }
1835
1836        // ----- Read the next header
1837        $v_binary_data = $this->_readBlock();
1838
1839        if (!$this->_readHeader($v_binary_data, $v_header)) {
1840            return false;
1841        }
1842
1843        $v_filename = rtrim(substr($v_filename, 0, $v_filesize), "\0");
1844        $v_header['filename'] = $v_filename;
1845        if ($this->_isMaliciousFilename($v_filename)) {
1846            $this->_error(
1847                'Malicious .tar detected, file "' . $v_filename .
1848                '" will not install in desired directory tree'
1849            );
1850            return false;
1851        }
1852
1853        return true;
1854    }
1855
1856    /**
1857     * This method extract from the archive one file identified by $p_filename.
1858     * The return value is a string with the file content, or null on error.
1859     *
1860     * @param string $p_filename The path of the file to extract in a string.
1861     *
1862     * @return a string with the file content or null.
1863     */
1864    private function _extractInString($p_filename)
1865    {
1866        $v_result_str = "";
1867
1868        while (strlen($v_binary_data = $this->_readBlock()) != 0) {
1869            if (!$this->_readHeader($v_binary_data, $v_header)) {
1870                return null;
1871            }
1872
1873            if ($v_header['filename'] == '') {
1874                continue;
1875            }
1876
1877            switch ($v_header['typeflag']) {
1878                case 'L':
1879                    {
1880                        if (!$this->_readLongHeader($v_header)) {
1881                            return null;
1882                        }
1883                    }
1884                    break;
1885
1886                case 'K':
1887                    {
1888                        $v_link_header = $v_header;
1889                        if (!$this->_readLongHeader($v_link_header)) {
1890                            return null;
1891                        }
1892                        $v_header['link'] = $v_link_header['filename'];
1893                    }
1894                    break;
1895            }
1896
1897            if ($v_header['filename'] == $p_filename) {
1898                if ($v_header['typeflag'] == "5") {
1899                    $this->_error(
1900                        'Unable to extract in string a directory '
1901                        . 'entry {' . $v_header['filename'] . '}'
1902                    );
1903                    return null;
1904                } else {
1905                    $n = floor($v_header['size'] / 512);
1906                    for ($i = 0; $i < $n; $i++) {
1907                        $v_result_str .= $this->_readBlock();
1908                    }
1909                    if (($v_header['size'] % 512) != 0) {
1910                        $v_content = $this->_readBlock();
1911                        $v_result_str .= substr(
1912                            $v_content,
1913                            0,
1914                            ($v_header['size'] % 512)
1915                        );
1916                    }
1917                    return $v_result_str;
1918                }
1919            } else {
1920                $this->_jumpBlock(ceil(($v_header['size'] / 512)));
1921            }
1922        }
1923
1924        return null;
1925    }
1926
1927    /**
1928     * @param string $p_path
1929     * @param string $p_list_detail
1930     * @param string $p_mode
1931     * @param string $p_file_list
1932     * @param string $p_remove_path
1933     * @param bool $p_preserve
1934     * @param bool $p_symlinks
1935     * @return bool
1936     */
1937    public function _extractList(
1938        $p_path,
1939        &$p_list_detail,
1940        $p_mode,
1941        $p_file_list,
1942        $p_remove_path,
1943        $p_preserve = false,
1944        $p_symlinks = true
1945    )
1946    {
1947        $v_result = true;
1948        $v_nb = 0;
1949        $v_extract_all = true;
1950        $v_listing = false;
1951
1952        $p_path = $this->_translateWinPath($p_path, false);
1953        if ($p_path == '' || (substr($p_path, 0, 1) != '/'
1954                && substr($p_path, 0, 3) != "../" && !strpos($p_path, ':'))
1955        ) {
1956            $p_path = "./" . $p_path;
1957        }
1958        $p_remove_path = $this->_translateWinPath($p_remove_path);
1959
1960        // ----- Look for path to remove format (should end by /)
1961        if (($p_remove_path != '') && (substr($p_remove_path, -1) != '/')) {
1962            $p_remove_path .= '/';
1963        }
1964        $p_remove_path_size = strlen($p_remove_path);
1965
1966        switch ($p_mode) {
1967            case "complete" :
1968                $v_extract_all = true;
1969                $v_listing = false;
1970                break;
1971            case "partial" :
1972                $v_extract_all = false;
1973                $v_listing = false;
1974                break;
1975            case "list" :
1976                $v_extract_all = false;
1977                $v_listing = true;
1978                break;
1979            default :
1980                $this->_error('Invalid extract mode (' . $p_mode . ')');
1981                return false;
1982        }
1983
1984        clearstatcache();
1985
1986        while (strlen($v_binary_data = $this->_readBlock()) != 0) {
1987            $v_extract_file = false;
1988            $v_extraction_stopped = 0;
1989
1990            if (!$this->_readHeader($v_binary_data, $v_header)) {
1991                return false;
1992            }
1993
1994            if ($v_header['filename'] == '') {
1995                continue;
1996            }
1997
1998            switch ($v_header['typeflag']) {
1999                case 'L':
2000                    {
2001                        if (!$this->_readLongHeader($v_header)) {
2002                            return null;
2003                        }
2004                    }
2005                    break;
2006
2007                case 'K':
2008                    {
2009                        $v_link_header = $v_header;
2010                        if (!$this->_readLongHeader($v_link_header)) {
2011                            return null;
2012                        }
2013                        $v_header['link'] = $v_link_header['filename'];
2014                    }
2015                    break;
2016            }
2017
2018            // ignore extended / pax headers
2019            if ($v_header['typeflag'] == 'x' || $v_header['typeflag'] == 'g') {
2020                $this->_jumpBlock(ceil(($v_header['size'] / 512)));
2021                continue;
2022            }
2023
2024            if ((!$v_extract_all) && (is_array($p_file_list))) {
2025                // ----- By default no unzip if the file is not found
2026                $v_extract_file = false;
2027
2028                for ($i = 0; $i < sizeof($p_file_list); $i++) {
2029                    // ----- Look if it is a directory
2030                    if (substr($p_file_list[$i], -1) == '/') {
2031                        // ----- Look if the directory is in the filename path
2032                        if ((strlen($v_header['filename']) > strlen($p_file_list[$i]))
2033                            && (substr($v_header['filename'], 0, strlen($p_file_list[$i]))
2034                                == $p_file_list[$i])
2035                        ) {
2036                            $v_extract_file = true;
2037                            break;
2038                        }
2039                    } // ----- It is a file, so compare the file names
2040                    elseif ($p_file_list[$i] == $v_header['filename']) {
2041                        $v_extract_file = true;
2042                        break;
2043                    }
2044                }
2045            } else {
2046                $v_extract_file = true;
2047            }
2048
2049            // ----- Look if this file need to be extracted
2050            if (($v_extract_file) && (!$v_listing)) {
2051                if (($p_remove_path != '')
2052                    && (substr($v_header['filename'] . '/', 0, $p_remove_path_size)
2053                        == $p_remove_path)
2054                ) {
2055                    $v_header['filename'] = substr(
2056                        $v_header['filename'],
2057                        $p_remove_path_size
2058                    );
2059                    if ($v_header['filename'] == '') {
2060                        continue;
2061                    }
2062                }
2063                if (($p_path != './') && ($p_path != '/')) {
2064                    while (substr($p_path, -1) == '/') {
2065                        $p_path = substr($p_path, 0, strlen($p_path) - 1);
2066                    }
2067
2068                    if (substr($v_header['filename'], 0, 1) == '/') {
2069                        $v_header['filename'] = $p_path . $v_header['filename'];
2070                    } else {
2071                        $v_header['filename'] = $p_path . '/' . $v_header['filename'];
2072                    }
2073                }
2074                if (file_exists($v_header['filename'])) {
2075                    if ((@is_dir($v_header['filename']))
2076                        && ($v_header['typeflag'] == '')
2077                    ) {
2078                        $this->_error(
2079                            'File ' . $v_header['filename']
2080                            . ' already exists as a directory'
2081                        );
2082                        return false;
2083                    }
2084                    if (($this->_isArchive($v_header['filename']))
2085                        && ($v_header['typeflag'] == "5")
2086                    ) {
2087                        $this->_error(
2088                            'Directory ' . $v_header['filename']
2089                            . ' already exists as a file'
2090                        );
2091                        return false;
2092                    }
2093                    if (!is_writeable($v_header['filename'])) {
2094                        $this->_error(
2095                            'File ' . $v_header['filename']
2096                            . ' already exists and is write protected'
2097                        );
2098                        return false;
2099                    }
2100                    if (filemtime($v_header['filename']) > $v_header['mtime']) {
2101                        // To be completed : An error or silent no replace ?
2102                    }
2103                } // ----- Check the directory availability and create it if necessary
2104                elseif (($v_result
2105                        = $this->_dirCheck(
2106                        ($v_header['typeflag'] == "5"
2107                            ? $v_header['filename']
2108                            : dirname($v_header['filename']))
2109                    )) != 1
2110                ) {
2111                    $this->_error('Unable to create path for ' . $v_header['filename']);
2112                    return false;
2113                }
2114
2115                if ($v_extract_file) {
2116                    if ($v_header['typeflag'] == "5") {
2117                        if (!@file_exists($v_header['filename'])) {
2118                            if (!@mkdir($v_header['filename'], 0777)) {
2119                                $this->_error(
2120                                    'Unable to create directory {'
2121                                    . $v_header['filename'] . '}'
2122                                );
2123                                return false;
2124                            }
2125                        }
2126                    } elseif ($v_header['typeflag'] == "2") {
2127                        if (strpos(realpath(dirname($v_header['link'])), realpath($p_path)) !== 0) {
2128                            $this->_error(
2129                                 'Out-of-path file extraction {'
2130                                 . $v_header['filename'] . ' --> ' .
2131                                 $v_header['link'] . '}'
2132                            );
2133                            return false;
2134                        }
2135                        if (!$p_symlinks) {
2136                            $this->_warning('Symbolic links are not allowed. '
2137                                . 'Unable to extract {'
2138                                . $v_header['filename'] . '}'
2139                            );
2140                            return false;
2141                        }
2142                        if (@file_exists($v_header['filename'])) {
2143                            @unlink($v_header['filename']);
2144                        }
2145                        if (!@symlink($v_header['link'], $v_header['filename'])) {
2146                            $this->_error(
2147                                'Unable to extract symbolic link {'
2148                                . $v_header['filename'] . '}'
2149                            );
2150                            return false;
2151                        }
2152                    } else {
2153                        if (($v_dest_file = @fopen($v_header['filename'], "wb")) == 0) {
2154                            $this->_error(
2155                                'Error while opening {' . $v_header['filename']
2156                                . '} in write binary mode'
2157                            );
2158                            return false;
2159                        } else {
2160                            $n = floor($v_header['size'] / 512);
2161                            for ($i = 0; $i < $n; $i++) {
2162                                $v_content = $this->_readBlock();
2163                                fwrite($v_dest_file, $v_content, 512);
2164                            }
2165                            if (($v_header['size'] % 512) != 0) {
2166                                $v_content = $this->_readBlock();
2167                                fwrite($v_dest_file, $v_content, ($v_header['size'] % 512));
2168                            }
2169
2170                            @fclose($v_dest_file);
2171
2172                            if ($p_preserve) {
2173                                @chown($v_header['filename'], $v_header['uid']);
2174                                @chgrp($v_header['filename'], $v_header['gid']);
2175                            }
2176
2177                            // ----- Change the file mode, mtime
2178                            @touch($v_header['filename'], $v_header['mtime']);
2179                            if ($v_header['mode'] & 0111) {
2180                                // make file executable, obey umask
2181                                $mode = fileperms($v_header['filename']) | (~umask() & 0111);
2182                                @chmod($v_header['filename'], $mode);
2183                            }
2184                        }
2185
2186                        // ----- Check the file size
2187                        clearstatcache();
2188                        if (!is_file($v_header['filename'])) {
2189                            $this->_error(
2190                                'Extracted file ' . $v_header['filename']
2191                                . 'does not exist. Archive may be corrupted.'
2192                            );
2193                            return false;
2194                        }
2195
2196                        $filesize = filesize($v_header['filename']);
2197                        if ($filesize != $v_header['size']) {
2198                            $this->_error(
2199                                'Extracted file ' . $v_header['filename']
2200                                . ' does not have the correct file size \''
2201                                . $filesize
2202                                . '\' (' . $v_header['size']
2203                                . ' expected). Archive may be corrupted.'
2204                            );
2205                            return false;
2206                        }
2207                    }
2208                } else {
2209                    $this->_jumpBlock(ceil(($v_header['size'] / 512)));
2210                }
2211            } else {
2212                $this->_jumpBlock(ceil(($v_header['size'] / 512)));
2213            }
2214
2215            /* TBC : Seems to be unused ...
2216            if ($this->_compress)
2217              $v_end_of_file = @gzeof($this->_file);
2218            else
2219              $v_end_of_file = @feof($this->_file);
2220              */
2221
2222            if ($v_listing || $v_extract_file || $v_extraction_stopped) {
2223                // ----- Log extracted files
2224                if (($v_file_dir = dirname($v_header['filename']))
2225                    == $v_header['filename']
2226                ) {
2227                    $v_file_dir = '';
2228                }
2229                if ((substr($v_header['filename'], 0, 1) == '/') && ($v_file_dir == '')) {
2230                    $v_file_dir = '/';
2231                }
2232
2233                $p_list_detail[$v_nb++] = $v_header;
2234                if (is_array($p_file_list) && (count($p_list_detail) == count($p_file_list))) {
2235                    return true;
2236                }
2237            }
2238        }
2239
2240        return true;
2241    }
2242
2243    /**
2244     * @return bool
2245     */
2246    public function _openAppend()
2247    {
2248        if (filesize($this->_tarname) == 0) {
2249            return $this->_openWrite();
2250        }
2251
2252        if ($this->_compress) {
2253            $this->_close();
2254
2255            if (!@rename($this->_tarname, $this->_tarname . ".tmp")) {
2256                $this->_error(
2257                    'Error while renaming \'' . $this->_tarname
2258                    . '\' to temporary file \'' . $this->_tarname
2259                    . '.tmp\''
2260                );
2261                return false;
2262            }
2263
2264            if ($this->_compress_type == 'gz') {
2265                $v_temp_tar = @gzopen($this->_tarname . ".tmp", "rb");
2266            } elseif ($this->_compress_type == 'bz2') {
2267                $v_temp_tar = @bzopen($this->_tarname . ".tmp", "r");
2268            } elseif ($this->_compress_type == 'lzma2') {
2269                $v_temp_tar = @xzopen($this->_tarname . ".tmp", "r");
2270            }
2271
2272
2273            if ($v_temp_tar == 0) {
2274                $this->_error(
2275                    'Unable to open file \'' . $this->_tarname
2276                    . '.tmp\' in binary read mode'
2277                );
2278                @rename($this->_tarname . ".tmp", $this->_tarname);
2279                return false;
2280            }
2281
2282            if (!$this->_openWrite()) {
2283                @rename($this->_tarname . ".tmp", $this->_tarname);
2284                return false;
2285            }
2286
2287            if ($this->_compress_type == 'gz') {
2288                $end_blocks = 0;
2289
2290                while (!@gzeof($v_temp_tar)) {
2291                    $v_buffer = @gzread($v_temp_tar, 512);
2292                    if ($v_buffer == ARCHIVE_TAR_END_BLOCK || strlen($v_buffer) == 0) {
2293                        $end_blocks++;
2294                        // do not copy end blocks, we will re-make them
2295                        // after appending
2296                        continue;
2297                    } elseif ($end_blocks > 0) {
2298                        for ($i = 0; $i < $end_blocks; $i++) {
2299                            $this->_writeBlock(ARCHIVE_TAR_END_BLOCK);
2300                        }
2301                        $end_blocks = 0;
2302                    }
2303                    $v_binary_data = pack("a512", $v_buffer);
2304                    $this->_writeBlock($v_binary_data);
2305                }
2306
2307                @gzclose($v_temp_tar);
2308            } elseif ($this->_compress_type == 'bz2') {
2309                $end_blocks = 0;
2310
2311                while (strlen($v_buffer = @bzread($v_temp_tar, 512)) > 0) {
2312                    if ($v_buffer == ARCHIVE_TAR_END_BLOCK || strlen($v_buffer) == 0) {
2313                        $end_blocks++;
2314                        // do not copy end blocks, we will re-make them
2315                        // after appending
2316                        continue;
2317                    } elseif ($end_blocks > 0) {
2318                        for ($i = 0; $i < $end_blocks; $i++) {
2319                            $this->_writeBlock(ARCHIVE_TAR_END_BLOCK);
2320                        }
2321                        $end_blocks = 0;
2322                    }
2323                    $v_binary_data = pack("a512", $v_buffer);
2324                    $this->_writeBlock($v_binary_data);
2325                }
2326
2327                @bzclose($v_temp_tar);
2328            } elseif ($this->_compress_type == 'lzma2') {
2329                $end_blocks = 0;
2330
2331                while (strlen($v_buffer = @xzread($v_temp_tar, 512)) > 0) {
2332                    if ($v_buffer == ARCHIVE_TAR_END_BLOCK || strlen($v_buffer) == 0) {
2333                        $end_blocks++;
2334                        // do not copy end blocks, we will re-make them
2335                        // after appending
2336                        continue;
2337                    } elseif ($end_blocks > 0) {
2338                        for ($i = 0; $i < $end_blocks; $i++) {
2339                            $this->_writeBlock(ARCHIVE_TAR_END_BLOCK);
2340                        }
2341                        $end_blocks = 0;
2342                    }
2343                    $v_binary_data = pack("a512", $v_buffer);
2344                    $this->_writeBlock($v_binary_data);
2345                }
2346
2347                @xzclose($v_temp_tar);
2348            }
2349
2350            if (!@unlink($this->_tarname . ".tmp")) {
2351                $this->_error(
2352                    'Error while deleting temporary file \''
2353                    . $this->_tarname . '.tmp\''
2354                );
2355            }
2356        } else {
2357            // ----- For not compressed tar, just add files before the last
2358            //       one or two 512 bytes block
2359            if (!$this->_openReadWrite()) {
2360                return false;
2361            }
2362
2363            clearstatcache();
2364            $v_size = filesize($this->_tarname);
2365
2366            // We might have zero, one or two end blocks.
2367            // The standard is two, but we should try to handle
2368            // other cases.
2369            fseek($this->_file, $v_size - 1024);
2370            if (fread($this->_file, 512) == ARCHIVE_TAR_END_BLOCK) {
2371                fseek($this->_file, $v_size - 1024);
2372            } elseif (fread($this->_file, 512) == ARCHIVE_TAR_END_BLOCK) {
2373                fseek($this->_file, $v_size - 512);
2374            }
2375        }
2376
2377        return true;
2378    }
2379
2380    /**
2381     * @param $p_filelist
2382     * @param string $p_add_dir
2383     * @param string $p_remove_dir
2384     * @return bool
2385     */
2386    public function _append($p_filelist, $p_add_dir = '', $p_remove_dir = '')
2387    {
2388        if (!$this->_openAppend()) {
2389            return false;
2390        }
2391
2392        if ($this->_addList($p_filelist, $p_add_dir, $p_remove_dir)) {
2393            $this->_writeFooter();
2394        }
2395
2396        $this->_close();
2397
2398        return true;
2399    }
2400
2401    /**
2402     * Check if a directory exists and create it (including parent
2403     * dirs) if not.
2404     *
2405     * @param string $p_dir directory to check
2406     *
2407     * @return bool true if the directory exists or was created
2408     */
2409    public function _dirCheck($p_dir)
2410    {
2411        clearstatcache();
2412        if ((@is_dir($p_dir)) || ($p_dir == '')) {
2413            return true;
2414        }
2415
2416        $p_parent_dir = dirname($p_dir);
2417
2418        if (($p_parent_dir != $p_dir) &&
2419            ($p_parent_dir != '') &&
2420            (!$this->_dirCheck($p_parent_dir))
2421        ) {
2422            return false;
2423        }
2424
2425        if (!@mkdir($p_dir, 0777)) {
2426            $this->_error("Unable to create directory '$p_dir'");
2427            return false;
2428        }
2429
2430        return true;
2431    }
2432
2433    /**
2434     * Compress path by changing for example "/dir/foo/../bar" to "/dir/bar",
2435     * rand emove double slashes.
2436     *
2437     * @param string $p_dir path to reduce
2438     *
2439     * @return string reduced path
2440     */
2441    private function _pathReduction($p_dir)
2442    {
2443        $v_result = '';
2444
2445        // ----- Look for not empty path
2446        if ($p_dir != '') {
2447            // ----- Explode path by directory names
2448            $v_list = explode('/', $p_dir);
2449
2450            // ----- Study directories from last to first
2451            for ($i = sizeof($v_list) - 1; $i >= 0; $i--) {
2452                // ----- Look for current path
2453                if ($v_list[$i] == ".") {
2454                    // ----- Ignore this directory
2455                    // Should be the first $i=0, but no check is done
2456                } else {
2457                    if ($v_list[$i] == "..") {
2458                        // ----- Ignore it and ignore the $i-1
2459                        $i--;
2460                    } else {
2461                        if (($v_list[$i] == '')
2462                            && ($i != (sizeof($v_list) - 1))
2463                            && ($i != 0)
2464                        ) {
2465                            // ----- Ignore only the double '//' in path,
2466                            // but not the first and last /
2467                        } else {
2468                            $v_result = $v_list[$i] . ($i != (sizeof($v_list) - 1) ? '/'
2469                                    . $v_result : '');
2470                        }
2471                    }
2472                }
2473            }
2474        }
2475
2476        if (defined('OS_WINDOWS') && OS_WINDOWS) {
2477            $v_result = strtr($v_result, '\\', '/');
2478        }
2479
2480        return $v_result;
2481    }
2482
2483    /**
2484     * @param $p_path
2485     * @param bool $p_remove_disk_letter
2486     * @return string
2487     */
2488    public function _translateWinPath($p_path, $p_remove_disk_letter = true)
2489    {
2490        if (defined('OS_WINDOWS') && OS_WINDOWS) {
2491            // ----- Look for potential disk letter
2492            if (($p_remove_disk_letter)
2493                && (($v_position = strpos($p_path, ':')) != false)
2494            ) {
2495                $p_path = substr($p_path, $v_position + 1);
2496            }
2497            // ----- Change potential windows directory separator
2498            if ((strpos($p_path, '\\') > 0) || (substr($p_path, 0, 1) == '\\')) {
2499                $p_path = strtr($p_path, '\\', '/');
2500            }
2501        }
2502        return $p_path;
2503    }
2504}
2505