diff core/lib/Drupal/Core/Archiver/ArchiveTar.php @ 17:129ea1e6d783

Update, including to Drupal core 8.6.10
author Chris Cannam
date Thu, 28 Feb 2019 13:21:36 +0000
parents 4c8ae668cc8c
children af1871eacc83
line wrap: on
line diff
--- a/core/lib/Drupal/Core/Archiver/ArchiveTar.php	Tue Jul 10 15:07:59 2018 +0100
+++ b/core/lib/Drupal/Core/Archiver/ArchiveTar.php	Thu Feb 28 13:21:36 2019 +0000
@@ -42,14 +42,14 @@
 
  /**
  * Note on Drupal 8 porting.
- * This file origin is Tar.php, release 1.4.0 (stable) with some code
+ * This file origin is Tar.php, release 1.4.5 (stable) with some code
  * from PEAR.php, release 1.9.5 (stable) both at http://pear.php.net.
  * To simplify future porting from pear of this file, you should not
  * do cosmetic or other non significant changes to this file.
  * The following changes have been done:
  *  Added namespace Drupal\Core\Archiver.
  *  Removed require_once 'PEAR.php'.
- *  Added defintion of OS_WINDOWS taken from PEAR.php.
+ *  Added definition of OS_WINDOWS taken from PEAR.php.
  *  Renamed class to ArchiveTar.
  *  Removed extends PEAR from class.
  *  Removed call parent:: __construct().
@@ -152,6 +152,12 @@
     public $error_object = null;
 
     /**
+     * Format for data extraction
+     *
+     * @var string
+     */
+    public $_fmt ='';
+    /**
      * 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.
@@ -181,7 +187,7 @@
                     if ($data == "\37\213") {
                         $this->_compress = true;
                         $this->_compress_type = 'gz';
-                        // No sure it's enought for a magic code ....
+                        // Not sure it's enough for a magic code ....
                     } elseif ($data == "BZ") {
                         $this->_compress = true;
                         $this->_compress_type = 'bz2';
@@ -257,6 +263,19 @@
                 return false;
             }
         }
+
+
+        if (version_compare(PHP_VERSION, "5.5.0-dev") < 0) {
+            $this->_fmt = "a100filename/a8mode/a8uid/a8gid/a12size/a12mtime/" .
+                   "a8checksum/a1typeflag/a100link/a6magic/a2version/" .
+                   "a32uname/a32gname/a8devmajor/a8devminor/a131prefix";
+        } else {
+            $this->_fmt = "Z100filename/Z8mode/Z8uid/Z8gid/Z12size/Z12mtime/" .
+                   "Z8checksum/Z1typeflag/Z100link/Z6magic/Z2version/" .
+                   "Z32uname/Z32gname/Z8devmajor/Z8devminor/Z131prefix";
+        }
+
+
     }
 
     public function __destruct()
@@ -577,7 +596,7 @@
      * 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
+     * While extracting a file, if the directory path does not exist it is
      * created.
      * While extracting a file, if the file already exists it is replaced
      * without looking for last modification date.
@@ -712,7 +731,7 @@
         }
 
         // ----- Get the arguments
-        $v_att_list = & func_get_args();
+        $v_att_list = func_get_args();
 
         // ----- Read the attributes
         $i = 0;
@@ -1392,10 +1411,22 @@
         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)) {
+        $v_reduced_filename = $this->_pathReduction($p_stored_filename);
+
+        if (strlen($v_reduced_filename) > 99) {
+            if (!$this->_writeLongHeader($v_reduced_filename, false)) {
+                return false;
+            }
+        }
+
+        $v_linkname = '';
+        if (@is_link($p_filename)) {
+            $v_linkname = readlink($p_filename);
+        }
+
+        if (strlen($v_linkname) > 99) {
+            if (!$this->_writeLongHeader($v_linkname, true)) {
                 return false;
             }
         }
@@ -1404,14 +1435,10 @@
         $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";
@@ -1423,7 +1450,6 @@
         }
 
         $v_magic = 'ustar ';
-
         $v_version = ' ';
 
         if (function_exists('posix_getpwuid')) {
@@ -1438,14 +1464,12 @@
         }
 
         $v_devmajor = '';
-
         $v_devminor = '';
-
         $v_prefix = '';
 
         $v_binary_data_first = pack(
             "a100a8a8a8a12a12",
-            $v_reduce_filename,
+            $v_reduced_filename,
             $v_perms,
             $v_uid,
             $v_gid,
@@ -1485,7 +1509,7 @@
         $this->_writeBlock($v_binary_data_first, 148);
 
         // ----- Write the calculated checksum
-        $v_checksum = sprintf("%06s ", DecOct($v_checksum));
+        $v_checksum = sprintf("%06s\0 ", DecOct($v_checksum));
         $v_binary_data = pack("a8", $v_checksum);
         $this->_writeBlock($v_binary_data, 8);
 
@@ -1517,7 +1541,7 @@
         $p_filename = $this->_pathReduction($p_filename);
 
         if (strlen($p_filename) > 99) {
-            if (!$this->_writeLongHeader($p_filename)) {
+            if (!$this->_writeLongHeader($p_filename, false)) {
                 return false;
             }
         }
@@ -1613,36 +1637,31 @@
      * @param string $p_filename
      * @return bool
      */
