]> source.dussan.org Git - nextcloud-server.git/commitdiff
add tar backend for OC_Archive
authorRobin Appelman <icewind@owncloud.com>
Wed, 28 Mar 2012 20:32:51 +0000 (22:32 +0200)
committerRobin Appelman <icewind@owncloud.com>
Wed, 28 Mar 2012 21:47:44 +0000 (23:47 +0200)
also handles bz2/gz compression

3rdparty/Archive/Tar.php [new file with mode: 0644]
apps/files_archive/appinfo/app.php
apps/files_archive/lib/tar.php [new file with mode: 0644]
apps/files_archive/tests/tar.php [new file with mode: 0644]

diff --git a/3rdparty/Archive/Tar.php b/3rdparty/Archive/Tar.php
new file mode 100644 (file)
index 0000000..d69bab6
--- /dev/null
@@ -0,0 +1,1954 @@
+<?php
+/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
+
+/**
+ * File::CSV
+ *
+ * PHP versions 4 and 5
+ *
+ * Copyright (c) 1997-2008,
+ * Vincent Blavet <vincent@phpconcept.net>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *     * Redistributions of source code must retain the above copyright notice,
+ *       this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above copyright
+ *       notice, this list of conditions and the following disclaimer in the
+ *       documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * @category  File_Formats
+ * @package   Archive_Tar
+ * @author    Vincent Blavet <vincent@phpconcept.net>
+ * @copyright 1997-2010 The Authors
+ * @license   http://www.opensource.org/licenses/bsd-license.php New BSD License
+ * @version   CVS: $Id: Tar.php 323476 2012-02-24 15:27:26Z mrook $
+ * @link      http://pear.php.net/package/Archive_Tar
+ */
+
+require_once 'PEAR.php';
+
+define('ARCHIVE_TAR_ATT_SEPARATOR', 90001);
+define('ARCHIVE_TAR_END_BLOCK', pack("a512", ''));
+
+/**
+* Creates a (compressed) Tar archive
+*
+* @package Archive_Tar
+* @author  Vincent Blavet <vincent@phpconcept.net>
+* @license http://www.opensource.org/licenses/bsd-license.php New BSD License
+* @version $Revision: 323476 $
+*/
+class Archive_Tar extends PEAR
+{
+    /**
+    * @var string Name of the Tar
+    */
+    var $_tarname='';
+
+    /**
+    * @var boolean if true, the Tar file will be gzipped
+    */
+    var $_compress=false;
+
+    /**
+    * @var string Type of compression : 'none', 'gz' or 'bz2'
+    */
+    var $_compress_type='none';
+
+    /**
+    * @var string Explode separator
+    */
+    var $_separator=' ';
+
+    /**
+    * @var file descriptor
+    */
+    var $_file=0;
+
+    /**
+    * @var string Local Tar name of a remote Tar (http:// or ftp://)
+    */
+    var $_temp_tarname='';
+
+    /**
+    * @var string regular expression for ignoring files or directories
+    */
+    var $_ignore_regexp='';
+
+    /**
+     * @var object PEAR_Error object
+     */
+    var $error_object=null; 
+
+    // {{{ constructor
+    /**
+    * Archive_Tar Class constructor. This flavour of the constructor only
+    * declare a new Archive_Tar object, identifying it by the name of the
+    * tar file.
+    * If the compress argument is set the tar will be read or created as a
+    * gzip or bz2 compressed TAR file.
+    *
+    * @param string $p_tarname  The name of the tar archive to create
+    * @param string $p_compress can be null, 'gz' or 'bz2'. This
+    *               parameter indicates if gzip or bz2 compression
+    *               is required.  For compatibility reason the
+    *               boolean value 'true' means 'gz'.
+    *
+    * @access public
+    */
+    function Archive_Tar($p_tarname, $p_compress = null)
+    {
+        $this->PEAR();
+        $this->_compress = false;
+        $this->_compress_type = 'none';
+        if (($p_compress === null) || ($p_compress == '')) {
+            if (@file_exists($p_tarname)) {
+                if ($fp = @fopen($p_tarname, "rb")) {
+                    // look for gzip magic cookie
+                    $data = fread($fp, 2);
+                    fclose($fp);
+                    if ($data == "\37\213") {
+                        $this->_compress = true;
+                        $this->_compress_type = 'gz';
+                        // No sure it's enought for a magic code ....
+                    } elseif ($data == "BZ") {
+                        $this->_compress = true;
+                        $this->_compress_type = 'bz2';
+                    }
+                }
+            } else {
+                // probably a remote file or some file accessible
+                // through a stream interface
+                if (substr($p_tarname, -2) == 'gz') {
+                    $this->_compress = true;
+                    $this->_compress_type = 'gz';
+                } elseif ((substr($p_tarname, -3) == 'bz2') ||
+                          (substr($p_tarname, -2) == 'bz')) {
+                    $this->_compress = true;
+                    $this->_compress_type = 'bz2';
+                }
+            }
+        } else {
+            if (($p_compress === true) || ($p_compress == 'gz')) {
+                $this->_compress = true;
+                $this->_compress_type = 'gz';
+            } else if ($p_compress == 'bz2') {
+                $this->_compress = true;
+                $this->_compress_type = 'bz2';
+            } else {
+                $this->_error("Unsupported compression type '$p_compress'\n".
+                    "Supported types are 'gz' and 'bz2'.\n");
+                return false;
+            }
+        }
+        $this->_tarname = $p_tarname;
+        if ($this->_compress) { // assert zlib or bz2 extension support
+            if ($this->_compress_type == 'gz')
+                $extname = 'zlib';
+            else if ($this->_compress_type == 'bz2')
+                $extname = 'bz2';
+
+            if (!extension_loaded($extname)) {
+                PEAR::loadExtension($extname);
+            }
+            if (!extension_loaded($extname)) {
+                $this->_error("The extension '$extname' couldn't be found.\n".
+                    "Please make sure your version of PHP was built ".
+                    "with '$extname' support.\n");
+                return false;
+            }
+        }
+    }
+    // }}}
+
+    // {{{ destructor
+    function _Archive_Tar()
+    {
+        $this->_close();
+        // ----- Look for a local copy to delete
+        if ($this->_temp_tarname != '')
+            @unlink($this->_temp_tarname);
+        $this->_PEAR();
+    }
+    // }}}
+
+    // {{{ create()
+    /**
+    * This method creates the archive file and add the files / directories
+    * that are listed in $p_filelist.
+    * If a file with the same name exist and is writable, it is replaced
+    * by the new tar.
+    * The method return false and a PEAR error text.
+    * The $p_filelist parameter can be an array of string, each string
+    * representing a filename or a directory name with their path if
+    * needed. It can also be a single string with names separated by a
+    * single blank.
+    * For each directory added in the archive, the files and
+    * sub-directories are also added.
+    * See also createModify() method for more details.
+    *
+    * @param array $p_filelist An array of filenames and directory names, or a
+    *              single string with names separated by a single
+    *              blank space.
+    *
+    * @return true on success, false on error.
+    * @see    createModify()
+    * @access public
+    */
+    function create($p_filelist)
+    {
+        return $this->createModify($p_filelist, '', '');
+    }
+    // }}}
+
+    // {{{ add()
+    /**
+    * This method add the files / directories that are listed in $p_filelist in
+    * the archive. If the archive does not exist it is created.
+    * The method return false and a PEAR error text.
+    * The files and directories listed are only added at the end of the archive,
+    * even if a file with the same name is already archived.
+    * See also createModify() method for more details.
+    *
+    * @param array $p_filelist An array of filenames and directory names, or a
+    *              single string with names separated by a single
+    *              blank space.
+    *
+    * @return true on success, false on error.
+    * @see    createModify()
+    * @access public
+    */
+    function add($p_filelist)
+    {
+        return $this->addModify($p_filelist, '', '');
+    }
+    // }}}
+
+    // {{{ extract()
+    function extract($p_path='', $p_preserve=false)
+    {
+        return $this->extractModify($p_path, '', $p_preserve);
+    }
+    // }}}
+
+    // {{{ listContent()
+    function listContent()
+    {
+        $v_list_detail = array();
+
+        if ($this->_openRead()) {
+            if (!$this->_extractList('', $v_list_detail, "list", '', '')) {
+                unset($v_list_detail);
+                $v_list_detail = 0;
+            }
+            $this->_close();
+        }
+
+        return $v_list_detail;
+    }
+    // }}}
+
+    // {{{ createModify()
+    /**
+    * This method creates the archive file and add the files / directories
+    * that are listed in $p_filelist.
+    * If the file already exists and is writable, it is replaced by the
+    * new tar. It is a create and not an add. If the file exists and is
+    * read-only or is a directory it is not replaced. The method return
+    * false and a PEAR error text.
+    * The $p_filelist parameter can be an array of string, each string
+    * representing a filename or a directory name with their path if
+    * needed. It can also be a single string with names separated by a
+    * single blank.
+    * The path indicated in $p_remove_dir will be removed from the
+    * memorized path of each file / directory listed when this path
+    * exists. By default nothing is removed (empty path '')
+    * The path indicated in $p_add_dir will be added at the beginning of
+    * the memorized path of each file / directory listed. However it can
+    * be set to empty ''. The adding of a path is done after the removing
+    * of path.
+    * The path add/remove ability enables the user to prepare an archive
+    * for extraction in a different path than the origin files are.
+    * See also addModify() method for file adding properties.
+    *
+    * @param array  $p_filelist   An array of filenames and directory names,
+    *                             or a single string with names separated by
+    *                             a single blank space.
+    * @param string $p_add_dir    A string which contains a path to be added
+    *                             to the memorized path of each element in
+    *                             the list.
+    * @param string $p_remove_dir A string which contains a path to be
+    *                             removed from the memorized path of each
+    *                             element in the list, when relevant.
+    *
+    * @return boolean true on success, false on error.
+    * @access public
+    * @see addModify()
+    */
+    function createModify($p_filelist, $p_add_dir, $p_remove_dir='')
+    {
+        $v_result = true;
+
+        if (!$this->_openWrite())
+            return false;
+
+        if ($p_filelist != '') {
+            if (is_array($p_filelist))
+                $v_list = $p_filelist;
+            elseif (is_string($p_filelist))
+                $v_list = explode($this->_separator, $p_filelist);
+            else {
+                $this->_cleanFile();
+                $this->_error('Invalid file list');
+                return false;
+            }
+
+            $v_result = $this->_addList($v_list, $p_add_dir, $p_remove_dir);
+        }
+
+        if ($v_result) {
+            $this->_writeFooter();
+            $this->_close();
+        } else
+            $this->_cleanFile();
+
+        return $v_result;
+    }
+    // }}}
+
+    // {{{ addModify()
+    /**
+    * This method add the files / directories listed in $p_filelist at the
+    * end of the existing archive. If the archive does not yet exists it
+    * is created.
+    * The $p_filelist parameter can be an array of string, each string
+    * representing a filename or a directory name with their path if
+    * needed. It can also be a single string with names separated by a
+    * single blank.
+    * The path indicated in $p_remove_dir will be removed from the
+    * memorized path of each file / directory listed when this path
+    * exists. By default nothing is removed (empty path '')
+    * The path indicated in $p_add_dir will be added at the beginning of
+    * the memorized path of each file / directory listed. However it can
+    * be set to empty ''. The adding of a path is done after the removing
+    * of path.
+    * The path add/remove ability enables the user to prepare an archive
+    * for extraction in a different path than the origin files are.
+    * If a file/dir is already in the archive it will only be added at the
+    * end of the archive. There is no update of the existing archived
+    * file/dir. However while extracting the archive, the last file will
+    * replace the first one. This results in a none optimization of the
+    * archive size.
+    * If a file/dir does not exist the file/dir is ignored. However an
+    * error text is send to PEAR error.
+    * If a file/dir is not readable the file/dir is ignored. However an
+    * error text is send to PEAR error.
+    *
+    * @param array  $p_filelist   An array of filenames and directory
+    *                             names, or a single string with names
+    *                             separated by a single blank space.
+    * @param string $p_add_dir    A string which contains a path to be
+    *                             added to the memorized path of each
+    *                             element in the list.
+    * @param string $p_remove_dir A string which contains a path to be
+    *                             removed from the memorized path of
+    *                             each element in the list, when
+    *                             relevant.
+    *
+    * @return true on success, false on error.
+    * @access public
+    */
+    function addModify($p_filelist, $p_add_dir, $p_remove_dir='')
+    {
+        $v_result = true;
+
+        if (!$this->_isArchive())
+            $v_result = $this->createModify($p_filelist, $p_add_dir,
+                                            $p_remove_dir);
+        else {
+            if (is_array($p_filelist))
+                $v_list = $p_filelist;
+            elseif (is_string($p_filelist))
+                $v_list = explode($this->_separator, $p_filelist);
+            else {
+                $this->_error('Invalid file list');
+                return false;
+            }
+
+            $v_result = $this->_append($v_list, $p_add_dir, $p_remove_dir);
+        }
+
+        return $v_result;
+    }
+    // }}}
+
+    // {{{ addString()
+    /**
+    * This method add a single string as a file at the
+    * end of the existing archive. If the archive does not yet exists it
+    * is created.
+    *
+    * @param string $p_filename A string which contains the full
+    *                           filename path that will be associated
+    *                           with the string.
+    * @param string $p_string   The content of the file added in
+    *                           the archive.
+    *
+    * @return true on success, false on error.
+    * @access public
+    */
+    function addString($p_filename, $p_string)
+    {
+        $v_result = true;
+
+        if (!$this->_isArchive()) {
+            if (!$this->_openWrite()) {
+                return false;
+            }
+            $this->_close();
+        }
+
+        if (!$this->_openAppend())
+            return false;
+
+        // Need to check the get back to the temporary file ? ....
+        $v_result = $this->_addString($p_filename, $p_string);
+
+        $this->_writeFooter();
+
+        $this->_close();
+
+        return $v_result;
+    }
+    // }}}
+
+    // {{{ extractModify()
+    /**
+    * This method extract all the content of the archive in the directory
+    * indicated by $p_path. When relevant the memorized path of the
+    * files/dir can be modified by removing the $p_remove_path path at the
+    * beginning of the file/dir path.
+    * While extracting a file, if the directory path does not exists it is
+    * created.
+    * While extracting a file, if the file already exists it is replaced
+    * without looking for last modification date.
+    * While extracting a file, if the file already exists and is write
+    * protected, the extraction is aborted.
+    * While extracting a file, if a directory with the same name already
+    * exists, the extraction is aborted.
+    * While extracting a directory, if a file with the same name already
+    * exists, the extraction is aborted.
+    * While extracting a file/directory if the destination directory exist
+    * and is write protected, or does not exist but can not be created,
+    * the extraction is aborted.
+    * If after extraction an extracted file does not show the correct
+    * stored file size, the extraction is aborted.
+    * When the extraction is aborted, a PEAR error text is set and false
+    * is returned. However the result can be a partial extraction that may
+    * need to be manually cleaned.
+    *
+    * @param string  $p_path        The path of the directory where the
+    *                               files/dir need to by extracted.
+    * @param string  $p_remove_path Part of the memorized path that can be
+    *                               removed if present at the beginning of
+    *                               the file/dir path.
+    * @param boolean $p_preserve    Preserve user/group ownership of files
+    *
+    * @return boolean true on success, false on error.
+    * @access public
+    * @see    extractList()
+    */
+    function extractModify($p_path, $p_remove_path, $p_preserve=false)
+    {
+        $v_result = true;
+        $v_list_detail = array();
+
+        if ($v_result = $this->_openRead()) {
+            $v_result = $this->_extractList($p_path, $v_list_detail,
+                "complete", 0, $p_remove_path, $p_preserve);
+            $this->_close();
+        }
+
+        return $v_result;
+    }
+    // }}}
+
+    // {{{ extractInString()
+    /**
+    * This method extract from the archive one file identified by $p_filename.
+    * The return value is a string with the file content, or NULL on error.
+    *
+    * @param string $p_filename The path of the file to extract in a string.
+    *
+    * @return a string with the file content or NULL.
+    * @access public
+    */
+    function extractInString($p_filename)
+    {
+        if ($this->_openRead()) {
+            $v_result = $this->_extractInString($p_filename);
+            $this->_close();
+        } else {
+            $v_result = null;
+        }
+
+        return $v_result;
+    }
+    // }}}
+
+    // {{{ extractList()
+    /**
+    * This method extract from the archive only the files indicated in the
+    * $p_filelist. These files are extracted in the current directory or
+    * in the directory indicated by the optional $p_path parameter.
+    * If indicated the $p_remove_path can be used in the same way as it is
+    * used in extractModify() method.
+    *
+    * @param array   $p_filelist    An array of filenames and directory names,
+    *                               or a single string with names separated
+    *                               by a single blank space.
+    * @param string  $p_path        The path of the directory where the
+    *                               files/dir need to by extracted.
+    * @param string  $p_remove_path Part of the memorized path that can be
+    *                               removed if present at the beginning of
+    *                               the file/dir path.
+    * @param boolean $p_preserve    Preserve user/group ownership of files
+    *
+    * @return true on success, false on error.
+    * @access public
+    * @see    extractModify()
+    */
+    function extractList($p_filelist, $p_path='', $p_remove_path='', $p_preserve=false)
+    {
+        $v_result = true;
+        $v_list_detail = array();
+
+        if (is_array($p_filelist))
+            $v_list = $p_filelist;
+        elseif (is_string($p_filelist))
+            $v_list = explode($this->_separator, $p_filelist);
+        else {
+            $this->_error('Invalid string list');
+            return false;
+        }
+
+        if ($v_result = $this->_openRead()) {
+            $v_result = $this->_extractList($p_path, $v_list_detail, "partial",
+                $v_list, $p_remove_path, $p_preserve);
+            $this->_close();
+        }
+
+        return $v_result;
+    }
+    // }}}
+
+    // {{{ setAttribute()
+    /**
+    * This method set specific attributes of the archive. It uses a variable
+    * list of parameters, in the format attribute code + attribute values :
+    * $arch->setAttribute(ARCHIVE_TAR_ATT_SEPARATOR, ',');
+    *
+    * @param mixed $argv variable list of attributes and values
+    *
+    * @return true on success, false on error.
+    * @access public
+    */
+    function setAttribute()
+    {
+        $v_result = true;
+
+        // ----- Get the number of variable list of arguments
+        if (($v_size = func_num_args()) == 0) {
+            return true;
+        }
+
+        // ----- Get the arguments
+        $v_att_list = &func_get_args();
+
+        // ----- Read the attributes
+        $i=0;
+        while ($i<$v_size) {
+
+            // ----- Look for next option
+            switch ($v_att_list[$i]) {
+                // ----- Look for options that request a string value
+                case ARCHIVE_TAR_ATT_SEPARATOR :
+                    // ----- Check the number of parameters
+                    if (($i+1) >= $v_size) {
+                        $this->_error('Invalid number of parameters for '
+                                                             .'attribute ARCHIVE_TAR_ATT_SEPARATOR');
+                        return false;
+                    }
+
+                    // ----- Get the value
+                    $this->_separator = $v_att_list[$i+1];
+                    $i++;
+                break;
+
+                default :
+                    $this->_error('Unknow attribute code '.$v_att_list[$i].'');
+                    return false;
+            }
+
+            // ----- Next attribute
+            $i++;
+        }
+
+        return $v_result;
+    }
+    // }}}
+
+    // {{{ setIgnoreRegexp()
+    /**
+    * This method sets the regular expression for ignoring files and directories
+    * at import, for example:
+    * $arch->setIgnoreRegexp("#CVS|\.svn#");
+    *
+    * @param string $regexp regular expression defining which files or directories to ignore
+    *
+    * @access public
+    */
+    function setIgnoreRegexp($regexp)
+    {
+       $this->_ignore_regexp = $regexp;
+    }
+    // }}}
+
+    // {{{ setIgnoreList()
+    /**
+    * This method sets the regular expression for ignoring all files and directories
+    * matching the filenames in the array list at import, for example:
+    * $arch->setIgnoreList(array('CVS', '.svn', 'bin/tool'));
+    *
+    * @param array $list a list of file or directory names to ignore
+    *
+    * @access public
+    */
+    function setIgnoreList($list)
+    {
+       $regexp = str_replace(array('#', '.', '^', '$'), array('\#', '\.', '\^', '\$'), $list);
+       $regexp = '#/'.join('$|/', $list).'#';
+       $this->setIgnoreRegexp($regexp);
+    }
+    // }}}
+
+    // {{{ _error()
+    function _error($p_message)
+    {
+        $this->error_object = &$this->raiseError($p_message); 
+    }
+    // }}}
+
+    // {{{ _warning()
+    function _warning($p_message)
+    {
+        $this->error_object = &$this->raiseError($p_message); 
+    }
+    // }}}
+
+    // {{{ _isArchive()
+    function _isArchive($p_filename=null)
+    {
+        if ($p_filename == null) {
+            $p_filename = $this->_tarname;
+        }
+        clearstatcache();
+        return @is_file($p_filename) && !@is_link($p_filename);
+    }
+    // }}}
+
+    // {{{ _openWrite()
+    function _openWrite()
+    {
+        if ($this->_compress_type == 'gz' && function_exists('gzopen'))
+            $this->_file = @gzopen($this->_tarname, "wb9");
+        else if ($this->_compress_type == 'bz2' && function_exists('bzopen'))
+            $this->_file = @bzopen($this->_tarname, "w");
+        else if ($this->_compress_type == 'none')
+            $this->_file = @fopen($this->_tarname, "wb");
+        else
+            $this->_error('Unknown or missing compression type ('
+                                     .$this->_compress_type.')');
+
+        if ($this->_file == 0) {
+            $this->_error('Unable to open in write mode \''
+                                     .$this->_tarname.'\'');
+            return false;
+        }
+
+        return true;
+    }
+    // }}}
+
+    // {{{ _openRead()
+    function _openRead()
+    {
+        if (strtolower(substr($this->_tarname, 0, 7)) == 'http://') {
+
+          // ----- Look if a local copy need to be done
+          if ($this->_temp_tarname == '') {
+              $this->_temp_tarname = uniqid('tar').'.tmp';
+              if (!$v_file_from = @fopen($this->_tarname, 'rb')) {
+                $this->_error('Unable to open in read mode \''
+                                             .$this->_tarname.'\'');
+                $this->_temp_tarname = '';
+                return false;
+              }
+              if (!$v_file_to = @fopen($this->_temp_tarname, 'wb')) {
+                $this->_error('Unable to open in write mode \''
+                                             .$this->_temp_tarname.'\'');
+                $this->_temp_tarname = '';
+                return false;
+              }
+              while ($v_data = @fread($v_file_from, 1024))
+                  @fwrite($v_file_to, $v_data);
+              @fclose($v_file_from);
+              @fclose($v_file_to);
+          }
+
+          // ----- File to open if the local copy
+          $v_filename = $this->_temp_tarname;
+
+        } else
+          // ----- File to open if the normal Tar file
+          $v_filename = $this->_tarname;
+
+        if ($this->_compress_type == 'gz')
+            $this->_file = @gzopen($v_filename, "rb");
+        else if ($this->_compress_type == 'bz2')
+            $this->_file = @bzopen($v_filename, "r");
+        else if ($this->_compress_type == 'none')
+            $this->_file = @fopen($v_filename, "rb");
+        else
+            $this->_error('Unknown or missing compression type ('
+                                     .$this->_compress_type.')');
+
+        if ($this->_file == 0) {
+            $this->_error('Unable to open in read mode \''.$v_filename.'\'');
+            return false;
+        }
+
+        return true;
+    }
+    // }}}
+
+    // {{{ _openReadWrite()
+    function _openReadWrite()
+    {
+        if ($this->_compress_type == 'gz')
+            $this->_file = @gzopen($this->_tarname, "r+b");
+        else if ($this->_compress_type == 'bz2') {
+            $this->_error('Unable to open bz2 in read/write mode \''
+                                     .$this->_tarname.'\' (limitation of bz2 extension)');
+            return false;
+        } else if ($this->_compress_type == 'none')
+            $this->_file = @fopen($this->_tarname, "r+b");
+        else
+            $this->_error('Unknown or missing compression type ('
+                                     .$this->_compress_type.')');
+
+        if ($this->_file == 0) {
+            $this->_error('Unable to open in read/write mode \''
+                                     .$this->_tarname.'\'');
+            return false;
+        }
+
+        return true;
+    }
+    // }}}
+
+    // {{{ _close()
+    function _close()
+    {
+        //if (isset($this->_file)) {
+        if (is_resource($this->_file)) {
+            if ($this->_compress_type == 'gz')
+                @gzclose($this->_file);
+            else if ($this->_compress_type == 'bz2')
+                @bzclose($this->_file);
+            else if ($this->_compress_type == 'none')
+                @fclose($this->_file);
+            else
+                $this->_error('Unknown or missing compression type ('
+                                             .$this->_compress_type.')');
+
+            $this->_file = 0;
+        }
+
+        // ----- Look if a local copy need to be erase
+        // Note that it might be interesting to keep the url for a time : ToDo
+        if ($this->_temp_tarname != '') {
+            @unlink($this->_temp_tarname);
+            $this->_temp_tarname = '';
+        }
+
+        return true;
+    }
+    // }}}
+
+    // {{{ _cleanFile()
+    function _cleanFile()
+    {
+        $this->_close();
+
+        // ----- Look for a local copy
+        if ($this->_temp_tarname != '') {
+            // ----- Remove the local copy but not the remote tarname
+            @unlink($this->_temp_tarname);
+            $this->_temp_tarname = '';
+        } else {
+            // ----- Remove the local tarname file
+            @unlink($this->_tarname);
+        }
+        $this->_tarname = '';
+
+        return true;
+    }
+    // }}}
+
+    // {{{ _writeBlock()
+    function _writeBlock($p_binary_data, $p_len=null)
+    {
+      if (is_resource($this->_file)) {
+          if ($p_len === null) {
+              if ($this->_compress_type == 'gz')
+                  @gzputs($this->_file, $p_binary_data);
+              else if ($this->_compress_type == 'bz2')
+                  @bzwrite($this->_file, $p_binary_data);
+              else if ($this->_compress_type == 'none')
+                  @fputs($this->_file, $p_binary_data);
+              else
+                  $this->_error('Unknown or missing compression type ('
+                                               .$this->_compress_type.')');
+          } else {
+              if ($this->_compress_type == 'gz')
+                  @gzputs($this->_file, $p_binary_data, $p_len);
+              else if ($this->_compress_type == 'bz2')
+                  @bzwrite($this->_file, $p_binary_data, $p_len);
+              else if ($this->_compress_type == 'none')
+                  @fputs($this->_file, $p_binary_data, $p_len);
+              else
+                  $this->_error('Unknown or missing compression type ('
+                                               .$this->_compress_type.')');
+
+          }
+      }
+      return true;
+    }
+    // }}}
+
+    // {{{ _readBlock()
+    function _readBlock()
+    {
+      $v_block = null;
+      if (is_resource($this->_file)) {
+          if ($this->_compress_type == 'gz')
+              $v_block = @gzread($this->_file, 512);
+          else if ($this->_compress_type == 'bz2')
+              $v_block = @bzread($this->_file, 512);
+          else if ($this->_compress_type == 'none')
+              $v_block = @fread($this->_file, 512);
+          else
+              $this->_error('Unknown or missing compression type ('
+                                       .$this->_compress_type.')');
+      }
+      return $v_block;
+    }
+    // }}}
+
+    // {{{ _jumpBlock()
+    function _jumpBlock($p_len=null)
+    {
+      if (is_resource($this->_file)) {
+          if ($p_len === null)
+              $p_len = 1;
+
+          if ($this->_compress_type == 'gz') {
+              @gzseek($this->_file, gztell($this->_file)+($p_len*512));
+          }
+          else if ($this->_compress_type == 'bz2') {
+              // ----- Replace missing bztell() and bzseek()
+              for ($i=0; $i<$p_len; $i++)
+                  $this->_readBlock();
+          } else if ($this->_compress_type == 'none')
+              @fseek($this->_file, $p_len*512, SEEK_CUR);
+          else
+              $this->_error('Unknown or missing compression type ('
+                                       .$this->_compress_type.')');
+
+      }
+      return true;
+    }
+    // }}}
+
+    // {{{ _writeFooter()
+    function _writeFooter()
+    {
+      if (is_resource($this->_file)) {
+          // ----- Write the last 0 filled block for end of archive
+          $v_binary_data = pack('a1024', '');
+          $this->_writeBlock($v_binary_data);
+      }
+      return true;
+    }
+    // }}}
+
+    // {{{ _addList()
+    function _addList($p_list, $p_add_dir, $p_remove_dir)
+    {
+      $v_result=true;
+      $v_header = array();
+
+      // ----- Remove potential windows directory separator
+      $p_add_dir = $this->_translateWinPath($p_add_dir);
+      $p_remove_dir = $this->_translateWinPath($p_remove_dir, false);
+
+      if (!$this->_file) {
+          $this->_error('Invalid file descriptor');
+          return false;
+      }
+
+      if (sizeof($p_list) == 0)
+          return true;
+
+      foreach ($p_list as $v_filename) {
+          if (!$v_result) {
+              break;
+          }
+
+        // ----- Skip the current tar name
+        if ($v_filename == $this->_tarname)
+            continue;
+
+        if ($v_filename == '')
+            continue;
+
+               // ----- ignore files and directories matching the ignore regular expression
+               if ($this->_ignore_regexp && preg_match($this->_ignore_regexp, '/'.$v_filename)) {
+            $this->_warning("File '$v_filename' ignored");
+                   continue;
+               }
+
+        if (!file_exists($v_filename) && !is_link($v_filename)) {
+            $this->_warning("File '$v_filename' does not exist");
+            continue;
+        }
+
+        // ----- Add the file or directory header
+        if (!$this->_addFile($v_filename, $v_header, $p_add_dir, $p_remove_dir))
+            return false;
+
+        if (@is_dir($v_filename) && !@is_link($v_filename)) {
+            if (!($p_hdir = opendir($v_filename))) {
+                $this->_warning("Directory '$v_filename' can not be read");
+                continue;
+            }
+            while (false !== ($p_hitem = readdir($p_hdir))) {
+                if (($p_hitem != '.') && ($p_hitem != '..')) {
+                    if ($v_filename != ".")
+                        $p_temp_list[0] = $v_filename.'/'.$p_hitem;
+                    else
+                        $p_temp_list[0] = $p_hitem;
+
+                    $v_result = $this->_addList($p_temp_list,
+                                                                   $p_add_dir,
+                                                                                               $p_remove_dir);
+                }
+            }
+
+            unset($p_temp_list);
+            unset($p_hdir);
+            unset($p_hitem);
+        }
+      }
+
+      return $v_result;
+    }
+    // }}}
+
+    // {{{ _addFile()
+    function _addFile($p_filename, &$p_header, $p_add_dir, $p_remove_dir,$v_stored_filename=null)
+    {
+      if (!$this->_file) {
+          $this->_error('Invalid file descriptor');
+          return false;
+      }
+
+      if ($p_filename == '') {
+          $this->_error('Invalid file name');
+          return false;
+      }
+      if(is_null($v_stored_filename)){
+
+               // ----- Calculate the stored filename
+               $p_filename = $this->_translateWinPath($p_filename, false);
+               $v_stored_filename = $p_filename;
+               if (strcmp($p_filename, $p_remove_dir) == 0) {
+                       return true;
+               }
+               if ($p_remove_dir != '') {
+                       if (substr($p_remove_dir, -1) != '/')
+                               $p_remove_dir .= '/';
+
+                       if (substr($p_filename, 0, strlen($p_remove_dir)) == $p_remove_dir)
+                               $v_stored_filename = substr($p_filename, strlen($p_remove_dir));
+               }
+               $v_stored_filename = $this->_translateWinPath($v_stored_filename);
+               if ($p_add_dir != '') {
+                       if (substr($p_add_dir, -1) == '/')
+                               $v_stored_filename = $p_add_dir.$v_stored_filename;
+                       else
+                               $v_stored_filename = $p_add_dir.'/'.$v_stored_filename;
+               }
+
+               $v_stored_filename = $this->_pathReduction($v_stored_filename);
+       }
+
+      if ($this->_isArchive($p_filename)) {
+          if (($v_file = @fopen($p_filename, "rb")) == 0) {
+              $this->_warning("Unable to open file '".$p_filename
+                                         ."' in binary read mode");
+              return true;
+          }
+
+          if (!$this->_writeHeader($p_filename, $v_stored_filename))
+              return false;
+
+          while (($v_buffer = fread($v_file, 512)) != '') {
+              $v_binary_data = pack("a512", "$v_buffer");
+              $this->_writeBlock($v_binary_data);
+          }
+
+          fclose($v_file);
+
+      } else {
+          // ----- Only header for dir
+          if (!$this->_writeHeader($p_filename, $v_stored_filename))
+              return false;
+      }
+
+      return true;
+    }
+    // }}}
+
+    // {{{ _addString()
+    function _addString($p_filename, $p_string)
+    {
+      if (!$this->_file) {
+          $this->_error('Invalid file descriptor');
+          return false;
+      }
+
+      if ($p_filename == '') {
+          $this->_error('Invalid file name');
+          return false;
+      }
+
+      // ----- Calculate the stored filename
+      $p_filename = $this->_translateWinPath($p_filename, false);;
+
+      if (!$this->_writeHeaderBlock($p_filename, strlen($p_string),
+                                         time(), 384, "", 0, 0))
+          return false;
+
+      $i=0;
+      while (($v_buffer = substr($p_string, (($i++)*512), 512)) != '') {
+          $v_binary_data = pack("a512", $v_buffer);
+          $this->_writeBlock($v_binary_data);
+      }
+
+      return true;
+    }
+    // }}}
+
+    // {{{ _writeHeader()
+    function _writeHeader($p_filename, $p_stored_filename)
+    {
+        if ($p_stored_filename == '')
+            $p_stored_filename = $p_filename;
+        $v_reduce_filename = $this->_pathReduction($p_stored_filename);
+
+        if (strlen($v_reduce_filename) > 99) {
+          if (!$this->_writeLongHeader($v_reduce_filename))
+            return false;
+        }
+
+        $v_info = lstat($p_filename);
+        $v_uid = sprintf("%07s", DecOct($v_info[4]));
+        $v_gid = sprintf("%07s", DecOct($v_info[5]));
+        $v_perms = sprintf("%07s", DecOct($v_info['mode'] & 000777));
+
+        $v_mtime = sprintf("%011s", DecOct($v_info['mtime']));
+
+        $v_linkname = '';
+
+        if (@is_link($p_filename)) {
+          $v_typeflag = '2';
+          $v_linkname = readlink($p_filename);
+          $v_size = sprintf("%011s", DecOct(0));
+        } elseif (@is_dir($p_filename)) {
+          $v_typeflag = "5";
+          $v_size = sprintf("%011s", DecOct(0));
+        } else {
+          $v_typeflag = '0';
+          clearstatcache();
+          $v_size = sprintf("%011s", DecOct($v_info['size']));
+        }
+
+        $v_magic = 'ustar ';
+
+        $v_version = ' ';
+        
+        if (function_exists('posix_getpwuid'))
+        {
+          $userinfo = posix_getpwuid($v_info[4]);
+          $groupinfo = posix_getgrgid($v_info[5]);
+          
+          $v_uname = $userinfo['name'];
+          $v_gname = $groupinfo['name'];
+        }
+        else
+        {
+          $v_uname = '';
+          $v_gname = '';
+        }
+
+        $v_devmajor = '';
+
+        $v_devminor = '';
+
+        $v_prefix = '';
+
+        $v_binary_data_first = pack("a100a8a8a8a12a12",
+                                           $v_reduce_filename, $v_perms, $v_uid,
+                                                                       $v_gid, $v_size, $v_mtime);
+        $v_binary_data_last = pack("a1a100a6a2a32a32a8a8a155a12",
+                                          $v_typeflag, $v_linkname, $v_magic,
+                                                                  $v_version, $v_uname, $v_gname,
+                                                                  $v_devmajor, $v_devminor, $v_prefix, '');
+
+        // ----- Calculate the checksum
+        $v_checksum = 0;
+        // ..... First part of the header
+        for ($i=0; $i<148; $i++)
+            $v_checksum += ord(substr($v_binary_data_first,$i,1));
+        // ..... Ignore the checksum value and replace it by ' ' (space)
+        for ($i=148; $i<156; $i++)
+            $v_checksum += ord(' ');
+        // ..... Last part of the header
+        for ($i=156, $j=0; $i<512; $i++, $j++)
+            $v_checksum += ord(substr($v_binary_data_last,$j,1));
+
+        // ----- Write the first 148 bytes of the header in the archive
+        $this->_writeBlock($v_binary_data_first, 148);
+
+        // ----- Write the calculated checksum
+        $v_checksum = sprintf("%06s ", DecOct($v_checksum));
+        $v_binary_data = pack("a8", $v_checksum);
+        $this->_writeBlock($v_binary_data, 8);
+
+        // ----- Write the last 356 bytes of the header in the archive
+        $this->_writeBlock($v_binary_data_last, 356);
+
+        return true;
+    }
+    // }}}
+
+    // {{{ _writeHeaderBlock()
+    function _writeHeaderBlock($p_filename, $p_size, $p_mtime=0, $p_perms=0,
+                                  $p_type='', $p_uid=0, $p_gid=0)
+    {
+        $p_filename = $this->_pathReduction($p_filename);
+
+        if (strlen($p_filename) > 99) {
+          if (!$this->_writeLongHeader($p_filename))
+            return false;
+        }
+
+        if ($p_type == "5") {
+          $v_size = sprintf("%011s", DecOct(0));
+        } else {
+          $v_size = sprintf("%011s", DecOct($p_size));
+        }
+
+        $v_uid = sprintf("%07s", DecOct($p_uid));
+        $v_gid = sprintf("%07s", DecOct($p_gid));
+        $v_perms = sprintf("%07s", DecOct($p_perms & 000777));
+
+        $v_mtime = sprintf("%11s", DecOct($p_mtime));
+
+        $v_linkname = '';
+
+        $v_magic = 'ustar ';
+
+        $v_version = ' ';
+
+        if (function_exists('posix_getpwuid'))
+        {
+          $userinfo = posix_getpwuid($p_uid);
+          $groupinfo = posix_getgrgid($p_gid);
+          
+          $v_uname = $userinfo['name'];
+          $v_gname = $groupinfo['name'];
+        }
+        else
+        {
+          $v_uname = '';
+          $v_gname = '';
+        }
+        
+        $v_devmajor = '';
+
+        $v_devminor = '';
+
+        $v_prefix = '';
+
+        $v_binary_data_first = pack("a100a8a8a8a12A12",
+                                           $p_filename, $v_perms, $v_uid, $v_gid,
+                                                                       $v_size, $v_mtime);
+        $v_binary_data_last = pack("a1a100a6a2a32a32a8a8a155a12",
+                                          $p_type, $v_linkname, $v_magic,
+                                                                  $v_version, $v_uname, $v_gname,
+                                                                  $v_devmajor, $v_devminor, $v_prefix, '');
+
+        // ----- Calculate the checksum
+        $v_checksum = 0;
+        // ..... First part of the header
+        for ($i=0; $i<148; $i++)
+            $v_checksum += ord(substr($v_binary_data_first,$i,1));
+        // ..... Ignore the checksum value and replace it by ' ' (space)
+        for ($i=148; $i<156; $i++)
+            $v_checksum += ord(' ');
+        // ..... Last part of the header
+        for ($i=156, $j=0; $i<512; $i++, $j++)
+            $v_checksum += ord(substr($v_binary_data_last,$j,1));
+
+        // ----- Write the first 148 bytes of the header in the archive
+        $this->_writeBlock($v_binary_data_first, 148);
+
+        // ----- Write the calculated checksum
+        $v_checksum = sprintf("%06s ", DecOct($v_checksum));
+        $v_binary_data = pack("a8", $v_checksum);
+        $this->_writeBlock($v_binary_data, 8);
+
+        // ----- Write the last 356 bytes of the header in the archive
+        $this->_writeBlock($v_binary_data_last, 356);
+
+        return true;
+    }
+    // }}}
+
+    // {{{ _writeLongHeader()
+    function _writeLongHeader($p_filename)
+    {
+        $v_size = sprintf("%11s ", DecOct(strlen($p_filename)));
+
+        $v_typeflag = 'L';
+
+        $v_linkname = '';
+
+        $v_magic = '';
+
+        $v_version = '';
+
+        $v_uname = '';
+
+        $v_gname = '';
+
+        $v_devmajor = '';
+
+        $v_devminor = '';
+
+        $v_prefix = '';
+
+        $v_binary_data_first = pack("a100a8a8a8a12a12",
+                                           '././@LongLink', 0, 0, 0, $v_size, 0);
+        $v_binary_data_last = pack("a1a100a6a2a32a32a8a8a155a12",
+                                          $v_typeflag, $v_linkname, $v_magic,
+                                                                  $v_version, $v_uname, $v_gname,
+                                                                  $v_devmajor, $v_devminor, $v_prefix, '');
+
+        // ----- Calculate the checksum
+        $v_checksum = 0;
+        // ..... First part of the header
+        for ($i=0; $i<148; $i++)
+            $v_checksum += ord(substr($v_binary_data_first,$i,1));
+        // ..... Ignore the checksum value and replace it by ' ' (space)
+        for ($i=148; $i<156; $i++)
+            $v_checksum += ord(' ');
+        // ..... Last part of the header
+        for ($i=156, $j=0; $i<512; $i++, $j++)
+            $v_checksum += ord(substr($v_binary_data_last,$j,1));
+
+        // ----- Write the first 148 bytes of the header in the archive
+        $this->_writeBlock($v_binary_data_first, 148);
+
+        // ----- Write the calculated checksum
+        $v_checksum = sprintf("%06s ", DecOct($v_checksum));
+        $v_binary_data = pack("a8", $v_checksum);
+        $this->_writeBlock($v_binary_data, 8);
+
+        // ----- Write the last 356 bytes of the header in the archive
+        $this->_writeBlock($v_binary_data_last, 356);
+
+        // ----- Write the filename as content of the block
+        $i=0;
+        while (($v_buffer = substr($p_filename, (($i++)*512), 512)) != '') {
+            $v_binary_data = pack("a512", "$v_buffer");
+            $this->_writeBlock($v_binary_data);
+        }
+
+        return true;
+    }
+    // }}}
+
+    // {{{ _readHeader()
+    function _readHeader($v_binary_data, &$v_header)
+    {
+        if (strlen($v_binary_data)==0) {
+            $v_header['filename'] = '';
+            return true;
+        }
+
+        if (strlen($v_binary_data) != 512) {
+            $v_header['filename'] = '';
+            $this->_error('Invalid block size : '.strlen($v_binary_data));
+            return false;
+        }
+
+        if (!is_array($v_header)) {
+            $v_header = array();
+        }
+        // ----- Calculate the checksum
+        $v_checksum = 0;
+        // ..... First part of the header
+        for ($i=0; $i<148; $i++)
+            $v_checksum+=ord(substr($v_binary_data,$i,1));
+        // ..... Ignore the checksum value and replace it by ' ' (space)
+        for ($i=148; $i<156; $i++)
+            $v_checksum += ord(' ');
+        // ..... Last part of the header
+        for ($i=156; $i<512; $i++)
+           $v_checksum+=ord(substr($v_binary_data,$i,1));
+
+        $v_data = unpack("a100filename/a8mode/a8uid/a8gid/a12size/a12mtime/" .
+                         "a8checksum/a1typeflag/a100link/a6magic/a2version/" .
+                         "a32uname/a32gname/a8devmajor/a8devminor/a131prefix",
+                         $v_binary_data);
+                         
+        if (strlen($v_data["prefix"]) > 0) {
+            $v_data["filename"] = "$v_data[prefix]/$v_data[filename]";
+        }
+
+        // ----- Extract the checksum
+        $v_header['checksum'] = OctDec(trim($v_data['checksum']));
+        if ($v_header['checksum'] != $v_checksum) {
+            $v_header['filename'] = '';
+
+            // ----- Look for last block (empty block)
+            if (($v_checksum == 256) && ($v_header['checksum'] == 0))
+                return true;
+
+            $this->_error('Invalid checksum for file "'.$v_data['filename']
+                                     .'" : '.$v_checksum.' calculated, '
+                                                 .$v_header['checksum'].' expected');
+            return false;
+        }
+
+        // ----- Extract the properties
+        $v_header['filename'] = $v_data['filename'];
+        if ($this->_maliciousFilename($v_header['filename'])) {
+            $this->_error('Malicious .tar detected, file "' . $v_header['filename'] .
+                '" will not install in desired directory tree');
+            return false;
+        }
+        $v_header['mode'] = OctDec(trim($v_data['mode']));
+        $v_header['uid'] = OctDec(trim($v_data['uid']));
+        $v_header['gid'] = OctDec(trim($v_data['gid']));
+        $v_header['size'] = OctDec(trim($v_data['size']));
+        $v_header['mtime'] = OctDec(trim($v_data['mtime']));
+        if (($v_header['typeflag'] = $v_data['typeflag']) == "5") {
+          $v_header['size'] = 0;
+        }
+        $v_header['link'] = trim($v_data['link']);
+        /* ----- All these fields are removed form the header because
+               they do not carry interesting info
+        $v_header[magic] = trim($v_data[magic]);
+        $v_header[version] = trim($v_data[version]);
+        $v_header[uname] = trim($v_data[uname]);
+        $v_header[gname] = trim($v_data[gname]);
+        $v_header[devmajor] = trim($v_data[devmajor]);
+        $v_header[devminor] = trim($v_data[devminor]);
+        */
+
+        return true;
+    }
+    // }}}
+
+    // {{{ _maliciousFilename()
+    /**
+     * Detect and report a malicious file name
+     *
+     * @param string $file
+     *
+     * @return bool
+     * @access private
+     */
+    function _maliciousFilename($file)
+    {
+        if (strpos($file, '/../') !== false) {
+            return true;
+        }
+        if (strpos($file, '../') === 0) {
+            return true;
+        }
+        return false;
+    }
+    // }}}
+
+    // {{{ _readLongHeader()
+    function _readLongHeader(&$v_header)
+    {
+      $v_filename = '';
+      $n = floor($v_header['size']/512);
+      for ($i=0; $i<$n; $i++) {
+        $v_content = $this->_readBlock();
+        $v_filename .= $v_content;
+      }
+      if (($v_header['size'] % 512) != 0) {
+        $v_content = $this->_readBlock();
+        $v_filename .= trim($v_content);
+      }
+
+      // ----- Read the next header
+      $v_binary_data = $this->_readBlock();
+
+      if (!$this->_readHeader($v_binary_data, $v_header))
+        return false;
+
+      $v_filename = trim($v_filename);
+      $v_header['filename'] = $v_filename;
+        if ($this->_maliciousFilename($v_filename)) {
+            $this->_error('Malicious .tar detected, file "' . $v_filename .
+                '" will not install in desired directory tree');
+            return false;
+      }
+
+      return true;
+    }
+    // }}}
+
+    // {{{ _extractInString()
+    /**
+    * This method extract from the archive one file identified by $p_filename.
+    * The return value is a string with the file content, or null on error.
+    *
+    * @param string $p_filename The path of the file to extract in a string.
+    *
+    * @return a string with the file content or null.
+    * @access private
+    */
+    function _extractInString($p_filename)
+    {
+        $v_result_str = "";
+
+        While (strlen($v_binary_data = $this->_readBlock()) != 0)
+        {
+          if (!$this->_readHeader($v_binary_data, $v_header))
+            return null;
+
+          if ($v_header['filename'] == '')
+            continue;
+
+          // ----- Look for long filename
+          if ($v_header['typeflag'] == 'L') {
+            if (!$this->_readLongHeader($v_header))
+              return null;
+          }
+
+          if ($v_header['filename'] == $p_filename) {
+              if ($v_header['typeflag'] == "5") {
+                  $this->_error('Unable to extract in string a directory '
+                                               .'entry {'.$v_header['filename'].'}');
+                  return null;
+              } else {
+                  $n = floor($v_header['size']/512);
+                  for ($i=0; $i<$n; $i++) {
+                      $v_result_str .= $this->_readBlock();
+                  }
+                  if (($v_header['size'] % 512) != 0) {
+                      $v_content = $this->_readBlock();
+                      $v_result_str .= substr($v_content, 0,
+                                                                 ($v_header['size'] % 512));
+                  }
+                  return $v_result_str;
+              }
+          } else {
+              $this->_jumpBlock(ceil(($v_header['size']/512)));
+          }
+        }
+
+        return null;
+    }
+    // }}}
+
+    // {{{ _extractList()
+    function _extractList($p_path, &$p_list_detail, $p_mode,
+                          $p_file_list, $p_remove_path, $p_preserve=false)
+    {
+    $v_result=true;
+    $v_nb = 0;
+    $v_extract_all = true;
+    $v_listing = false;
+
+    $p_path = $this->_translateWinPath($p_path, false);
+    if ($p_path == '' || (substr($p_path, 0, 1) != '/'
+           && substr($p_path, 0, 3) != "../" && !strpos($p_path, ':'))) {
+      $p_path = "./".$p_path;
+    }
+    $p_remove_path = $this->_translateWinPath($p_remove_path);
+
+    // ----- Look for path to remove format (should end by /)
+    if (($p_remove_path != '') && (substr($p_remove_path, -1) != '/'))
+      $p_remove_path .= '/';
+    $p_remove_path_size = strlen($p_remove_path);
+
+    switch ($p_mode) {
+      case "complete" :
+        $v_extract_all = true;
+        $v_listing = false;
+      break;
+      case "partial" :
+          $v_extract_all = false;
+          $v_listing = false;
+      break;
+      case "list" :
+          $v_extract_all = false;
+          $v_listing = true;
+      break;
+      default :
+        $this->_error('Invalid extract mode ('.$p_mode.')');
+        return false;
+    }
+
+    clearstatcache();
+
+    while (strlen($v_binary_data = $this->_readBlock()) != 0)
+    {
+      $v_extract_file = FALSE;
+      $v_extraction_stopped = 0;
+
+      if (!$this->_readHeader($v_binary_data, $v_header))
+        return false;
+
+      if ($v_header['filename'] == '') {
+        continue;
+      }
+
+      // ----- Look for long filename
+      if ($v_header['typeflag'] == 'L') {
+        if (!$this->_readLongHeader($v_header))
+          return false;
+      }
+
+      if ((!$v_extract_all) && (is_array($p_file_list))) {
+        // ----- By default no unzip if the file is not found
+        $v_extract_file = false;
+
+        for ($i=0; $i<sizeof($p_file_list); $i++) {
+          // ----- Look if it is a directory
+          if (substr($p_file_list[$i], -1) == '/') {
+            // ----- Look if the directory is in the filename path
+            if ((strlen($v_header['filename']) > strlen($p_file_list[$i]))
+                           && (substr($v_header['filename'], 0, strlen($p_file_list[$i]))
+                                   == $p_file_list[$i])) {
+              $v_extract_file = true;
+              break;
+            }
+          }
+
+          // ----- It is a file, so compare the file names
+          elseif ($p_file_list[$i] == $v_header['filename']) {
+            $v_extract_file = true;
+            break;
+          }
+        }
+      } else {
+        $v_extract_file = true;
+      }
+
+      // ----- Look if this file need to be extracted
+      if (($v_extract_file) && (!$v_listing))
+      {
+        if (($p_remove_path != '')
+            && (substr($v_header['filename'], 0, $p_remove_path_size)
+                           == $p_remove_path))
+          $v_header['filename'] = substr($v_header['filename'],
+                                                $p_remove_path_size);
+        if (($p_path != './') && ($p_path != '/')) {
+          while (substr($p_path, -1) == '/')
+            $p_path = substr($p_path, 0, strlen($p_path)-1);
+
+          if (substr($v_header['filename'], 0, 1) == '/')
+              $v_header['filename'] = $p_path.$v_header['filename'];
+          else
+            $v_header['filename'] = $p_path.'/'.$v_header['filename'];
+        }
+        if (file_exists($v_header['filename'])) {
+          if (   (@is_dir($v_header['filename']))
+                     && ($v_header['typeflag'] == '')) {
+            $this->_error('File '.$v_header['filename']
+                                     .' already exists as a directory');
+            return false;
+          }
+          if (   ($this->_isArchive($v_header['filename']))
+                     && ($v_header['typeflag'] == "5")) {
+            $this->_error('Directory '.$v_header['filename']
+                                     .' already exists as a file');
+            return false;
+          }
+          if (!is_writeable($v_header['filename'])) {
+            $this->_error('File '.$v_header['filename']
+                                     .' already exists and is write protected');
+            return false;
+          }
+          if (filemtime($v_header['filename']) > $v_header['mtime']) {
+            // To be completed : An error or silent no replace ?
+          }
+        }
+
+        // ----- Check the directory availability and create it if necessary
+        elseif (($v_result
+                        = $this->_dirCheck(($v_header['typeflag'] == "5"
+                                                   ?$v_header['filename']
+                                                                       :dirname($v_header['filename'])))) != 1) {
+            $this->_error('Unable to create path for '.$v_header['filename']);
+            return false;
+        }
+
+        if ($v_extract_file) {
+          if ($v_header['typeflag'] == "5") {
+            if (!@file_exists($v_header['filename'])) {
+                if (!@mkdir($v_header['filename'], 0777)) {
+                    $this->_error('Unable to create directory {'
+                                                     .$v_header['filename'].'}');
+                    return false;
+                }
+            }
+          } elseif ($v_header['typeflag'] == "2") {
+              if (@file_exists($v_header['filename'])) {
+                  @unlink($v_header['filename']);
+              }
+              if (!@symlink($v_header['link'], $v_header['filename'])) {
+                  $this->_error('Unable to extract symbolic link {'
+                                .$v_header['filename'].'}');
+                  return false;
+              }
+          } else {
+              if (($v_dest_file = @fopen($v_header['filename'], "wb")) == 0) {
+                  $this->_error('Error while opening {'.$v_header['filename']
+                                               .'} in write binary mode');
+                  return false;
+              } else {
+                  $n = floor($v_header['size']/512);
+                  for ($i=0; $i<$n; $i++) {
+                      $v_content = $this->_readBlock();
+                      fwrite($v_dest_file, $v_content, 512);
+                  }
+            if (($v_header['size'] % 512) != 0) {
+              $v_content = $this->_readBlock();
+              fwrite($v_dest_file, $v_content, ($v_header['size'] % 512));
+            }
+
+            @fclose($v_dest_file);
+            
+            if ($p_preserve) {
+                @chown($v_header['filename'], $v_header['uid']);
+                @chgrp($v_header['filename'], $v_header['gid']);
+            }
+
+            // ----- Change the file mode, mtime
+            @touch($v_header['filename'], $v_header['mtime']);
+            if ($v_header['mode'] & 0111) {
+                // make file executable, obey umask
+                $mode = fileperms($v_header['filename']) | (~umask() & 0111);
+                @chmod($v_header['filename'], $mode);
+            }
+          }
+
+          // ----- Check the file size
+          clearstatcache();
+          if (!is_file($v_header['filename'])) {
+              $this->_error('Extracted file '.$v_header['filename']
+                            .'does not exist. Archive may be corrupted.');
+              return false;
+          }
+          
+          $filesize = filesize($v_header['filename']);
+          if ($filesize != $v_header['size']) {
+              $this->_error('Extracted file '.$v_header['filename']
+                            .' does not have the correct file size \''
+                            .$filesize
+                            .'\' ('.$v_header['size']
+                            .' expected). Archive may be corrupted.');
+              return false;
+          }
+          }
+        } else {
+          $this->_jumpBlock(ceil(($v_header['size']/512)));
+        }
+      } else {
+          $this->_jumpBlock(ceil(($v_header['size']/512)));
+      }
+
+      /* TBC : Seems to be unused ...
+      if ($this->_compress)
+        $v_end_of_file = @gzeof($this->_file);
+      else
+        $v_end_of_file = @feof($this->_file);
+        */
+
+      if ($v_listing || $v_extract_file || $v_extraction_stopped) {
+        // ----- Log extracted files
+        if (($v_file_dir = dirname($v_header['filename']))
+                   == $v_header['filename'])
+          $v_file_dir = '';
+        if ((substr($v_header['filename'], 0, 1) == '/') && ($v_file_dir == ''))
+          $v_file_dir = '/';
+
+        $p_list_detail[$v_nb++] = $v_header;
+        if (is_array($p_file_list) && (count($p_list_detail) == count($p_file_list))) {
+            return true;
+        }
+      }
+    }
+
+        return true;
+    }
+    // }}}
+
+    // {{{ _openAppend()
+    function _openAppend()
+    {
+        if (filesize($this->_tarname) == 0)
+          return $this->_openWrite();
+
+        if ($this->_compress) {
+            $this->_close();
+
+            if (!@rename($this->_tarname, $this->_tarname.".tmp")) {
+                $this->_error('Error while renaming \''.$this->_tarname
+                                             .'\' to temporary file \''.$this->_tarname
+                                                         .'.tmp\'');
+                return false;
+            }
+
+            if ($this->_compress_type == 'gz')
+                $v_temp_tar = @gzopen($this->_tarname.".tmp", "rb");
+            elseif ($this->_compress_type == 'bz2')
+                $v_temp_tar = @bzopen($this->_tarname.".tmp", "r");
+
+            if ($v_temp_tar == 0) {
+                $this->_error('Unable to open file \''.$this->_tarname
+                                             .'.tmp\' in binary read mode');
+                @rename($this->_tarname.".tmp", $this->_tarname);
+                return false;
+            }
+
+            if (!$this->_openWrite()) {
+                @rename($this->_tarname.".tmp", $this->_tarname);
+                return false;
+            }
+
+            if ($this->_compress_type == 'gz') {
+                while (!@gzeof($v_temp_tar)) {
+                    $v_buffer = @gzread($v_temp_tar, 512);
+                    if ($v_buffer == ARCHIVE_TAR_END_BLOCK || strlen($v_buffer) == 0) {
+                        // do not copy end blocks, we will re-make them
+                        // after appending
+                        continue;
+                    }
+                    $v_binary_data = pack("a512", $v_buffer);
+                    $this->_writeBlock($v_binary_data);
+                }
+
+                @gzclose($v_temp_tar);
+            }
+            elseif ($this->_compress_type == 'bz2') {
+                while (strlen($v_buffer = @bzread($v_temp_tar, 512)) > 0) {
+                    if ($v_buffer == ARCHIVE_TAR_END_BLOCK) {
+                        continue;
+                    }
+                    $v_binary_data = pack("a512", $v_buffer);
+                    $this->_writeBlock($v_binary_data);
+                }
+
+                @bzclose($v_temp_tar);
+            }
+
+            if (!@unlink($this->_tarname.".tmp")) {
+                $this->_error('Error while deleting temporary file \''
+                                             .$this->_tarname.'.tmp\'');
+            }
+
+        } else {
+            // ----- For not compressed tar, just add files before the last
+                       //       one or two 512 bytes block
+            if (!$this->_openReadWrite())
+               return false;
+
+            clearstatcache();
+            $v_size = filesize($this->_tarname);
+
+            // We might have zero, one or two end blocks.
+            // The standard is two, but we should try to handle
+            // other cases.
+            fseek($this->_file, $v_size - 1024);
+            if (fread($this->_file, 512) == ARCHIVE_TAR_END_BLOCK) {
+                fseek($this->_file, $v_size - 1024);
+            }
+            elseif (fread($this->_file, 512) == ARCHIVE_TAR_END_BLOCK) {
+                fseek($this->_file, $v_size - 512);
+            }
+        }
+
+        return true;
+    }
+    // }}}
+
+    // {{{ _append()
+    function _append($p_filelist, $p_add_dir='', $p_remove_dir='')
+    {
+        if (!$this->_openAppend())
+            return false;
+
+        if ($this->_addList($p_filelist, $p_add_dir, $p_remove_dir))
+           $this->_writeFooter();
+
+        $this->_close();
+
+        return true;
+    }
+    // }}}
+
+    // {{{ _dirCheck()
+
+    /**
+     * Check if a directory exists and create it (including parent
+     * dirs) if not.
+     *
+     * @param string $p_dir directory to check
+     *
+     * @return bool true if the directory exists or was created
+     */
+    function _dirCheck($p_dir)
+    {
+        clearstatcache();
+        if ((@is_dir($p_dir)) || ($p_dir == ''))
+            return true;
+
+        $p_parent_dir = dirname($p_dir);
+
+        if (($p_parent_dir != $p_dir) &&
+            ($p_parent_dir != '') &&
+            (!$this->_dirCheck($p_parent_dir)))
+             return false;
+
+        if (!@mkdir($p_dir, 0777)) {
+            $this->_error("Unable to create directory '$p_dir'");
+            return false;
+        }
+
+        return true;
+    }
+
+    // }}}
+
+    // {{{ _pathReduction()
+
+    /**
+     * Compress path by changing for example "/dir/foo/../bar" to "/dir/bar",
+     * rand emove double slashes.
+     *
+     * @param string $p_dir path to reduce
+     *
+     * @return string reduced path
+     *
+     * @access private
+     *
+     */
+    function _pathReduction($p_dir)
+    {
+        $v_result = '';
+
+        // ----- Look for not empty path
+        if ($p_dir != '') {
+            // ----- Explode path by directory names
+            $v_list = explode('/', $p_dir);
+
+            // ----- Study directories from last to first
+            for ($i=sizeof($v_list)-1; $i>=0; $i--) {
+                // ----- Look for current path
+                if ($v_list[$i] == ".") {
+                    // ----- Ignore this directory
+                    // Should be the first $i=0, but no check is done
+                }
+                else if ($v_list[$i] == "..") {
+                    // ----- Ignore it and ignore the $i-1
+                    $i--;
+                }
+                else if (   ($v_list[$i] == '')
+                                        && ($i!=(sizeof($v_list)-1))
+                                                && ($i!=0)) {
+                    // ----- Ignore only the double '//' in path,
+                    // but not the first and last /
+                } else {
+                    $v_result = $v_list[$i].($i!=(sizeof($v_list)-1)?'/'
+                                                   .$v_result:'');
+                }
+            }
+        }
+        
+        if (defined('OS_WINDOWS') && OS_WINDOWS) {
+            $v_result = strtr($v_result, '\\', '/');
+        }
+        
+        return $v_result;
+    }
+
+    // }}}
+
+    // {{{ _translateWinPath()
+    function _translateWinPath($p_path, $p_remove_disk_letter=true)
+    {
+      if (defined('OS_WINDOWS') && OS_WINDOWS) {
+          // ----- Look for potential disk letter
+          if (   ($p_remove_disk_letter)
+                     && (($v_position = strpos($p_path, ':')) != false)) {
+              $p_path = substr($p_path, $v_position+1);
+          }
+          // ----- Change potential windows directory separator
+          if ((strpos($p_path, '\\') > 0) || (substr($p_path, 0,1) == '\\')) {
+              $p_path = strtr($p_path, '\\', '/');
+          }
+      }
+      return $p_path;
+    }
+    // }}}
+
+}
+?>
index 693c28d98a07a44d4b6e7fffc8a9d80c02fb895d..67809ec980aee9446f65a3a4efe2d9b4f45a9521 100644 (file)
@@ -7,7 +7,8 @@
  */
 
 OC::$CLASSPATH['OC_Archive'] = 'apps/files_archive/lib/archive.php';
