mirror of
https://github.com/splitbrain/php-archive.git
synced 2025-01-17 21:49:09 +01:00
started to refactor into hierarchy
This commit is contained in:
parent
737f1a5a47
commit
04fbc74452
122
src/splitbrain/Archive/Archive.php
Normal file
122
src/splitbrain/Archive/Archive.php
Normal file
@ -0,0 +1,122 @@
|
||||
<?php
|
||||
|
||||
abstract class Archive
|
||||
{
|
||||
|
||||
const COMPRESS_AUTO = -1;
|
||||
const COMPRESS_NONE = 0;
|
||||
const COMPRESS_GZIP = 1;
|
||||
const COMPRESS_BZIP = 2;
|
||||
|
||||
/**
|
||||
* @param int $type Type of compression to use (use COMPRESS_* constants)
|
||||
* @param int $level Compression level (0 to 9)
|
||||
* @return mixed
|
||||
*/
|
||||
abstract public function setCompression($type = Archive::COMPRESS_AUTO, $level = 9);
|
||||
|
||||
/**
|
||||
* Open an existing archive file for reading
|
||||
*
|
||||
* @param string $file
|
||||
* @throws TarIOException
|
||||
*/
|
||||
abstract public function open($file);
|
||||
|
||||
/**
|
||||
* Read the contents of an archive
|
||||
*
|
||||
* This function lists the files stored in the archive, and returns an indexed array of FileInfo objects
|
||||
*
|
||||
* The archive is closed afer reading the contents, because rewinding is not possible in bzip2 streams.
|
||||
* Reopen the file with open() again if you want to do additional operations
|
||||
*/
|
||||
abstract public function contents();
|
||||
|
||||
/**
|
||||
* Extract an existing archive
|
||||
*
|
||||
* The $strip parameter allows you to strip a certain number of path components from the filenames
|
||||
* found in the tar file, similar to the --strip-components feature of GNU tar. This is triggered when
|
||||
* an integer is passed as $strip.
|
||||
* Alternatively a fixed string prefix may be passed in $strip. If the filename matches this prefix,
|
||||
* the prefix will be stripped. It is recommended to give prefixes with a trailing slash.
|
||||
*
|
||||
* By default this will extract all files found in the archive. You can restrict the output using the $include
|
||||
* and $exclude parameter. Both expect a full regular expression (including delimiters and modifiers). If
|
||||
* $include is set only files that match this expression will be extracted. Files that match the $exclude
|
||||
* expression will never be extracted. Both parameters can be used in combination. Expressions are matched against
|
||||
* stripped filenames as described above.
|
||||
*
|
||||
* The archive is closed afer reading the contents, because rewinding is not possible in bzip2 streams.
|
||||
* Reopen the file with open() again if you want to do additional operations
|
||||
*
|
||||
* @param string $outdir the target directory for extracting
|
||||
* @param int|string $strip either the number of path components or a fixed prefix to strip
|
||||
* @param string $exclude a regular expression of files to exclude
|
||||
* @param string $include a regular expression of files to include
|
||||
* @throws TarIOException
|
||||
* @return array
|
||||
*/
|
||||
abstract public function extract($outdir, $strip = '', $exclude = '', $include = '');
|
||||
|
||||
/**
|
||||
* Create a new archive file
|
||||
*
|
||||
* If $file is empty, the tar file will be created in memory
|
||||
*
|
||||
* @param string $file
|
||||
*/
|
||||
abstract public function create($file = '');
|
||||
|
||||
/**
|
||||
* Add a file to the current TAR archive using an existing file in the filesystem
|
||||
*
|
||||
* @todo handle directory adding
|
||||
* @param string $file path to the original file
|
||||
* @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
|
||||
* @throws TarIOException
|
||||
*/
|
||||
abstract public function addFile($file, $fileinfo = '');
|
||||
|
||||
/**
|
||||
* Add a file to the current TAR archive using the given $data as content
|
||||
*
|
||||
* @param string $data binary content of the file to add
|
||||
* @param string|FileInfo $fileinfo either the name to us in archive (string) or a FileInfo oject with all meta data
|
||||
* @return
|
||||
*/
|
||||
abstract public function addData($data, $fileinfo);
|
||||
|
||||
/**
|
||||
* Close the archive, close all file handles
|
||||
*
|
||||
* After a call to this function no more data can be added to the archive, for
|
||||
* read access no reading is allowed anymore
|
||||
*/
|
||||
abstract public function close();
|
||||
|
||||
/**
|
||||
* Returns the created in-memory archive data
|
||||
*
|
||||
* This implicitly calls close() on the Archive
|
||||
*/
|
||||
abstract public function getArchive();
|
||||
|
||||
/**
|
||||
* Save the created in-memory archive data
|
||||
*
|
||||
* Note: It is more memory effective to specify the filename in the create() function and
|
||||
* let the library work on the new file directly.
|
||||
*
|
||||
* @param string $file
|
||||
*/
|
||||
abstract public function save($file);
|
||||
|
||||
}
|
||||
|
||||
class ArchiveIOException extends Exception {
|
||||
}
|
||||
|
||||
class ArchiveIllegalCompressionException extends Exception {
|
||||
}
|
288
src/splitbrain/Archive/FileInfo.php
Normal file
288
src/splitbrain/Archive/FileInfo.php
Normal file
@ -0,0 +1,288 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Class FileInfo
|
||||
*
|
||||
* stores meta data over a file in an Archive
|
||||
*/
|
||||
class FileInfo
|
||||
{
|
||||
|
||||
protected $isdir = false;
|
||||
protected $path = '';
|
||||
protected $size = 0;
|
||||
protected $mtime = 0;
|
||||
protected $mode = 0664;
|
||||
protected $owner = '';
|
||||
protected $group = '';
|
||||
protected $uid = 0;
|
||||
protected $gid = 0;
|
||||
protected $comment = '';
|
||||
|
||||
/**
|
||||
* initialize dynamic defaults
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->mtime = time();
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory to build FileInfo from existing file or directory
|
||||
*
|
||||
* @param $path
|
||||
* @return FileInfo
|
||||
* @throws FileInfoException
|
||||
*/
|
||||
public static function fromPath($path)
|
||||
{
|
||||
if (!file_exists($path)) {
|
||||
throw new FileInfoException("$path does not exist");
|
||||
}
|
||||
|
||||
$stat = stat($path);
|
||||
$file = new FileInfo();
|
||||
|
||||
$file->setPath($path);
|
||||
$file->setIsdir(is_dir($path));
|
||||
$file->setMode(fileperms($path));
|
||||
$file->setOwner(fileowner($path));
|
||||
$file->setGroup(filegroup($path));
|
||||
$file->setUid($stat['uid']);
|
||||
$file->setGid($stat['gid']);
|
||||
$file->setMtime($stat['mtime']);
|
||||
|
||||
return $file;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getSize()
|
||||
{
|
||||
return $this->size;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $size
|
||||
*/
|
||||
public function setSize($size)
|
||||
{
|
||||
$this->size = $size;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getMtime()
|
||||
{
|
||||
return $this->mtime;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $mtime
|
||||
*/
|
||||
public function setMtime($mtime)
|
||||
{
|
||||
$this->mtime = $mtime;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getGid()
|
||||
{
|
||||
return $this->gid;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $gid
|
||||
*/
|
||||
public function setGid($gid)
|
||||
{
|
||||
$this->gid = $gid;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getUid()
|
||||
{
|
||||
return $this->uid;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $uid
|
||||
*/
|
||||
public function setUid($uid)
|
||||
{
|
||||
$this->uid = $uid;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getComment()
|
||||
{
|
||||
return $this->comment;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $comment
|
||||
*/
|
||||
public function setComment($comment)
|
||||
{
|
||||
$this->comment = $comment;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getGroup()
|
||||
{
|
||||
return $this->group;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $group
|
||||
*/
|
||||
public function setGroup($group)
|
||||
{
|
||||
$this->group = $group;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return boolean
|
||||
*/
|
||||
public function getIsdir()
|
||||
{
|
||||
return $this->isdir;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param boolean $isdir
|
||||
*/
|
||||
public function setIsdir($isdir)
|
||||
{
|
||||
// default mode for directories
|
||||
if ($isdir && $this->mode === 0664) {
|
||||
$this->mode = 0775;
|
||||
}
|
||||
$this->isdir = $isdir;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getMode()
|
||||
{
|
||||
return $this->mode;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $mode
|
||||
*/
|
||||
public function setMode($mode)
|
||||
{
|
||||
$this->mode = $mode;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getOwner()
|
||||
{
|
||||
return $this->owner;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $owner
|
||||
*/
|
||||
public function setOwner($owner)
|
||||
{
|
||||
$this->owner = $owner;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getPath()
|
||||
{
|
||||
return $this->path;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $path
|
||||
*/
|
||||
public function setPath($path)
|
||||
{
|
||||
$this->path = $this->cleanPath($path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleans up a path and removes relative parts, also strips leading slashes
|
||||
*
|
||||
* @param string $path
|
||||
* @return string
|
||||
*/
|
||||
protected function cleanPath($path)
|
||||
{
|
||||
$path = str_replace('\\', '/', $path);
|
||||
$path = explode('/', $path);
|
||||
$newpath = array();
|
||||
foreach ($path as $p) {
|
||||
if ($p === '' || $p === '.') {
|
||||
continue;
|
||||
}
|
||||
if ($p === '..') {
|
||||
array_pop($newpath);
|
||||
continue;
|
||||
}
|
||||
array_push($newpath, $p);
|
||||
}
|
||||
return trim(implode('/', $newpath), '/');
|
||||
}
|
||||
|
||||
/**
|
||||
* Strip given prefix or number of path segments from the filename
|
||||
*
|
||||
* The $strip parameter allows you to strip a certain number of path components from the filenames
|
||||
* found in the tar file, similar to the --strip-components feature of GNU tar. This is triggered when
|
||||
* an integer is passed as $strip.
|
||||
* Alternatively a fixed string prefix may be passed in $strip. If the filename matches this prefix,
|
||||
* the prefix will be stripped. It is recommended to give prefixes with a trailing slash.
|
||||
*
|
||||
* @param int|string $strip
|
||||
* @return FileInfo
|
||||
*/
|
||||
public function strip($strip)
|
||||
{
|
||||
$filename = $this->getPath();
|
||||
$striplen = strlen($strip);
|
||||
if (is_int($strip)) {
|
||||
// if $strip is an integer we strip this many path components
|
||||
$parts = explode('/', $filename);
|
||||
if (!$this->getIsdir()) {
|
||||
$base = array_pop($parts); // keep filename itself
|
||||
} else {
|
||||
$base = '';
|
||||
}
|
||||
$filename = join('/', array_slice($parts, $strip));
|
||||
if ($base) {
|
||||
$filename .= "/$base";
|
||||
}
|
||||
} else {
|
||||
// if strip is a string, we strip a prefix here
|
||||
if (substr($filename, 0, $striplen) == $strip) {
|
||||
$filename = substr($filename, $striplen);
|
||||
}
|
||||
}
|
||||
|
||||
$this->setPath($filename);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class FileInfoException extends Exception
|
||||
{
|
||||
}
|
@ -1,4 +1,5 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This class allows the extraction of existing and the creation of new Unix TAR archives.
|
||||
* To keep things simple, the modification of existing archives is not supported. It handles
|
||||
@ -38,50 +39,64 @@
|
||||
* $tar->addData(...);
|
||||
* ...
|
||||
* $tar->save('myfile.tgz'); // compresses and saves it
|
||||
* echo $tar->getArchive(Tar::COMPRESS_GZIP); // compresses and returns it
|
||||
* echo $tar->getArchive(Archive::COMPRESS_GZIP); // compresses and returns it
|
||||
*
|
||||
* @author Andreas Gohr <andi@splitbrain.org>
|
||||
* @author Bouchon <tarlib@bouchon.org> (Maxg)
|
||||
* @author Andreas Gohr <andi@splitbrain.org>
|
||||
* @author Bouchon <tarlib@bouchon.org> (Maxg)
|
||||
* @license GPL 2
|
||||
*/
|
||||
class Tar {
|
||||
|
||||
const COMPRESS_AUTO = 0;
|
||||
const COMPRESS_NONE = 1;
|
||||
const COMPRESS_GZIP = 2;
|
||||
const COMPRESS_BZIP = 3;
|
||||
class Tar extends Archive
|
||||
{
|
||||
|
||||
protected $file = '';
|
||||
protected $comptype = Tar::COMPRESS_AUTO;
|
||||
protected $comptype = Archive::COMPRESS_AUTO;
|
||||
protected $complevel = 9;
|
||||
protected $fh;
|
||||
protected $memory = '';
|
||||
protected $closed = true;
|
||||
protected $writeaccess = false;
|
||||
|
||||
/**
|
||||
* Sets the compression to use
|
||||
*
|
||||
* @param int $type Type of compression to use (use COMPRESS_* constants)
|
||||
* @param int $level Compression level (0 to 9)
|
||||
* @return mixed
|
||||
*/
|
||||
public function setCompression($type = Archive::COMPRESS_AUTO, $level = 9)
|
||||
{
|
||||
$this->compressioncheck($type);
|
||||
$this->comptype = $type;
|
||||
$this->complevel = $level;
|
||||
}
|
||||
|
||||
/**
|
||||
* Open an existing TAR file for reading
|
||||
*
|
||||
* @param string $file
|
||||
* @param int $comptype
|
||||
* @throws TarIOException
|
||||
* @throws ArchiveIOException
|
||||
*/
|
||||
public function open($file, $comptype = Tar::COMPRESS_AUTO) {
|
||||
// determine compression
|
||||
if($comptype == Tar::COMPRESS_AUTO) $comptype = $this->filetype($file);
|
||||
$this->compressioncheck($comptype);
|
||||
public function open($file)
|
||||
{
|
||||
$this->file = $file;
|
||||
|
||||
$this->comptype = $comptype;
|
||||
$this->file = $file;
|
||||
// update compression to mach file
|
||||
if ($this->comptype == Tar::COMPRESS_AUTO) {
|
||||
$this->setCompression($this->filetype($file), $this->complevel);
|
||||
}
|
||||
|
||||
if($this->comptype === Tar::COMPRESS_GZIP) {
|
||||
// open file handles
|
||||
if ($this->comptype === Archive::COMPRESS_GZIP) {
|
||||
$this->fh = @gzopen($this->file, 'rb');
|
||||
} elseif($this->comptype === Tar::COMPRESS_BZIP) {
|
||||
} elseif ($this->comptype === Archive::COMPRESS_BZIP) {
|
||||
$this->fh = @bzopen($this->file, 'r');
|
||||
} else {
|
||||
$this->fh = @fopen($this->file, 'rb');
|
||||
}
|
||||
|
||||
if(!$this->fh) throw new TarIOException('Could not open file for reading: '.$this->file);
|
||||
if (!$this->fh) {
|
||||
throw new ArchiveIOException('Could not open file for reading: '.$this->file);
|
||||
}
|
||||
$this->closed = false;
|
||||
}
|
||||
|
||||
@ -106,13 +121,18 @@ class Tar {
|
||||
* The archive is closed afer reading the contents, because rewinding is not possible in bzip2 streams.
|
||||
* Reopen the file with open() again if you want to do additional operations
|
||||
*/
|
||||
public function contents() {
|
||||
if($this->closed || !$this->file) throw new TarIOException('Can not read from a closed archive');
|
||||
public function contents()
|
||||
{
|
||||
if ($this->closed || !$this->file) {
|
||||
throw new ArchiveIOException('Can not read from a closed archive');
|
||||
}
|
||||
|
||||
$result = array();
|
||||
while($read = $this->readbytes(512)) {
|
||||
while ($read = $this->readbytes(512)) {
|
||||
$header = $this->parseHeader($read);
|
||||
if(!is_array($header)) continue;
|
||||
if (!is_array($header)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->skipbytes(ceil($header['size'] / 512) * 512);
|
||||
$result[] = $header;
|
||||
@ -144,76 +164,94 @@ class Tar {
|
||||
* @param int|string $strip either the number of path components or a fixed prefix to strip
|
||||
* @param string $exclude a regular expression of files to exclude
|
||||
* @param string $include a regular expression of files to include
|
||||
* @throws TarIOException
|
||||
* @throws ArchiveIOException
|
||||
* @return array
|
||||
*/
|
||||
public function extract($outdir, $strip = '', $exclude = '', $include = '') {
|
||||
if($this->closed || !$this->file) throw new TarIOException('Can not read from a closed archive');
|
||||
public function extract($outdir, $strip = '', $exclude = '', $include = '')
|
||||
{
|
||||
if ($this->closed || !$this->file) {
|
||||
throw new ArchiveIOException('Can not read from a closed archive');
|
||||
}
|
||||
|
||||
$outdir = rtrim($outdir, '/');
|
||||
io_mkdir_p($outdir);
|
||||
$striplen = strlen($strip);
|
||||
@mkdir($outdir, 0777, true);
|
||||
if (!is_dir($outdir)) {
|
||||
throw new ArchiveIOException("Could not create directory '$outdir'");
|
||||
}
|
||||
|
||||
$striplen = strlen($strip);
|
||||
$extracted = array();
|
||||
|
||||
while($dat = $this->readbytes(512)) {
|
||||
while ($dat = $this->readbytes(512)) {
|
||||
// read the file header
|
||||
$header = $this->parseHeader($dat);
|
||||
if(!is_array($header)) continue;
|
||||
if(!$header['filename']) continue;
|
||||
if (!is_array($header)) {
|
||||
continue;
|
||||
}
|
||||
if (!$header['filename']) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// strip prefix
|
||||
$filename = $this->cleanPath($header['filename']);
|
||||
if(is_int($strip)) {
|
||||
if (is_int($strip)) {
|
||||
// if $strip is an integer we strip this many path components
|
||||
$parts = explode('/', $filename);
|
||||
if(!$header['typeflag']) {
|
||||
if (!$header['typeflag']) {
|
||||
$base = array_pop($parts); // keep filename itself
|
||||
} else {
|
||||
$base = '';
|
||||
}
|
||||
$filename = join('/', array_slice($parts, $strip));
|
||||
if($base) $filename .= "/$base";
|
||||
if ($base) {
|
||||
$filename .= "/$base";
|
||||
}
|
||||
} else {
|
||||
// ifstrip is a string, we strip a prefix here
|
||||
if(substr($filename, 0, $striplen) == $strip) $filename = substr($filename, $striplen);
|
||||
if (substr($filename, 0, $striplen) == $strip) {
|
||||
$filename = substr($filename, $striplen);
|
||||
}
|
||||
}
|
||||
|
||||
// check if this should be extracted
|
||||
$extract = true;
|
||||
if(!$filename) {
|
||||
if (!$filename) {
|
||||
$extract = false;
|
||||
} else {
|
||||
if($include) {
|
||||
if(preg_match($include, $filename)) {
|
||||
if ($include) {
|
||||
if (preg_match($include, $filename)) {
|
||||
$extract = true;
|
||||
} else {
|
||||
$extract = false;
|
||||
}
|
||||
}
|
||||
if($exclude && preg_match($exclude, $filename)) {
|
||||
if ($exclude && preg_match($exclude, $filename)) {
|
||||
$extract = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Now do the extraction (or not)
|
||||
if($extract) {
|
||||
if ($extract) {
|
||||
$extracted[] = $header;
|
||||
|
||||
$output = "$outdir/$filename";
|
||||
$directory = ($header['typeflag']) ? $output : dirname($output);
|
||||
io_mkdir_p($directory);
|
||||
@mkdir($directory, 0777, true);
|
||||
|
||||
// is this a file?
|
||||
if(!$header['typeflag']) {
|
||||
if (!$header['typeflag']) {
|
||||
$fp = fopen($output, "wb");
|
||||
if(!$fp) throw new TarIOException('Could not open file for writing: '.$output);
|
||||
if (!$fp) {
|
||||
throw new ArchiveIOException('Could not open file for writing: '.$output);
|
||||
}
|
||||
|
||||
$size = floor($header['size'] / 512);
|
||||
for($i = 0; $i < $size; $i++) {
|
||||
for ($i = 0; $i < $size; $i++) {
|
||||
fwrite($fp, $this->readbytes(512), 512);
|
||||
}
|
||||
if(($header['size'] % 512) != 0) fwrite($fp, $this->readbytes(512), $header['size'] % 512);
|
||||
if (($header['size'] % 512) != 0) {
|
||||
fwrite($fp, $this->readbytes(512), $header['size'] % 512);
|
||||
}
|
||||
|
||||
fclose($fp);
|
||||
touch($output, $header['mtime']);
|
||||
@ -236,31 +274,31 @@ class Tar {
|
||||
* If $file is empty, the tar file will be created in memory
|
||||
*
|
||||
* @param string $file
|
||||
* @param int $comptype
|
||||
* @param int $complevel
|
||||
* @throws TarIOException
|
||||
* @throws TarIllegalCompressionException
|
||||
* @throws ArchiveIOException
|
||||
*/
|
||||
public function create($file = '', $comptype = Tar::COMPRESS_AUTO, $complevel = 9) {
|
||||
// determine compression
|
||||
if($comptype == Tar::COMPRESS_AUTO) $comptype = $this->filetype($file);
|
||||
$this->compressioncheck($comptype);
|
||||
public function create($file = '')
|
||||
{
|
||||
$this->file = $file;
|
||||
$this->memory = '';
|
||||
$this->fh = 0;
|
||||
|
||||
$this->comptype = $comptype;
|
||||
$this->file = $file;
|
||||
$this->memory = '';
|
||||
$this->fh = 0;
|
||||
if ($this->file) {
|
||||
// determine compression
|
||||
if ($this->comptype == Archive::COMPRESS_AUTO) {
|
||||
$this->setCompression($this->filetype($file), $this->complevel);
|
||||
}
|
||||
|
||||
if($this->file) {
|
||||
if($this->comptype === Tar::COMPRESS_GZIP) {
|
||||
$this->fh = @gzopen($this->file, 'wb'.$complevel);
|
||||
} elseif($this->comptype === Tar::COMPRESS_BZIP) {
|
||||
if ($this->comptype === Archive::COMPRESS_GZIP) {
|
||||
$this->fh = @gzopen($this->file, 'wb'.$this->complevel);
|
||||
} elseif ($this->comptype === Archive::COMPRESS_BZIP) {
|
||||
$this->fh = @bzopen($this->file, 'w');
|
||||
} else {
|
||||
$this->fh = @fopen($this->file, 'wb');
|
||||
}
|
||||
|
||||
if(!$this->fh) throw new TarIOException('Could not open file for writing: '.$this->file);
|
||||
if (!$this->fh) {
|
||||
throw new ArchiveIOException('Could not open file for writing: '.$this->file);
|
||||
}
|
||||
}
|
||||
$this->writeaccess = true;
|
||||
$this->closed = false;
|
||||
@ -272,16 +310,23 @@ class Tar {
|
||||
* @todo handle directory adding
|
||||
* @param string $file the original file
|
||||
* @param string $name the name to use for the file in the archive
|
||||
* @throws TarIOException
|
||||
* @throws ArchiveIOException
|
||||
*/
|
||||
public function addFile($file, $name = '') {
|
||||
if($this->closed) throw new TarIOException('Archive has been closed, files can no longer be added');
|
||||
public function addFile($file, $name = '')
|
||||
{
|
||||
if ($this->closed) {
|
||||
throw new ArchiveIOException('Archive has been closed, files can no longer be added');
|
||||
}
|
||||
|
||||
if(!$name) $name = $file;
|
||||
if (!$name) {
|
||||
$name = $file;
|
||||
}
|
||||
$name = $this->cleanPath($name);
|
||||
|
||||
$fp = fopen($file, 'rb');
|
||||
if(!$fp) throw new TarIOException('Could not open file for reading: '.$file);
|
||||
if (!$fp) {
|
||||
throw new ArchiveIOException('Could not open file for reading: '.$file);
|
||||
}
|
||||
|
||||
// create file header and copy all stat info from the original file
|
||||
clearstatcache(false, $file);
|
||||
@ -295,10 +340,14 @@ class Tar {
|
||||
filemtime($file)
|
||||
);
|
||||
|
||||
while(!feof($fp)) {
|
||||
while (!feof($fp)) {
|
||||
$data = fread($fp, 512);
|
||||
if($data === false) break;
|
||||
if($data === '') break;
|
||||
if ($data === false) {
|
||||
break;
|
||||
}
|
||||
if ($data === '') {
|
||||
break;
|
||||
}
|
||||
$packed = pack("a512", $data);
|
||||
$this->writebytes($packed);
|
||||
}
|
||||
@ -314,10 +363,13 @@ class Tar {
|
||||
* @param int $gid
|
||||
* @param int $perm
|
||||
* @param int $mtime
|
||||
* @throws TarIOException
|
||||
* @throws ArchiveIOException
|
||||
*/
|
||||
public function addData($name, $data, $uid = 0, $gid = 0, $perm = 0666, $mtime = 0) {
|
||||
if($this->closed) throw new TarIOException('Archive has been closed, files can no longer be added');
|
||||
public function addData($name, $data, $uid = 0, $gid = 0, $perm = 0666, $mtime = 0)
|
||||
{
|
||||
if ($this->closed) {
|
||||
throw new ArchiveIOException('Archive has been closed, files can no longer be added');
|
||||
}
|
||||
|
||||
$name = $this->cleanPath($name);
|
||||
$len = strlen($data);
|
||||
@ -331,7 +383,7 @@ class Tar {
|
||||
($mtime) ? $mtime : time()
|
||||
);
|
||||
|
||||
for($s = 0; $s < $len; $s += 512) {
|
||||
for ($s = 0; $s < $len; $s += 512) {
|
||||
$this->writebytes(pack("a512", substr($data, $s, 512)));
|
||||
}
|
||||
}
|
||||
@ -347,20 +399,23 @@ class Tar {
|
||||
*
|
||||
* @link http://www.gnu.org/software/tar/manual/html_chapter/tar_8.html#SEC134
|
||||
*/
|
||||
public function close() {
|
||||
if($this->closed) return; // we did this already
|
||||
public function close()
|
||||
{
|
||||
if ($this->closed) {
|
||||
return;
|
||||
} // we did this already
|
||||
|
||||
// write footer
|
||||
if($this->writeaccess) {
|
||||
if ($this->writeaccess) {
|
||||
$this->writebytes(pack("a512", ""));
|
||||
$this->writebytes(pack("a512", ""));
|
||||
}
|
||||
|
||||
// close file handles
|
||||
if($this->file) {
|
||||
if($this->comptype === Tar::COMPRESS_GZIP) {
|
||||
if ($this->file) {
|
||||
if ($this->comptype === Archive::COMPRESS_GZIP) {
|
||||
gzclose($this->fh);
|
||||
} elseif($this->comptype === Tar::COMPRESS_BZIP) {
|
||||
} elseif ($this->comptype === Archive::COMPRESS_BZIP) {
|
||||
bzclose($this->fh);
|
||||
} else {
|
||||
fclose($this->fh);
|
||||
@ -371,7 +426,7 @@ class Tar {
|
||||
}
|
||||
|
||||
$this->writeaccess = false;
|
||||
$this->closed = true;
|
||||
$this->closed = true;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -379,14 +434,20 @@ class Tar {
|
||||
*
|
||||
* This implicitly calls close() on the Archive
|
||||
*/
|
||||
public function getArchive($comptype = Tar::COMPRESS_AUTO, $complevel = 9) {
|
||||
public function getArchive()
|
||||
{
|
||||
$this->close();
|
||||
|
||||
if($comptype === Tar::COMPRESS_AUTO) $comptype = $this->comptype;
|
||||
$this->compressioncheck($comptype);
|
||||
if ($this->comptype === Archive::COMPRESS_AUTO) {
|
||||
$this->comptype = Archive::COMPRESS_NONE;
|
||||
}
|
||||
|
||||
if($comptype === Tar::COMPRESS_GZIP) return gzcompress($this->memory, $complevel);
|
||||
if($comptype === Tar::COMPRESS_BZIP) return bzcompress($this->memory);
|
||||
if ($this->comptype === Archive::COMPRESS_GZIP) {
|
||||
return gzcompress($this->memory, $this->complevel);
|
||||
}
|
||||
if ($this->comptype === Archive::COMPRESS_BZIP) {
|
||||
return bzcompress($this->memory);
|
||||
}
|
||||
return $this->memory;
|
||||
}
|
||||
|
||||
@ -397,15 +458,16 @@ class Tar {
|
||||
* let the library work on the new file directly.
|
||||
*
|
||||
* @param string $file
|
||||
* @param int $comptype
|
||||
* @param int $complevel
|
||||
* @throws TarIOException
|
||||
* @throws ArchiveIOException
|
||||
*/
|
||||
public function save($file, $comptype = Tar::COMPRESS_AUTO, $complevel = 9) {
|
||||
if($comptype === Tar::COMPRESS_AUTO) $comptype = $this->filetype($file);
|
||||
public function save($file)
|
||||
{
|
||||
if ($this->comptype === Archive::COMPRESS_AUTO) {
|
||||
$this->setCompression($this->filetype($file), $this->complevel);
|
||||
}
|
||||
|
||||
if(!file_put_contents($file, $this->getArchive($comptype, $complevel))) {
|
||||
throw new TarIOException('Could not write to file: '.$file);
|
||||
if (!file_put_contents($file, $this->getArchive())) {
|
||||
throw new ArchiveIOException('Could not write to file: '.$file);
|
||||
}
|
||||
}
|
||||
|
||||
@ -415,10 +477,11 @@ class Tar {
|
||||
* @param int $length bytes to read
|
||||
* @return string
|
||||
*/
|
||||
protected function readbytes($length) {
|
||||
if($this->comptype === Tar::COMPRESS_GZIP) {
|
||||
protected function readbytes($length)
|
||||
{
|
||||
if ($this->comptype === Archive::COMPRESS_GZIP) {
|
||||
return @gzread($this->fh, $length);
|
||||
} elseif($this->comptype === Tar::COMPRESS_BZIP) {
|
||||
} elseif ($this->comptype === Archive::COMPRESS_BZIP) {
|
||||
return @bzread($this->fh, $length);
|
||||
} else {
|
||||
return @fread($this->fh, $length);
|
||||
@ -429,21 +492,24 @@ class Tar {
|
||||
* Write to the open filepointer or memory
|
||||
*
|
||||
* @param string $data
|
||||
* @throws TarIOException
|
||||
* @throws ArchiveIOException
|
||||
* @return int number of bytes written
|
||||
*/
|
||||
protected function writebytes($data) {
|
||||
if(!$this->file) {
|
||||
protected function writebytes($data)
|
||||
{
|
||||
if (!$this->file) {
|
||||
$this->memory .= $data;
|
||||
$written = strlen($data);
|
||||
} elseif($this->comptype === Tar::COMPRESS_GZIP) {
|
||||
} elseif ($this->comptype === Archive::COMPRESS_GZIP) {
|
||||
$written = @gzwrite($this->fh, $data);
|
||||
} elseif($this->comptype === Tar::COMPRESS_BZIP) {
|
||||
} elseif ($this->comptype === Archive::COMPRESS_BZIP) {
|
||||
$written = @bzwrite($this->fh, $data);
|
||||
} else {
|
||||
$written = @fwrite($this->fh, $data);
|
||||
}
|
||||
if($written === false) throw new TarIOException('Failed to write to archive stream');
|
||||
if ($written === false) {
|
||||
throw new ArchiveIOException('Failed to write to archive stream');
|
||||
}
|
||||
return $written;
|
||||
}
|
||||
|
||||
@ -452,12 +518,13 @@ class Tar {
|
||||
*
|
||||
* This is basically a wrapper around seek() (and a workaround for bzip2)
|
||||
*
|
||||
* @param int $bytes seek to this position
|
||||
* @param int $bytes seek to this position
|
||||
*/
|
||||
function skipbytes($bytes) {
|
||||
if($this->comptype === Tar::COMPRESS_GZIP) {
|
||||
function skipbytes($bytes)
|
||||
{
|
||||
if ($this->comptype === Archive::COMPRESS_GZIP) {
|
||||
@gzseek($this->fh, $bytes, SEEK_CUR);
|
||||
} elseif($this->comptype === Tar::COMPRESS_BZIP) {
|
||||
} elseif ($this->comptype === Archive::COMPRESS_BZIP) {
|
||||
// there is no seek in bzip2, we simply read on
|
||||
@bzread($this->fh, $bytes);
|
||||
} else {
|
||||
@ -476,17 +543,18 @@ class Tar {
|
||||
* @param int $mtime
|
||||
* @param string $typeflag Set to '5' for directories
|
||||
*/
|
||||
protected function writeFileHeader($name, $uid, $gid, $perm, $size, $mtime, $typeflag = '') {
|
||||
protected function writeFileHeader($name, $uid, $gid, $perm, $size, $mtime, $typeflag = '')
|
||||
{
|
||||
// handle filename length restrictions
|
||||
$prefix = '';
|
||||
$namelen = strlen($name);
|
||||
if($namelen > 100) {
|
||||
if ($namelen > 100) {
|
||||
$file = basename($name);
|
||||
$dir = dirname($name);
|
||||
if(strlen($file) > 100 || strlen($dir) > 155) {
|
||||
if (strlen($file) > 100 || strlen($dir) > 155) {
|
||||
// we're still too large, let's use GNU longlink
|
||||
$this->writeFileHeader('././@LongLink', 0, 0, 0, $namelen, 0, 'L');
|
||||
for($s = 0; $s < $namelen; $s += 512) {
|
||||
for ($s = 0; $s < $namelen; $s += 512) {
|
||||
$this->writebytes(pack("a512", substr($name, $s, 512)));
|
||||
}
|
||||
$name = substr($name, 0, 100); // cut off name
|
||||
@ -507,11 +575,13 @@ class Tar {
|
||||
$data_first = pack("a100a8a8a8a12A12", $name, $perm, $uid, $gid, $size, $mtime);
|
||||
$data_last = pack("a1a100a6a2a32a32a8a8a155a12", $typeflag, '', 'ustar', '', '', '', '', '', $prefix, "");
|
||||
|
||||
for($i = 0, $chks = 0; $i < 148; $i++)
|
||||
for ($i = 0, $chks = 0; $i < 148; $i++) {
|
||||
$chks += ord($data_first[$i]);
|
||||
}
|
||||
|
||||
for($i = 156, $chks += 256, $j = 0; $i < 512; $i++, $j++)
|
||||
for ($i = 156, $chks += 256, $j = 0; $i < 512; $i++, $j++) {
|
||||
$chks += ord($data_last[$j]);
|
||||
}
|
||||
|
||||
$this->writebytes($data_first);
|
||||
|
||||
@ -525,20 +595,32 @@ class Tar {
|
||||
* @param string $block a 512 byte block containign the header data
|
||||
* @return array|bool
|
||||
*/
|
||||
protected function parseHeader($block) {
|
||||
if(!$block || strlen($block) != 512) return false;
|
||||
protected function parseHeader($block)
|
||||
{
|
||||
if (!$block || strlen($block) != 512) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for($i = 0, $chks = 0; $i < 148; $i++)
|
||||
for ($i = 0, $chks = 0; $i < 148; $i++) {
|
||||
$chks += ord($block[$i]);
|
||||
}
|
||||
|
||||
for($i = 156, $chks += 256; $i < 512; $i++)
|
||||
for ($i = 156, $chks += 256; $i < 512; $i++) {
|
||||
$chks += ord($block[$i]);
|
||||
}
|
||||
|
||||
$header = @unpack("a100filename/a8perm/a8uid/a8gid/a12size/a12mtime/a8checksum/a1typeflag/a100link/a6magic/a2version/a32uname/a32gname/a8devmajor/a8devminor/a155prefix", $block);
|
||||
if(!$header) return false;
|
||||
$header = @unpack(
|
||||
"a100filename/a8perm/a8uid/a8gid/a12size/a12mtime/a8checksum/a1typeflag/a100link/a6magic/a2version/a32uname/a32gname/a8devmajor/a8devminor/a155prefix",
|
||||
$block
|
||||
);
|
||||
if (!$header) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$return['checksum'] = OctDec(trim($header['checksum']));
|
||||
if($return['checksum'] != $chks) return false;
|
||||
if ($return['checksum'] != $chks) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$return['filename'] = trim($header['filename']);
|
||||
$return['perm'] = OctDec(trim($header['perm']));
|
||||
@ -552,10 +634,12 @@ class Tar {
|
||||
$return['gname'] = trim($header['gname']);
|
||||
|
||||
// Handle ustar Posix compliant path prefixes
|
||||
if(trim($header['prefix'])) $return['filename'] = trim($header['prefix']).'/'.$return['filename'];
|
||||
if (trim($header['prefix'])) {
|
||||
$return['filename'] = trim($header['prefix']).'/'.$return['filename'];
|
||||
}
|
||||
|
||||
// Handle Long-Link entries from GNU Tar
|
||||
if($return['typeflag'] == 'L') {
|
||||
if ($return['typeflag'] == 'L') {
|
||||
// following data block(s) is the filename
|
||||
$filename = trim($this->readbytes(ceil($header['size'] / 512) * 512));
|
||||
// next block is the real header
|
||||
@ -574,13 +658,16 @@ class Tar {
|
||||
* @param string $path
|
||||
* @return string
|
||||
*/
|
||||
public function cleanPath($path) {
|
||||
$path = str_replace('\\', '/', $path);
|
||||
$path = explode('/', $path);
|
||||
$newpath=array();
|
||||
foreach($path as $p) {
|
||||
if ($p === '' || $p === '.') continue;
|
||||
if ($p==='..') {
|
||||
public function cleanPath($path)
|
||||
{
|
||||
$path = str_replace('\\', '/', $path);
|
||||
$path = explode('/', $path);
|
||||
$newpath = array();
|
||||
foreach ($path as $p) {
|
||||
if ($p === '' || $p === '.') {
|
||||
continue;
|
||||
}
|
||||
if ($p === '..') {
|
||||
array_pop($newpath);
|
||||
continue;
|
||||
}
|
||||
@ -593,41 +680,37 @@ class Tar {
|
||||
* Checks if the given compression type is available and throws an exception if not
|
||||
*
|
||||
* @param $comptype
|
||||
* @throws TarIllegalCompressionException
|
||||
* @throws ArchiveIllegalCompressionException
|
||||
*/
|
||||
protected function compressioncheck($comptype) {
|
||||
if($comptype === Tar::COMPRESS_GZIP && !function_exists('gzopen')) {
|
||||
throw new TarIllegalCompressionException('No gzip support available');
|
||||
protected function compressioncheck($comptype)
|
||||
{
|
||||
if ($comptype === Archive::COMPRESS_GZIP && !function_exists('gzopen')) {
|
||||
throw new ArchiveIllegalCompressionException('No gzip support available');
|
||||
}
|
||||
|
||||
if($comptype === Tar::COMPRESS_BZIP && !function_exists('bzopen')) {
|
||||
throw new TarIllegalCompressionException('No bzip2 support available');
|
||||
if ($comptype === Archive::COMPRESS_BZIP && !function_exists('bzopen')) {
|
||||
throw new ArchiveIllegalCompressionException('No bzip2 support available');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Guesses the wanted compression from the given filename extension
|
||||
*
|
||||
* You don't need to call this yourself. It's used when you pass Tar::COMPRESS_AUTO somewhere
|
||||
* You don't need to call this yourself. It's used when you pass Archive::COMPRESS_AUTO somewhere
|
||||
*
|
||||
* @param string $file
|
||||
* @return int
|
||||
*/
|
||||
public function filetype($file) {
|
||||
public function filetype($file)
|
||||
{
|
||||
$file = strtolower($file);
|
||||
if(substr($file, -3) == '.gz' || substr($file, -4) == '.tgz') {
|
||||
$comptype = Tar::COMPRESS_GZIP;
|
||||
} elseif(substr($file, -4) == '.bz2' || substr($file, -4) == '.tbz') {
|
||||
$comptype = Tar::COMPRESS_BZIP;
|
||||
if (substr($file, -3) == '.gz' || substr($file, -4) == '.tgz') {
|
||||
$comptype = Archive::COMPRESS_GZIP;
|
||||
} elseif (substr($file, -4) == '.bz2' || substr($file, -4) == '.tbz') {
|
||||
$comptype = Archive::COMPRESS_BZIP;
|
||||
} else {
|
||||
$comptype = Tar::COMPRESS_NONE;
|
||||
$comptype = Archive::COMPRESS_NONE;
|
||||
}
|
||||
return $comptype;
|
||||
}
|
||||
}
|
||||
|
||||
class TarIOException extends Exception {
|
||||
}
|
||||
|
||||
class TarIllegalCompressionException extends Exception {
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user