1<?php 2/** 3 * Copyright 2002-2017 Horde LLC (http://www.horde.org/) 4 * 5 * See the enclosed file COPYING for license information (LGPL). If you 6 * did not receive this file, see http://www.horde.org/licenses/lgpl21. 7 * 8 * @author Michael Cochrane <mike@graftonhall.co.nz> 9 * @author Michael Slusarz <slusarz@horde.org> 10 * @author Jan Schneider <jan@horde.org> 11 * @category Horde 12 * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1 13 * @package Compress 14 */ 15 16/** 17 * This class allows tar files to be read. 18 * 19 * @author Michael Cochrane <mike@graftonhall.co.nz> 20 * @author Michael Slusarz <slusarz@horde.org> 21 * @author Jan Schneider <jan@horde.org> 22 * @category Horde 23 * @copyright 2002-2017 Horde LLC 24 * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1 25 * @package Compress 26 */ 27class Horde_Compress_Tar extends Horde_Compress_Base 28{ 29 /** 30 */ 31 public $canCompress = true; 32 33 /** 34 */ 35 public $canDecompress = true; 36 37 /** 38 * Tar file types. 39 * 40 * @var array 41 */ 42 protected $_types = array( 43 0x0 => 'Unix file', 44 0x30 => 'File', 45 0x31 => 'Link', 46 0x32 => 'Symbolic link', 47 0x33 => 'Character special file', 48 0x34 => 'Block special file', 49 0x35 => 'Directory', 50 0x36 => 'FIFO special file', 51 0x37 => 'Contiguous file' 52 ); 53 54 /** 55 * Temporary contents for compressing files. 56 * 57 * @var resource 58 */ 59 protected $_tmp; 60 61 /** 62 * @since Horde_Compress 2.2.0 63 * 64 * @param array $data The data to compress. Requires an array of 65 * arrays. Each subarray should contain these 66 * fields: 67 * - data: (string/resource) The data to compress. 68 * - name: (string) The pathname to the file. 69 * - time: (integer) [optional] The timestamp to use for the file. 70 * - spl: (SplFileInfo) [optional] Complete file information. 71 * @param array $params The parameter array. 72 * - stream: (boolean) If set, return a stream instead of a string. 73 * DEFAULT: Return string 74 * 75 * @return mixed The TAR file as either a string or a stream resource. 76 */ 77 public function compress($data, array $params = array()) 78 { 79 $this->_tmp = fopen('php://temp', 'r+'); 80 81 foreach ($data as $file) { 82 /* Split up long file names. */ 83 $name = str_replace('\\', '/', $file['name']); 84 $prefix = ''; 85 if (strlen($name) > 99) { 86 $prefix = $name; 87 $name = ''; 88 if (strlen($prefix) > 154) { 89 $name = substr($prefix, 154); 90 $prefix = substr($prefix, 0, 154); 91 } 92 } 93 94 /* See if time/date information has been provided. */ 95 $ftime = (isset($file['time'])) ? $file['time'] : null; 96 97 /* "Local file header" segment. */ 98 if (is_resource($file['data'])) { 99 fseek($file['data'], 0, SEEK_END); 100 $length = ftell($file['data']); 101 } else { 102 $length = strlen($file['data']); 103 } 104 105 /* Gather extended information. */ 106 if (isset($file['spl'])) { 107 $isLink = $file['spl']->isLink(); 108 $link = $isLink ? $this->_getLink($file['spl']) : ''; 109 if (function_exists('posix_getpwuid')) { 110 $posix = posix_getpwuid($file['spl']->getOwner()); 111 } 112 $owner = !empty($posix['name']) ? $posix['name'] : ''; 113 114 if (function_exists('posix_getgrgid')) { 115 $posix = posix_getgrgid($file['spl']->getGroup()); 116 } 117 $group = !empty($posix['name']) ? $posix['name'] : ''; 118 } else { 119 $isLink = false; 120 $link = $owner = $group = ''; 121 } 122 123 /* Header data for the file entries. */ 124 $header = 125 pack('a99', $name) . "\0" . /* Name. */ 126 $this->_formatNumber($file, 'getPerms') . /* Permissions. */ 127 $this->_formatNumber($file, 'getOwner') . /* Owner ID. */ 128 $this->_formatNumber($file, 'getGroup') . /* Group ID. */ 129 sprintf("%011o\0", $isLink ? 0 : $length) . /* Size. */ 130 sprintf("%011o\0", $ftime) . /* MTime. */ 131 ' ' . /* Checksum. */ 132 ($isLink ? '1' : '0') . /* Type. */ 133 pack('a99', $link) . "\0" . /* Link target. */ 134 "ustar\0" . "00" . /* Magic marker. */ 135 pack('a31', $owner) . "\0" . /* Owner name. */ 136 pack('a31', $group) . "\0" . /* Group name. */ 137 pack('a16', '') . /* Device numbers. */ 138 pack('a154', $prefix) . "\0"; /* Name prefix. */ 139 $header = pack('a512', $header); 140 $checksum = array_sum(array_map('ord', str_split($header))); 141 $header = substr($header, 0, 148) 142 . sprintf("%06o\0 ", $checksum) 143 . substr($header, 156); 144 145 /* Add this entry to TAR data. */ 146 fwrite($this->_tmp, $header); 147 148 /* "File data" segment. */ 149 if (is_resource($file['data'])) { 150 rewind($file['data']); 151 stream_copy_to_stream($file['data'], $this->_tmp); 152 } else { 153 fwrite($this->_tmp, $file['data']); 154 } 155 156 /* Add 512 byte block padding. */ 157 fwrite($this->_tmp, str_repeat("\0", 512 - ($length % 512))); 158 } 159 160 /* End of archive. */ 161 fwrite($this->_tmp, str_repeat("\0", 1024)); 162 163 rewind($this->_tmp); 164 165 if (empty($params['stream'])) { 166 $out = stream_get_contents($this->_tmp); 167 fclose($this->_tmp); 168 return $out; 169 } 170 171 return $this->_tmp; 172 } 173 174 /** 175 * Returns the relative path of a symbolic link 176 * 177 * @param SplFileInfo $spl An SplFileInfo object. 178 * 179 * @return string The relative path of the symbolic link. 180 */ 181 protected function _getLink($spl) 182 { 183 $ds = DIRECTORY_SEPARATOR; 184 $from = explode($ds, rtrim($spl->getPathname(), $ds)); 185 $to = explode($ds, rtrim($spl->getRealPath(), $ds)); 186 while (count($from) && count($to) && ($from[0] == $to[0])) { 187 array_shift($from); 188 array_shift($to); 189 } 190 return str_repeat('..' . $ds, count($from)) . implode($ds, $to); 191 } 192 193 /** 194 * Formats a number from the file information for the TAR format. 195 * 196 * @param array $file A file hash from compress() that may include a 197 * 'spl' entry with an . 198 * @param string $method The method of the SplFileInfo object that returns 199 * the requested number. 200 * 201 * @return string The correctly formatted number. 202 */ 203 protected function _formatNumber($file, $method) 204 { 205 if (isset($file['spl'])) { 206 return sprintf("%07o\0", $file['spl']->$method()); 207 } 208 return pack('a8', ''); 209 } 210 211 /** 212 * @return array Tar file data: 213 * <pre> 214 * KEY: Position in the array 215 * VALUES: 216 * attr - File attributes 217 * data - Raw file contents 218 * date - File modification time 219 * name - Filename 220 * size - Original file size 221 * type - File type 222 * </pre> 223 * 224 * @throws Horde_Compress_Exception 225 */ 226 public function decompress($data, array $params = array()) 227 { 228 $data_len = strlen($data); 229 $position = 0; 230 $return_array = array(); 231 232 while ($position < $data_len) { 233 if (version_compare(PHP_VERSION, '5.5', '>=')) { 234 $info = @unpack('Z100filename/Z8mode/Z8uid/Z8gid/Z12size/Z12mtime/Z8checksum/Ctypeflag/Z100link/Z6magic/Z2version/Z32uname/Z32gname/Z8devmajor/Z8devminor', substr($data, $position)); 235 } else { 236 $info = @unpack('a100filename/a8mode/a8uid/a8gid/a12size/a12mtime/a8checksum/Ctypeflag/a100link/a6magic/a2version/a32uname/a32gname/a8devmajor/a8devminor', substr($data, $position)); 237 } 238 if (!$info) { 239 throw new Horde_Compress_Exception(Horde_Compress_Translation::t("Unable to decompress data.")); 240 } 241 242 $position += 512; 243 $contents = substr($data, $position, octdec($info['size'])); 244 $position += ceil(octdec($info['size']) / 512) * 512; 245 246 if ($info['filename']) { 247 $file = array( 248 'attr' => null, 249 'data' => null, 250 'date' => octdec($info['mtime']), 251 'name' => trim($info['filename']), 252 'size' => octdec($info['size']), 253 'type' => isset($this->_types[$info['typeflag']]) ? $this->_types[$info['typeflag']] : null 254 ); 255 256 if (($info['typeflag'] == 0) || 257 ($info['typeflag'] == 0x30) || 258 ($info['typeflag'] == 0x35)) { 259 /* File or folder. */ 260 $file['data'] = $contents; 261 262 $mode = hexdec(substr($info['mode'], 4, 3)); 263 $file['attr'] = 264 (($info['typeflag'] == 0x35) ? 'd' : '-') . 265 (($mode & 0x400) ? 'r' : '-') . 266 (($mode & 0x200) ? 'w' : '-') . 267 (($mode & 0x100) ? 'x' : '-') . 268 (($mode & 0x040) ? 'r' : '-') . 269 (($mode & 0x020) ? 'w' : '-') . 270 (($mode & 0x010) ? 'x' : '-') . 271 (($mode & 0x004) ? 'r' : '-') . 272 (($mode & 0x002) ? 'w' : '-') . 273 (($mode & 0x001) ? 'x' : '-'); 274 } 275 276 $return_array[] = $file; 277 } 278 } 279 280 return $return_array; 281 } 282 283} 284