Index: branches/version-2_11-dev/data/module/Tar.php
===================================================================
--- branches/version-2_11-dev/data/module/Tar.php	(revision 20116)
+++ branches/version-2_11-dev/data/module/Tar.php	(revision 21018)
@@ -1,31 +1,53 @@
 <?php
-/* vim: set ts=4 sw=4: */
-// +----------------------------------------------------------------------+
-// | PHP Version 4                                                        |
-// +----------------------------------------------------------------------+
-// | Copyright (c) 1997-2003 The PHP Group                                |
-// +----------------------------------------------------------------------+
-// | This source file is subject to version 3.0 of the PHP license,       |
-// | that is bundled with this package in the file LICENSE, and is        |
-// | available through the world-wide-web at the following url:           |
-// | http://www.php.net/license/3_0.txt.                                  |
-// | If you did not receive a copy of the PHP license and are unable to   |
-// | obtain it through the world-wide-web, please send a note to          |
-// | license@php.net so we can mail you a copy immediately.               |
-// +----------------------------------------------------------------------+
-// | Author: Vincent Blavet <vincent@phpconcept.net>                      |
-// +----------------------------------------------------------------------+
-//
-// $Id$
+/* 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-2008 The Authors
+ * @license     http://www.opensource.org/licenses/bsd-license.php New BSD License
+ * @version     CVS: $Id$
+ * @link        http://pear.php.net/package/Archive_Tar
+ */
 
 if(!defined('TAR_PHP_DIR')) {
 	$TAR_PHP_DIR = realpath(dirname( __FILE__));
-	define("TAR_PHP_DIR", $TAR_PHP_DIR);	
+	define("TAR_PHP_DIR", $TAR_PHP_DIR);
 }
 
 require_once TAR_PHP_DIR . '/PEAR.php';
 
-
 define ('ARCHIVE_TAR_ATT_SEPARATOR', 90001);
+define ('ARCHIVE_TAR_END_BLOCK', pack("a512", ''));
 
 /**
@@ -33,6 +55,7 @@
 *
 * @author   Vincent Blavet <vincent@phpconcept.net>
-* @version  $Revision: 4772 $
-* @package  Archive
+* @version  $Revision: 295988 $
+* @license  http://www.opensource.org/licenses/bsd-license.php New BSD License
+* @package  Archive_Tar
 */
 class Archive_Tar extends PEAR
@@ -67,4 +90,9 @@
     */
     var $_temp_tarname='';
+
+    /**
+    * @var string regular expression for ignoring files or directories
+    */
+    var $_ignore_regexp='';
 
     // {{{ constructor
