diff options
-rw-r--r-- | _test/tests/inc/tar.test.php | 319 | ||||
-rw-r--r-- | _test/tests/inc/tar/foobar/testdata2.txt | 1 | ||||
-rw-r--r-- | _test/tests/inc/tar/longpath-gnu.tgz | bin | 0 -> 413 bytes | |||
-rw-r--r-- | _test/tests/inc/tar/longpath-ustar.tgz | bin | 0 -> 311 bytes | |||
-rw-r--r-- | _test/tests/inc/tar/tarbomb.tgz | bin | 0 -> 183 bytes | |||
-rw-r--r-- | _test/tests/inc/tar/test.tar | bin | 0 -> 10240 bytes | |||
-rw-r--r-- | _test/tests/inc/tar/test.tbz | bin | 0 -> 217 bytes | |||
-rw-r--r-- | _test/tests/inc/tar/test.tgz | bin | 0 -> 220 bytes | |||
-rw-r--r-- | _test/tests/inc/tar/testdata1.txt | 1 | ||||
-rw-r--r-- | _test/tests/inc/tarlib.test.php | 0 | ||||
-rw-r--r-- | inc/Tar.class.php | 634 | ||||
-rw-r--r-- | inc/TarLib.class.php | 918 | ||||
-rw-r--r-- | inc/load.php | 1 | ||||
-rw-r--r-- | lib/plugins/plugin/classes/ap_download.class.php | 25 |
14 files changed, 1008 insertions, 891 deletions
diff --git a/_test/tests/inc/tar.test.php b/_test/tests/inc/tar.test.php new file mode 100644 index 000000000..e8805a75d --- /dev/null +++ b/_test/tests/inc/tar.test.php @@ -0,0 +1,319 @@ +<?php + +class Tar_TestCase extends DokuWikiTest { + + /** + * simple test that checks that the given filenames and contents can be grepped from + * the uncompressed tar stream + * + * No check for format correctness + */ + public function test_createdynamic() { + $tar = new Tar(); + + $dir = dirname(__FILE__).'/tar'; + + $tar->create(); + $tar->AddFile("$dir/testdata1.txt"); + $tar->AddFile("$dir/foobar/testdata2.txt", 'noway/testdata2.txt'); + $tar->addData('another/testdata3.txt', 'testcontent3'); + + $data = $tar->getArchive(); + + $this->assertTrue(strpos($data, 'testcontent1') !== false, 'Content in TAR'); + $this->assertTrue(strpos($data, 'testcontent2') !== false, 'Content in TAR'); + $this->assertTrue(strpos($data, 'testcontent3') !== false, 'Content in TAR'); + + $this->assertTrue(strpos($data, "$dir/testdata1.txt") !== false, 'Path in TAR'); + $this->assertTrue(strpos($data, 'noway/testdata2.txt') !== false, 'Path in TAR'); + $this->assertTrue(strpos($data, 'another/testdata3.txt') !== false, 'Path in TAR'); + + $this->assertTrue(strpos($data, "$dir/foobar/testdata2.txt") === false, 'Path not in TAR'); + $this->assertTrue(strpos($data, "foobar") === false, 'Path not in TAR'); + } + + /** + * simple test that checks that the given filenames and contents can be grepped from the + * uncompressed tar file + * + * No check for format correctness + */ + public function test_createfile() { + $tar = new Tar(); + + $dir = dirname(__FILE__).'/tar'; + $tmp = tempnam(sys_get_temp_dir(), 'dwtartest'); + + $tar->create($tmp, Tar::COMPRESS_NONE); + $tar->AddFile("$dir/testdata1.txt"); + $tar->AddFile("$dir/foobar/testdata2.txt", 'noway/testdata2.txt'); + $tar->addData('another/testdata3.txt', 'testcontent3'); + $tar->close(); + + $this->assertTrue(filesize($tmp) > 30); //arbitrary non-zero number + $data = file_get_contents($tmp); + + $this->assertTrue(strpos($data, 'testcontent1') !== false, 'Content in TAR'); + $this->assertTrue(strpos($data, 'testcontent2') !== false, 'Content in TAR'); + $this->assertTrue(strpos($data, 'testcontent3') !== false, 'Content in TAR'); + + $this->assertTrue(strpos($data, "$dir/testdata1.txt") !== false, 'Path in TAR'); + $this->assertTrue(strpos($data, 'noway/testdata2.txt') !== false, 'Path in TAR'); + $this->assertTrue(strpos($data, 'another/testdata3.txt') !== false, 'Path in TAR'); + + $this->assertTrue(strpos($data, "$dir/foobar/testdata2.txt") === false, 'Path not in TAR'); + $this->assertTrue(strpos($data, "foobar") === false, 'Path not in TAR'); + + @unlink($tmp); + } + + /** + * List the contents of the prebuilt TAR files + */ + public function test_tarcontent() { + $dir = dirname(__FILE__).'/tar'; + + foreach(array('tar', 'tgz', 'tbz') as $ext) { + $tar = new Tar(); + $file = "$dir/test.$ext"; + + $tar->open($file); + $content = $tar->contents(); + + $this->assertCount(4, $content, "Contents of $file"); + $this->assertEquals('tar/testdata1.txt', $content[1]['filename'], "Contents of $file"); + $this->assertEquals(13, $content[1]['size'], "Contents of $file"); + + $this->assertEquals('tar/foobar/testdata2.txt', $content[3]['filename'], "Contents of $file"); + $this->assertEquals(13, $content[1]['size'], "Contents of $file"); + } + } + + /** + * Extract the prebuilt tar files + */ + public function test_tarextract() { + $dir = dirname(__FILE__).'/tar'; + $out = sys_get_temp_dir().'/dwtartest'.md5(time()); + + foreach(array('tar', 'tgz', 'tbz') as $ext) { + $tar = new Tar(); + $file = "$dir/test.$ext"; + + $tar->open($file); + $tar->extract($out); + + clearstatcache(); + + $this->assertFileExists($out.'/tar/testdata1.txt', "Extracted $file"); + $this->assertEquals(13, filesize($out.'/tar/testdata1.txt'), "Extracted $file"); + + $this->assertFileExists($out.'/tar/foobar/testdata2.txt', "Extracted $file"); + $this->assertEquals(13, filesize($out.'/tar/foobar/testdata2.txt'), "Extracted $file"); + + TestUtils::rdelete($out); + } + } + + /** + * Extract the prebuilt tar files with component stripping + */ + public function test_compstripextract() { + $dir = dirname(__FILE__).'/tar'; + $out = sys_get_temp_dir().'/dwtartest'.md5(time()); + + foreach(array('tar', 'tgz', 'tbz') as $ext) { + $tar = new Tar(); + $file = "$dir/test.$ext"; + + $tar->open($file); + $tar->extract($out, 1); + + clearstatcache(); + + $this->assertFileExists($out.'/testdata1.txt', "Extracted $file"); + $this->assertEquals(13, filesize($out.'/testdata1.txt'), "Extracted $file"); + + $this->assertFileExists($out.'/foobar/testdata2.txt', "Extracted $file"); + $this->assertEquals(13, filesize($out.'/foobar/testdata2.txt'), "Extracted $file"); + + TestUtils::rdelete($out); + } + } + + /** + * Extract the prebuilt tar files with prefix stripping + */ + public function test_prefixstripextract() { + $dir = dirname(__FILE__).'/tar'; + $out = sys_get_temp_dir().'/dwtartest'.md5(time()); + + foreach(array('tar', 'tgz', 'tbz') as $ext) { + $tar = new Tar(); + $file = "$dir/test.$ext"; + + $tar->open($file); + $tar->extract($out, 'tar/foobar/'); + + clearstatcache(); + + $this->assertFileExists($out.'/tar/testdata1.txt', "Extracted $file"); + $this->assertEquals(13, filesize($out.'/tar/testdata1.txt'), "Extracted $file"); + + $this->assertFileExists($out.'/testdata2.txt', "Extracted $file"); + $this->assertEquals(13, filesize($out.'/testdata2.txt'), "Extracted $file"); + + TestUtils::rdelete($out); + } + } + + /** + * Extract the prebuilt tar files with include regex + */ + public function test_includeextract() { + $dir = dirname(__FILE__).'/tar'; + $out = sys_get_temp_dir().'/dwtartest'.md5(time()); + + foreach(array('tar', 'tgz', 'tbz') as $ext) { + $tar = new Tar(); + $file = "$dir/test.$ext"; + + $tar->open($file); + $tar->extract($out, '', '', '/\/foobar\//'); + + clearstatcache(); + + $this->assertFileNotExists($out.'/tar/testdata1.txt', "Extracted $file"); + + $this->assertFileExists($out.'/tar/foobar/testdata2.txt', "Extracted $file"); + $this->assertEquals(13, filesize($out.'/tar/foobar/testdata2.txt'), "Extracted $file"); + + TestUtils::rdelete($out); + } + } + + /** + * Extract the prebuilt tar files with exclude regex + */ + public function test_excludeextract() { + $dir = dirname(__FILE__).'/tar'; + $out = sys_get_temp_dir().'/dwtartest'.md5(time()); + + foreach(array('tar', 'tgz', 'tbz') as $ext) { + $tar = new Tar(); + $file = "$dir/test.$ext"; + + $tar->open($file); + $tar->extract($out, '', '/\/foobar\//'); + + clearstatcache(); + + $this->assertFileExists($out.'/tar/testdata1.txt', "Extracted $file"); + $this->assertEquals(13, filesize($out.'/tar/testdata1.txt'), "Extracted $file"); + + $this->assertFileNotExists($out.'/tar/foobar/testdata2.txt', "Extracted $file"); + + TestUtils::rdelete($out); + } + } + + /** + * Check the extension to compression guesser + */ + public function test_filetype() { + $tar = new Tar(); + $this->assertEquals(Tar::COMPRESS_NONE, $tar->filetype('foo')); + $this->assertEquals(Tar::COMPRESS_GZIP, $tar->filetype('foo.tgz')); + $this->assertEquals(Tar::COMPRESS_GZIP, $tar->filetype('foo.tGZ')); + $this->assertEquals(Tar::COMPRESS_GZIP, $tar->filetype('foo.tar.GZ')); + $this->assertEquals(Tar::COMPRESS_GZIP, $tar->filetype('foo.tar.gz')); + $this->assertEquals(Tar::COMPRESS_BZIP, $tar->filetype('foo.tbz')); + $this->assertEquals(Tar::COMPRESS_BZIP, $tar->filetype('foo.tBZ')); + $this->assertEquals(Tar::COMPRESS_BZIP, $tar->filetype('foo.tar.BZ2')); + $this->assertEquals(Tar::COMPRESS_BZIP, $tar->filetype('foo.tar.bz2')); + } + + public function test_longpathextract() { + $dir = dirname(__FILE__).'/tar'; + $out = sys_get_temp_dir().'/dwtartest'.md5(time()); + + foreach(array('ustar', 'gnu') as $format) { + $tar = new Tar(); + $tar->open("$dir/longpath-$format.tgz"); + $tar->extract($out); + + $this->assertFileExists($out.'/1234567890/1234567890/1234567890/1234567890/1234567890/1234567890/1234567890/1234567890/1234567890/1234567890/1234567890/1234567890/test.txt'); + + TestUtils::rdelete($out); + } + } + + public function test_createlongpathustar() { + $tar = new Tar(); + $tmp = tempnam(sys_get_temp_dir(), 'dwtartest'); + + $path = ''; + for($i=0; $i<11; $i++) $path .= '1234567890/'; + $path = rtrim($path,'/'); + + $tar->create($tmp, Tar::COMPRESS_NONE); + $tar->addData("$path/test.txt", 'testcontent1'); + $tar->close(); + + $this->assertTrue(filesize($tmp) > 30); //arbitrary non-zero number + $data = file_get_contents($tmp); + + // We should find the path and filename separated, no longlink entry + $this->assertTrue(strpos($data, 'testcontent1') !== false, 'content in TAR'); + $this->assertTrue(strpos($data, 'test.txt') !== false, 'filename in TAR'); + $this->assertTrue(strpos($data, $path) !== false, 'path in TAR'); + $this->assertFalse(strpos($data, "$path/test.txt") !== false, 'full filename in TAR'); + $this->assertFalse(strpos($data, '@LongLink') !== false, '@LongLink in TAR'); + + @unlink($tmp); + } + + public function test_createlongpathgnu() { + $tar = new Tar(); + $tmp = tempnam(sys_get_temp_dir(), 'dwtartest'); + + $path = ''; + for($i=0; $i<20; $i++) $path .= '1234567890/'; + $path = rtrim($path,'/'); + + $tar->create($tmp, Tar::COMPRESS_NONE); + $tar->addData("$path/test.txt", 'testcontent1'); + $tar->close(); + + $this->assertTrue(filesize($tmp) > 30); //arbitrary non-zero number + $data = file_get_contents($tmp); + + // We should find the complete path/filename and a longlink entry + $this->assertTrue(strpos($data, 'testcontent1') !== false, 'content in TAR'); + $this->assertTrue(strpos($data, 'test.txt') !== false, 'filename in TAR'); + $this->assertTrue(strpos($data, $path) !== false, 'path in TAR'); + $this->assertTrue(strpos($data, "$path/test.txt") !== false, 'full filename in TAR'); + $this->assertTrue(strpos($data, '@LongLink') !== false, '@LongLink in TAR'); + + @unlink($tmp); + } + + /** + * Extract a tarbomomb + */ + public function test_tarbomb() { + $dir = dirname(__FILE__).'/tar'; + $out = sys_get_temp_dir().'/dwtartest'.md5(time()); + + $tar = new Tar(); + + $tar->open("$dir/tarbomb.tgz"); + $tar->extract($out); + + clearstatcache(); + + $this->assertFileExists($out.'/AAAAAAAAAAAAAAAAA/BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB.txt'); + + TestUtils::rdelete($out); + } +}
\ No newline at end of file diff --git a/_test/tests/inc/tar/foobar/testdata2.txt b/_test/tests/inc/tar/foobar/testdata2.txt new file mode 100644 index 000000000..a7db15771 --- /dev/null +++ b/_test/tests/inc/tar/foobar/testdata2.txt @@ -0,0 +1 @@ +testcontent2 diff --git a/_test/tests/inc/tar/longpath-gnu.tgz b/_test/tests/inc/tar/longpath-gnu.tgz Binary files differnew file mode 100644 index 000000000..6c937c8fe --- /dev/null +++ b/_test/tests/inc/tar/longpath-gnu.tgz diff --git a/_test/tests/inc/tar/longpath-ustar.tgz b/_test/tests/inc/tar/longpath-ustar.tgz Binary files differnew file mode 100644 index 000000000..59efbff66 --- /dev/null +++ b/_test/tests/inc/tar/longpath-ustar.tgz diff --git a/_test/tests/inc/tar/tarbomb.tgz b/_test/tests/inc/tar/tarbomb.tgz Binary files differnew file mode 100644 index 000000000..8418d4073 --- /dev/null +++ b/_test/tests/inc/tar/tarbomb.tgz diff --git a/_test/tests/inc/tar/test.tar b/_test/tests/inc/tar/test.tar Binary files differnew file mode 100644 index 000000000..931866b0b --- /dev/null +++ b/_test/tests/inc/tar/test.tar diff --git a/_test/tests/inc/tar/test.tbz b/_test/tests/inc/tar/test.tbz Binary files differnew file mode 100644 index 000000000..5a7374019 --- /dev/null +++ b/_test/tests/inc/tar/test.tbz diff --git a/_test/tests/inc/tar/test.tgz b/_test/tests/inc/tar/test.tgz Binary files differnew file mode 100644 index 000000000..b00319649 --- /dev/null +++ b/_test/tests/inc/tar/test.tgz diff --git a/_test/tests/inc/tar/testdata1.txt b/_test/tests/inc/tar/testdata1.txt new file mode 100644 index 000000000..ac65bb32e --- /dev/null +++ b/_test/tests/inc/tar/testdata1.txt @@ -0,0 +1 @@ +testcontent1 diff --git a/_test/tests/inc/tarlib.test.php b/_test/tests/inc/tarlib.test.php deleted file mode 100644 index e69de29bb..000000000 --- a/_test/tests/inc/tarlib.test.php +++ /dev/null diff --git a/inc/Tar.class.php b/inc/Tar.class.php new file mode 100644 index 000000000..59e14c705 --- /dev/null +++ b/inc/Tar.class.php @@ -0,0 +1,634 @@ +<?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 + * uncompressed, gzip and bzip2 compressed tar files. + * + * Long pathnames (>100 chars) are supported in POSIX ustar and GNU longlink formats. + * + * To list the contents of an existing TAR archive, open() it and use contents() on it: + * + * $tar = new Tar(); + * $tar->open('myfile.tgz'); + * $toc = $tar->contents(); + * print_r($toc); + * + * To extract the contents of an existing TAR archive, open() it and use extract() on it: + * + * $tar = new Tar(); + * $tar->open('myfile.tgz'); + * $tar->extract(/tmp); + * + * To create a new TAR archive directly on the filesystem (low memory requirements), create() it, + * add*() files and close() it: + * + * $tar = new Tar(); + * $tar->create('myfile.tgz'); + * $tar->addFile(...); + * $tar->addData(...); + * ... + * $tar->close(); + * + * To create a TAR archive directly in memory, create() it, add*() files and then either save() + * or getData() it: + * + * $tar = new Tar(); + * $tar->create(); + * $tar->addFile(...); + * $tar->addData(...); + * ... + * $tar->save('myfile.tgz'); // compresses and saves it + * echo $tar->getArchive(Tar::COMPRESS_GZIP); // compresses and returns it + * + * @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; + + protected $file = ''; + protected $comptype = Tar::COMPRESS_AUTO; + protected $fh; + protected $memory = ''; + protected $closed = true; + protected $writeaccess = false; + + /** + * Open an existing TAR file for reading + * + * @param string $file + * @param int $comptype + * @throws TarIOException + */ + public function open($file, $comptype = Tar::COMPRESS_AUTO) { + // determine compression + if($comptype == Tar::COMPRESS_AUTO) $comptype = $this->filetype($file); + $this->compressioncheck($comptype); + + $this->comptype = $comptype; + $this->file = $file; + + if($this->comptype === Tar::COMPRESS_GZIP) { + $this->fh = @gzopen($this->file, 'rb'); + } elseif($this->comptype === Tar::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)); + $this->closed = false; + } + + /** + * Read the contents of a TAR archive + * + * This function lists the files stored in the archive, and returns an indexed array of associative + * arrays containing for each file the following information: + * + * checksum Tar Checksum of the file + * filename The full name of the stored file (up to 100 c.) + * mode UNIX permissions in DECIMAL, not octal + * uid The Owner ID + * gid The Group ID + * size Uncompressed filesize + * mtime Timestamp of last modification + * typeflag Empty for files, set for folders + * link Is it a symlink? + * uname Owner name + * gname Group name + * + * 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')); + + $result = Array(); + while($read = $this->readbytes(512)) { + $header = $this->parseHeader($read); + if(!is_array($header)) continue; + + $this->skipbytes(ceil($header['size'] / 512) * 512, 1); + $result[] = $header; + } + + $this->close(); + return $result; + } + + /** + * Extract an existing TAR 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 + */ + function extract($outdir, $strip = '', $exclude = '', $include = '') { + if($this->closed || !$this->file) throw(new TarIOException('Can not read from a closed archive')); + + $outdir = rtrim($outdir, '/'); + io_mkdir_p($outdir); + $striplen = strlen($strip); + + $extracted = array(); + + while($dat = $this->readbytes(512)) { + // read the file header + $header = $this->parseHeader($dat); + if(!is_array($header)) continue; + if(!$header['filename']) continue; + + // strip prefix + $filename = $this->cleanPath($header['filename']); + if(is_int($strip)) { + // if $strip is an integer we strip this many path components + $parts = explode('/', $filename); + if(!$header['typeflag']) { + $base = array_pop($parts); // keep filename itself + } else { + $base = ''; + } + $filename = join('/', array_slice($parts, $strip)); + if($base) $filename .= "/$base"; + } else { + // ifstrip is a string, we strip a prefix here + if(substr($filename, 0, $striplen) == $strip) $filename = substr($filename, $striplen); + } + + // check if this should be extracted + $extract = true; + if(!$filename) { + $extract = false; + } else { + if($include) { + if(preg_match($include, $filename)) { + $extract = true; + } else { + $extract = false; + } + } + if($exclude && preg_match($exclude, $filename)) { + $extract = false; + } + } + + // Now do the extraction (or not) + if($extract) { + $extracted[] = $header; + + $output = "$outdir/$filename"; + $directory = ($header['typeflag']) ? $output : dirname($output); + io_mkdir_p($directory); + + // is this a file? + if(!$header['typeflag']) { + $fp = fopen($output, "wb"); + if(!$fp) throw(new TarIOException('Could not open file for writing: '.$output)); + + $size = floor($header['size'] / 512); + 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); + + fclose($fp); + touch($output, $header['mtime']); + chmod($output, $header['perm']); + } else { + $this->skipbytes(ceil($header['size'] / 512) * 512); // the size is usually 0 for directories + } + } else { + $this->skipbytes(ceil($header['size'] / 512) * 512); + } + } + + $this->close(); + return $extracted; + } + + /** + * Create a new TAR file + * + * 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 + */ + public function create($file = '', $comptype = Tar::COMPRESS_AUTO, $complevel = 9) { + // determine compression + if($comptype == Tar::COMPRESS_AUTO) $comptype = $this->filetype($file); + $this->compressioncheck($comptype); + + $this->comptype = $comptype; + $this->file = $file; + $this->memory = ''; + $this->fh = 0; + + if($this->file) { + if($this->comptype === Tar::COMPRESS_GZIP) { + $this->fh = @gzopen($this->file, 'wb'.$complevel); + } elseif($this->comptype === Tar::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)); + } + $this->writeaccess = false; + $this->closed = false; + } + + /** + * Add a file to the current TAR archive using an existing file in the filesystem + * + * @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 + */ + public function addFile($file, $name = '') { + if($this->closed) throw(new TarIOException('Archive has been closed, files can no longer be added')); + + if(!$name) $name = $file; + $name = $this->cleanPath($name); + + $fp = fopen($file, 'rb'); + if(!$fp) throw(new TarIOException('Could not open file for reading: '.$file)); + + // create file header and copy all stat info from the original file + clearstatcache(false, $file); + $stat = stat($file); + $this->writeFileHeader( + $name, + $stat[4], + $stat[5], + fileperms($file), + filesize($file), + filemtime($file) + ); + + while(!feof($fp)) { + $packed = pack("a512", fread($fp, 512)); + $this->writebytes($packed); + } + fclose($fp); + } + + /** + * Add a file to the current TAR archive using the given $data as content + * + * @param string $name + * @param string $data + * @param int $uid + * @param int $gid + * @param int $perm + * @param int $mtime + * @throws TarIOException + */ + 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')); + + $name = $this->cleanPath($name); + $len = strlen($data); + + $this->writeFileHeader( + $name, + $uid, + $gid, + $perm, + $len, + ($mtime) ? $mtime : time() + ); + + for($s = 0; $s < $len; $s += 512) { + $this->writebytes(pack("a512", substr($data, $s, 512))); + } + } + + /** + * Add the closing footer to the archive if in write mode, 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 + * + * "Physically, an archive consists of a series of file entries terminated by an end-of-archive entry, which + * consists of two 512 blocks of zero bytes" + * + * @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 + + // write footer + if($this->writeaccess) { + $this->writebytes(pack("a512", "")); + $this->writebytes(pack("a512", "")); + } + + // close file handles + if($this->file) { + if($this->comptype === Tar::COMPRESS_GZIP) { + gzclose($this->fh); + } elseif($this->comptype === Tar::COMPRESS_BZIP) { + bzclose($this->fh); + } else { + fclose($this->fh); + } + + $this->file = ''; + $this->fh = 0; + } + + $this->closed = true; + } + + /** + * Returns the created in-memory archive data + * + * This implicitly calls close() on the Archive + */ + public function getArchive($comptype = Tar::COMPRESS_AUTO, $complevel = 9) { + $this->close(); + + if($comptype === Tar::COMPRESS_AUTO) $comptype = $this->comptype; + $this->compressioncheck($comptype); + + if($comptype === Tar::COMPRESS_GZIP) return gzcompress($this->memory, $complevel); + if($comptype === Tar::COMPRESS_BZIP) return bzcompress($this->memory); + return $this->memory; + } + + /** + * Save the created in-memory archive data + * + * Note: It more memory effective to specify the filename in the create() function and + * let the library work on the new file directly. + * + * @param $file + * @param int $comptype + * @param int $complevel + * @throws TarIOException + */ + public function save($file, $comptype = Tar::COMPRESS_AUTO, $complevel = 9) { + if($comptype === Tar::COMPRESS_AUTO) $comptype = $this->filetype($file); + + if(!file_put_contents($file, $this->getArchive($comptype, $complevel))) { + throw(new TarIOException('Could not write to file: '.$file)); + } + } + + /** + * Read from the open file pointer + * + * @param int $length bytes to read + * @return string + */ + protected function readbytes($length) { + if($this->comptype === Tar::COMPRESS_GZIP) { + return @gzread($this->fh, $length); + } elseif($this->comptype === Tar::COMPRESS_BZIP) { + return @bzread($this->fh, $length); + } else { + return @fread($this->fh, $length); + } + } + + /** + * Write to the open filepointer or memory + * + * @param string $data + * @throws TarIOException + * @return int number of bytes written + */ + protected function writebytes($data) { + if(!$this->file) { + $this->memory .= $data; + $written = strlen($data); + } elseif($this->comptype === Tar::COMPRESS_GZIP) { + $written = @gzwrite($this->fh, $data); + } elseif($this->comptype === Tar::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')); + return $written; + } + + /** + * Skip forward in the open file pointer + * + * This is basically a wrapper around seek() (and a workarounf for bzip2) + * + * @param int $bytes seek to this position + */ + function skipbytes($bytes) { + if($this->comptype === Tar::COMPRESS_GZIP) { + @gzseek($this->fh, $bytes, SEEK_CUR); + } elseif($this->comptype === Tar::COMPRESS_BZIP) { + // there is no seek in bzip2, we simply read on + @bzread($this->fh, $bytes); + } else { + @fseek($this->fh, $bytes, SEEK_CUR); + } + } + + /** + * Write a file header + * + * @param string $name + * @param int $uid + * @param int $gid + * @param int $perm + * @param int $size + * @param int $mtime + * @param string $typeflag Set to '5' for directories + */ + protected function writeFileHeader($name, $uid, $gid, $perm, $size, $mtime, $typeflag = '') { + // handle filename length restrictions + $prefix = ''; + $namelen = strlen($name); + if($namelen > 100) { + $file = basename($name); + $dir = dirname($name); + 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) { + $this->writebytes(pack("a512", substr($name, $s, 512))); + } + $name = substr($name, 0, 100); // cut off name + } else { + // we're fine when splitting, use POSIX ustar + $prefix = $dir; + $name = $file; + } + } + + // values are needed in octal + $uid = sprintf("%6s ", DecOct($uid)); + $gid = sprintf("%6s ", DecOct($gid)); + $perm = sprintf("%6s ", DecOct($perm)); + $size = sprintf("%11s ", DecOct($size)); + $mtime = sprintf("%11s", DecOct($mtime)); + + $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++) + $chks += ord($data_first[$i]); + + for($i = 156, $chks += 256, $j = 0; $i < 512; $i++, $j++) + $chks += ord($data_last[$j]); + + $this->writebytes($data_first); + + $chks = pack("a8", sprintf("%6s ", DecOct($chks))); + $this->writebytes($chks.$data_last); + } + + /** + * Decode the given tar file header + * + * @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; + + for($i = 0, $chks = 0; $i < 148; $i++) + $chks += ord($block[$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; + + $return['checksum'] = OctDec(trim($header['checksum'])); + if($return['checksum'] != $chks) return false; + + $return['filename'] = trim($header['filename']); + $return['perm'] = OctDec(trim($header['perm'])); + $return['uid'] = OctDec(trim($header['uid'])); + $return['gid'] = OctDec(trim($header['gid'])); + $return['size'] = OctDec(trim($header['size'])); + $return['mtime'] = OctDec(trim($header['mtime'])); + $return['typeflag'] = $header['typeflag']; + $return['link'] = trim($header['link']); + $return['uname'] = trim($header['uname']); + $return['gname'] = trim($header['gname']); + + // Handle ustar Posix compliant path prefixes + if(trim($header['prefix'])) $return['filename'] = trim($header['prefix']).'/'.$return['filename']; + + // Handle Long-Link entries from GNU Tar + 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 + $block = $this->readbytes(512); + $return = $this->parseHeader($block); + // overwrite the filename + $return['filename'] = $filename; + } + + return $return; + } + + /** + * Cleans up a path and removes relative parts + * + * @param string $p_dir + * @return string + */ + protected function cleanPath($p_dir) { + $r = ''; + if($p_dir) { + $subf = explode("/", $p_dir); + + for($i = count($subf) - 1; $i >= 0; $i--) { + if($subf[$i] == ".") { + # do nothing + } elseif($subf[$i] == "..") { + $i--; + } elseif(!$subf[$i] && $i != count($subf) - 1 && $i) { + # do nothing + } else { + $r = $subf[$i].($i != (count($subf) - 1) ? "/".$r : ""); + } + } + } + return $r; + } + + /** + * Checks if the given compression type is available and throws an exception if not + * + * @param $comptype + * @throws TarIllegalCompressionException + */ + protected function compressioncheck($comptype) { + if($comptype === Tar::COMPRESS_GZIP && !function_exists('gzopen')) { + throw(new TarIllegalCompressionException('No gzip support available')); + } + + if($comptype === Tar::COMPRESS_BZIP && !function_exists('bzopen')) { + throw(new TarIllegalCompressionException('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 + * + * @param string $file + * @return int + */ + 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; + } else { + $comptype = Tar::COMPRESS_NONE; + } + return $comptype; + } +} + +class TarIOException extends Exception { +} + +class TarIllegalCompressionException extends Exception { +}
\ No newline at end of file diff --git a/inc/TarLib.class.php b/inc/TarLib.class.php index e04c47cb8..ae08039ec 100644 --- a/inc/TarLib.class.php +++ b/inc/TarLib.class.php @@ -1,71 +1,14 @@ <?php -/** - * TAR format class - Creates TAR archives - * - * This class is part or the MaxgComp suite and originally named - * MaxgTar class. - * - * Modified for Dokuwiki - * - * @license LGPL-2.1 - * @link http://docs.maxg.info - * @author Bouchon <tarlib@bouchon.org> (Maxg) - * @author Christopher Smith <chris@jalakai.co.uk> - */ /** - * Those constants represent the compression method to use. - * COMPRESS_GZIP is used for the GZIP compression; COMPRESS_BZIP for - * BZIP2 and COMPRESS_NONE for no compression. - * - * On the other hand, COMPRESS_AUTO is a bit harder. It will first check - * if the zlib extensions are loaded. - * If it is, GZIP will be used. Else it will check if the bz2 extensions - * are loaded. If it is, BZIP2 will be used. Else no compression will be - * performed. - * - * You can then use getCompression() to know the compression chosen. - * - * If you selected a compression which can't be used (i.e extension not - * present), it will be just disabled, and won't produce any error ! - * As a consequence, getCompression() will return COMPRESS_NONE - * - * ARCHIVE_DYNAMIC can be passed as the first argument of the constructor, to - * create an archive in memory instead of a file. See also: MaxgTar(), - * getDynamicArchive() and writeArchive() - * - * ARCHIVE_RENAMECOMP is a flag that can be multiplied by the compression method - * (i.e COMPRESS_AUTO * ARCHIVE_RENAMECOMP). This will add the correct extension - * to the archive name, which is useful with COMPRESS_AUTO, ie .bz2 if you gave - * COMPRESS_BZIP. See also getCompression(TRUE) which does exactly the - * same + * This is a compatibility wrapper around the new Tar class * - * COMPRESS_DETECT does exactly the opposite and try to detect the - * compression to use to read the archive depending on its extension. (i.e if - * the archive ends with .tar.gz TarLib will try to decompress it with - * GZIP). See also setCompression() + * Use of this library is strongly discouraged. Only basic extraction is wrapped, + * everything else will fail. * - * FULL_ARCHIVE is a -1 constant that means "the complete archive" when - * extracting. This is explained in Extract() + * @deprecated 2012-11-06 */ -#define('COMPRESS_GZIP',1); -#define('COMPRESS_BZIP',2); -#define('COMPRESS_AUTO',3); -#define('COMPRESS_NONE',0); -#define('TARLIB_VERSION','1.2'); -#define('FULL_ARCHIVE',-1); -#define('ARCHIVE_DYNAMIC',0); -#define('ARCHIVE_RENAMECOMP',5); -#define('COMPRESS_DETECT',-1); - class TarLib { - var $_comptype; - var $_compzlevel; - var $_fp; - var $_memdat; - var $_nomf; - var $_result; - var $_initerror; const COMPRESS_GZIP = 1; const COMPRESS_BZIP = 2; @@ -77,845 +20,68 @@ class TarLib { const ARCHIVE_RENAMECOMP = 5; const COMPRESS_DETECT = -1; - /** - * constructor, initialize the class - * - * The constructor initialize the variables and prepare the class for the - * archive, and return the object created. Note that you can use multiple - * instances of the MaxgTar class, if you call this function another time and - * store the object in an other variable. - * - * In fact, MaxgTar accepts the following arguments (all are optional) : - * - * filename can be either a file name (absolute or relative). In this - * case, it can be used both for reading and writing. You can also open - * remote archive if you add a protocole name at the beginning of the file - * (ie https://host.dom/archive.tar.gz), but for reading only and if the - * directive allow_url_fopen is enabled in PHP.INI (this can be checked with - * TarInfo()). If you pass a file that doesn't exist, the script - * will try to create it. If the archive already exists and contains files, - * you can use Add() to append files.But by default this parameter - * is ARCHIVE_DYNAMIC (write only) so the archive is created in memory and - * can be sent to a file [writeArchive()] or to the client - * [sendClient()] - * - * compression_type should be a constant that represents a type of - * compression, or its integer value. The different values are described in - * the constants. - * - * compression_level is an integer between 1 and 9 (by default) an - * represent the GZIP or BZIP compression level. 1 produce fast compression, - * and 9 produce smaller files. See the RFC 1952 for more infos. - */ - function __construct($p_filen = TarLib::ARCHIVE_DYNAMIC, $p_comptype = TarLib::COMPRESS_AUTO, $p_complevel = 9) { - $this->_initerror = 0; - $this->_nomf = $p_filen; - $flag = 0; - if($p_comptype && $p_comptype % 5 == 0) { - $p_comptype /= TarLib::ARCHIVE_RENAMECOMP; - $flag = 1; - } + private $file = ''; + private $tar; - if($p_complevel > 0 && $p_complevel <= 9) $this->_compzlevel = $p_complevel; - else $this->_compzlevel = 9; + public $_result = true; - if($p_comptype == TarLib::COMPRESS_DETECT) { - if(strtolower(substr($p_filen, -3)) == '.gz') $p_comptype = TarLib::COMPRESS_GZIP; - elseif(strtolower(substr($p_filen, -4)) == '.bz2') $p_comptype = TarLib::COMPRESS_BZIP; - else $p_comptype = TarLib::COMPRESS_NONE; - } + function __construct($file, $comptype = TarLib::COMPRESS_AUTO, $complevel = 9) { + if(!$file) $this->error('__construct', '$file'); - switch($p_comptype) { + $this->file = $file; + switch($comptype) { + case TarLib::COMPRESS_AUTO: + case TarLib::COMPRESS_DETECT: + $comptype = Tar::COMPRESS_AUTO; + break; case TarLib::COMPRESS_GZIP: - if(!extension_loaded('zlib')) $this->_initerror = -1; - $this->_comptype = TarLib::COMPRESS_GZIP; + $comptype = Tar::COMPRESS_GZIP; break; - case TarLib::COMPRESS_BZIP: - if(!extension_loaded('bz2')) $this->_initerror = -2; - $this->_comptype = TarLib::COMPRESS_BZIP; + $comptype = Tar::COMPRESS_BZIP; break; - - case TarLib::COMPRESS_AUTO: - if(extension_loaded('zlib')) - $this->_comptype = TarLib::COMPRESS_GZIP; - elseif(extension_loaded('bz2')) - $this->_comptype = TarLib::COMPRESS_BZIP; - else - $this->_comptype = TarLib::COMPRESS_NONE; - break; - default: - $this->_comptype = TarLib::COMPRESS_NONE; - } - - if($this->_initerror < 0) $this->_comptype = TarLib::COMPRESS_NONE; - - if($flag) $this->_nomf .= '.'.$this->getCompression(1); - $this->_result = true; - } - - /** - * Recycle a TAR object. - * - * This function does exactly the same as TarLib (constructor), except it - * returns a status code. - */ - function setArchive($p_name = '', $p_comp = TarLib::COMPRESS_AUTO, $p_level = 9) { - $this->_CompTar(); - $this->__construct($p_name, $p_comp, $p_level); - return $this->_result; - } - - /** - * Get the compression used to generate the archive - * - * This is a very useful function when you're using dynamical archives. - * Besides, if you let the script chose which compression to use, you'll have - * a problem when you'll want to send it to the client if you don't know - * which compression was used. - * - * There are two ways to call this function : if you call it without argument - * or with FALSE, it will return the compression constants, explained on the - * MaxgTar Constants. If you call it with GetExtension on TRUE it will - * return the extension without starting dot (ie "tar" or "tar.bz2" or - * "tar.gz") - * - * NOTE: This can be done with the flag ARCHIVE_RENAMECOMP, see the - * MaxgTar Constants - */ - function getCompression($ext = false) { - $exts = Array('tar', 'tar.gz', 'tar.bz2'); - if($ext) return $exts[$this->_comptype]; - return $this->_comptype; - } - - /** - * Change the compression mode. - * - * This function will change the compression methode to read or write - * the archive. See the MaxgTar Constants to see which constants you can use. - * It may look strange, but it returns the GZIP compression level. - */ - function setCompression($p_comp = TarLib::COMPRESS_AUTO) { - $this->setArchive($this->_nomf, $p_comp, $this->_compzlevel); - return $this->_compzlevel; - } - - /** - * Returns the compressed dynamic archive. - * - * When you're working with dynamic archives, use this function to grab - * the final compressed archive in a string ready to be put in a SQL table or - * in a file. - */ - function getDynamicArchive() { - return $this->_encode($this->_memdat); - } - - /** - * Write a dynamical archive into a file - * - * This function attempts to write a dynamicaly-genrated archive into - * TargetFile on the webserver. It returns a TarErrorStr() status - * code. - * - * To know the extension to add to the file if you're using AUTO_DETECT - * compression, you can use getCompression(). - */ - function writeArchive($p_archive) { - if(!$this->_memdat) return -7; - $fp = @fopen($p_archive, 'wb'); - if(!$fp) return -6; - - fwrite($fp, $this->_memdat); - fclose($fp); - - return true; - } - - /** - * Send a TAR archive to the client browser. - * - * This function will send an archive to the client, and return a status - * code, but can behave differently depending on the arguments you give. All - * arguments are optional. - * - * ClientName is used to specify the archive name to give to the browser. If - * you don't give one, it will send the constructor filename or return an - * error code in case of dynamical archive. - * - * FileName is optional and used to send a specific archive. Leave it blank - * to send dynamical archives or the current working archive. - * - * If SendHeaders is enabled (by default), the library will send the HTTP - * headers itself before it sends the contents. This headers are : - * Content-Type, Content-Disposition, Content-Length and Accept-Range. - * - * Please note that this function DOES NOT stops the script so don't forget - * to exit() to avoid your script sending other data and corrupt the archive. - * Another note : for AUTO_DETECT dynamical archives you can know the - * extension to add to the name with getCompression() - */ - function sendClient($name = '', $archive = '', $headers = true) { - if(!$name && !$this->_nomf) return -9; - if(!$archive && !$this->_memdat) return -10; - if(!$name) $name = utf8_basename($this->_nomf); - - if($archive) { - if(!file_exists($archive)) return -11; - } - $decoded = $this->getDynamicArchive(); - - if($headers) { - header('Content-Type: application/x-gtar'); - header('Content-Disposition: attachment; filename='.utf8_basename($name)); - header('Accept-Ranges: bytes'); - header('Content-Length: '.($archive ? filesize($archive) : strlen($decoded))); + $comptype = Tar::COMPRESS_NONE; } - if($archive) { - $fp = @fopen($archive, 'rb'); - if(!$fp) return -4; + $this->complevel = $complevel; - while(!feof($fp)) echo fread($fp, 2048); - } else { - echo $decoded; + try { + $this->tar = new Tar(); + $this->tar->open($file, $comptype); + } catch(Exception $e) { + $this->_result = false; } - - return true; } - /** - * Extract part or totality of the archive. - * - * This function can extract files from an archive, and returns then a - * status codes that can be converted with TarErrorStr() into a - * human readable message. - * - * Only the first argument is required, What and it can be either the - * constant FULL_ARCHIVE or an indexed array containing each file you want to - * extract. - * - * To contains the target folder to extract the archive. It is optional and - * the default value is '.' which means the current folder. If the target - * folder doesn't exist, the script attempts to create it and give it - * permissions 0777 by default. - * - * RemovePath is very usefull when you want to extract files from a subfoler - * in the archive to a root folder. For instance, if you have a file in the - * archive called some/sub/folder/test.txt and you want to extract it to the - * script folder, you can call Extract with To = '.' and RemovePath = - * 'some/sub/folder/' - * - * FileMode is optional and its default value is 0755. It is in fact the UNIX - * permission in octal mode (prefixed with a 0) that will be given on each - * extracted file. - */ function Extract($p_what = TarLib::FULL_ARCHIVE, $p_to = '.', $p_remdir = '', $p_mode = 0755) { - if(!$this->_OpenRead()) return -4; - // if(!@is_dir($p_to)) if(!@mkdir($p_to, 0777)) return -8; --CS - if(!@is_dir($p_to)) if(!$this->_dirApp($p_to)) return -8; //--CS (route through correct dir fn) - - $ok = $this->_extractList($p_to, $p_what, $p_remdir, $p_mode); - $this->_CompTar(); - - return $ok; - } - - /** - * Create a new package with the given files - * - * This function will attempt to create a new archive with global headers - * then add the given files into. If the archive is a real file, the - * contents are written directly into the file. If it is a dynamic archive, - * contents are only stored in memory. This function should not be used to - * add files to an existing archive, you should use Add() instead. - * - * The FileList actually supports three different modes: - * - * - You can pass a string containing filenames separated by pipes '|'. - * In this case thes file are read from the filesystem and the root folder - * is the folder running script located. NOT RECOMMENDED - * - * - You can also give an indexed array containing the filenames. The - * behaviour for the content reading is the same as above. - * - * - You can pass an array of arrays. For each file use an array where the - * first element contains the filename and the second contains the file - * contents. You can even add empty folders to the package if the filename - * has a leading '/'. Once again, have a look at the exemples to understand - * better. - * - * Note you can also give arrays with both dynamic contents and static files. - * - * The optional parameter RemovePath can be used to delete a part of the tree - * of the filename you're adding, for instance if you're adding in the root - * of a package a file that is stored somewhere in the server tree. - * - * On the contrary the parameter AddPath can be used to add a prefix folder - * to the file you store. Note also that the RemovePath is applied before the - * AddPath is added, so it HAS a sense to use both parameters together. - */ - function Create($p_filelist, $p_add = '', $p_rem = '') { - if(!$fl = $this->_fetchFilelist($p_filelist)) return -5; - if(!$this->_OpenWrite()) return -6; - - $ok = $this->_addFileList($fl, $p_add, $p_rem); - - if($ok) { - $this->_writeFooter(); - } else { - $this->_CompTar(); - @unlink($this->_nomf); + if($p_what != TarLib::FULL_ARCHIVE) { + $this->error('Extract', 'Ep_what'); + return 0; } - return $ok; - } - - /** - * Add files to an existing package. - * - * This function does exactly the same as Create() exept it - * will append the given files at the end of the archive. - * - * Note: This is only supported for dynamic in memory files and uncompressed - * tar files - * - * This function returns a status code, you can use TarErrorStr() on - * it to get the human-readable description of the error. - */ - function Add($p_filelist, $p_add = '', $p_rem = '') { - if($this->_nomf !== TarLib::ARCHIVE_DYNAMIC && - $this->_comptype !== TarLib::COMPRESS_NONE - ) { - return -12; + try { + $this->tar->extract($p_to, $p_remdir); + } catch(Exception $e) { + return 0; } - - if(($this->_nomf !== TarLib::ARCHIVE_DYNAMIC && !$this->_fp) || - ($this->_nomf === TarLib::ARCHIVE_DYNAMIC && !$this->_memdat) - ) { - return $this->Create($p_filelist, $p_add, $p_rem); - } - - if(!$fl = $this->_fetchFilelist($p_filelist)) return -5; - return $this->_append($fl, $p_add, $p_rem); + return 1; } - /** - * Read the contents of a TAR archive - * - * This function attempts to get the list of the files stored in the - * archive, and return either an error code or an indexed array of - * associative array containing for each file the following information : - * - * checksum Tar Checksum of the file - * filename The full name of the stored file (up to 100 c.) - * mode UNIX permissions in DECIMAL, not octal - * uid The Owner ID - * gid The Group ID - * size Uncompressed filesize - * mtime Timestamp of last modification - * typeflag Empty for files, set for folders - * link For the links, did you guess it ? - * uname Owner name - * gname Group name - */ - function ListContents() { - if(!$this->_nomf) return -3; - if(!$this->_OpenRead()) return -4; + function error($func, $param = '') { + $error = 'TarLib is deprecated and should no longer be used.'; - $result = Array(); - - while($dat = $this->_read(512)) { - $dat = $this->_readHeader($dat); - if(!is_array($dat)) continue; - - $this->_seek(ceil($dat['size'] / 512) * 512, 1); - $result[] = $dat; - } - - return $result; - } - - /** - * Convert a status code into a human readable message - * - * Some MaxgTar functions like Create(), Add() ... return numerical - * status code. You can pass them to this function to grab their english - * equivalent. - */ - function TarErrorStr($i) { - $ecodes = Array( - 1 => true, - 0 => "Undocumented error", - -1 => "Can't use COMPRESS_GZIP compression : ZLIB extensions are not loaded !", - -2 => "Can't use COMPRESS_BZIP compression : BZ2 extensions are not loaded !", - -3 => "You must set a archive file to read the contents !", - -4 => "Can't open the archive file for read !", - -5 => "Invalide file list !", - -6 => "Can't open the archive in write mode !", - -7 => "There is no ARCHIVE_DYNAMIC to write !", - -8 => "Can't create the directory to extract files !", - -9 => "Please pass a archive name to send if you made created an ARCHIVE_DYNAMIC !", - -10 => "You didn't pass an archive filename and there is no stored ARCHIVE_DYNAMIC !", - -11 => "Given archive doesn't exist !", - -12 => "Appending not supported for compressed files" - ); - - return isset($ecodes[$i]) ? $ecodes[$i] : $ecodes[0]; - } - - /** - * Seek in the data stream - * - * @todo probably broken for bzip tars - * @param int $p_flen seek to this position - * @param bool $tell seek from current position? - */ - function _seek($p_flen, $tell = false) { - if($this->_nomf === TarLib::ARCHIVE_DYNAMIC) - $this->_memdat = substr($this->_memdat, 0, ($tell ? strlen($this->_memdat) : 0) + $p_flen); - elseif($this->_comptype == TarLib::COMPRESS_GZIP) - @gzseek($this->_fp, ($tell ? @gztell($this->_fp) : 0) + $p_flen); - elseif($this->_comptype == TarLib::COMPRESS_BZIP) - @fseek($this->_fp, ($tell ? @ftell($this->_fp) : 0) + $p_flen); - else - @fseek($this->_fp, ($tell ? @ftell($this->_fp) : 0) + $p_flen); - } - - /** - * Open the archive for reading - * - * @return bool true if succesfull - */ - function _OpenRead() { - if($this->_comptype == TarLib::COMPRESS_GZIP) - $this->_fp = @gzopen($this->_nomf, 'rb'); - elseif($this->_comptype == TarLib::COMPRESS_BZIP) - $this->_fp = @bzopen($this->_nomf, 'rb'); - else - $this->_fp = @fopen($this->_nomf, 'rb'); - - return ($this->_fp ? true : false); - } - - /** - * Open the archive for writing - * - * @param string $add filemode - * @return bool true on success - */ - function _OpenWrite($add = 'w') { - if($this->_nomf === TarLib::ARCHIVE_DYNAMIC) return true; - - if($this->_comptype == TarLib::COMPRESS_GZIP) - $this->_fp = @gzopen($this->_nomf, $add.'b'.$this->_compzlevel); - elseif($this->_comptype == TarLib::COMPRESS_BZIP) - $this->_fp = @bzopen($this->_nomf, $add.'b'); - else - $this->_fp = @fopen($this->_nomf, $add.'b'); - - return ($this->_fp ? true : false); - } - - /** - * Closes the open file pointer - */ - function _CompTar() { - if($this->_nomf === TarLib::ARCHIVE_DYNAMIC || !$this->_fp) return; - - if($this->_comptype == TarLib::COMPRESS_GZIP) @gzclose($this->_fp); - elseif($this->_comptype == TarLib::COMPRESS_BZIP) @bzclose($this->_fp); - else @fclose($this->_fp); - } - - /** - * Read from the open file pointer - * - * @param int $p_len bytes to read - * @return string - */ - function _read($p_len) { - if($this->_comptype == TarLib::COMPRESS_GZIP) - return @gzread($this->_fp, $p_len); - elseif($this->_comptype == TarLib::COMPRESS_BZIP) - return @bzread($this->_fp, $p_len); - else - return @fread($this->_fp, $p_len); - } - - /** - * Write to the open filepointer or memory - * - * @param string $p_data - * @return int - */ - function _write($p_data) { - if($this->_nomf === TarLib::ARCHIVE_DYNAMIC) { - $this->_memdat .= $p_data; - return strlen($p_data); - } elseif($this->_comptype == TarLib::COMPRESS_GZIP) { - return @gzwrite($this->_fp, $p_data); - } elseif($this->_comptype == TarLib::COMPRESS_BZIP) { - return @bzwrite($this->_fp, $p_data); + if($param) { + $error .= "In this compatibility wrapper, the function '$func' does not accept your value for". + "the parameter '$param' anymore."; } else { - return @fwrite($this->_fp, $p_data); + $error .= "The function '$func' no longer exists in this compatibility wrapper."; } - } - /** - * Compress given data according to the set compression method - * - * @param $p_dat - * @return string - */ - function _encode($p_dat) { - if($this->_comptype == TarLib::COMPRESS_GZIP) - return gzencode($p_dat, $this->_compzlevel); - elseif($this->_comptype == TarLib::COMPRESS_BZIP) - return bzcompress($p_dat, $this->_compzlevel); - else return $p_dat; + msg($error, -1); } - /** - * Decode the given tar file header - * - * @param $p_dat - * @return array|bool - */ - function _readHeader($p_dat) { - if(!$p_dat || strlen($p_dat) != 512) return false; - - for($i = 0, $chks = 0; $i < 148; $i++) - $chks += ord($p_dat[$i]); - - for($i = 156, $chks += 256; $i < 512; $i++) - $chks += ord($p_dat[$i]); - - $headers = @unpack("a100filename/a8mode/a8uid/a8gid/a12size/a12mtime/a8checksum/a1typeflag/a100link/a6magic/a2version/a32uname/a32gname/a8devmajor/a8devminor", $p_dat); - if(!$headers) return false; - - $return['checksum'] = OctDec(trim($headers['checksum'])); - if($return['checksum'] != $chks) return false; - - $return['filename'] = trim($headers['filename']); - $return['mode'] = OctDec(trim($headers['mode'])); - $return['uid'] = OctDec(trim($headers['uid'])); - $return['gid'] = OctDec(trim($headers['gid'])); - $return['size'] = OctDec(trim($headers['size'])); - $return['mtime'] = OctDec(trim($headers['mtime'])); - $return['typeflag'] = $headers['typeflag']; - $return['link'] = trim($headers['link']); - $return['uname'] = trim($headers['uname']); - $return['gname'] = trim($headers['gname']); - - return $return; - } - - /** - * Builds a normalized file list - * - * @todo remove string support, use saner format - * - * @param $p_filelist - * @return array|bool - */ - function _fetchFilelist($p_filelist) { - if(!$p_filelist || (is_array($p_filelist) && !@count($p_filelist))) return false; - - if(is_string($p_filelist)) { - $p_filelist = explode('|', $p_filelist); - if(!is_array($p_filelist)) $p_filelist = Array($p_filelist); - } - - return $p_filelist; - } - - /** - * Adds files given as file list - * - * @param array $p_fl - * @param string $p_addir - * @param string $p_remdir - * @return bool - */ - function _addFileList($p_fl, $p_addir, $p_remdir) { - foreach($p_fl as $file) { - if(($file == $this->_nomf && $this->_nomf !== TarLib::ARCHIVE_DYNAMIC) || !$file || (!is_array($file) && !file_exists($file))) - continue; - - if(!$this->_addFile($file, $p_addir, $p_remdir)) - continue; - - if(@is_dir($file)) { - $d = @opendir($file); - - if(!$d) continue; - readdir($d); - readdir($d); - - while($f = readdir($d)) { - if($file != ".") $tmplist[0] = "$file/$f"; - else $tmplist[0] = $d; - - $this->_addFileList($tmplist, $p_addir, $p_remdir); - } - - closedir($d); - unset($tmplist, $f); - } - } - return true; + function __call($name, $arguments) { + $this->error($name); } - - /** - * Adds a single file - * - * @param array|string $p_fn - * @param string $p_addir - * @param string $p_remdir - * @return bool - */ - function _addFile($p_fn, $p_addir = '', $p_remdir = '') { - $data = false; - if(is_array($p_fn)) list($p_fn, $data) = $p_fn; - $sname = $p_fn; - - if($p_remdir) { - if(substr($p_remdir, -1) != '/') $p_remdir .= "/"; - - if(substr($sname, 0, strlen($p_remdir)) == $p_remdir) - $sname = substr($sname, strlen($p_remdir)); - } - - if($p_addir) $sname = $p_addir.(substr($p_addir, -1) == '/' ? '' : "/").$sname; - - // FIXME ustar should support up 256 chars - if(strlen($sname) > 99) return false; - - if(@is_dir($p_fn)) { - if(!$this->_writeFileHeader($p_fn, $sname)) return false; - } else { - if(!$data) { - $fp = fopen($p_fn, 'rb'); - if(!$fp) return false; - } - - if(!$this->_writeFileHeader($p_fn, $sname, ($data ? strlen($data) : false))) return false; - - if(!$data) { - while(!feof($fp)) { - $packed = pack("a512", fread($fp, 512)); - $this->_write($packed); - } - fclose($fp); - } else { - $len = strlen($data); - for($s = 0; $s < $len; $s += 512) { - $this->_write(pack("a512", substr($data, $s, 512))); - } - } - } - - return true; - } - - /** - * Write the header for a file in the TAR archive - * - * @param string $p_file - * @param string $p_sname - * @param bool $p_data - * @return bool - */ - function _writeFileHeader($p_file, $p_sname, $p_data = false) { - if(!$p_data) { - if(!$p_sname) $p_sname = $p_file; - $p_sname = $this->_pathTrans($p_sname); - - $h_info = stat($p_file); - $h[0] = sprintf("%6s ", DecOct($h_info[4])); - $h[] = sprintf("%6s ", DecOct($h_info[5])); - $h[] = sprintf("%6s ", DecOct(fileperms($p_file))); - clearstatcache(); - $h[] = sprintf("%11s ", DecOct(filesize($p_file))); - $h[] = sprintf("%11s", DecOct(filemtime($p_file))); - - $dir = @is_dir($p_file) ? '5' : ''; - } else { - $dir = ''; - $p_data = sprintf("%11s ", DecOct($p_data)); - $time = sprintf("%11s ", DecOct(time())); - $h = Array(" 0 ", " 0 ", " 40777 ", $p_data, $time); - } - - $data_first = pack("a100a8a8a8a12A12", $p_sname, $h[2], $h[0], $h[1], $h[3], $h[4]); - $data_last = pack("a1a100a6a2a32a32a8a8a155a12", $dir, '', '', '', '', '', '', '', '', ""); - - for($i = 0, $chks = 0; $i < 148; $i++) - $chks += ord($data_first[$i]); - - for($i = 156, $chks += 256, $j = 0; $i < 512; $i++, $j++) - $chks += ord($data_last[$j]); - - $this->_write($data_first); - - $chks = pack("a8", sprintf("%6s ", DecOct($chks))); - $this->_write($chks.$data_last); - - return true; - } - - /** - * Append the given files to the already open archive - * - * @param array $p_filelist - * @param string $p_addir - * @param string $p_remdir - * @return bool|int - */ - function _append($p_filelist, $p_addir = "", $p_remdir = "") { - if(!$this->_fp) if(!$this->_OpenWrite('a')) return -6; - - if($this->_nomf === TarLib::ARCHIVE_DYNAMIC) { - $this->_memdat = substr($this->_memdat, 0, -512 * 2); // remove footer - } else { - clearstatcache(); - $s = filesize($this->_nomf); - - $this->_seek($s - (512 * 2)); // remove footer - } - - $ok = $this->_addFileList($p_filelist, $p_addir, $p_remdir); - $this->_writeFooter(); - - return $ok; - } - - /** - * Cleans up a path and removes relative parts - * - * @param string $p_dir - * @return string - */ - function _pathTrans($p_dir) { - $r = ''; - if($p_dir) { - $subf = explode("/", $p_dir); - - for($i = count($subf) - 1; $i >= 0; $i--) { - if($subf[$i] == ".") { - # do nothing - } elseif($subf[$i] == "..") { - $i--; - } elseif(!$subf[$i] && $i != count($subf) - 1 && $i) { - # do nothing - } else { - $r = $subf[$i].($i != (count($subf) - 1) ? "/".$r : ""); - } - } - } - return $r; - } - - /** - * Add the closing footer to the archive - * - * Physically, an archive consists of a series of file entries terminated by an end-of-archive entry, which - * consists of two 512 blocks of zero bytes - * - * @link http://www.gnu.org/software/tar/manual/html_chapter/tar_8.html#SEC134 - */ - function _writeFooter() { - $this->_write(pack("a512", "")); - $this->_write(pack("a512", "")); - } - - /** - * @param $p_to - * @param $p_files - * @param $p_remdir - * @param int $p_mode - * @return array|bool|int|string - */ - function _extractList($p_to, $p_files, $p_remdir, $p_mode = 0755) { - if(!$p_to || ($p_to[0] != "/" && substr($p_to, 0, 3) != "../" && substr($p_to, 1, 3) != ":\\" && substr($p_to, 1, 2) != ":/")) /*" // <- PHP Coder bug */ - $p_to = "./$p_to"; - - if($p_remdir && substr($p_remdir, -1) != '/') $p_remdir .= '/'; - $p_remdirs = strlen($p_remdir); - while($dat = $this->_read(512)) { - $headers = $this->_readHeader($dat); - if(!$headers['filename']) continue; - - if($p_files == -1 || $p_files[0] == -1) { - $extract = true; - } else { - $extract = false; - - foreach($p_files as $f) { - if(substr($f, -1) == "/") { - if((strlen($headers['filename']) > strlen($f)) && (substr($headers['filename'], 0, strlen($f)) == $f)) { - $extract = true; - break; - } - } elseif($f == $headers['filename']) { - $extract = true; - break; - } - } - } - - if($extract) { - $det[] = $headers; - if($p_remdir && substr($headers['filename'], 0, $p_remdirs) == $p_remdir) - $headers['filename'] = substr($headers['filename'], $p_remdirs); - - if($headers['filename'].'/' == $p_remdir && $headers['typeflag'] == '5') continue; - - if($p_to != "./" && $p_to != "/") { - while($p_to{-1} == "/") $p_to = substr($p_to, 0, -1); - - if($headers['filename']{0} == "/") - $headers['filename'] = $p_to.$headers['filename']; - else - $headers['filename'] = $p_to."/".$headers['filename']; - } - - $ok = $this->_dirApp($headers['typeflag'] == "5" ? $headers['filename'] : dirname($headers['filename'])); - if($ok < 0) return $ok; - - if(!$headers['typeflag']) { - if(!$fp = @fopen($headers['filename'], "wb")) return -6; - $n = floor($headers['size'] / 512); - - for($i = 0; $i < $n; $i++) { - fwrite($fp, $this->_read(512), 512); - } - if(($headers['size'] % 512) != 0) fwrite($fp, $this->_read(512), $headers['size'] % 512); - - fclose($fp); - touch($headers['filename'], $headers['mtime']); - chmod($headers['filename'], $p_mode); - } else { - $this->_seek(ceil($headers['size'] / 512) * 512, 1); - } - } else $this->_seek(ceil($headers['size'] / 512) * 512, 1); - } - return $det; - } - - /** - * Create a directory hierarchy in filesystem - * - * @param string $d - * @return bool - */ - function _dirApp($d) { - // map to dokuwiki function (its more robust) - return io_mkdir_p($d); - } - -} - +}
\ No newline at end of file diff --git a/inc/load.php b/inc/load.php index b8a279523..49c307054 100644 --- a/inc/load.php +++ b/inc/load.php @@ -71,6 +71,7 @@ function load_autoload($name){ 'IXR_IntrospectionServer' => DOKU_INC.'inc/IXR_Library.php', 'Doku_Plugin_Controller'=> DOKU_INC.'inc/plugincontroller.class.php', 'GeSHi' => DOKU_INC.'inc/geshi.php', + 'Tar' => DOKU_INC.'inc/Tar.class.php', 'TarLib' => DOKU_INC.'inc/TarLib.class.php', 'ZipLib' => DOKU_INC.'inc/ZipLib.class.php', 'DokuWikiFeedCreator' => DOKU_INC.'inc/feedcreator.class.php', diff --git a/lib/plugins/plugin/classes/ap_download.class.php b/lib/plugins/plugin/classes/ap_download.class.php index d1b518d9d..0eb567c80 100644 --- a/lib/plugins/plugin/classes/ap_download.class.php +++ b/lib/plugins/plugin/classes/ap_download.class.php @@ -200,31 +200,26 @@ class ap_download extends ap_manage { if (in_array($ext, array('tar','bz','gz'))) { switch($ext){ case 'bz': - $compress_type = TarLib::COMPRESS_BZIP; + $compress_type = Tar::COMPRESS_BZIP; break; case 'gz': - $compress_type = TarLib::COMPRESS_GZIP; + $compress_type = Tar::COMPRESS_GZIP; break; default: - $compress_type = TarLib::COMPRESS_NONE; + $compress_type = Tar::COMPRESS_NONE; } - $tar = new TarLib($file, $compress_type); - if($tar->_initerror < 0){ + $tar = new Tar(); + try { + $tar->open($file, $compress_type); + $tar->extract($target); + return true; + }catch(Exception $e){ if($conf['allowdebug']){ - msg('TarLib Error: '.$tar->TarErrorStr($tar->_initerror),-1); + msg('Tar Error: '.$e->getMessage().' ['.$e->getFile().':'.$e->getLine().']',-1); } return false; } - $ok = $tar->Extract(TarLib::FULL_ARCHIVE, $target, '', 0777); - - if($ok<1){ - if($conf['allowdebug']){ - msg('TarLib Error: '.$tar->TarErrorStr($ok),-1); - } - return false; - } - return true; } else if ($ext == 'zip') { $zip = new ZipLib(); |