-foreach(array('ZIP') as $type){
+OC::$CLASSPATH['Archive_Tar'] = '3rdparty/Archive/Tar.php';
+foreach(array('ZIP','TAR') as $type){
        OC::$CLASSPATH['OC_Archive_'.$type] = 'apps/files_archive/lib/'.strtolower($type).'.php';
 }
 
diff --git a/apps/files_archive/lib/tar.php b/apps/files_archive/lib/tar.php
new file mode 100644 (file)
index 0000000..40b314a
--- /dev/null
@@ -0,0 +1,271 @@
+<?php
+/**
+ * Copyright (c) 2012 Robin Appelman <icewind@owncloud.com>
+ * This file is licensed under the Affero General Public License version 3 or
+ * later.
+ * See the COPYING-README file.
+ */
+
+class OC_Archive_TAR extends OC_Archive{
+       const PLAIN=0;
+       const GZIP=1;
+       const BZIP=2;
+       
+       /**
+        * @var Archive_Tar tar
+        */
+       private $tar=null;
+       private $headers=array();
+       private $path;
+
+       function __construct($source){
+               $types=array(null,'gz','bz');
+               $this->path=$source;
+               $this->tar=new Archive_Tar($source,$types[self::getTarType($source)]);
+       }
+
+       /**
+        * try to detect the type of tar compression
+        * @param string file
+        * @return str
+        */
+       static public function getTarType($file){
+               if(strpos($file,'.')){
+                       $extention=substr($file,strrpos($file,'.'));
+                       switch($extention){
+                               case 'gz':
+                               case 'tgz':
+                                       return self::GZIP;
+                               case 'bz':
+                               case 'bz2':
+                                       return self::BZIP;
+                               default:
+                                       return self::PLAIN;
+                       }
+               }else{
+                       return self::PLAIN;
+               }
+       }
+
+       /**
+        * add an empty folder to the archive
+        * @param string path
+        * @return bool
+        */
+       function addFolder($path){
+               if(substr($path,-1)!=='/'){
+                       $path.='/';
+               }
+               return $this->tar->add(array($path));
+       }
+       /**
+        * add a file to the archive
+        * @param string path
+        * @param string source either a local file or string data
+        * @return bool
+        */
+       function addFile($path,$source=''){
+               if($this->fileExists($path)){
+                       $this->remove($path);
+               }
+               if(file_exists($source)){
+                       $header=array();
+                       $dummy='';
+                       $this->tar->_openAppend();
+                       $result=$this->tar->_addfile($source,$header,$dummy,$dummy,$path);
+               }else{
+                       $result=$this->tar->addString($path,$source);
+               }
+//             $this->reopen();
+//             var_dump($this->getFiles());
+//             exit();
+               
+               return $result;
+       }
+
+       /**
+        * rename a file or folder in the archive
+        * @param string source
+        * @param string dest
+        * @return bool
+        */
+       function rename($source,$dest){
+               //no proper way to delete, rename entire archive, rename file and remake archive
+               $tmp=OC_Helper::tmpFolder();
+               $this->tar->extract($tmp);
+               rename($tmp.$source,$tmp.$dest);
+               $this->tar=null;
+               unlink($this->path);
+               $types=array(null,'gz','bz');
+               $this->tar=new Archive_Tar($this->path,$types[self::getTarType($this->path)]);
+               $this->tar->createModify(array($tmp),'',$tmp);
+       }
+
+       private function getHeader($file){
+               if(isset($this->headers[$file])){
+                       return $this->headers[$file];
+               }
+               $headers=$this->tar->listContent();
+               foreach($headers as $header){
+                       if($file==$header['filename']){
+                               return $header;
+                       }
+               }
+               return null;
+       }
+       
+       /**
+        * get the uncompressed size of a file in the archive
+        * @param string path
+        * @return int
+        */
+       function filesize($path){
+               $stat=$this->getHeader($path);
+               return $stat['size'];
+       }
+       /**
+        * get the last modified time of a file in the archive
+        * @param string path
+        * @return int
+        */
+       function mtime($path){
+               $stat=$this->getHeader($path);
+               return $stat['mtime'];
+       }
+
+       /**
+        * get the files in a folder
+        * @param path
+        * @return array
+        */
+       function getFolder($path){
+               $files=$this->getFiles();
+               $folderContent=array();
+               $pathLength=strlen($path);
+               foreach($files as $file){
+                       if(substr($file,0,$pathLength)==$path and $file!=$path){
+                               if(strrpos(substr($file,0,-1),'/')<=$pathLength){
+                                       $folderContent[]=substr($file,$pathLength);
+                               }
+                       }
+               }
+               return $folderContent;
+       }
+       /**
+        *get all files in the archive
+        * @return array
+        */
+       function getFiles(){
+               $headers=$this->tar->listContent();
+               $files=array();
+               foreach($headers as $header){
+                       $files[]=$header['filename'];
+               }
+               return $files;
+       }
+       /**
+        * get the content of a file
+        * @param string path
+        * @return string
+        */
+       function getFile($path){
+               return $this->tar->extractInString($path);
+       }
+       /**
+        * extract a single file from the archive
+        * @param string path
+        * @param string dest
+        * @return bool
+        */
+       function extractFile($path,$dest){
+               $tmp=OC_Helper::tmpFolder();
+               $success=$this->tar->extractList(array($path),$tmp);
+               if($success){
+                       rename($tmp.$path,$dest);
+               }
+               OC_Helper::rmdirr($tmp);
+               return $success;
+       }
+       /**
+        * extract the archive
+        * @param string path
+        * @param string dest
+        * @return bool
+        */
+       function extract($dest){
+               return $this->tar->extract($dest);
+       }
+       /**
+        * check if a file or folder exists in the archive
+        * @param string path
+        * @return bool
+        */
+       function fileExists($path){
+               return $this->getHeader($path)!==null;
+       }
+       
+       /**
+        * remove a file or folder from the archive
+        * @param string path
+        * @return bool
+        */
+       function remove($path){
+               //no proper way to delete, extract entire archive, delete file and remake archive
+               $tmp=OC_Helper::tmpFolder();
+               $this->tar->extract($tmp);
+               OC_Helper::rmdirr($tmp.$path);
+               $this->tar=null;
+               unlink($this->path);
+               $this->reopen();
+               $this->tar->createModify(array($tmp),'',$tmp);
+       }
+       /**
+        * get a file handler
+        * @param string path
+        * @param string mode
+        * @return resource
+        */
+       function getStream($path,$mode){
+               if(strrpos($path,'.')!==false){
+                       $ext=substr($path,strrpos($path,'.'));
+               }else{
+                       $ext='';
+               }
+               $tmpFile=OC_Helper::tmpFile($ext);
+               if($this->fileExists($path)){
+                       $this->extractFile($path,$tmpFile);
+               }elseif($mode=='r' or $mode=='rb'){
+                       return false;
+               }
+               if($mode=='r' or $mode=='rb'){
+                       return fopen($tmpFile,$mode);
+               }else{
+                       OC_CloseStreamWrapper::$callBacks[$tmpFile]=array($this,'writeBack');
+                       self::$tempFiles[$tmpFile]=$path;
+                       return fopen('close://'.$tmpFile,$mode);
+               }
+       }
+
+       private static $tempFiles=array();
+       /**
+        * write back temporary files
+        */
+       function writeBack($tmpFile){
+               if(isset(self::$tempFiles[$tmpFile])){
+                       $this->addFile(self::$tempFiles[$tmpFile],$tmpFile);
+                       unlink($tmpFile);
+               }
+       }
+
+       /**
+        * reopen the archive to ensure everything is written
+        */
+       private function reopen(){
+               if($this->tar){
+                       $this->tar->_close();
+                       $this->tar=null;
+               }
+               $types=array(null,'gz','bz');
+               $this->tar=new Archive_Tar($this->path,$types[self::getTarType($this->path)]);
+       }
+}
diff --git a/apps/files_archive/tests/tar.php b/apps/files_archive/tests/tar.php
new file mode 100644 (file)
index 0000000..193a65b
--- /dev/null
@@ -0,0 +1,20 @@
+<?php
+/**
+ * Copyright (c) 2012 Robin Appelman <icewind@owncloud.com>
+ * This file is licensed under the Affero General Public License version 3 or
+ * later.
+ * See the COPYING-README file.
+ */
+
+require_once('archive.php');
+
+class Test_Archive_TAR extends Test_Archive{
+       protected function getExisting(){
+               $dir=OC::$SERVERROOT.'/apps/files_archive/tests/data';
+               return new OC_Archive_TAR($dir.'/data.tar.gz');
+       }
+
+       protected function getNew(){
+               return new OC_Archive_TAR(OC_Helper::tmpFile('.tar.gz'));
+       }
+}