1<?php 2/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */ 3 4/** 5 * Read a tar archive 6 * 7 * PHP versions 4 and 5 8 * 9 * This library is free software; you can redistribute it and/or 10 * modify it under the terms of the GNU Lesser General Public 11 * License as published by the Free Software Foundation; either 12 * version 2.1 of the License, or (at your option) any later version. 13 * 14 * This library is distributed in the hope that it will be useful, 15 * but WITHOUT ANY WARRANTY; without even the implied warranty of 16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 17 * Lesser General Public License for more details. 18 * 19 * You should have received a copy of the GNU Lesser General Public 20 * License along with this library; if not, write to the Free Software 21 * Foundation, Inc., 59 Temple Place, Suite 330,Boston,MA 02111-1307 USA 22 * 23 * @category File Formats 24 * @package File_Archive 25 * @author Vincent Lascaux <vincentlascaux@php.net> 26 * @copyright 1997-2005 The PHP Group 27 * @license http://www.gnu.org/copyleft/lesser.html LGPL 28 * @version CVS: $Id: Tar.php,v 1.29 2005/07/11 11:53:53 vincentlascaux Exp $ 29 * @link http://pear.php.net/package/File_Archive 30 */ 31 32require_once "File/Archive/Reader/Archive.php"; 33 34/** 35 * Read a tar archive 36 */ 37class File_Archive_Reader_Tar extends File_Archive_Reader_Archive 38{ 39 /** 40 * @var String Name of the file being read 41 * @access private 42 */ 43 var $currentFilename = null; 44 /** 45 * @var Array Stats of the file being read 46 * In TAR reader, indexes 2, 4, 5, 7, 9 are set 47 * @access private 48 */ 49 var $currentStat = null; 50 /** 51 * @var int Number of bytes that still have to be read before the end of 52 * file 53 * @access private 54 */ 55 var $leftLength = 0; 56 /** 57 * @var int Size of the footer 58 * A TAR file is made of chunks of 512 bytes. If 512 does not 59 * divide the file size a footer is added 60 * @access private 61 */ 62 var $footerLength = 0; 63 /** 64 * @var int nb bytes to seek back in order to reach the end of the archive 65 * or null if the end of the archive has not been reached 66 */ 67 var $seekToEnd = null; 68 69 /** 70 * @see File_Archive_Reader::skip() 71 */ 72 function skip($length = -1) 73 { 74 if ($length == -1) { 75 $length = $this->leftLength; 76 } else { 77 $length = min($this->leftLength, $length); 78 } 79 $skipped = $this->source->skip($length); 80 if (!PEAR::isError($skipped)) { 81 $this->leftLength -= $skipped; 82 } 83 return $skipped; 84 } 85 86 /** 87 * @see File_Archive_Reader::rewind() 88 */ 89 function rewind($length = -1) 90 { 91 if ($length == -1) { 92 $length = $this->currentStat[7] - $this->leftLength; 93 } else { 94 $length = min($length, $this->currentStat[7] - $this->leftLength); 95 } 96 $rewinded = $this->source->rewind($length); 97 if (!PEAR::isError($rewinded)) { 98 $this->leftLength += $rewinded; 99 } 100 return $rewinded; 101 } 102 103 /** 104 * @see File_Archive_Reader::tell() 105 */ 106 function tell() 107 { 108 return $this->currentStat[7] - $this->leftLength; 109 } 110 111 /** 112 * @see File_Archive_Reader::close() 113 */ 114 function close() 115 { 116 $this->leftLength = 0; 117 $this->currentFilename = null; 118 $this->currentStat = null; 119 $this->seekToEnd = null; 120 return parent::close(); 121 } 122 123 /** 124 * @see File_Archive_Reader::getFilename() 125 */ 126 function getFilename() { return $this->currentFilename; } 127 /** 128 * @see File_Archive_Reader::getStat() 129 */ 130 function getStat() { return $this->currentStat; } 131 132 /** 133 * @see File_Archive_Reader::next() 134 */ 135 function next() 136 { 137 $error = parent::next(); 138 if ($error !== true) { 139 return $error; 140 } 141 if ($this->seekToEnd !== null) { 142 return false; 143 } 144 145 do 146 { 147 $error = $this->source->skip($this->leftLength + $this->footerLength); 148 if (PEAR::isError($error)) { 149 return $error; 150 } 151 $rawHeader = $this->source->getData(512); 152 if (PEAR::isError($rawHeader)) { 153 return $rawHeader; 154 } 155 if (strlen($rawHeader)<512 || $rawHeader == pack("a512", "")) { 156 $this->seekToEnd = strlen($rawHeader); 157 $this->currentFilename = null; 158 return false; 159 } 160 161 $header = unpack( 162 "a100filename/a8mode/a8uid/a8gid/a12size/a12mtime/". 163 "a8checksum/a1type/a100linkname/a6magic/a2version/". 164 "a32uname/a32gname/a8devmajor/a8devminor/a155prefix", 165 $rawHeader); 166 $this->currentStat = array( 167 2 => octdec($header['mode']), 168 4 => octdec($header['uid']), 169 5 => octdec($header['gid']), 170 7 => octdec($header['size']), 171 9 => octdec($header['mtime']) 172 ); 173 $this->currentStat['mode'] = $this->currentStat[2]; 174 $this->currentStat['uid'] = $this->currentStat[4]; 175 $this->currentStat['gid'] = $this->currentStat[5]; 176 $this->currentStat['size'] = $this->currentStat[7]; 177 $this->currentStat['mtime'] = $this->currentStat[9]; 178 179 if ($header['magic'] == 'ustar') { 180 $this->currentFilename = $this->getStandardURL( 181 $header['prefix'] . $header['filename'] 182 ); 183 } else { 184 $this->currentFilename = $this->getStandardURL( 185 $header['filename'] 186 ); 187 } 188 189 $this->leftLength = $this->currentStat[7]; 190 if ($this->leftLength % 512 == 0) { 191 $this->footerLength = 0; 192 } else { 193 $this->footerLength = 512 - $this->leftLength%512; 194 } 195 196 $checksum = 8*ord(" "); 197 for ($i = 0; $i < 148; $i++) { 198 $checksum += ord($rawHeader{$i}); 199 } 200 for ($i = 156; $i < 512; $i++) { 201 $checksum += ord($rawHeader{$i}); 202 } 203 204 if (octdec($header['checksum']) != $checksum) { 205 die('Checksum error on entry '.$this->currentFilename); 206 } 207 } while ($header['type'] != 0); 208 209 return true; 210 } 211 212 /** 213 * @see File_Archive_Reader::getData() 214 */ 215 function getData($length = -1) 216 { 217 if ($length == -1) { 218 $actualLength = $this->leftLength; 219 } else { 220 $actualLength = min($this->leftLength, $length); 221 } 222 223 if ($this->leftLength == 0) { 224 return null; 225 } else { 226 $data = $this->source->getData($actualLength); 227 if (strlen($data) != $actualLength) { 228 return PEAR::raiseError('Unexpected end of tar archive'); 229 } 230 $this->leftLength -= $actualLength; 231 return $data; 232 } 233 } 234 235 /** 236 * @see File_Archive_Reader::makeWriterRemoveFiles() 237 */ 238 function makeWriterRemoveFiles($pred) 239 { 240 require_once "File/Archive/Writer/Tar.php"; 241 242 $blocks = array(); 243 $seek = null; 244 $gap = 0; 245 if ($this->currentFilename !== null && $pred->isTrue($this)) { 246 $seek = 512 + $this->currentStat[7] + $this->footerLength; 247 $blocks[] = $seek; //Remove this file 248 } 249 250 while (($error = $this->next()) === true) { 251 $size = 512 + $this->currentStat[7] + $this->footerLength; 252 if ($pred->isTrue($this)) { 253 if ($seek === null) { 254 $seek = $size; 255 $blocks[] = $size; 256 } else if ($gap > 0) { 257 $blocks[] = $gap; //Don't remove the files between the gap 258 $blocks[] = $size; 259 $seek += $size; 260 } else { 261 $blocks[count($blocks)-1] += $size; //Also remove this file 262 $seek += $size; 263 } 264 $gap = 0; 265 } else { 266 if ($seek !== null) { 267 $seek += $size; 268 $gap += $size; 269 } 270 } 271 } 272 if ($seek === null) { 273 $seek = $this->seekToEnd; 274 } else { 275 $seek += $this->seekToEnd; 276 if ($gap == 0) { 277 array_pop($blocks); 278 } else { 279 $blocks[] = $gap; 280 } 281 } 282 283 $writer = new File_Archive_Writer_Tar(null, 284 $this->source->makeWriterRemoveBlocks($blocks, -$seek) 285 ); 286 $this->close(); 287 return $writer; 288 } 289 290 /** 291 * @see File_Archive_Reader::makeWriterRemoveBlocks() 292 */ 293 function makeWriterRemoveBlocks($blocks, $seek = 0) 294 { 295 if ($this->seekToEnd !== null || $this->currentStat === null) { 296 return PEAR::raiseError('No file selected'); 297 } 298 299 $blockPos = $this->currentStat[7] - $this->leftLength + $seek; 300 301 $this->rewind(); 302 $keep = false; 303 304 $data = $this->getData($blockPos); 305 foreach ($blocks as $length) { 306 if ($keep) { 307 $data .= $this->getData($length); 308 } else { 309 $this->skip($length); 310 } 311 $keep = !$keep; 312 } 313 if ($keep) { 314 $data .= $this->getData(); 315 } 316 317 $filename = $this->currentFilename; 318 $stat = $this->currentStat; 319 320 $writer = $this->makeWriterRemove(); 321 if (PEAR::isError($writer)) { 322 return $writer; 323 } 324 325 unset($stat[7]); 326 $stat[9] = $stat['mtime'] = time(); 327 $writer->newFile($filename, $stat); 328 $writer->writeData($data); 329 return $writer; 330 } 331 332 /** 333 * @see File_Archive_Reader::makeAppendWriter 334 */ 335 function makeAppendWriter() 336 { 337 require_once "File/Archive/Writer/Tar.php"; 338 339 while (($error = $this->next()) === true) { } 340 if (PEAR::isError($error)) { 341 $this->close(); 342 return $error; 343 } 344 345 $innerWriter = $this->source->makeWriterRemoveBlocks(array(), -$this->seekToEnd); 346 if (PEAR::isError($innerWriter)) { 347 return $innerWriter; 348 } 349 350 $this->close(); 351 return new File_Archive_Writer_Tar(null, $innerWriter); 352 } 353} 354 355?>