source: branches/version-2_13-dev/data/module/Archive/Tar.php @ 22964

Revision 22964, 65.4 KB checked in by kimoto, 8 years ago (diff)

#2275 PHP5.5対応
Archive_Tar を最新版に更新

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