1*605f8e8dSAndreas Gohr<?php 2*605f8e8dSAndreas Gohr 3*605f8e8dSAndreas Gohrnamespace splitbrain\PHPArchive; 4*605f8e8dSAndreas Gohr 5*605f8e8dSAndreas Gohr/** 6*605f8e8dSAndreas Gohr * Class Tar 7*605f8e8dSAndreas Gohr * 8*605f8e8dSAndreas Gohr * Creates or extracts Tar archives. Supports gz and bzip compression 9*605f8e8dSAndreas Gohr * 10*605f8e8dSAndreas Gohr * Long pathnames (>100 chars) are supported in POSIX ustar and GNU longlink formats. 11*605f8e8dSAndreas Gohr * 12*605f8e8dSAndreas Gohr * @author Andreas Gohr <andi@splitbrain.org> 13*605f8e8dSAndreas Gohr * @package splitbrain\PHPArchive 14*605f8e8dSAndreas Gohr * @license MIT 15*605f8e8dSAndreas Gohr */ 16*605f8e8dSAndreas Gohrclass Tar extends Archive 17*605f8e8dSAndreas Gohr{ 18*605f8e8dSAndreas Gohr 19*605f8e8dSAndreas Gohr protected $file = ''; 20*605f8e8dSAndreas Gohr protected $comptype = Archive::COMPRESS_AUTO; 21*605f8e8dSAndreas Gohr protected $complevel = 9; 22*605f8e8dSAndreas Gohr protected $fh; 23*605f8e8dSAndreas Gohr protected $memory = ''; 24*605f8e8dSAndreas Gohr protected $closed = true; 25*605f8e8dSAndreas Gohr protected $writeaccess = false; 26*605f8e8dSAndreas Gohr 27*605f8e8dSAndreas Gohr /** 28*605f8e8dSAndreas Gohr * Sets the compression to use 29*605f8e8dSAndreas Gohr * 30*605f8e8dSAndreas Gohr * @param int $level Compression level (0 to 9) 31*605f8e8dSAndreas Gohr * @param int $type Type of compression to use (use COMPRESS_* constants) 32*605f8e8dSAndreas Gohr * @return mixed 33*605f8e8dSAndreas Gohr */ 34*605f8e8dSAndreas Gohr public function setCompression($level = 9, $type = Archive::COMPRESS_AUTO) 35*605f8e8dSAndreas Gohr { 36*605f8e8dSAndreas Gohr $this->compressioncheck($type); 37*605f8e8dSAndreas Gohr $this->comptype = $type; 38*605f8e8dSAndreas Gohr $this->complevel = $level; 39*605f8e8dSAndreas Gohr } 40*605f8e8dSAndreas Gohr 41*605f8e8dSAndreas Gohr /** 42*605f8e8dSAndreas Gohr * Open an existing TAR file for reading 43*605f8e8dSAndreas Gohr * 44*605f8e8dSAndreas Gohr * @param string $file 45*605f8e8dSAndreas Gohr * @throws ArchiveIOException 46*605f8e8dSAndreas Gohr */ 47*605f8e8dSAndreas Gohr public function open($file) 48*605f8e8dSAndreas Gohr { 49*605f8e8dSAndreas Gohr $this->file = $file; 50*605f8e8dSAndreas Gohr 51*605f8e8dSAndreas Gohr // update compression to mach file 52*605f8e8dSAndreas Gohr if ($this->comptype == Tar::COMPRESS_AUTO) { 53*605f8e8dSAndreas Gohr $this->setCompression($this->complevel, $this->filetype($file)); 54*605f8e8dSAndreas Gohr } 55*605f8e8dSAndreas Gohr 56*605f8e8dSAndreas Gohr // open file handles 57*605f8e8dSAndreas Gohr if ($this->comptype === Archive::COMPRESS_GZIP) { 58*605f8e8dSAndreas Gohr $this->fh = @gzopen($this->file, 'rb'); 59*605f8e8dSAndreas Gohr } elseif ($this->comptype === Archive::COMPRESS_BZIP) { 60*605f8e8dSAndreas Gohr $this->fh = @bzopen($this->file, 'r'); 61*605f8e8dSAndreas Gohr } else { 62*605f8e8dSAndreas Gohr $this->fh = @fopen($this->file, 'rb'); 63*605f8e8dSAndreas Gohr } 64*605f8e8dSAndreas Gohr 65*605f8e8dSAndreas Gohr if (!$this->fh) { 66*605f8e8dSAndreas Gohr throw new ArchiveIOException('Could not open file for reading: '.$this->file); 67*605f8e8dSAndreas Gohr } 68*605f8e8dSAndreas Gohr $this->closed = false; 69*605f8e8dSAndreas Gohr } 70*605f8e8dSAndreas Gohr 71*605f8e8dSAndreas Gohr /** 72*605f8e8dSAndreas Gohr * Read the contents of a TAR archive 73*605f8e8dSAndreas Gohr * 74*605f8e8dSAndreas Gohr * This function lists the files stored in the archive 75*605f8e8dSAndreas Gohr * 76*605f8e8dSAndreas Gohr * The archive is closed afer reading the contents, because rewinding is not possible in bzip2 streams. 77*605f8e8dSAndreas Gohr * Reopen the file with open() again if you want to do additional operations 78*605f8e8dSAndreas Gohr * 79*605f8e8dSAndreas Gohr * @throws ArchiveIOException 80*605f8e8dSAndreas Gohr * @returns FileInfo[] 81*605f8e8dSAndreas Gohr */ 82*605f8e8dSAndreas Gohr public function contents() 83*605f8e8dSAndreas Gohr { 84*605f8e8dSAndreas Gohr if ($this->closed || !$this->file) { 85*605f8e8dSAndreas Gohr throw new ArchiveIOException('Can not read from a closed archive'); 86*605f8e8dSAndreas Gohr } 87*605f8e8dSAndreas Gohr 88*605f8e8dSAndreas Gohr $result = array(); 89*605f8e8dSAndreas Gohr while ($read = $this->readbytes(512)) { 90*605f8e8dSAndreas Gohr $header = $this->parseHeader($read); 91*605f8e8dSAndreas Gohr if (!is_array($header)) { 92*605f8e8dSAndreas Gohr continue; 93*605f8e8dSAndreas Gohr } 94*605f8e8dSAndreas Gohr 95*605f8e8dSAndreas Gohr $this->skipbytes(ceil($header['size'] / 512) * 512); 96*605f8e8dSAndreas Gohr $result[] = $this->header2fileinfo($header); 97*605f8e8dSAndreas Gohr } 98*605f8e8dSAndreas Gohr 99*605f8e8dSAndreas Gohr $this->close(); 100*605f8e8dSAndreas Gohr return $result; 101*605f8e8dSAndreas Gohr } 102*605f8e8dSAndreas Gohr 103*605f8e8dSAndreas Gohr /** 104*605f8e8dSAndreas Gohr * Extract an existing TAR archive 105*605f8e8dSAndreas Gohr * 106*605f8e8dSAndreas Gohr * The $strip parameter allows you to strip a certain number of path components from the filenames 107*605f8e8dSAndreas Gohr * found in the tar file, similar to the --strip-components feature of GNU tar. This is triggered when 108*605f8e8dSAndreas Gohr * an integer is passed as $strip. 109*605f8e8dSAndreas Gohr * Alternatively a fixed string prefix may be passed in $strip. If the filename matches this prefix, 110*605f8e8dSAndreas Gohr * the prefix will be stripped. It is recommended to give prefixes with a trailing slash. 111*605f8e8dSAndreas Gohr * 112*605f8e8dSAndreas Gohr * By default this will extract all files found in the archive. You can restrict the output using the $include 113*605f8e8dSAndreas Gohr * and $exclude parameter. Both expect a full regular expression (including delimiters and modifiers). If 114*605f8e8dSAndreas Gohr * $include is set only files that match this expression will be extracted. Files that match the $exclude 115*605f8e8dSAndreas Gohr * expression will never be extracted. Both parameters can be used in combination. Expressions are matched against 116*605f8e8dSAndreas Gohr * stripped filenames as described above. 117*605f8e8dSAndreas Gohr * 118*605f8e8dSAndreas Gohr * The archive is closed afer reading the contents, because rewinding is not possible in bzip2 streams. 119*605f8e8dSAndreas Gohr * Reopen the file with open() again if you want to do additional operations 120*605f8e8dSAndreas Gohr * 121*605f8e8dSAndreas Gohr * @param string $outdir the target directory for extracting 122*605f8e8dSAndreas Gohr * @param int|string $strip either the number of path components or a fixed prefix to strip 123*605f8e8dSAndreas Gohr * @param string $exclude a regular expression of files to exclude 124*605f8e8dSAndreas Gohr * @param string $include a regular expression of files to include 125*605f8e8dSAndreas Gohr * @throws ArchiveIOException 126*605f8e8dSAndreas Gohr * @return FileInfo[] 127*605f8e8dSAndreas Gohr */ 128*605f8e8dSAndreas Gohr public function extract($outdir, $strip = '', $exclude = '', $include = '') 129*605f8e8dSAndreas Gohr { 130*605f8e8dSAndreas Gohr if ($this->closed || !$this->file) { 131*605f8e8dSAndreas Gohr throw new ArchiveIOException('Can not read from a closed archive'); 132*605f8e8dSAndreas Gohr } 133*605f8e8dSAndreas Gohr 134*605f8e8dSAndreas Gohr $outdir = rtrim($outdir, '/'); 135*605f8e8dSAndreas Gohr @mkdir($outdir, 0777, true); 136*605f8e8dSAndreas Gohr if (!is_dir($outdir)) { 137*605f8e8dSAndreas Gohr throw new ArchiveIOException("Could not create directory '$outdir'"); 138*605f8e8dSAndreas Gohr } 139*605f8e8dSAndreas Gohr 140*605f8e8dSAndreas Gohr $extracted = array(); 141*605f8e8dSAndreas Gohr while ($dat = $this->readbytes(512)) { 142*605f8e8dSAndreas Gohr // read the file header 143*605f8e8dSAndreas Gohr $header = $this->parseHeader($dat); 144*605f8e8dSAndreas Gohr if (!is_array($header)) { 145*605f8e8dSAndreas Gohr continue; 146*605f8e8dSAndreas Gohr } 147*605f8e8dSAndreas Gohr $fileinfo = $this->header2fileinfo($header); 148*605f8e8dSAndreas Gohr 149*605f8e8dSAndreas Gohr // apply strip rules 150*605f8e8dSAndreas Gohr $fileinfo->strip($strip); 151*605f8e8dSAndreas Gohr 152*605f8e8dSAndreas Gohr // skip unwanted files 153*605f8e8dSAndreas Gohr if (!strlen($fileinfo->getPath()) || !$fileinfo->match($include, $exclude)) { 154*605f8e8dSAndreas Gohr $this->skipbytes(ceil($header['size'] / 512) * 512); 155*605f8e8dSAndreas Gohr continue; 156*605f8e8dSAndreas Gohr } 157*605f8e8dSAndreas Gohr 158*605f8e8dSAndreas Gohr // create output directory 159*605f8e8dSAndreas Gohr $output = $outdir.'/'.$fileinfo->getPath(); 160*605f8e8dSAndreas Gohr $directory = ($fileinfo->getIsdir()) ? $output : dirname($output); 161*605f8e8dSAndreas Gohr @mkdir($directory, 0777, true); 162*605f8e8dSAndreas Gohr 163*605f8e8dSAndreas Gohr // extract data 164*605f8e8dSAndreas Gohr if (!$fileinfo->getIsdir()) { 165*605f8e8dSAndreas Gohr $fp = fopen($output, "wb"); 166*605f8e8dSAndreas Gohr if (!$fp) { 167*605f8e8dSAndreas Gohr throw new ArchiveIOException('Could not open file for writing: '.$output); 168*605f8e8dSAndreas Gohr } 169*605f8e8dSAndreas Gohr 170*605f8e8dSAndreas Gohr $size = floor($header['size'] / 512); 171*605f8e8dSAndreas Gohr for ($i = 0; $i < $size; $i++) { 172*605f8e8dSAndreas Gohr fwrite($fp, $this->readbytes(512), 512); 173*605f8e8dSAndreas Gohr } 174*605f8e8dSAndreas Gohr if (($header['size'] % 512) != 0) { 175*605f8e8dSAndreas Gohr fwrite($fp, $this->readbytes(512), $header['size'] % 512); 176*605f8e8dSAndreas Gohr } 177*605f8e8dSAndreas Gohr 178*605f8e8dSAndreas Gohr fclose($fp); 179*605f8e8dSAndreas Gohr touch($output, $fileinfo->getMtime()); 180*605f8e8dSAndreas Gohr chmod($output, $fileinfo->getMode()); 181*605f8e8dSAndreas Gohr } else { 182*605f8e8dSAndreas Gohr $this->skipbytes(ceil($header['size'] / 512) * 512); // the size is usually 0 for directories 183*605f8e8dSAndreas Gohr } 184*605f8e8dSAndreas Gohr 185*605f8e8dSAndreas Gohr $extracted[] = $fileinfo; 186*605f8e8dSAndreas Gohr } 187*605f8e8dSAndreas Gohr 188*605f8e8dSAndreas Gohr $this->close(); 189*605f8e8dSAndreas Gohr return $extracted; 190*605f8e8dSAndreas Gohr } 191*605f8e8dSAndreas Gohr 192*605f8e8dSAndreas Gohr /** 193*605f8e8dSAndreas Gohr * Create a new TAR file 194*605f8e8dSAndreas Gohr * 195*605f8e8dSAndreas Gohr * If $file is empty, the tar file will be created in memory 196*605f8e8dSAndreas Gohr * 197*605f8e8dSAndreas Gohr * @param string $file 198*605f8e8dSAndreas Gohr * @throws ArchiveIOException 199*605f8e8dSAndreas Gohr */ 200*605f8e8dSAndreas Gohr public function create($file = '') 201*605f8e8dSAndreas Gohr { 202*605f8e8dSAndreas Gohr $this->file = $file; 203*605f8e8dSAndreas Gohr $this->memory = ''; 204*605f8e8dSAndreas Gohr $this->fh = 0; 205*605f8e8dSAndreas Gohr 206*605f8e8dSAndreas Gohr if ($this->file) { 207*605f8e8dSAndreas Gohr // determine compression 208*605f8e8dSAndreas Gohr if ($this->comptype == Archive::COMPRESS_AUTO) { 209*605f8e8dSAndreas Gohr $this->setCompression($this->complevel, $this->filetype($file)); 210*605f8e8dSAndreas Gohr } 211*605f8e8dSAndreas Gohr 212*605f8e8dSAndreas Gohr if ($this->comptype === Archive::COMPRESS_GZIP) { 213*605f8e8dSAndreas Gohr $this->fh = @gzopen($this->file, 'wb'.$this->complevel); 214*605f8e8dSAndreas Gohr } elseif ($this->comptype === Archive::COMPRESS_BZIP) { 215*605f8e8dSAndreas Gohr $this->fh = @bzopen($this->file, 'w'); 216*605f8e8dSAndreas Gohr } else { 217*605f8e8dSAndreas Gohr $this->fh = @fopen($this->file, 'wb'); 218*605f8e8dSAndreas Gohr } 219*605f8e8dSAndreas Gohr 220*605f8e8dSAndreas Gohr if (!$this->fh) { 221*605f8e8dSAndreas Gohr throw new ArchiveIOException('Could not open file for writing: '.$this->file); 222*605f8e8dSAndreas Gohr } 223*605f8e8dSAndreas Gohr } 224*605f8e8dSAndreas Gohr $this->writeaccess = true; 225*605f8e8dSAndreas Gohr $this->closed = false; 226*605f8e8dSAndreas Gohr } 227*605f8e8dSAndreas Gohr 228*605f8e8dSAndreas Gohr /** 229*605f8e8dSAndreas Gohr * Add a file to the current TAR archive using an existing file in the filesystem 230*605f8e8dSAndreas Gohr * 231*605f8e8dSAndreas Gohr * @param string $file path to the original file 232*605f8e8dSAndreas 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 233*605f8e8dSAndreas Gohr * @throws ArchiveIOException 234*605f8e8dSAndreas Gohr */ 235*605f8e8dSAndreas Gohr public function addFile($file, $fileinfo = '') 236*605f8e8dSAndreas Gohr { 237*605f8e8dSAndreas Gohr if (is_string($fileinfo)) { 238*605f8e8dSAndreas Gohr $fileinfo = FileInfo::fromPath($file, $fileinfo); 239*605f8e8dSAndreas Gohr } 240*605f8e8dSAndreas Gohr 241*605f8e8dSAndreas Gohr if ($this->closed) { 242*605f8e8dSAndreas Gohr throw new ArchiveIOException('Archive has been closed, files can no longer be added'); 243*605f8e8dSAndreas Gohr } 244*605f8e8dSAndreas Gohr 245*605f8e8dSAndreas Gohr $fp = fopen($file, 'rb'); 246*605f8e8dSAndreas Gohr if (!$fp) { 247*605f8e8dSAndreas Gohr throw new ArchiveIOException('Could not open file for reading: '.$file); 248*605f8e8dSAndreas Gohr } 249*605f8e8dSAndreas Gohr 250*605f8e8dSAndreas Gohr // create file header 251*605f8e8dSAndreas Gohr $this->writeFileHeader($fileinfo); 252*605f8e8dSAndreas Gohr 253*605f8e8dSAndreas Gohr // write data 254*605f8e8dSAndreas Gohr while (!feof($fp)) { 255*605f8e8dSAndreas Gohr $data = fread($fp, 512); 256*605f8e8dSAndreas Gohr if ($data === false) { 257*605f8e8dSAndreas Gohr break; 258*605f8e8dSAndreas Gohr } 259*605f8e8dSAndreas Gohr if ($data === '') { 260*605f8e8dSAndreas Gohr break; 261*605f8e8dSAndreas Gohr } 262*605f8e8dSAndreas Gohr $packed = pack("a512", $data); 263*605f8e8dSAndreas Gohr $this->writebytes($packed); 264*605f8e8dSAndreas Gohr } 265*605f8e8dSAndreas Gohr fclose($fp); 266*605f8e8dSAndreas Gohr } 267*605f8e8dSAndreas Gohr 268*605f8e8dSAndreas Gohr /** 269*605f8e8dSAndreas Gohr * Add a file to the current TAR archive using the given $data as content 270*605f8e8dSAndreas Gohr * 271*605f8e8dSAndreas Gohr * @param string|FileInfo $fileinfo either the name to us in archive (string) or a FileInfo oject with all meta data 272*605f8e8dSAndreas Gohr * @param string $data binary content of the file to add 273*605f8e8dSAndreas Gohr * @throws ArchiveIOException 274*605f8e8dSAndreas Gohr */ 275*605f8e8dSAndreas Gohr public function addData($fileinfo, $data) 276*605f8e8dSAndreas Gohr { 277*605f8e8dSAndreas Gohr if (is_string($fileinfo)) { 278*605f8e8dSAndreas Gohr $fileinfo = new FileInfo($fileinfo); 279*605f8e8dSAndreas Gohr } 280*605f8e8dSAndreas Gohr 281*605f8e8dSAndreas Gohr if ($this->closed) { 282*605f8e8dSAndreas Gohr throw new ArchiveIOException('Archive has been closed, files can no longer be added'); 283*605f8e8dSAndreas Gohr } 284*605f8e8dSAndreas Gohr 285*605f8e8dSAndreas Gohr $len = strlen($data); 286*605f8e8dSAndreas Gohr $fileinfo->setSize($len); 287*605f8e8dSAndreas Gohr $this->writeFileHeader($fileinfo); 288*605f8e8dSAndreas Gohr 289*605f8e8dSAndreas Gohr for ($s = 0; $s < $len; $s += 512) { 290*605f8e8dSAndreas Gohr $this->writebytes(pack("a512", substr($data, $s, 512))); 291*605f8e8dSAndreas Gohr } 292*605f8e8dSAndreas Gohr } 293*605f8e8dSAndreas Gohr 294*605f8e8dSAndreas Gohr /** 295*605f8e8dSAndreas Gohr * Add the closing footer to the archive if in write mode, close all file handles 296*605f8e8dSAndreas Gohr * 297*605f8e8dSAndreas Gohr * After a call to this function no more data can be added to the archive, for 298*605f8e8dSAndreas Gohr * read access no reading is allowed anymore 299*605f8e8dSAndreas Gohr * 300*605f8e8dSAndreas Gohr * "Physically, an archive consists of a series of file entries terminated by an end-of-archive entry, which 301*605f8e8dSAndreas Gohr * consists of two 512 blocks of zero bytes" 302*605f8e8dSAndreas Gohr * 303*605f8e8dSAndreas Gohr * @link http://www.gnu.org/software/tar/manual/html_chapter/tar_8.html#SEC134 304*605f8e8dSAndreas Gohr */ 305*605f8e8dSAndreas Gohr public function close() 306*605f8e8dSAndreas Gohr { 307*605f8e8dSAndreas Gohr if ($this->closed) { 308*605f8e8dSAndreas Gohr return; 309*605f8e8dSAndreas Gohr } // we did this already 310*605f8e8dSAndreas Gohr 311*605f8e8dSAndreas Gohr // write footer 312*605f8e8dSAndreas Gohr if ($this->writeaccess) { 313*605f8e8dSAndreas Gohr $this->writebytes(pack("a512", "")); 314*605f8e8dSAndreas Gohr $this->writebytes(pack("a512", "")); 315*605f8e8dSAndreas Gohr } 316*605f8e8dSAndreas Gohr 317*605f8e8dSAndreas Gohr // close file handles 318*605f8e8dSAndreas Gohr if ($this->file) { 319*605f8e8dSAndreas Gohr if ($this->comptype === Archive::COMPRESS_GZIP) { 320*605f8e8dSAndreas Gohr gzclose($this->fh); 321*605f8e8dSAndreas Gohr } elseif ($this->comptype === Archive::COMPRESS_BZIP) { 322*605f8e8dSAndreas Gohr bzclose($this->fh); 323*605f8e8dSAndreas Gohr } else { 324*605f8e8dSAndreas Gohr fclose($this->fh); 325*605f8e8dSAndreas Gohr } 326*605f8e8dSAndreas Gohr 327*605f8e8dSAndreas Gohr $this->file = ''; 328*605f8e8dSAndreas Gohr $this->fh = 0; 329*605f8e8dSAndreas Gohr } 330*605f8e8dSAndreas Gohr 331*605f8e8dSAndreas Gohr $this->writeaccess = false; 332*605f8e8dSAndreas Gohr $this->closed = true; 333*605f8e8dSAndreas Gohr } 334*605f8e8dSAndreas Gohr 335*605f8e8dSAndreas Gohr /** 336*605f8e8dSAndreas Gohr * Returns the created in-memory archive data 337*605f8e8dSAndreas Gohr * 338*605f8e8dSAndreas Gohr * This implicitly calls close() on the Archive 339*605f8e8dSAndreas Gohr */ 340*605f8e8dSAndreas Gohr public function getArchive() 341*605f8e8dSAndreas Gohr { 342*605f8e8dSAndreas Gohr $this->close(); 343*605f8e8dSAndreas Gohr 344*605f8e8dSAndreas Gohr if ($this->comptype === Archive::COMPRESS_AUTO) { 345*605f8e8dSAndreas Gohr $this->comptype = Archive::COMPRESS_NONE; 346*605f8e8dSAndreas Gohr } 347*605f8e8dSAndreas Gohr 348*605f8e8dSAndreas Gohr if ($this->comptype === Archive::COMPRESS_GZIP) { 349*605f8e8dSAndreas Gohr return gzcompress($this->memory, $this->complevel); 350*605f8e8dSAndreas Gohr } 351*605f8e8dSAndreas Gohr if ($this->comptype === Archive::COMPRESS_BZIP) { 352*605f8e8dSAndreas Gohr return bzcompress($this->memory); 353*605f8e8dSAndreas Gohr } 354*605f8e8dSAndreas Gohr return $this->memory; 355*605f8e8dSAndreas Gohr } 356*605f8e8dSAndreas Gohr 357*605f8e8dSAndreas Gohr /** 358*605f8e8dSAndreas Gohr * Save the created in-memory archive data 359*605f8e8dSAndreas Gohr * 360*605f8e8dSAndreas Gohr * Note: It more memory effective to specify the filename in the create() function and 361*605f8e8dSAndreas Gohr * let the library work on the new file directly. 362*605f8e8dSAndreas Gohr * 363*605f8e8dSAndreas Gohr * @param string $file 364*605f8e8dSAndreas Gohr * @throws ArchiveIOException 365*605f8e8dSAndreas Gohr */ 366*605f8e8dSAndreas Gohr public function save($file) 367*605f8e8dSAndreas Gohr { 368*605f8e8dSAndreas Gohr if ($this->comptype === Archive::COMPRESS_AUTO) { 369*605f8e8dSAndreas Gohr $this->setCompression($this->filetype($this->complevel, $file)); 370*605f8e8dSAndreas Gohr } 371*605f8e8dSAndreas Gohr 372*605f8e8dSAndreas Gohr if (!file_put_contents($file, $this->getArchive())) { 373*605f8e8dSAndreas Gohr throw new ArchiveIOException('Could not write to file: '.$file); 374*605f8e8dSAndreas Gohr } 375*605f8e8dSAndreas Gohr } 376*605f8e8dSAndreas Gohr 377*605f8e8dSAndreas Gohr /** 378*605f8e8dSAndreas Gohr * Read from the open file pointer 379*605f8e8dSAndreas Gohr * 380*605f8e8dSAndreas Gohr * @param int $length bytes to read 381*605f8e8dSAndreas Gohr * @return string 382*605f8e8dSAndreas Gohr */ 383*605f8e8dSAndreas Gohr protected function readbytes($length) 384*605f8e8dSAndreas Gohr { 385*605f8e8dSAndreas Gohr if ($this->comptype === Archive::COMPRESS_GZIP) { 386*605f8e8dSAndreas Gohr return @gzread($this->fh, $length); 387*605f8e8dSAndreas Gohr } elseif ($this->comptype === Archive::COMPRESS_BZIP) { 388*605f8e8dSAndreas Gohr return @bzread($this->fh, $length); 389*605f8e8dSAndreas Gohr } else { 390*605f8e8dSAndreas Gohr return @fread($this->fh, $length); 391*605f8e8dSAndreas Gohr } 392*605f8e8dSAndreas Gohr } 393*605f8e8dSAndreas Gohr 394*605f8e8dSAndreas Gohr /** 395*605f8e8dSAndreas Gohr * Write to the open filepointer or memory 396*605f8e8dSAndreas Gohr * 397*605f8e8dSAndreas Gohr * @param string $data 398*605f8e8dSAndreas Gohr * @throws ArchiveIOException 399*605f8e8dSAndreas Gohr * @return int number of bytes written 400*605f8e8dSAndreas Gohr */ 401*605f8e8dSAndreas Gohr protected function writebytes($data) 402*605f8e8dSAndreas Gohr { 403*605f8e8dSAndreas Gohr if (!$this->file) { 404*605f8e8dSAndreas Gohr $this->memory .= $data; 405*605f8e8dSAndreas Gohr $written = strlen($data); 406*605f8e8dSAndreas Gohr } elseif ($this->comptype === Archive::COMPRESS_GZIP) { 407*605f8e8dSAndreas Gohr $written = @gzwrite($this->fh, $data); 408*605f8e8dSAndreas Gohr } elseif ($this->comptype === Archive::COMPRESS_BZIP) { 409*605f8e8dSAndreas Gohr $written = @bzwrite($this->fh, $data); 410*605f8e8dSAndreas Gohr } else { 411*605f8e8dSAndreas Gohr $written = @fwrite($this->fh, $data); 412*605f8e8dSAndreas Gohr } 413*605f8e8dSAndreas Gohr if ($written === false) { 414*605f8e8dSAndreas Gohr throw new ArchiveIOException('Failed to write to archive stream'); 415*605f8e8dSAndreas Gohr } 416*605f8e8dSAndreas Gohr return $written; 417*605f8e8dSAndreas Gohr } 418*605f8e8dSAndreas Gohr 419*605f8e8dSAndreas Gohr /** 420*605f8e8dSAndreas Gohr * Skip forward in the open file pointer 421*605f8e8dSAndreas Gohr * 422*605f8e8dSAndreas Gohr * This is basically a wrapper around seek() (and a workaround for bzip2) 423*605f8e8dSAndreas Gohr * 424*605f8e8dSAndreas Gohr * @param int $bytes seek to this position 425*605f8e8dSAndreas Gohr */ 426*605f8e8dSAndreas Gohr function skipbytes($bytes) 427*605f8e8dSAndreas Gohr { 428*605f8e8dSAndreas Gohr if ($this->comptype === Archive::COMPRESS_GZIP) { 429*605f8e8dSAndreas Gohr @gzseek($this->fh, $bytes, SEEK_CUR); 430*605f8e8dSAndreas Gohr } elseif ($this->comptype === Archive::COMPRESS_BZIP) { 431*605f8e8dSAndreas Gohr // there is no seek in bzip2, we simply read on 432*605f8e8dSAndreas Gohr @bzread($this->fh, $bytes); 433*605f8e8dSAndreas Gohr } else { 434*605f8e8dSAndreas Gohr @fseek($this->fh, $bytes, SEEK_CUR); 435*605f8e8dSAndreas Gohr } 436*605f8e8dSAndreas Gohr } 437*605f8e8dSAndreas Gohr 438*605f8e8dSAndreas Gohr /** 439*605f8e8dSAndreas Gohr * Write the given file metat data as header 440*605f8e8dSAndreas Gohr * 441*605f8e8dSAndreas Gohr * @param FileInfo $fileinfo 442*605f8e8dSAndreas Gohr */ 443*605f8e8dSAndreas Gohr protected function writeFileHeader(FileInfo $fileinfo) 444*605f8e8dSAndreas Gohr { 445*605f8e8dSAndreas Gohr $this->writeRawFileHeader( 446*605f8e8dSAndreas Gohr $fileinfo->getPath(), 447*605f8e8dSAndreas Gohr $fileinfo->getUid(), 448*605f8e8dSAndreas Gohr $fileinfo->getGid(), 449*605f8e8dSAndreas Gohr $fileinfo->getMode(), 450*605f8e8dSAndreas Gohr $fileinfo->getSize(), 451*605f8e8dSAndreas Gohr $fileinfo->getMtime(), 452*605f8e8dSAndreas Gohr $fileinfo->getIsdir() ? '5' : '0' 453*605f8e8dSAndreas Gohr ); 454*605f8e8dSAndreas Gohr } 455*605f8e8dSAndreas Gohr 456*605f8e8dSAndreas Gohr /** 457*605f8e8dSAndreas Gohr * Write a file header to the stream 458*605f8e8dSAndreas Gohr * 459*605f8e8dSAndreas Gohr * @param string $name 460*605f8e8dSAndreas Gohr * @param int $uid 461*605f8e8dSAndreas Gohr * @param int $gid 462*605f8e8dSAndreas Gohr * @param int $perm 463*605f8e8dSAndreas Gohr * @param int $size 464*605f8e8dSAndreas Gohr * @param int $mtime 465*605f8e8dSAndreas Gohr * @param string $typeflag Set to '5' for directories 466*605f8e8dSAndreas Gohr */ 467*605f8e8dSAndreas Gohr protected function writeRawFileHeader($name, $uid, $gid, $perm, $size, $mtime, $typeflag = '') 468*605f8e8dSAndreas Gohr { 469*605f8e8dSAndreas Gohr // handle filename length restrictions 470*605f8e8dSAndreas Gohr $prefix = ''; 471*605f8e8dSAndreas Gohr $namelen = strlen($name); 472*605f8e8dSAndreas Gohr if ($namelen > 100) { 473*605f8e8dSAndreas Gohr $file = basename($name); 474*605f8e8dSAndreas Gohr $dir = dirname($name); 475*605f8e8dSAndreas Gohr if (strlen($file) > 100 || strlen($dir) > 155) { 476*605f8e8dSAndreas Gohr // we're still too large, let's use GNU longlink 477*605f8e8dSAndreas Gohr $this->writeRawFileHeader('././@LongLink', 0, 0, 0, $namelen, 0, 'L'); 478*605f8e8dSAndreas Gohr for ($s = 0; $s < $namelen; $s += 512) { 479*605f8e8dSAndreas Gohr $this->writebytes(pack("a512", substr($name, $s, 512))); 480*605f8e8dSAndreas Gohr } 481*605f8e8dSAndreas Gohr $name = substr($name, 0, 100); // cut off name 482*605f8e8dSAndreas Gohr } else { 483*605f8e8dSAndreas Gohr // we're fine when splitting, use POSIX ustar 484*605f8e8dSAndreas Gohr $prefix = $dir; 485*605f8e8dSAndreas Gohr $name = $file; 486*605f8e8dSAndreas Gohr } 487*605f8e8dSAndreas Gohr } 488*605f8e8dSAndreas Gohr 489*605f8e8dSAndreas Gohr // values are needed in octal 490*605f8e8dSAndreas Gohr $uid = sprintf("%6s ", decoct($uid)); 491*605f8e8dSAndreas Gohr $gid = sprintf("%6s ", decoct($gid)); 492*605f8e8dSAndreas Gohr $perm = sprintf("%6s ", decoct($perm)); 493*605f8e8dSAndreas Gohr $size = sprintf("%11s ", decoct($size)); 494*605f8e8dSAndreas Gohr $mtime = sprintf("%11s", decoct($mtime)); 495*605f8e8dSAndreas Gohr 496*605f8e8dSAndreas Gohr $data_first = pack("a100a8a8a8a12A12", $name, $perm, $uid, $gid, $size, $mtime); 497*605f8e8dSAndreas Gohr $data_last = pack("a1a100a6a2a32a32a8a8a155a12", $typeflag, '', 'ustar', '', '', '', '', '', $prefix, ""); 498*605f8e8dSAndreas Gohr 499*605f8e8dSAndreas Gohr for ($i = 0, $chks = 0; $i < 148; $i++) { 500*605f8e8dSAndreas Gohr $chks += ord($data_first[$i]); 501*605f8e8dSAndreas Gohr } 502*605f8e8dSAndreas Gohr 503*605f8e8dSAndreas Gohr for ($i = 156, $chks += 256, $j = 0; $i < 512; $i++, $j++) { 504*605f8e8dSAndreas Gohr $chks += ord($data_last[$j]); 505*605f8e8dSAndreas Gohr } 506*605f8e8dSAndreas Gohr 507*605f8e8dSAndreas Gohr $this->writebytes($data_first); 508*605f8e8dSAndreas Gohr 509*605f8e8dSAndreas Gohr $chks = pack("a8", sprintf("%6s ", decoct($chks))); 510*605f8e8dSAndreas Gohr $this->writebytes($chks.$data_last); 511*605f8e8dSAndreas Gohr } 512*605f8e8dSAndreas Gohr 513*605f8e8dSAndreas Gohr /** 514*605f8e8dSAndreas Gohr * Decode the given tar file header 515*605f8e8dSAndreas Gohr * 516*605f8e8dSAndreas Gohr * @param string $block a 512 byte block containign the header data 517*605f8e8dSAndreas Gohr * @return array|bool 518*605f8e8dSAndreas Gohr */ 519*605f8e8dSAndreas Gohr protected function parseHeader($block) 520*605f8e8dSAndreas Gohr { 521*605f8e8dSAndreas Gohr if (!$block || strlen($block) != 512) { 522*605f8e8dSAndreas Gohr return false; 523*605f8e8dSAndreas Gohr } 524*605f8e8dSAndreas Gohr 525*605f8e8dSAndreas Gohr for ($i = 0, $chks = 0; $i < 148; $i++) { 526*605f8e8dSAndreas Gohr $chks += ord($block[$i]); 527*605f8e8dSAndreas Gohr } 528*605f8e8dSAndreas Gohr 529*605f8e8dSAndreas Gohr for ($i = 156, $chks += 256; $i < 512; $i++) { 530*605f8e8dSAndreas Gohr $chks += ord($block[$i]); 531*605f8e8dSAndreas Gohr } 532*605f8e8dSAndreas Gohr 533*605f8e8dSAndreas Gohr $header = @unpack( 534*605f8e8dSAndreas Gohr "a100filename/a8perm/a8uid/a8gid/a12size/a12mtime/a8checksum/a1typeflag/a100link/a6magic/a2version/a32uname/a32gname/a8devmajor/a8devminor/a155prefix", 535*605f8e8dSAndreas Gohr $block 536*605f8e8dSAndreas Gohr ); 537*605f8e8dSAndreas Gohr if (!$header) { 538*605f8e8dSAndreas Gohr return false; 539*605f8e8dSAndreas Gohr } 540*605f8e8dSAndreas Gohr 541*605f8e8dSAndreas Gohr $return['checksum'] = OctDec(trim($header['checksum'])); 542*605f8e8dSAndreas Gohr if ($return['checksum'] != $chks) { 543*605f8e8dSAndreas Gohr return false; 544*605f8e8dSAndreas Gohr } 545*605f8e8dSAndreas Gohr 546*605f8e8dSAndreas Gohr $return['filename'] = trim($header['filename']); 547*605f8e8dSAndreas Gohr $return['perm'] = OctDec(trim($header['perm'])); 548*605f8e8dSAndreas Gohr $return['uid'] = OctDec(trim($header['uid'])); 549*605f8e8dSAndreas Gohr $return['gid'] = OctDec(trim($header['gid'])); 550*605f8e8dSAndreas Gohr $return['size'] = OctDec(trim($header['size'])); 551*605f8e8dSAndreas Gohr $return['mtime'] = OctDec(trim($header['mtime'])); 552*605f8e8dSAndreas Gohr $return['typeflag'] = $header['typeflag']; 553*605f8e8dSAndreas Gohr $return['link'] = trim($header['link']); 554*605f8e8dSAndreas Gohr $return['uname'] = trim($header['uname']); 555*605f8e8dSAndreas Gohr $return['gname'] = trim($header['gname']); 556*605f8e8dSAndreas Gohr 557*605f8e8dSAndreas Gohr // Handle ustar Posix compliant path prefixes 558*605f8e8dSAndreas Gohr if (trim($header['prefix'])) { 559*605f8e8dSAndreas Gohr $return['filename'] = trim($header['prefix']).'/'.$return['filename']; 560*605f8e8dSAndreas Gohr } 561*605f8e8dSAndreas Gohr 562*605f8e8dSAndreas Gohr // Handle Long-Link entries from GNU Tar 563*605f8e8dSAndreas Gohr if ($return['typeflag'] == 'L') { 564*605f8e8dSAndreas Gohr // following data block(s) is the filename 565*605f8e8dSAndreas Gohr $filename = trim($this->readbytes(ceil($header['size'] / 512) * 512)); 566*605f8e8dSAndreas Gohr // next block is the real header 567*605f8e8dSAndreas Gohr $block = $this->readbytes(512); 568*605f8e8dSAndreas Gohr $return = $this->parseHeader($block); 569*605f8e8dSAndreas Gohr // overwrite the filename 570*605f8e8dSAndreas Gohr $return['filename'] = $filename; 571*605f8e8dSAndreas Gohr } 572*605f8e8dSAndreas Gohr 573*605f8e8dSAndreas Gohr return $return; 574*605f8e8dSAndreas Gohr } 575*605f8e8dSAndreas Gohr 576*605f8e8dSAndreas Gohr /** 577*605f8e8dSAndreas Gohr * Creates a FileInfo object from the given parsed header 578*605f8e8dSAndreas Gohr * 579*605f8e8dSAndreas Gohr * @param $header 580*605f8e8dSAndreas Gohr * @return FileInfo 581*605f8e8dSAndreas Gohr */ 582*605f8e8dSAndreas Gohr protected function header2fileinfo($header) 583*605f8e8dSAndreas Gohr { 584*605f8e8dSAndreas Gohr $fileinfo = new FileInfo(); 585*605f8e8dSAndreas Gohr $fileinfo->setPath($header['filename']); 586*605f8e8dSAndreas Gohr $fileinfo->setMode($header['perm']); 587*605f8e8dSAndreas Gohr $fileinfo->setUid($header['uid']); 588*605f8e8dSAndreas Gohr $fileinfo->setGid($header['gid']); 589*605f8e8dSAndreas Gohr $fileinfo->setSize($header['size']); 590*605f8e8dSAndreas Gohr $fileinfo->setMtime($header['mtime']); 591*605f8e8dSAndreas Gohr $fileinfo->setOwner($header['uname']); 592*605f8e8dSAndreas Gohr $fileinfo->setGroup($header['gname']); 593*605f8e8dSAndreas Gohr $fileinfo->setIsdir((bool) $header['typeflag']); 594*605f8e8dSAndreas Gohr 595*605f8e8dSAndreas Gohr return $fileinfo; 596*605f8e8dSAndreas Gohr } 597*605f8e8dSAndreas Gohr 598*605f8e8dSAndreas Gohr /** 599*605f8e8dSAndreas Gohr * Checks if the given compression type is available and throws an exception if not 600*605f8e8dSAndreas Gohr * 601*605f8e8dSAndreas Gohr * @param $comptype 602*605f8e8dSAndreas Gohr * @throws ArchiveIllegalCompressionException 603*605f8e8dSAndreas Gohr */ 604*605f8e8dSAndreas Gohr protected function compressioncheck($comptype) 605*605f8e8dSAndreas Gohr { 606*605f8e8dSAndreas Gohr if ($comptype === Archive::COMPRESS_GZIP && !function_exists('gzopen')) { 607*605f8e8dSAndreas Gohr throw new ArchiveIllegalCompressionException('No gzip support available'); 608*605f8e8dSAndreas Gohr } 609*605f8e8dSAndreas Gohr 610*605f8e8dSAndreas Gohr if ($comptype === Archive::COMPRESS_BZIP && !function_exists('bzopen')) { 611*605f8e8dSAndreas Gohr throw new ArchiveIllegalCompressionException('No bzip2 support available'); 612*605f8e8dSAndreas Gohr } 613*605f8e8dSAndreas Gohr } 614*605f8e8dSAndreas Gohr 615*605f8e8dSAndreas Gohr /** 616*605f8e8dSAndreas Gohr * Guesses the wanted compression from the given filename extension 617*605f8e8dSAndreas Gohr * 618*605f8e8dSAndreas Gohr * You don't need to call this yourself. It's used when you pass Archive::COMPRESS_AUTO somewhere 619*605f8e8dSAndreas Gohr * 620*605f8e8dSAndreas Gohr * @param string $file 621*605f8e8dSAndreas Gohr * @return int 622*605f8e8dSAndreas Gohr */ 623*605f8e8dSAndreas Gohr public function filetype($file) 624*605f8e8dSAndreas Gohr { 625*605f8e8dSAndreas Gohr $file = strtolower($file); 626*605f8e8dSAndreas Gohr if (substr($file, -3) == '.gz' || substr($file, -4) == '.tgz') { 627*605f8e8dSAndreas Gohr $comptype = Archive::COMPRESS_GZIP; 628*605f8e8dSAndreas Gohr } elseif (substr($file, -4) == '.bz2' || substr($file, -4) == '.tbz') { 629*605f8e8dSAndreas Gohr $comptype = Archive::COMPRESS_BZIP; 630*605f8e8dSAndreas Gohr } else { 631*605f8e8dSAndreas Gohr $comptype = Archive::COMPRESS_NONE; 632*605f8e8dSAndreas Gohr } 633*605f8e8dSAndreas Gohr return $comptype; 634*605f8e8dSAndreas Gohr } 635*605f8e8dSAndreas Gohr} 636