@@ -123,5 +151,5 @@
                 $this->_compress_type = 'bz2';
             } else {
-                die("Unsupported compression type '$p_compress'\n".
+                $this->_error("Unsupported compression type '$p_compress'\n".
                     "Supported types are 'gz' and 'bz2'.\n");
                 return false;
@@ -139,5 +167,5 @@
             }
             if (!extension_loaded($extname)) {
-                die("The extension '$extname' couldn't be found.\n".
+                $this->_error("The extension '$extname' couldn't be found.\n".
                     "Please make sure your version of PHP was built ".
                     "with '$extname' support.\n");
@@ -183,5 +211,5 @@
     function create($p_filelist)
     {
-		return $this->createModify($p_filelist, '', '');
+        return $this->createModify($p_filelist, '', '');
     }
     // }}}
@@ -382,5 +410,5 @@
     {
         $v_result = true;
-        
+
         if (!$this->_isArchive()) {
             if (!$this->_openWrite()) {
@@ -389,5 +417,5 @@
             $this->_close();
         }
-        
+
         if (!$this->_openAppend())
             return false;
@@ -529,10 +557,10 @@
     {
         $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();
@@ -571,4 +599,34 @@
     // }}}
 
+    // {{{ 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)
@@ -594,5 +652,5 @@
         }
         clearstatcache();
-        return @is_file($p_filename);
+        return @is_file($p_filename) && !@is_link($p_filename);
     }
     // }}}
@@ -604,5 +662,5 @@
             $this->_file = @gzopen($this->_tarname, "wb9");
         else if ($this->_compress_type == 'bz2')
-            $this->_file = @bzopen($this->_tarname, "wb");
+            $this->_file = @bzopen($this->_tarname, "w");
         else if ($this->_compress_type == 'none')
             $this->_file = @fopen($this->_tarname, "wb");
@@ -657,5 +715,5 @@
             $this->_file = @gzopen($v_filename, "rb");
         else if ($this->_compress_type == 'bz2')
-            $this->_file = @bzopen($v_filename, "rb");
+            $this->_file = @bzopen($v_filename, "r");
         else if ($this->_compress_type == 'none')
             $this->_file = @fopen($v_filename, "rb");
@@ -678,7 +736,9 @@
         if ($this->_compress_type == 'gz')
             $this->_file = @gzopen($this->_tarname, "r+b");
-        else if ($this->_compress_type == 'bz2')
-            $this->_file = @bzopen($this->_tarname, "r+b");
-        else if ($this->_compress_type == 'none')
+        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
@@ -803,5 +863,5 @@
 
           if ($this->_compress_type == 'gz') {
-              @gzseek($this->_file, @gztell($this->_file)+($p_len*512));
+              @gzseek($this->_file, gztell($this->_file)+($p_len*512));
           }
           else if ($this->_compress_type == 'bz2') {
@@ -810,5 +870,5 @@
                   $this->_readBlock();
           } else if ($this->_compress_type == 'none')
-              @fseek($this->_file, @ftell($this->_file)+($p_len*512));
+              @fseek($this->_file, $p_len*512, SEEK_CUR);
           else
               $this->_error('Unknown or missing compression type ('
@@ -825,5 +885,5 @@
       if (is_resource($this->_file)) {
           // ----- Write the last 0 filled block for end of archive
-          $v_binary_data = pack("a512", '');
+          $v_binary_data = pack('a1024', '');
           $this->_writeBlock($v_binary_data);
       }
@@ -862,4 +922,10 @@
             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)) {
             $this->_warning("File '$v_filename' does not exist");
@@ -871,5 +937,5 @@
             return false;
 
-        if (@is_dir($v_filename)) {
+        if (@is_dir($v_filename) && !@is_link($v_filename)) {
             if (!($p_hdir = opendir($v_filename))) {
                 $this->_warning("Directory '$v_filename' can not be read");
@@ -979,5 +1045,5 @@
 
       if (!$this->_writeHeaderBlock($p_filename, strlen($p_string),
-	                                0, 0, "", 0, 0))
+	                                  time(), 384, "", 0, 0))
           return false;
 
@@ -1004,29 +1070,43 @@
         }
 
-        $v_info = stat($p_filename);
-        $v_uid = sprintf("%6s ", DecOct($v_info[4]));
-        $v_gid = sprintf("%6s ", DecOct($v_info[5]));
-        $v_perms = sprintf("%6s ", DecOct(fileperms($p_filename)));
-
-        $v_mtime = sprintf("%11s", DecOct(filemtime($p_filename)));
-
-        if (@is_dir($p_filename)) {
+        $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("%11s ", DecOct(0));
+          $v_size = sprintf("%011s", DecOct(0));
         } else {
-          $v_typeflag = '';
+          $v_typeflag = '0';
           clearstatcache();
-          $v_size = sprintf("%11s ", DecOct(filesize($p_filename)));
-        }
-
-        $v_linkname = '';
-
-        $v_magic = '';
-
-        $v_version = '';
-
-        $v_uname = '';
-
-        $v_gname = '';
+          $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 = '';
@@ -1036,5 +1116,5 @@
         $v_prefix = '';
 
-        $v_binary_data_first = pack("a100a8a8a8a12A12",
+        $v_binary_data_first = pack("a100a8a8a8a12a12",
 		                            $v_reduce_filename, $v_perms, $v_uid,
 									$v_gid, $v_size, $v_mtime);
@@ -1060,5 +1140,5 @@
 
         // ----- Write the calculated checksum
-        $v_checksum = sprintf("%6s ", DecOct($v_checksum));
+        $v_checksum = sprintf("%06s ", DecOct($v_checksum));
         $v_binary_data = pack("a8", $v_checksum);
         $this->_writeBlock($v_binary_data, 8);
@@ -1083,12 +1163,12 @@
 
         if ($p_type == "5") {
-          $v_size = sprintf("%11s ", DecOct(0));
+          $v_size = sprintf("%011s", DecOct(0));
         } else {
-          $v_size = sprintf("%11s ", DecOct($p_size));
-        }
-
-        $v_uid = sprintf("%6s ", DecOct($p_uid));
-        $v_gid = sprintf("%6s ", DecOct($p_gid));
-        $v_perms = sprintf("%6s ", DecOct($p_perms));
+          $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));
@@ -1096,11 +1176,21 @@
         $v_linkname = '';
 
-        $v_magic = '';
-
-        $v_version = '';
-
-        $v_uname = '';
-
-        $v_gname = '';
+        $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 = '';
@@ -1134,5 +1224,5 @@
 
         // ----- Write the calculated checksum
-        $v_checksum = sprintf("%6s ", DecOct($v_checksum));
+        $v_checksum = sprintf("%06s ", DecOct($v_checksum));
         $v_binary_data = pack("a8", $v_checksum);
         $this->_writeBlock($v_binary_data, 8);
@@ -1168,5 +1258,5 @@
         $v_prefix = '';
 
-        $v_binary_data_first = pack("a100a8a8a8a12A12",
+        $v_binary_data_first = pack("a100a8a8a8a12a12",
 		                            '././@LongLink', 0, 0, 0, $v_size, 0);
         $v_binary_data_last = pack("a1a100a6a2a32a32a8a8a155a12",
@@ -1191,5 +1281,5 @@
 
         // ----- Write the calculated checksum
-        $v_checksum = sprintf("%6s ", DecOct($v_checksum));
+        $v_checksum = sprintf("%06s ", DecOct($v_checksum));
         $v_binary_data = pack("a8", $v_checksum);
         $this->_writeBlock($v_binary_data, 8);
@@ -1223,4 +1313,7 @@
         }
 
+        if (!is_array($v_header)) {
+            $v_header = array();
+        }
         // ----- Calculate the checksum
         $v_checksum = 0;
@@ -1256,5 +1349,10 @@
 
         // ----- Extract the properties
-        $v_header['filename'] = trim($v_data['filename']);
+        $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']));
@@ -1265,7 +1363,7 @@
           $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[link] = trim($v_data[link]);
         $v_header[magic] = trim($v_data[magic]);
         $v_header[version] = trim($v_data[version]);
@@ -1280,4 +1378,24 @@
     // }}}
 
+    // {{{ _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)
@@ -1291,5 +1409,5 @@
       if (($v_header['size'] % 512) != 0) {
         $v_content = $this->_readBlock();
-        $v_filename .= $v_content;
+        $v_filename .= trim($v_content);
       }
 
@@ -1300,5 +1418,11 @@
         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;
@@ -1501,4 +1625,13 @@
                 }
             }
+          } 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) {
@@ -1521,6 +1654,9 @@
             // ----- Change the file mode, mtime
             @touch($v_header['filename'], $v_header['mtime']);
-            // To be completed
-            //chmod($v_header[filename], DecOct($v_header[mode]));
+            if ($v_header['mode'] & 0111) {
+                // make file executable, obey umask
+                $mode = fileperms($v_header['filename']) | (~umask() & 0111);
+                @chmod($v_header['filename'], $mode);
+            }
           }
 
