source: branches/version-2_12-dev/data/module/Archive/Tar.php @ 21495

Revision 21495, 63.6 KB checked in by Seasoft, 12 years ago (diff)

#1639 (PEAR::Archive_Tar 配置が誤っている)
#1640 (PEAR::Archive_Tar アップデート)

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