1*ab8e5256SAndreas Gohr<?php 2*ab8e5256SAndreas Gohr 3*ab8e5256SAndreas Gohrnamespace splitbrain\PHPArchive; 4*ab8e5256SAndreas Gohr 5*ab8e5256SAndreas Gohr/** 6*ab8e5256SAndreas Gohr * Class Tar 7*ab8e5256SAndreas Gohr * 8*ab8e5256SAndreas Gohr * Creates or extracts Tar archives. Supports gz and bzip compression 9*ab8e5256SAndreas Gohr * 10*ab8e5256SAndreas Gohr * Long pathnames (>100 chars) are supported in POSIX ustar and GNU longlink formats. 11*ab8e5256SAndreas Gohr * 12*ab8e5256SAndreas Gohr * @author Andreas Gohr <andi@splitbrain.org> 13*ab8e5256SAndreas Gohr * @package splitbrain\PHPArchive 14*ab8e5256SAndreas Gohr * @license MIT 15*ab8e5256SAndreas Gohr */ 16*ab8e5256SAndreas Gohrclass Tar extends Archive 17*ab8e5256SAndreas Gohr{ 18*ab8e5256SAndreas Gohr const READ_CHUNK_SIZE = 1048576; // 1MB 19*ab8e5256SAndreas Gohr 20*ab8e5256SAndreas Gohr protected $file = ''; 21*ab8e5256SAndreas Gohr protected $comptype = Archive::COMPRESS_AUTO; 22*ab8e5256SAndreas Gohr protected $complevel = 9; 23*ab8e5256SAndreas Gohr protected $fh; 24*ab8e5256SAndreas Gohr protected $memory = ''; 25*ab8e5256SAndreas Gohr protected $closed = true; 26*ab8e5256SAndreas Gohr protected $writeaccess = false; 27*ab8e5256SAndreas Gohr protected $position = 0; 28*ab8e5256SAndreas Gohr protected $contentUntil = 0; 29*ab8e5256SAndreas Gohr protected $skipUntil = 0; 30*ab8e5256SAndreas Gohr 31*ab8e5256SAndreas Gohr /** 32*ab8e5256SAndreas Gohr * Sets the compression to use 33*ab8e5256SAndreas Gohr * 34*ab8e5256SAndreas Gohr * @param int $level Compression level (0 to 9) 35*ab8e5256SAndreas Gohr * @param int $type Type of compression to use (use COMPRESS_* constants) 36*ab8e5256SAndreas Gohr * @throws ArchiveIllegalCompressionException 37*ab8e5256SAndreas Gohr */ 38*ab8e5256SAndreas Gohr public function setCompression($level = 9, $type = Archive::COMPRESS_AUTO) 39*ab8e5256SAndreas Gohr { 40*ab8e5256SAndreas Gohr $this->compressioncheck($type); 41*ab8e5256SAndreas Gohr if ($level < -1 || $level > 9) { 42*ab8e5256SAndreas Gohr throw new ArchiveIllegalCompressionException('Compression level should be between -1 and 9'); 43*ab8e5256SAndreas Gohr } 44*ab8e5256SAndreas Gohr $this->comptype = $type; 45*ab8e5256SAndreas Gohr $this->complevel = $level; 46*ab8e5256SAndreas Gohr if($level == 0) $this->comptype = Archive::COMPRESS_NONE; 47*ab8e5256SAndreas Gohr if($type == Archive::COMPRESS_NONE) $this->complevel = 0; 48*ab8e5256SAndreas Gohr } 49*ab8e5256SAndreas Gohr 50*ab8e5256SAndreas Gohr /** 51*ab8e5256SAndreas Gohr * Open an existing TAR file for reading 52*ab8e5256SAndreas Gohr * 53*ab8e5256SAndreas Gohr * @param string $file 54*ab8e5256SAndreas Gohr * @throws ArchiveIOException 55*ab8e5256SAndreas Gohr * @throws ArchiveIllegalCompressionException 56*ab8e5256SAndreas Gohr */ 57*ab8e5256SAndreas Gohr public function open($file) 58*ab8e5256SAndreas Gohr { 59*ab8e5256SAndreas Gohr $this->file = $file; 60*ab8e5256SAndreas Gohr 61*ab8e5256SAndreas Gohr // update compression to mach file 62*ab8e5256SAndreas Gohr if ($this->comptype == Tar::COMPRESS_AUTO) { 63*ab8e5256SAndreas Gohr $this->setCompression($this->complevel, $this->filetype($file)); 64*ab8e5256SAndreas Gohr } 65*ab8e5256SAndreas Gohr 66*ab8e5256SAndreas Gohr // open file handles 67*ab8e5256SAndreas Gohr if ($this->comptype === Archive::COMPRESS_GZIP) { 68*ab8e5256SAndreas Gohr $this->fh = @gzopen($this->file, 'rb'); 69*ab8e5256SAndreas Gohr } elseif ($this->comptype === Archive::COMPRESS_BZIP) { 70*ab8e5256SAndreas Gohr $this->fh = @bzopen($this->file, 'r'); 71*ab8e5256SAndreas Gohr } else { 72*ab8e5256SAndreas Gohr $this->fh = @fopen($this->file, 'rb'); 73*ab8e5256SAndreas Gohr } 74*ab8e5256SAndreas Gohr 75*ab8e5256SAndreas Gohr if (!$this->fh) { 76*ab8e5256SAndreas Gohr throw new ArchiveIOException('Could not open file for reading: '.$this->file); 77*ab8e5256SAndreas Gohr } 78*ab8e5256SAndreas Gohr $this->closed = false; 79*ab8e5256SAndreas Gohr $this->position = 0; 80*ab8e5256SAndreas Gohr } 81*ab8e5256SAndreas Gohr 82*ab8e5256SAndreas Gohr /** 83*ab8e5256SAndreas Gohr * Read the contents of a TAR archive 84*ab8e5256SAndreas Gohr * 85*ab8e5256SAndreas Gohr * This function lists the files stored in the archive 86*ab8e5256SAndreas Gohr * 87*ab8e5256SAndreas Gohr * The archive is closed afer reading the contents, because rewinding is not possible in bzip2 streams. 88*ab8e5256SAndreas Gohr * Reopen the file with open() again if you want to do additional operations 89*ab8e5256SAndreas Gohr * 90*ab8e5256SAndreas Gohr * @throws ArchiveIOException 91*ab8e5256SAndreas Gohr * @throws ArchiveCorruptedException 92*ab8e5256SAndreas Gohr * @returns FileInfo[] 93*ab8e5256SAndreas Gohr */ 94*ab8e5256SAndreas Gohr public function contents() 95*ab8e5256SAndreas Gohr { 96*ab8e5256SAndreas Gohr $result = array(); 97*ab8e5256SAndreas Gohr 98*ab8e5256SAndreas Gohr foreach ($this->yieldContents() as $fileinfo) { 99*ab8e5256SAndreas Gohr $result[] = $fileinfo; 100*ab8e5256SAndreas Gohr } 101*ab8e5256SAndreas Gohr 102*ab8e5256SAndreas Gohr return $result; 103*ab8e5256SAndreas Gohr } 104*ab8e5256SAndreas Gohr 105*ab8e5256SAndreas Gohr /** 106*ab8e5256SAndreas Gohr * Read the contents of a TAR archive and return each entry using yield 107*ab8e5256SAndreas Gohr * for memory efficiency. 108*ab8e5256SAndreas Gohr * 109*ab8e5256SAndreas Gohr * @see contents() 110*ab8e5256SAndreas Gohr * @throws ArchiveIOException 111*ab8e5256SAndreas Gohr * @throws ArchiveCorruptedException 112*ab8e5256SAndreas Gohr * @returns FileInfo[] 113*ab8e5256SAndreas Gohr */ 114*ab8e5256SAndreas Gohr public function yieldContents() 115*ab8e5256SAndreas Gohr { 116*ab8e5256SAndreas Gohr if ($this->closed || !$this->file) { 117*ab8e5256SAndreas Gohr throw new ArchiveIOException('Can not read from a closed archive'); 118*ab8e5256SAndreas Gohr } 119*ab8e5256SAndreas Gohr 120*ab8e5256SAndreas Gohr while ($read = $this->readbytes(512)) { 121*ab8e5256SAndreas Gohr $header = $this->parseHeader($read); 122*ab8e5256SAndreas Gohr if (!is_array($header)) { 123*ab8e5256SAndreas Gohr continue; 124*ab8e5256SAndreas Gohr } 125*ab8e5256SAndreas Gohr 126*ab8e5256SAndreas Gohr $this->contentUntil = $this->position + $header['size']; 127*ab8e5256SAndreas Gohr $this->skipUntil = $this->position + ceil($header['size'] / 512) * 512; 128*ab8e5256SAndreas Gohr 129*ab8e5256SAndreas Gohr yield $this->header2fileinfo($header); 130*ab8e5256SAndreas Gohr 131*ab8e5256SAndreas Gohr $skip = $this->skipUntil - $this->position; 132*ab8e5256SAndreas Gohr if ($skip > 0) { 133*ab8e5256SAndreas Gohr $this->skipbytes($skip); 134*ab8e5256SAndreas Gohr } 135*ab8e5256SAndreas Gohr } 136*ab8e5256SAndreas Gohr 137*ab8e5256SAndreas Gohr $this->close(); 138*ab8e5256SAndreas Gohr } 139*ab8e5256SAndreas Gohr 140*ab8e5256SAndreas Gohr /** 141*ab8e5256SAndreas Gohr * Reads content of a current archive entry. 142*ab8e5256SAndreas Gohr * 143*ab8e5256SAndreas Gohr * Works only when iterating trough the archive using the generator returned 144*ab8e5256SAndreas Gohr * by the yieldContents(). 145*ab8e5256SAndreas Gohr * 146*ab8e5256SAndreas Gohr * @param int $length maximum number of bytes to read 147*ab8e5256SAndreas Gohr * 148*ab8e5256SAndreas Gohr * @return string 149*ab8e5256SAndreas Gohr */ 150*ab8e5256SAndreas Gohr public function readCurrentEntry($length = PHP_INT_MAX) 151*ab8e5256SAndreas Gohr { 152*ab8e5256SAndreas Gohr $length = (int) min($length, $this->contentUntil - $this->position); 153*ab8e5256SAndreas Gohr if ($length === 0) { 154*ab8e5256SAndreas Gohr return ''; 155*ab8e5256SAndreas Gohr } 156*ab8e5256SAndreas Gohr return $this->readbytes($length); 157*ab8e5256SAndreas Gohr } 158*ab8e5256SAndreas Gohr 159*ab8e5256SAndreas Gohr /** 160*ab8e5256SAndreas Gohr * Extract an existing TAR archive 161*ab8e5256SAndreas Gohr * 162*ab8e5256SAndreas Gohr * The $strip parameter allows you to strip a certain number of path components from the filenames 163*ab8e5256SAndreas Gohr * found in the tar file, similar to the --strip-components feature of GNU tar. This is triggered when 164*ab8e5256SAndreas Gohr * an integer is passed as $strip. 165*ab8e5256SAndreas Gohr * Alternatively a fixed string prefix may be passed in $strip. If the filename matches this prefix, 166*ab8e5256SAndreas Gohr * the prefix will be stripped. It is recommended to give prefixes with a trailing slash. 167*ab8e5256SAndreas Gohr * 168*ab8e5256SAndreas Gohr * By default this will extract all files found in the archive. You can restrict the output using the $include 169*ab8e5256SAndreas Gohr * and $exclude parameter. Both expect a full regular expression (including delimiters and modifiers). If 170*ab8e5256SAndreas Gohr * $include is set only files that match this expression will be extracted. Files that match the $exclude 171*ab8e5256SAndreas Gohr * expression will never be extracted. Both parameters can be used in combination. Expressions are matched against 172*ab8e5256SAndreas Gohr * stripped filenames as described above. 173*ab8e5256SAndreas Gohr * 174*ab8e5256SAndreas Gohr * The archive is closed afer reading the contents, because rewinding is not possible in bzip2 streams. 175*ab8e5256SAndreas Gohr * Reopen the file with open() again if you want to do additional operations 176*ab8e5256SAndreas Gohr * 177*ab8e5256SAndreas Gohr * @param string $outdir the target directory for extracting 178*ab8e5256SAndreas Gohr * @param int|string $strip either the number of path components or a fixed prefix to strip 179*ab8e5256SAndreas Gohr * @param string $exclude a regular expression of files to exclude 180*ab8e5256SAndreas Gohr * @param string $include a regular expression of files to include 181*ab8e5256SAndreas Gohr * @throws ArchiveIOException 182*ab8e5256SAndreas Gohr * @throws ArchiveCorruptedException 183*ab8e5256SAndreas Gohr * @return FileInfo[] 184*ab8e5256SAndreas Gohr */ 185*ab8e5256SAndreas Gohr public function extract($outdir, $strip = '', $exclude = '', $include = '') 186*ab8e5256SAndreas Gohr { 187*ab8e5256SAndreas Gohr if ($this->closed || !$this->file) { 188*ab8e5256SAndreas Gohr throw new ArchiveIOException('Can not read from a closed archive'); 189*ab8e5256SAndreas Gohr } 190*ab8e5256SAndreas Gohr 191*ab8e5256SAndreas Gohr $outdir = rtrim($outdir, '/'); 192*ab8e5256SAndreas Gohr @mkdir($outdir, 0777, true); 193*ab8e5256SAndreas Gohr if (!is_dir($outdir)) { 194*ab8e5256SAndreas Gohr throw new ArchiveIOException("Could not create directory '$outdir'"); 195*ab8e5256SAndreas Gohr } 196*ab8e5256SAndreas Gohr 197*ab8e5256SAndreas Gohr $extracted = array(); 198*ab8e5256SAndreas Gohr while ($dat = $this->readbytes(512)) { 199*ab8e5256SAndreas Gohr // read the file header 200*ab8e5256SAndreas Gohr $header = $this->parseHeader($dat); 201*ab8e5256SAndreas Gohr if (!is_array($header)) { 202*ab8e5256SAndreas Gohr continue; 203*ab8e5256SAndreas Gohr } 204*ab8e5256SAndreas Gohr $fileinfo = $this->header2fileinfo($header); 205*ab8e5256SAndreas Gohr 206*ab8e5256SAndreas Gohr // apply strip rules 207*ab8e5256SAndreas Gohr $fileinfo->strip($strip); 208*ab8e5256SAndreas Gohr 209*ab8e5256SAndreas Gohr // skip unwanted files 210*ab8e5256SAndreas Gohr if (!strlen($fileinfo->getPath()) || !$fileinfo->matchExpression($include, $exclude)) { 211*ab8e5256SAndreas Gohr $this->skipbytes(ceil($header['size'] / 512) * 512); 212*ab8e5256SAndreas Gohr continue; 213*ab8e5256SAndreas Gohr } 214*ab8e5256SAndreas Gohr 215*ab8e5256SAndreas Gohr // create output directory 216*ab8e5256SAndreas Gohr $output = $outdir.'/'.$fileinfo->getPath(); 217*ab8e5256SAndreas Gohr $directory = ($fileinfo->getIsdir()) ? $output : dirname($output); 218*ab8e5256SAndreas Gohr if (!file_exists($directory)) { 219*ab8e5256SAndreas Gohr mkdir($directory, 0777, true); 220*ab8e5256SAndreas Gohr } 221*ab8e5256SAndreas Gohr 222*ab8e5256SAndreas Gohr // extract data 223*ab8e5256SAndreas Gohr if (!$fileinfo->getIsdir()) { 224*ab8e5256SAndreas Gohr $fp = @fopen($output, "wb"); 225*ab8e5256SAndreas Gohr if (!$fp) { 226*ab8e5256SAndreas Gohr throw new ArchiveIOException('Could not open file for writing: '.$output); 227*ab8e5256SAndreas Gohr } 228*ab8e5256SAndreas Gohr 229*ab8e5256SAndreas Gohr $size = floor($header['size'] / 512); 230*ab8e5256SAndreas Gohr for ($i = 0; $i < $size; $i++) { 231*ab8e5256SAndreas Gohr fwrite($fp, $this->readbytes(512), 512); 232*ab8e5256SAndreas Gohr } 233*ab8e5256SAndreas Gohr if (($header['size'] % 512) != 0) { 234*ab8e5256SAndreas Gohr fwrite($fp, $this->readbytes(512), $header['size'] % 512); 235*ab8e5256SAndreas Gohr } 236*ab8e5256SAndreas Gohr 237*ab8e5256SAndreas Gohr fclose($fp); 238*ab8e5256SAndreas Gohr @touch($output, $fileinfo->getMtime()); 239*ab8e5256SAndreas Gohr @chmod($output, $fileinfo->getMode()); 240*ab8e5256SAndreas Gohr } else { 241*ab8e5256SAndreas Gohr $this->skipbytes(ceil($header['size'] / 512) * 512); // the size is usually 0 for directories 242*ab8e5256SAndreas Gohr } 243*ab8e5256SAndreas Gohr 244*ab8e5256SAndreas Gohr if(is_callable($this->callback)) { 245*ab8e5256SAndreas Gohr call_user_func($this->callback, $fileinfo); 246*ab8e5256SAndreas Gohr } 247*ab8e5256SAndreas Gohr $extracted[] = $fileinfo; 248*ab8e5256SAndreas Gohr } 249*ab8e5256SAndreas Gohr 250*ab8e5256SAndreas Gohr $this->close(); 251*ab8e5256SAndreas Gohr return $extracted; 252*ab8e5256SAndreas Gohr } 253*ab8e5256SAndreas Gohr 254*ab8e5256SAndreas Gohr /** 255*ab8e5256SAndreas Gohr * Create a new TAR file 256*ab8e5256SAndreas Gohr * 257*ab8e5256SAndreas Gohr * If $file is empty, the tar file will be created in memory 258*ab8e5256SAndreas Gohr * 259*ab8e5256SAndreas Gohr * @param string $file 260*ab8e5256SAndreas Gohr * @throws ArchiveIOException 261*ab8e5256SAndreas Gohr * @throws ArchiveIllegalCompressionException 262*ab8e5256SAndreas Gohr */ 263*ab8e5256SAndreas Gohr public function create($file = '') 264*ab8e5256SAndreas Gohr { 265*ab8e5256SAndreas Gohr $this->file = $file; 266*ab8e5256SAndreas Gohr $this->memory = ''; 267*ab8e5256SAndreas Gohr $this->fh = 0; 268*ab8e5256SAndreas Gohr 269*ab8e5256SAndreas Gohr if ($this->file) { 270*ab8e5256SAndreas Gohr // determine compression 271*ab8e5256SAndreas Gohr if ($this->comptype == Archive::COMPRESS_AUTO) { 272*ab8e5256SAndreas Gohr $this->setCompression($this->complevel, $this->filetype($file)); 273*ab8e5256SAndreas Gohr } 274*ab8e5256SAndreas Gohr 275*ab8e5256SAndreas Gohr if ($this->comptype === Archive::COMPRESS_GZIP) { 276*ab8e5256SAndreas Gohr $this->fh = @gzopen($this->file, 'wb'.$this->complevel); 277*ab8e5256SAndreas Gohr } elseif ($this->comptype === Archive::COMPRESS_BZIP) { 278*ab8e5256SAndreas Gohr $this->fh = @bzopen($this->file, 'w'); 279*ab8e5256SAndreas Gohr } else { 280*ab8e5256SAndreas Gohr $this->fh = @fopen($this->file, 'wb'); 281*ab8e5256SAndreas Gohr } 282*ab8e5256SAndreas Gohr 283*ab8e5256SAndreas Gohr if (!$this->fh) { 284*ab8e5256SAndreas Gohr throw new ArchiveIOException('Could not open file for writing: '.$this->file); 285*ab8e5256SAndreas Gohr } 286*ab8e5256SAndreas Gohr } 287*ab8e5256SAndreas Gohr $this->writeaccess = true; 288*ab8e5256SAndreas Gohr $this->closed = false; 289*ab8e5256SAndreas Gohr } 290*ab8e5256SAndreas Gohr 291*ab8e5256SAndreas Gohr /** 292*ab8e5256SAndreas Gohr * Add a file to the current TAR archive using an existing file in the filesystem 293*ab8e5256SAndreas Gohr * 294*ab8e5256SAndreas Gohr * @param string $file path to the original file 295*ab8e5256SAndreas Gohr * @param string|FileInfo $fileinfo either the name to us in archive (string) or a FileInfo oject with all meta data, empty to take from original 296*ab8e5256SAndreas Gohr * @throws ArchiveCorruptedException when the file changes while reading it, the archive will be corrupt and should be deleted 297*ab8e5256SAndreas Gohr * @throws ArchiveIOException there was trouble reading the given file, it was not added 298*ab8e5256SAndreas Gohr * @throws FileInfoException trouble reading file info, it was not added 299*ab8e5256SAndreas Gohr */ 300*ab8e5256SAndreas Gohr public function addFile($file, $fileinfo = '') 301*ab8e5256SAndreas Gohr { 302*ab8e5256SAndreas Gohr if (is_string($fileinfo)) { 303*ab8e5256SAndreas Gohr $fileinfo = FileInfo::fromPath($file, $fileinfo); 304*ab8e5256SAndreas Gohr } 305*ab8e5256SAndreas Gohr 306*ab8e5256SAndreas Gohr if ($this->closed) { 307*ab8e5256SAndreas Gohr throw new ArchiveIOException('Archive has been closed, files can no longer be added'); 308*ab8e5256SAndreas Gohr } 309*ab8e5256SAndreas Gohr 310*ab8e5256SAndreas Gohr // create file header 311*ab8e5256SAndreas Gohr $this->writeFileHeader($fileinfo); 312*ab8e5256SAndreas Gohr 313*ab8e5256SAndreas Gohr // write data, but only if we have data to write. 314*ab8e5256SAndreas Gohr // note: on Windows fopen() on a directory will fail, so we prevent 315*ab8e5256SAndreas Gohr // errors on Windows by testing if we have data to write. 316*ab8e5256SAndreas Gohr if (!$fileinfo->getIsdir() && $fileinfo->getSize() > 0) { 317*ab8e5256SAndreas Gohr $read = 0; 318*ab8e5256SAndreas Gohr $fp = @fopen($file, 'rb'); 319*ab8e5256SAndreas Gohr if (!$fp) { 320*ab8e5256SAndreas Gohr throw new ArchiveIOException('Could not open file for reading: ' . $file); 321*ab8e5256SAndreas Gohr } 322*ab8e5256SAndreas Gohr while (!feof($fp)) { 323*ab8e5256SAndreas Gohr // for performance reasons read bigger chunks at once 324*ab8e5256SAndreas Gohr $data = fread($fp, self::READ_CHUNK_SIZE); 325*ab8e5256SAndreas Gohr if ($data === false) { 326*ab8e5256SAndreas Gohr break; 327*ab8e5256SAndreas Gohr } 328*ab8e5256SAndreas Gohr if ($data === '') { 329*ab8e5256SAndreas Gohr break; 330*ab8e5256SAndreas Gohr } 331*ab8e5256SAndreas Gohr $dataLen = strlen($data); 332*ab8e5256SAndreas Gohr $read += $dataLen; 333*ab8e5256SAndreas Gohr // how much of data read fully fills 512-byte blocks? 334*ab8e5256SAndreas Gohr $passLen = ($dataLen >> 9) << 9; 335*ab8e5256SAndreas Gohr if ($passLen === $dataLen) { 336*ab8e5256SAndreas Gohr // all - just write the data 337*ab8e5256SAndreas Gohr $this->writebytes($data); 338*ab8e5256SAndreas Gohr } else { 339*ab8e5256SAndreas Gohr // directly write what fills 512-byte blocks fully 340*ab8e5256SAndreas Gohr $this->writebytes(substr($data, 0, $passLen)); 341*ab8e5256SAndreas Gohr // pad the reminder to 512 bytes 342*ab8e5256SAndreas Gohr $this->writebytes(pack("a512", substr($data, $passLen))); 343*ab8e5256SAndreas Gohr } 344*ab8e5256SAndreas Gohr } 345*ab8e5256SAndreas Gohr fclose($fp); 346*ab8e5256SAndreas Gohr 347*ab8e5256SAndreas Gohr if ($read != $fileinfo->getSize()) { 348*ab8e5256SAndreas Gohr $this->close(); 349*ab8e5256SAndreas Gohr throw new ArchiveCorruptedException("The size of $file changed while reading, archive corrupted. read $read expected ".$fileinfo->getSize()); 350*ab8e5256SAndreas Gohr } 351*ab8e5256SAndreas Gohr } 352*ab8e5256SAndreas Gohr 353*ab8e5256SAndreas Gohr if(is_callable($this->callback)) { 354*ab8e5256SAndreas Gohr call_user_func($this->callback, $fileinfo); 355*ab8e5256SAndreas Gohr } 356*ab8e5256SAndreas Gohr } 357*ab8e5256SAndreas Gohr 358*ab8e5256SAndreas Gohr /** 359*ab8e5256SAndreas Gohr * Add a file to the current TAR archive using the given $data as content 360*ab8e5256SAndreas Gohr * 361*ab8e5256SAndreas Gohr * @param string|FileInfo $fileinfo either the name to us in archive (string) or a FileInfo oject with all meta data 362*ab8e5256SAndreas Gohr * @param string $data binary content of the file to add 363*ab8e5256SAndreas Gohr * @throws ArchiveIOException 364*ab8e5256SAndreas Gohr */ 365*ab8e5256SAndreas Gohr public function addData($fileinfo, $data) 366*ab8e5256SAndreas Gohr { 367*ab8e5256SAndreas Gohr if (is_string($fileinfo)) { 368*ab8e5256SAndreas Gohr $fileinfo = new FileInfo($fileinfo); 369*ab8e5256SAndreas Gohr } 370*ab8e5256SAndreas Gohr 371*ab8e5256SAndreas Gohr if ($this->closed) { 372*ab8e5256SAndreas Gohr throw new ArchiveIOException('Archive has been closed, files can no longer be added'); 373*ab8e5256SAndreas Gohr } 374*ab8e5256SAndreas Gohr 375*ab8e5256SAndreas Gohr $len = strlen($data); 376*ab8e5256SAndreas Gohr $fileinfo->setSize($len); 377*ab8e5256SAndreas Gohr $this->writeFileHeader($fileinfo); 378*ab8e5256SAndreas Gohr 379*ab8e5256SAndreas Gohr // write directly everything but the last block which needs padding 380*ab8e5256SAndreas Gohr $passLen = ($len >> 9) << 9; 381*ab8e5256SAndreas Gohr $this->writebytes(substr($data, 0, $passLen)); 382*ab8e5256SAndreas Gohr if ($passLen < $len) { 383*ab8e5256SAndreas Gohr $this->writebytes(pack("a512", substr($data, $passLen, 512))); 384*ab8e5256SAndreas Gohr } 385*ab8e5256SAndreas Gohr 386*ab8e5256SAndreas Gohr if (is_callable($this->callback)) { 387*ab8e5256SAndreas Gohr call_user_func($this->callback, $fileinfo); 388*ab8e5256SAndreas Gohr } 389*ab8e5256SAndreas Gohr } 390*ab8e5256SAndreas Gohr 391*ab8e5256SAndreas Gohr /** 392*ab8e5256SAndreas Gohr * Add the closing footer to the archive if in write mode, close all file handles 393*ab8e5256SAndreas Gohr * 394*ab8e5256SAndreas Gohr * After a call to this function no more data can be added to the archive, for 395*ab8e5256SAndreas Gohr * read access no reading is allowed anymore 396*ab8e5256SAndreas Gohr * 397*ab8e5256SAndreas Gohr * "Physically, an archive consists of a series of file entries terminated by an end-of-archive entry, which 398*ab8e5256SAndreas Gohr * consists of two 512 blocks of zero bytes" 399*ab8e5256SAndreas Gohr * 400*ab8e5256SAndreas Gohr * @link http://www.gnu.org/software/tar/manual/html_chapter/tar_8.html#SEC134 401*ab8e5256SAndreas Gohr * @throws ArchiveIOException 402*ab8e5256SAndreas Gohr */ 403*ab8e5256SAndreas Gohr public function close() 404*ab8e5256SAndreas Gohr { 405*ab8e5256SAndreas Gohr if ($this->closed) { 406*ab8e5256SAndreas Gohr return; 407*ab8e5256SAndreas Gohr } // we did this already 408*ab8e5256SAndreas Gohr 409*ab8e5256SAndreas Gohr // write footer 410*ab8e5256SAndreas Gohr if ($this->writeaccess) { 411*ab8e5256SAndreas Gohr $this->writebytes(pack("a512", "")); 412*ab8e5256SAndreas Gohr $this->writebytes(pack("a512", "")); 413*ab8e5256SAndreas Gohr } 414*ab8e5256SAndreas Gohr 415*ab8e5256SAndreas Gohr // close file handles 416*ab8e5256SAndreas Gohr if ($this->file) { 417*ab8e5256SAndreas Gohr if ($this->comptype === Archive::COMPRESS_GZIP) { 418*ab8e5256SAndreas Gohr gzclose($this->fh); 419*ab8e5256SAndreas Gohr } elseif ($this->comptype === Archive::COMPRESS_BZIP) { 420*ab8e5256SAndreas Gohr bzclose($this->fh); 421*ab8e5256SAndreas Gohr } else { 422*ab8e5256SAndreas Gohr fclose($this->fh); 423*ab8e5256SAndreas Gohr } 424*ab8e5256SAndreas Gohr 425*ab8e5256SAndreas Gohr $this->file = ''; 426*ab8e5256SAndreas Gohr $this->fh = 0; 427*ab8e5256SAndreas Gohr } 428*ab8e5256SAndreas Gohr 429*ab8e5256SAndreas Gohr $this->writeaccess = false; 430*ab8e5256SAndreas Gohr $this->closed = true; 431*ab8e5256SAndreas Gohr } 432*ab8e5256SAndreas Gohr 433*ab8e5256SAndreas Gohr /** 434*ab8e5256SAndreas Gohr * Returns the created in-memory archive data 435*ab8e5256SAndreas Gohr * 436*ab8e5256SAndreas Gohr * This implicitly calls close() on the Archive 437*ab8e5256SAndreas Gohr * @throws ArchiveIOException 438*ab8e5256SAndreas Gohr */ 439*ab8e5256SAndreas Gohr public function getArchive() 440*ab8e5256SAndreas Gohr { 441*ab8e5256SAndreas Gohr $this->close(); 442*ab8e5256SAndreas Gohr 443*ab8e5256SAndreas Gohr if ($this->comptype === Archive::COMPRESS_AUTO) { 444*ab8e5256SAndreas Gohr $this->comptype = Archive::COMPRESS_NONE; 445*ab8e5256SAndreas Gohr } 446*ab8e5256SAndreas Gohr 447*ab8e5256SAndreas Gohr if ($this->comptype === Archive::COMPRESS_GZIP) { 448*ab8e5256SAndreas Gohr return gzencode($this->memory, $this->complevel); 449*ab8e5256SAndreas Gohr } 450*ab8e5256SAndreas Gohr if ($this->comptype === Archive::COMPRESS_BZIP) { 451*ab8e5256SAndreas Gohr return bzcompress($this->memory); 452*ab8e5256SAndreas Gohr } 453*ab8e5256SAndreas Gohr return $this->memory; 454*ab8e5256SAndreas Gohr } 455*ab8e5256SAndreas Gohr 456*ab8e5256SAndreas Gohr /** 457*ab8e5256SAndreas Gohr * Save the created in-memory archive data 458*ab8e5256SAndreas Gohr * 459*ab8e5256SAndreas Gohr * Note: It more memory effective to specify the filename in the create() function and 460*ab8e5256SAndreas Gohr * let the library work on the new file directly. 461*ab8e5256SAndreas Gohr * 462*ab8e5256SAndreas Gohr * @param string $file 463*ab8e5256SAndreas Gohr * @throws ArchiveIOException 464*ab8e5256SAndreas Gohr * @throws ArchiveIllegalCompressionException 465*ab8e5256SAndreas Gohr */ 466*ab8e5256SAndreas Gohr public function save($file) 467*ab8e5256SAndreas Gohr { 468*ab8e5256SAndreas Gohr if ($this->comptype === Archive::COMPRESS_AUTO) { 469*ab8e5256SAndreas Gohr $this->setCompression($this->complevel, $this->filetype($file)); 470*ab8e5256SAndreas Gohr } 471*ab8e5256SAndreas Gohr 472*ab8e5256SAndreas Gohr if (!@file_put_contents($file, $this->getArchive())) { 473*ab8e5256SAndreas Gohr throw new ArchiveIOException('Could not write to file: '.$file); 474*ab8e5256SAndreas Gohr } 475*ab8e5256SAndreas Gohr } 476*ab8e5256SAndreas Gohr 477*ab8e5256SAndreas Gohr /** 478*ab8e5256SAndreas Gohr * Read from the open file pointer 479*ab8e5256SAndreas Gohr * 480*ab8e5256SAndreas Gohr * @param int $length bytes to read 481*ab8e5256SAndreas Gohr * @return string 482*ab8e5256SAndreas Gohr */ 483*ab8e5256SAndreas Gohr protected function readbytes($length) 484*ab8e5256SAndreas Gohr { 485*ab8e5256SAndreas Gohr if ($this->comptype === Archive::COMPRESS_GZIP) { 486*ab8e5256SAndreas Gohr $ret = @gzread($this->fh, $length); 487*ab8e5256SAndreas Gohr } elseif ($this->comptype === Archive::COMPRESS_BZIP) { 488*ab8e5256SAndreas Gohr $ret = @bzread($this->fh, $length); 489*ab8e5256SAndreas Gohr } else { 490*ab8e5256SAndreas Gohr $ret = @fread($this->fh, $length); 491*ab8e5256SAndreas Gohr } 492*ab8e5256SAndreas Gohr $this->position += strlen($ret); 493*ab8e5256SAndreas Gohr return $ret; 494*ab8e5256SAndreas Gohr } 495*ab8e5256SAndreas Gohr 496*ab8e5256SAndreas Gohr /** 497*ab8e5256SAndreas Gohr * Write to the open filepointer or memory 498*ab8e5256SAndreas Gohr * 499*ab8e5256SAndreas Gohr * @param string $data 500*ab8e5256SAndreas Gohr * @throws ArchiveIOException 501*ab8e5256SAndreas Gohr * @return int number of bytes written 502*ab8e5256SAndreas Gohr */ 503*ab8e5256SAndreas Gohr protected function writebytes($data) 504*ab8e5256SAndreas Gohr { 505*ab8e5256SAndreas Gohr if (!$this->file) { 506*ab8e5256SAndreas Gohr $this->memory .= $data; 507*ab8e5256SAndreas Gohr $written = strlen($data); 508*ab8e5256SAndreas Gohr } elseif ($this->comptype === Archive::COMPRESS_GZIP) { 509*ab8e5256SAndreas Gohr $written = @gzwrite($this->fh, $data); 510*ab8e5256SAndreas Gohr } elseif ($this->comptype === Archive::COMPRESS_BZIP) { 511*ab8e5256SAndreas Gohr $written = @bzwrite($this->fh, $data); 512*ab8e5256SAndreas Gohr } else { 513*ab8e5256SAndreas Gohr $written = @fwrite($this->fh, $data); 514*ab8e5256SAndreas Gohr } 515*ab8e5256SAndreas Gohr if ($written === false) { 516*ab8e5256SAndreas Gohr throw new ArchiveIOException('Failed to write to archive stream'); 517*ab8e5256SAndreas Gohr } 518*ab8e5256SAndreas Gohr return $written; 519*ab8e5256SAndreas Gohr } 520*ab8e5256SAndreas Gohr 521*ab8e5256SAndreas Gohr /** 522*ab8e5256SAndreas Gohr * Skip forward in the open file pointer 523*ab8e5256SAndreas Gohr * 524*ab8e5256SAndreas Gohr * This is basically a wrapper around seek() (and a workaround for bzip2) 525*ab8e5256SAndreas Gohr * 526*ab8e5256SAndreas Gohr * @param int $bytes seek to this position 527*ab8e5256SAndreas Gohr */ 528*ab8e5256SAndreas Gohr protected function skipbytes($bytes) 529*ab8e5256SAndreas Gohr { 530*ab8e5256SAndreas Gohr if ($this->comptype === Archive::COMPRESS_GZIP) { 531*ab8e5256SAndreas Gohr @gzseek($this->fh, $bytes, SEEK_CUR); 532*ab8e5256SAndreas Gohr } elseif ($this->comptype === Archive::COMPRESS_BZIP) { 533*ab8e5256SAndreas Gohr // there is no seek in bzip2, we simply read on 534*ab8e5256SAndreas Gohr // bzread allows to read a max of 8kb at once 535*ab8e5256SAndreas Gohr while($bytes) { 536*ab8e5256SAndreas Gohr $toread = min(8192, $bytes); 537*ab8e5256SAndreas Gohr @bzread($this->fh, $toread); 538*ab8e5256SAndreas Gohr $bytes -= $toread; 539*ab8e5256SAndreas Gohr } 540*ab8e5256SAndreas Gohr } else { 541*ab8e5256SAndreas Gohr @fseek($this->fh, $bytes, SEEK_CUR); 542*ab8e5256SAndreas Gohr } 543*ab8e5256SAndreas Gohr $this->position += $bytes; 544*ab8e5256SAndreas Gohr } 545*ab8e5256SAndreas Gohr 546*ab8e5256SAndreas Gohr /** 547*ab8e5256SAndreas Gohr * Write the given file meta data as header 548*ab8e5256SAndreas Gohr * 549*ab8e5256SAndreas Gohr * @param FileInfo $fileinfo 550*ab8e5256SAndreas Gohr * @throws ArchiveIOException 551*ab8e5256SAndreas Gohr */ 552*ab8e5256SAndreas Gohr protected function writeFileHeader(FileInfo $fileinfo) 553*ab8e5256SAndreas Gohr { 554*ab8e5256SAndreas Gohr $this->writeRawFileHeader( 555*ab8e5256SAndreas Gohr $fileinfo->getPath(), 556*ab8e5256SAndreas Gohr $fileinfo->getUid(), 557*ab8e5256SAndreas Gohr $fileinfo->getGid(), 558*ab8e5256SAndreas Gohr $fileinfo->getMode(), 559*ab8e5256SAndreas Gohr $fileinfo->getSize(), 560*ab8e5256SAndreas Gohr $fileinfo->getMtime(), 561*ab8e5256SAndreas Gohr $fileinfo->getIsdir() ? '5' : '0' 562*ab8e5256SAndreas Gohr ); 563*ab8e5256SAndreas Gohr } 564*ab8e5256SAndreas Gohr 565*ab8e5256SAndreas Gohr /** 566*ab8e5256SAndreas Gohr * Write a file header to the stream 567*ab8e5256SAndreas Gohr * 568*ab8e5256SAndreas Gohr * @param string $name 569*ab8e5256SAndreas Gohr * @param int $uid 570*ab8e5256SAndreas Gohr * @param int $gid 571*ab8e5256SAndreas Gohr * @param int $perm 572*ab8e5256SAndreas Gohr * @param int $size 573*ab8e5256SAndreas Gohr * @param int $mtime 574*ab8e5256SAndreas Gohr * @param string $typeflag Set to '5' for directories 575*ab8e5256SAndreas Gohr * @throws ArchiveIOException 576*ab8e5256SAndreas Gohr */ 577*ab8e5256SAndreas Gohr protected function writeRawFileHeader($name, $uid, $gid, $perm, $size, $mtime, $typeflag = '') 578*ab8e5256SAndreas Gohr { 579*ab8e5256SAndreas Gohr // handle filename length restrictions 580*ab8e5256SAndreas Gohr $prefix = ''; 581*ab8e5256SAndreas Gohr $namelen = strlen($name); 582*ab8e5256SAndreas Gohr if ($namelen > 100) { 583*ab8e5256SAndreas Gohr $file = basename($name); 584*ab8e5256SAndreas Gohr $dir = dirname($name); 585*ab8e5256SAndreas Gohr if (strlen($file) > 100 || strlen($dir) > 155) { 586*ab8e5256SAndreas Gohr // we're still too large, let's use GNU longlink 587*ab8e5256SAndreas Gohr $this->writeRawFileHeader('././@LongLink', 0, 0, 0, $namelen, 0, 'L'); 588*ab8e5256SAndreas Gohr for ($s = 0; $s < $namelen; $s += 512) { 589*ab8e5256SAndreas Gohr $this->writebytes(pack("a512", substr($name, $s, 512))); 590*ab8e5256SAndreas Gohr } 591*ab8e5256SAndreas Gohr $name = substr($name, 0, 100); // cut off name 592*ab8e5256SAndreas Gohr } else { 593*ab8e5256SAndreas Gohr // we're fine when splitting, use POSIX ustar 594*ab8e5256SAndreas Gohr $prefix = $dir; 595*ab8e5256SAndreas Gohr $name = $file; 596*ab8e5256SAndreas Gohr } 597*ab8e5256SAndreas Gohr } 598*ab8e5256SAndreas Gohr 599*ab8e5256SAndreas Gohr // values are needed in octal 600*ab8e5256SAndreas Gohr $uid = sprintf("%6s ", decoct($uid)); 601*ab8e5256SAndreas Gohr $gid = sprintf("%6s ", decoct($gid)); 602*ab8e5256SAndreas Gohr $perm = sprintf("%6s ", decoct($perm)); 603*ab8e5256SAndreas Gohr $size = self::numberEncode($size, 12); 604*ab8e5256SAndreas Gohr $mtime = self::numberEncode($mtime, 12); 605*ab8e5256SAndreas Gohr 606*ab8e5256SAndreas Gohr $data_first = pack("a100a8a8a8a12A12", $name, $perm, $uid, $gid, $size, $mtime); 607*ab8e5256SAndreas Gohr $data_last = pack("a1a100a6a2a32a32a8a8a155a12", $typeflag, '', 'ustar', '', '', '', '', '', $prefix, ""); 608*ab8e5256SAndreas Gohr 609*ab8e5256SAndreas Gohr for ($i = 0, $chks = 0; $i < 148; $i++) { 610*ab8e5256SAndreas Gohr $chks += ord($data_first[$i]); 611*ab8e5256SAndreas Gohr } 612*ab8e5256SAndreas Gohr 613*ab8e5256SAndreas Gohr for ($i = 156, $chks += 256, $j = 0; $i < 512; $i++, $j++) { 614*ab8e5256SAndreas Gohr $chks += ord($data_last[$j]); 615*ab8e5256SAndreas Gohr } 616*ab8e5256SAndreas Gohr 617*ab8e5256SAndreas Gohr $this->writebytes($data_first); 618*ab8e5256SAndreas Gohr 619*ab8e5256SAndreas Gohr $chks = pack("a8", sprintf("%6s ", decoct($chks))); 620*ab8e5256SAndreas Gohr $this->writebytes($chks.$data_last); 621*ab8e5256SAndreas Gohr } 622*ab8e5256SAndreas Gohr 623*ab8e5256SAndreas Gohr /** 624*ab8e5256SAndreas Gohr * Decode the given tar file header 625*ab8e5256SAndreas Gohr * 626*ab8e5256SAndreas Gohr * @param string $block a 512 byte block containing the header data 627*ab8e5256SAndreas Gohr * @return array|false returns false when this was a null block 628*ab8e5256SAndreas Gohr * @throws ArchiveCorruptedException 629*ab8e5256SAndreas Gohr */ 630*ab8e5256SAndreas Gohr protected function parseHeader($block) 631*ab8e5256SAndreas Gohr { 632*ab8e5256SAndreas Gohr if (!$block || strlen($block) != 512) { 633*ab8e5256SAndreas Gohr throw new ArchiveCorruptedException('Unexpected length of header'); 634*ab8e5256SAndreas Gohr } 635*ab8e5256SAndreas Gohr 636*ab8e5256SAndreas Gohr // null byte blocks are ignored 637*ab8e5256SAndreas Gohr if(trim($block) === '') return false; 638*ab8e5256SAndreas Gohr 639*ab8e5256SAndreas Gohr for ($i = 0, $chks = 0; $i < 148; $i++) { 640*ab8e5256SAndreas Gohr $chks += ord($block[$i]); 641*ab8e5256SAndreas Gohr } 642*ab8e5256SAndreas Gohr 643*ab8e5256SAndreas Gohr for ($i = 156, $chks += 256; $i < 512; $i++) { 644*ab8e5256SAndreas Gohr $chks += ord($block[$i]); 645*ab8e5256SAndreas Gohr } 646*ab8e5256SAndreas Gohr 647*ab8e5256SAndreas Gohr $header = @unpack( 648*ab8e5256SAndreas Gohr "a100filename/a8perm/a8uid/a8gid/a12size/a12mtime/a8checksum/a1typeflag/a100link/a6magic/a2version/a32uname/a32gname/a8devmajor/a8devminor/a155prefix", 649*ab8e5256SAndreas Gohr $block 650*ab8e5256SAndreas Gohr ); 651*ab8e5256SAndreas Gohr if (!$header) { 652*ab8e5256SAndreas Gohr throw new ArchiveCorruptedException('Failed to parse header'); 653*ab8e5256SAndreas Gohr } 654*ab8e5256SAndreas Gohr 655*ab8e5256SAndreas Gohr $return['checksum'] = OctDec(trim($header['checksum'])); 656*ab8e5256SAndreas Gohr if ($return['checksum'] != $chks) { 657*ab8e5256SAndreas Gohr throw new ArchiveCorruptedException('Header does not match its checksum'); 658*ab8e5256SAndreas Gohr } 659*ab8e5256SAndreas Gohr 660*ab8e5256SAndreas Gohr $return['filename'] = trim($header['filename']); 661*ab8e5256SAndreas Gohr $return['perm'] = OctDec(trim($header['perm'])); 662*ab8e5256SAndreas Gohr $return['uid'] = OctDec(trim($header['uid'])); 663*ab8e5256SAndreas Gohr $return['gid'] = OctDec(trim($header['gid'])); 664*ab8e5256SAndreas Gohr $return['size'] = self::numberDecode($header['size']); 665*ab8e5256SAndreas Gohr $return['mtime'] = self::numberDecode($header['mtime']); 666*ab8e5256SAndreas Gohr $return['typeflag'] = $header['typeflag']; 667*ab8e5256SAndreas Gohr $return['link'] = trim($header['link']); 668*ab8e5256SAndreas Gohr $return['uname'] = trim($header['uname']); 669*ab8e5256SAndreas Gohr $return['gname'] = trim($header['gname']); 670*ab8e5256SAndreas Gohr 671*ab8e5256SAndreas Gohr // Handle ustar Posix compliant path prefixes 672*ab8e5256SAndreas Gohr if (trim($header['prefix'])) { 673*ab8e5256SAndreas Gohr $return['filename'] = trim($header['prefix']).'/'.$return['filename']; 674*ab8e5256SAndreas Gohr } 675*ab8e5256SAndreas Gohr 676*ab8e5256SAndreas Gohr // Handle Long-Link entries from GNU Tar 677*ab8e5256SAndreas Gohr if ($return['typeflag'] == 'L') { 678*ab8e5256SAndreas Gohr // following data block(s) is the filename 679*ab8e5256SAndreas Gohr $filename = trim($this->readbytes(ceil($return['size'] / 512) * 512)); 680*ab8e5256SAndreas Gohr // next block is the real header 681*ab8e5256SAndreas Gohr $block = $this->readbytes(512); 682*ab8e5256SAndreas Gohr $return = $this->parseHeader($block); 683*ab8e5256SAndreas Gohr // overwrite the filename 684*ab8e5256SAndreas Gohr $return['filename'] = $filename; 685*ab8e5256SAndreas Gohr } 686*ab8e5256SAndreas Gohr 687*ab8e5256SAndreas Gohr return $return; 688*ab8e5256SAndreas Gohr } 689*ab8e5256SAndreas Gohr 690*ab8e5256SAndreas Gohr /** 691*ab8e5256SAndreas Gohr * Creates a FileInfo object from the given parsed header 692*ab8e5256SAndreas Gohr * 693*ab8e5256SAndreas Gohr * @param $header 694*ab8e5256SAndreas Gohr * @return FileInfo 695*ab8e5256SAndreas Gohr */ 696*ab8e5256SAndreas Gohr protected function header2fileinfo($header) 697*ab8e5256SAndreas Gohr { 698*ab8e5256SAndreas Gohr $fileinfo = new FileInfo(); 699*ab8e5256SAndreas Gohr $fileinfo->setPath($header['filename']); 700*ab8e5256SAndreas Gohr $fileinfo->setMode($header['perm']); 701*ab8e5256SAndreas Gohr $fileinfo->setUid($header['uid']); 702*ab8e5256SAndreas Gohr $fileinfo->setGid($header['gid']); 703*ab8e5256SAndreas Gohr $fileinfo->setSize($header['size']); 704*ab8e5256SAndreas Gohr $fileinfo->setMtime($header['mtime']); 705*ab8e5256SAndreas Gohr $fileinfo->setOwner($header['uname']); 706*ab8e5256SAndreas Gohr $fileinfo->setGroup($header['gname']); 707*ab8e5256SAndreas Gohr $fileinfo->setIsdir((bool) $header['typeflag']); 708*ab8e5256SAndreas Gohr 709*ab8e5256SAndreas Gohr return $fileinfo; 710*ab8e5256SAndreas Gohr } 711*ab8e5256SAndreas Gohr 712*ab8e5256SAndreas Gohr /** 713*ab8e5256SAndreas Gohr * Checks if the given compression type is available and throws an exception if not 714*ab8e5256SAndreas Gohr * 715*ab8e5256SAndreas Gohr * @param $comptype 716*ab8e5256SAndreas Gohr * @throws ArchiveIllegalCompressionException 717*ab8e5256SAndreas Gohr */ 718*ab8e5256SAndreas Gohr protected function compressioncheck($comptype) 719*ab8e5256SAndreas Gohr { 720*ab8e5256SAndreas Gohr if ($comptype === Archive::COMPRESS_GZIP && !function_exists('gzopen')) { 721*ab8e5256SAndreas Gohr throw new ArchiveIllegalCompressionException('No gzip support available'); 722*ab8e5256SAndreas Gohr } 723*ab8e5256SAndreas Gohr 724*ab8e5256SAndreas Gohr if ($comptype === Archive::COMPRESS_BZIP && !function_exists('bzopen')) { 725*ab8e5256SAndreas Gohr throw new ArchiveIllegalCompressionException('No bzip2 support available'); 726*ab8e5256SAndreas Gohr } 727*ab8e5256SAndreas Gohr } 728*ab8e5256SAndreas Gohr 729*ab8e5256SAndreas Gohr /** 730*ab8e5256SAndreas Gohr * Guesses the wanted compression from the given file 731*ab8e5256SAndreas Gohr * 732*ab8e5256SAndreas Gohr * Uses magic bytes for existing files, the file extension otherwise 733*ab8e5256SAndreas Gohr * 734*ab8e5256SAndreas Gohr * You don't need to call this yourself. It's used when you pass Archive::COMPRESS_AUTO somewhere 735*ab8e5256SAndreas Gohr * 736*ab8e5256SAndreas Gohr * @param string $file 737*ab8e5256SAndreas Gohr * @return int 738*ab8e5256SAndreas Gohr */ 739*ab8e5256SAndreas Gohr public function filetype($file) 740*ab8e5256SAndreas Gohr { 741*ab8e5256SAndreas Gohr // for existing files, try to read the magic bytes 742*ab8e5256SAndreas Gohr if(file_exists($file) && is_readable($file) && filesize($file) > 5) { 743*ab8e5256SAndreas Gohr $fh = @fopen($file, 'rb'); 744*ab8e5256SAndreas Gohr if(!$fh) return false; 745*ab8e5256SAndreas Gohr $magic = fread($fh, 5); 746*ab8e5256SAndreas Gohr fclose($fh); 747*ab8e5256SAndreas Gohr 748*ab8e5256SAndreas Gohr if(strpos($magic, "\x42\x5a") === 0) return Archive::COMPRESS_BZIP; 749*ab8e5256SAndreas Gohr if(strpos($magic, "\x1f\x8b") === 0) return Archive::COMPRESS_GZIP; 750*ab8e5256SAndreas Gohr } 751*ab8e5256SAndreas Gohr 752*ab8e5256SAndreas Gohr // otherwise rely on file name 753*ab8e5256SAndreas Gohr $file = strtolower($file); 754*ab8e5256SAndreas Gohr if (substr($file, -3) == '.gz' || substr($file, -4) == '.tgz') { 755*ab8e5256SAndreas Gohr return Archive::COMPRESS_GZIP; 756*ab8e5256SAndreas Gohr } elseif (substr($file, -4) == '.bz2' || substr($file, -4) == '.tbz') { 757*ab8e5256SAndreas Gohr return Archive::COMPRESS_BZIP; 758*ab8e5256SAndreas Gohr } 759*ab8e5256SAndreas Gohr 760*ab8e5256SAndreas Gohr return Archive::COMPRESS_NONE; 761*ab8e5256SAndreas Gohr } 762*ab8e5256SAndreas Gohr 763*ab8e5256SAndreas Gohr /** 764*ab8e5256SAndreas Gohr * Decodes numeric values according to the 765*ab8e5256SAndreas Gohr * https://www.gnu.org/software/tar/manual/html_node/Extensions.html#Extensions 766*ab8e5256SAndreas Gohr * (basically with support for big numbers) 767*ab8e5256SAndreas Gohr * 768*ab8e5256SAndreas Gohr * @param string $field 769*ab8e5256SAndreas Gohr * $return int 770*ab8e5256SAndreas Gohr */ 771*ab8e5256SAndreas Gohr static public function numberDecode($field) 772*ab8e5256SAndreas Gohr { 773*ab8e5256SAndreas Gohr $firstByte = ord(substr($field, 0, 1)); 774*ab8e5256SAndreas Gohr if ($firstByte === 255) { 775*ab8e5256SAndreas Gohr $value = -1 << (8 * strlen($field)); 776*ab8e5256SAndreas Gohr $shift = 0; 777*ab8e5256SAndreas Gohr for ($i = strlen($field) - 1; $i >= 0; $i--) { 778*ab8e5256SAndreas Gohr $value += ord(substr($field, $i, 1)) << $shift; 779*ab8e5256SAndreas Gohr $shift += 8; 780*ab8e5256SAndreas Gohr } 781*ab8e5256SAndreas Gohr } elseif ($firstByte === 128) { 782*ab8e5256SAndreas Gohr $value = 0; 783*ab8e5256SAndreas Gohr $shift = 0; 784*ab8e5256SAndreas Gohr for ($i = strlen($field) - 1; $i > 0; $i--) { 785*ab8e5256SAndreas Gohr $value += ord(substr($field, $i, 1)) << $shift; 786*ab8e5256SAndreas Gohr $shift += 8; 787*ab8e5256SAndreas Gohr } 788*ab8e5256SAndreas Gohr } else { 789*ab8e5256SAndreas Gohr $value = octdec(trim($field)); 790*ab8e5256SAndreas Gohr } 791*ab8e5256SAndreas Gohr return $value; 792*ab8e5256SAndreas Gohr } 793*ab8e5256SAndreas Gohr 794*ab8e5256SAndreas Gohr /** 795*ab8e5256SAndreas Gohr * Encodes numeric values according to the 796*ab8e5256SAndreas Gohr * https://www.gnu.org/software/tar/manual/html_node/Extensions.html#Extensions 797*ab8e5256SAndreas Gohr * (basically with support for big numbers) 798*ab8e5256SAndreas Gohr * 799*ab8e5256SAndreas Gohr * @param int $value 800*ab8e5256SAndreas Gohr * @param int $length field length 801*ab8e5256SAndreas Gohr * @return string 802*ab8e5256SAndreas Gohr */ 803*ab8e5256SAndreas Gohr static public function numberEncode($value, $length) 804*ab8e5256SAndreas Gohr { 805*ab8e5256SAndreas Gohr // old implementations leave last byte empty 806*ab8e5256SAndreas Gohr // octal encoding encodes three bits per byte 807*ab8e5256SAndreas Gohr $maxValue = 1 << (($length - 1) * 3); 808*ab8e5256SAndreas Gohr if ($value < 0) { 809*ab8e5256SAndreas Gohr // PHP already stores integers as 2's complement 810*ab8e5256SAndreas Gohr $value = pack(PHP_INT_SIZE === 8 ? 'J' : 'N', (int) $value); 811*ab8e5256SAndreas Gohr $encoded = str_repeat(chr(255), max(1, $length - PHP_INT_SIZE)); 812*ab8e5256SAndreas Gohr $encoded .= substr($value, max(0, PHP_INT_SIZE - $length + 1)); 813*ab8e5256SAndreas Gohr } elseif ($value >= $maxValue) { 814*ab8e5256SAndreas Gohr $value = pack(PHP_INT_SIZE === 8 ? 'J' : 'N', (int) $value); 815*ab8e5256SAndreas Gohr $encoded = chr(128) . str_repeat(chr(0), max(0, $length - PHP_INT_SIZE - 1)); 816*ab8e5256SAndreas Gohr $encoded .= substr($value, max(0, PHP_INT_SIZE - $length + 1)); 817*ab8e5256SAndreas Gohr } else { 818*ab8e5256SAndreas Gohr $encoded = sprintf("%" . ($length - 1) . "s ", decoct($value)); 819*ab8e5256SAndreas Gohr } 820*ab8e5256SAndreas Gohr return $encoded; 821*ab8e5256SAndreas Gohr } 822*ab8e5256SAndreas Gohr} 823*ab8e5256SAndreas Gohr 824