@@ -1559,4 +1695,7 @@
 
         $p_list_detail[$v_nb++] = $v_header;
+        if (is_array($p_file_list) && (count($p_list_detail) == count($p_file_list))) {
+            return true;
+        }
       }
     }
@@ -1571,5 +1710,5 @@
         if (filesize($this->_tarname) == 0)
           return $this->_openWrite();
-          
+
         if ($this->_compress) {
             $this->_close();
@@ -1585,6 +1724,6 @@
                 $v_temp_tar = @gzopen($this->_tarname.".tmp", "rb");
             elseif ($this->_compress_type == 'bz2')
-                $v_temp_tar = @bzopen($this->_tarname.".tmp", "rb");
-                
+                $v_temp_tar = @bzopen($this->_tarname.".tmp", "r");
+
             if ($v_temp_tar == 0) {
                 $this->_error('Unable to open file \''.$this->_tarname
@@ -1600,14 +1739,13 @@
 
             if ($this->_compress_type == 'gz') {
-                $v_buffer = @gzread($v_temp_tar, 512);
-
-                // ----- Read the following blocks but not the last one
-                if (!@gzeof($v_temp_tar)) {
-                    do{
-                        $v_binary_data = pack("a512", $v_buffer);
-                        $this->_writeBlock($v_binary_data);
-                        $v_buffer = @gzread($v_temp_tar, 512);
-
-                    } while (!@gzeof($v_temp_tar));
+                while (!@gzeof($v_temp_tar)) {
+                    $v_buffer = @gzread($v_temp_tar, 512);
+                    if ($v_buffer == ARCHIVE_TAR_END_BLOCK) {
+                        // 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);
                 }
 
@@ -1615,12 +1753,9 @@
             }
             elseif ($this->_compress_type == 'bz2') {
-                $v_buffered_lines   = array();
-                $v_buffered_lines[] = @bzread($v_temp_tar, 512);
-
-                // ----- Read the following blocks but not the last one
-                while (strlen($v_buffered_lines[]
-				              = @bzread($v_temp_tar, 512)) > 0) {
-                    $v_binary_data = pack("a512",
-					                      array_shift($v_buffered_lines));
+                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);
                 }
@@ -1636,5 +1771,5 @@
         } else {
             // ----- For not compressed tar, just add files before the last
-			//       512 bytes block
+			//       one or two 512 bytes block
             if (!$this->_openReadWrite())
                return false;
@@ -1642,5 +1777,15 @@
             clearstatcache();
             $v_size = filesize($this->_tarname);
-            fseek($this->_file, $v_size-512);
+
+            // 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);
+            }
         }
 
@@ -1654,5 +1799,5 @@
         if (!$this->_openAppend())
             return false;
-            
+
         if ($this->_addList($p_filelist, $p_add_dir, $p_remove_dir))
            $this->_writeFooter();
@@ -1676,4 +1821,5 @@
     function _dirCheck($p_dir)
     {
+        clearstatcache();
         if ((@is_dir($p_dir)) || ($p_dir == ''))
             return true;
@@ -1699,5 +1845,5 @@
 
     /**
-     * Compress path by changing for example "/dir/foo/../bar" to "/dir/bar", 
+     * Compress path by changing for example "/dir/foo/../bar" to "/dir/bar",
      * rand emove double slashes.
      *
@@ -1749,5 +1895,5 @@
     function _translateWinPath($p_path, $p_remove_disk_letter=true)
     {
-      if (OS_WINDOWS) {
+      if (defined('OS_WINDOWS') && OS_WINDOWS) {
           // ----- Look for potential disk letter
           if (   ($p_remove_disk_letter)