-    public function _writeLongHeader($p_filename)
+    public function _writeLongHeader($p_filename, $is_link = false)
     {
-        $v_size = sprintf("%11s ", DecOct(strlen($p_filename)));
-
-        $v_typeflag = 'L';
-
+        $v_uid = sprintf("%07s", 0);
+        $v_gid = sprintf("%07s", 0);
+        $v_perms = sprintf("%07s", 0);
+        $v_size = sprintf("%'011s", DecOct(strlen($p_filename)));
+        $v_mtime = sprintf("%011s", 0);
+        $v_typeflag = ($is_link ? 'K' : 'L');
         $v_linkname = '';
-
-        $v_magic = '';
-
-        $v_version = '';
-
+        $v_magic = 'ustar ';
+        $v_version = ' ';
         $v_uname = '';
-
         $v_gname = '';
-
         $v_devmajor = '';
-
         $v_devminor = '';
-
         $v_prefix = '';
 
         $v_binary_data_first = pack(
             "a100a8a8a8a12a12",
             '././@LongLink',
-            0,
-            0,
-            0,
+            $v_perms,
+            $v_uid,
+            $v_gid,
             $v_size,
-            0
+            $v_mtime
         );
         $v_binary_data_last = pack(
             "a1a100a6a2a32a32a8a8a155a12",
@@ -1677,7 +1696,7 @@
         $this->_writeBlock($v_binary_data_first, 148);
 
         // ----- Write the calculated checksum
-        $v_checksum = sprintf("%06s ", DecOct($v_checksum));
+        $v_checksum = sprintf("%06s\0 ", DecOct($v_checksum));
         $v_binary_data = pack("a8", $v_checksum);
         $this->_writeBlock($v_binary_data, 8);
 
@@ -1718,28 +1737,13 @@
         // ----- 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_binary_split = str_split($v_binary_data);
+        $v_checksum += array_sum(array_map('ord', array_slice($v_binary_split, 0, 148)));
+        $v_checksum += array_sum(array_map('ord', array(' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ',)));
+        $v_checksum += array_sum(array_map('ord', array_slice($v_binary_split, 156, 512)));
 
-        if (version_compare(PHP_VERSION, "5.5.0-dev") < 0) {
-            $fmt = "a100filename/a8mode/a8uid/a8gid/a12size/a12mtime/" .
-                "a8checksum/a1typeflag/a100link/a6magic/a2version/" .
-                "a32uname/a32gname/a8devmajor/a8devminor/a131prefix";
-        } else {
-            $fmt = "Z100filename/Z8mode/Z8uid/Z8gid/Z12size/Z12mtime/" .
-                "Z8checksum/Z1typeflag/Z100link/Z6magic/Z2version/" .
-                "Z32uname/Z32gname/Z8devmajor/Z8devminor/Z131prefix";
-        }
-        $v_data = unpack($fmt, $v_binary_data);
+
+        $v_data = unpack($this->_fmt, $v_binary_data);
 
         if (strlen($v_data["prefix"]) > 0) {
             $v_data["filename"] = "$v_data[prefix]/$v_data[filename]";
@@ -1775,7 +1779,7 @@
         $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['size'] = $this->_tarRecToSize($v_data['size']);
         $v_header['mtime'] = OctDec(trim($v_data['mtime']));
         if (($v_header['typeflag'] = $v_data['typeflag']) == "5") {
             $v_header['size'] = 0;
@@ -1795,6 +1799,40 @@
     }
 
     /**
+     * Convert Tar record size to actual size
+     *
+     * @param string $tar_size
+     * @return size of tar record in bytes
+     */
+    private function _tarRecToSize($tar_size)
+    {
+        /*
+         * First byte of size has a special meaning if bit 7 is set.
+         *
+         * Bit 7 indicates base-256 encoding if set.
+         * Bit 6 is the sign bit.
+         * Bits 5:0 are most significant value bits.
+         */
+        $ch = ord($tar_size[0]);
+        if ($ch & 0x80) {
+            // Full 12-bytes record is required.
+            $rec_str = $tar_size . "\x00";
+
+            $size = ($ch & 0x40) ? -1 : 0;
+            $size = ($size << 6) | ($ch & 0x3f);
+
+            for ($num_ch = 1; $num_ch < 12; ++$num_ch) {
+                $size = ($size * 256) + ord($rec_str[$num_ch]);
+            }
+
+            return $size;
+
+        } else {
+            return OctDec(trim($tar_size));
+        }
+    }
+
+    /**
      * Detect and report a malicious file name
      *
      * @param string $file
@@ -1803,10 +1841,13 @@
      */
     private function _maliciousFilename($file)
     {
-        if (strpos($file, '/../') !== false) {
+        if (strpos($file, 'phar://') === 0) {
             return true;
         }
-        if (strpos($file, '../') === 0) {
+        if (strpos($file, DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR) !== false) {
+            return true;
+        }
+        if (strpos($file, '..' . DIRECTORY_SEPARATOR) === 0) {
             return true;
         }
         return false;
@@ -1871,11 +1912,20 @@
                 continue;
             }
 
-            // ----- Look for long filename
-            if ($v_header['typeflag'] == 'L') {
-                if (!$this->_readLongHeader($v_header)) {
-                    return null;
-                }
+            switch ($v_header['typeflag']) {
+                case 'L': {
+                    if (!$this->_readLongHeader($v_header)) {
+                        return null;
+                    }
+                } break;
+
+                case 'K': {
+                    $v_link_header = $v_header;
+                    if (!$this->_readLongHeader($v_link_header)) {
+                        return null;
+                    }
+                    $v_header['link'] = $v_link_header['filename'];
+                } break;
             }
 
             if ($v_header['filename'] == $p_filename) {
@@ -1976,11 +2026,20 @@
                 continue;
             }
 
-            // ----- Look for long filename
-            if ($v_header['typeflag'] == 'L') {
-                if (!$this->_readLongHeader($v_header)) {
-                    return false;
-                }
+            switch ($v_header['typeflag']) {
+                case 'L': {
+                    if (!$this->_readLongHeader($v_header)) {
+                        return null;
+                    }
+                } break;
+
+                case 'K': {
+                    $v_link_header = $v_header;
+                    if (!$this->_readLongHeader($v_link_header)) {
+                        return null;
+                    }
+                    $v_header['link'] = $v_link_header['filename'];
+                } break;
             }
 
             // ignore extended / pax headers
@@ -2385,7 +2444,7 @@
 
     /**
      * Compress path by changing for example "/dir/foo/../bar" to "/dir/bar",
-     * rand emove double slashes.
+     * and remove double slashes.
      *
      * @param string $p_dir path to reduce
      *