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