source: branches/version-2_11-dev/data/module/Tar.php @ 21018

Revision 21018, 62.9 KB checked in by eccuore, 10 years ago (diff)

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