aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/ZipStream.php69
-rw-r--r--tests/AddFileTest.php127
-rw-r--r--tests/ArchiveTest.php63
3 files changed, 222 insertions, 37 deletions
diff --git a/src/ZipStream.php b/src/ZipStream.php
index aa7e7e4..437ff1d 100644
--- a/src/ZipStream.php
+++ b/src/ZipStream.php
@@ -27,20 +27,14 @@ const VERSION = '0.3.0';
const VERSION_NEEDED = 45;
/**
- * Valid compression methods.
+ * Base class for all exceptions raised by ZipStream.
*/
-final class Methods {
- /** Store file without compression. */
- const STORE = 0;
-
- /** Store file using DEFLATE compression. */
- const DEFLATE = 8;
-};
+class Error extends \Exception { };
/**
- * Base class for all exceptions raised by ZipStream.
+ * Comment length error.
*/
-class Error extends \Exception { };
+final class CommentError extends Error { };
/**
* Deflate context error.
@@ -104,6 +98,34 @@ final class PathError extends Error {
};
/**
+ * Valid compression methods.
+ */
+final class Methods {
+ /** Store file without compression. */
+ const STORE = 0;
+
+ /** Store file using DEFLATE compression. */
+ const DEFLATE = 8;
+
+ /**
+ * Check for supported compression method.
+ *
+ * @internal
+ *
+ * @param int $method Compression method.
+ *
+ * @return void
+ *
+ * @throw UnsupportedMethodError if compression method is unsupported.
+ */
+ static public function check(int $method) : void {
+ if ($method != Methods::DEFLATE && $method != Methods::STORE) {
+ throw new UnknownMethodError($method);
+ }
+ }
+};
+
+/**
* Abstract interface for stream output.
*
* @api
@@ -826,7 +848,7 @@ final class Entry {
# check comment length
if (strlen($comment) >= 0xFFFF) {
$this->state = self::ENTRY_STATE_ERROR;
- throw new Error('comment too long');
+ throw new CommentError('entry comment too long');
}
$this->uncompressed_size = 0;
@@ -862,7 +884,7 @@ final class Entry {
*
* @return int Number of bytes written to output.
*/
- public function write(string &$data) : int {
+ public function write(string $data) : int {
try {
# check entry state
if ($this->state != self::ENTRY_STATE_DATA) {
@@ -1237,8 +1259,20 @@ final class ZipStream {
'time' => time(),
], $args);
+ # check archive method
+ Methods::check($this->args['method']);
+
+ # check archive comment length
+ if (strlen($this->args['comment']) >= 0xFFFF) {
+ throw new CommentError('archive comment too long');
+ }
+
# initialize output
if (isset($args['output']) && $args['output']) {
+ if (!is_subclass_of($args['output'], Writer::class)) {
+ throw new Error('output must implement Writer interface');
+ }
+
# use specified output writer
$this->output = $args['output'];
} else {
@@ -1596,7 +1630,7 @@ final class ZipStream {
# get comment, check length
$comment = $this->args['comment'];
if (strlen($comment) >= 0xFFFF) {
- throw new Error('comment too long');
+ throw new CommentError('archive comment too long');
}
return pack('VvvvvVVv',
@@ -1624,10 +1658,13 @@ final class ZipStream {
*/
private function get_entry_time(array &$args) : int {
if (isset($args['time'])) {
+ # use entry time
return $args['time'];
} else if (isset($this->args['time'])) {
+ # use archive time
return $this->args['time'];
} else {
+ # fall back to current time
return time();
}
}
@@ -1649,10 +1686,10 @@ final class ZipStream {
$r = Methods::DEFLATE;
}
- if ($r != Methods::DEFLATE && $r != Methods::STORE) {
- throw new UnknownMethodError($r);
- }
+ # check method
+ Methods::check($r);
+ # return method
return $r;
}
};
diff --git a/tests/AddFileTest.php b/tests/AddFileTest.php
index 3ea65e0..d4b5d5a 100644
--- a/tests/AddFileTest.php
+++ b/tests/AddFileTest.php
@@ -7,11 +7,11 @@ use \PHPUnit\Framework\TestCase;
use \Pablotron\ZipStream\ZipStream;
final class AddFileTest extends BaseTestCase {
- public function testCreateFile() : void {
+ public function testAddFile() : void {
$this->with_temp_zip(function(ZipStream &$zip) {
$zip->add_file('hello.txt', 'hello!');
- }, function(string $path) {
- $zip = $this->open_archive($path);
+ }, function(string $zip_path) {
+ $zip = $this->open_archive($zip_path);
$this->assertEquals(
'hello!',
@@ -20,14 +20,57 @@ final class AddFileTest extends BaseTestCase {
});
}
- public function testCreateFileWithComment() : void {
+ public function testAddFileFromPath() : void {
+ $this->with_temp_zip(function(ZipStream &$zip) {
+ $zip->add_file_from_path('test.php', __FILE__);
+ }, function(string $zip_path) {
+ $zip = $this->open_archive($zip_path);
+
+ $this->assertEquals(
+ sha1(file_get_contents(__FILE__)),
+ sha1($zip->getFromName('test.php'))
+ );
+ });
+ }
+
+ public function testAddStream() : void {
+ $this->with_temp_zip(function(ZipStream &$zip) {
+ $fh = fopen(__FILE__, 'rb');
+ $zip->add_stream('test.php', $fh);
+ fclose($fh);
+ }, function(string $zip_path) {
+ $zip = $this->open_archive($zip_path);
+
+ $this->assertEquals(
+ sha1(file_get_contents(__FILE__)),
+ sha1($zip->getFromName('test.php'))
+ );
+ });
+ }
+
+ public function testAdd() : void {
+ $this->with_temp_zip(function(ZipStream &$zip) {
+ $zip->add('test.php', function(&$e) {
+ $e->write(file_get_contents(__FILE__));
+ });
+ }, function(string $zip_path) {
+ $zip = $this->open_archive($zip_path);
+
+ $this->assertEquals(
+ sha1(file_get_contents(__FILE__)),
+ sha1($zip->getFromName('test.php'))
+ );
+ });
+ }
+
+ public function testAddFileWithComment() : void {
$comment = 'test comment';
$this->with_temp_zip(function(ZipStream &$zip) use ($comment) {
$zip->add_file('hello.txt', 'hello!', [
'comment' => $comment,
]);
- }, function(string $path) use ($comment) {
- $zip = $this->open_archive($path);
+ }, function(string $zip_path) use ($comment) {
+ $zip = $this->open_archive($zip_path);
$this->assertEquals(
$comment,
@@ -36,7 +79,7 @@ final class AddFileTest extends BaseTestCase {
});
}
- public function testCreateFileWithUnknownMethod() : void {
+ public function testAddFileWithUnknownMethod() : void {
$this->expectException(\Pablotron\ZipStream\UnknownMethodError::class);
$this->with_temp_zip(function(ZipStream &$zip) {
@@ -46,22 +89,84 @@ final class AddFileTest extends BaseTestCase {
});
}
- public function testCreateFileTimestamp() : void {
+ public function testAddFileTimestamp() : void {
# get timezone offset
# $ofs = \DateTimeZone::getOffset(\DateTime::getTimezone());
- $ofs = 4 * 3600; # hard-coded to EDT for now
+ $ofs = 4 * 3600; # FIXME: hard-coded to EDT for now
# get time from 2 hours ago (round to even number of seconds)
$time = ((time() - (2 * 3600)) >> 1) << 1;
+ # get test time
+ $expected_time = $time + $ofs;
+
$this->with_temp_zip(function(ZipStream &$zip) use ($time) {
$zip->add_file('hello.txt', 'hello!', [
'time' => $time,
]);
- }, function($zip_path) use ($time, $ofs) {
+ }, function($zip_path) use ($expected_time) {
$zip = $this->open_archive($zip_path);
$st = $zip->statName('hello.txt');
- $this->assertEquals($time, $st['mtime'] - $ofs);
+
+ $this->assertEquals($expected_time, $st['mtime']);
+ });
+ }
+
+ public function testAddFileCRC() : void {
+ $data = 'hello!';
+
+ # calculate crc32b of file data
+ $hash = hash('crc32b', $data, true);
+
+ # pack expected crc as integer
+ $expected_crc = (
+ (ord($hash[0]) << 24) |
+ (ord($hash[1]) << 16) |
+ (ord($hash[2]) << 8) |
+ (ord($hash[3]))
+ );
+
+ $this->with_temp_zip(function(ZipStream &$zip) use ($data) {
+ $zip->add_file('hello.txt', $data);
+ }, function($zip_path) use ($expected_crc) {
+ $zip = $this->open_archive($zip_path);
+ $st = $zip->statName('hello.txt');
+
+ $this->assertEquals($expected_crc, $st['crc']);
+ });
+ }
+
+ public function testAddFileWithMethodStore() : void {
+ $data = file_get_contents(__FILE__);
+
+ $this->with_temp_zip(function(ZipStream &$zip) use ($data) {
+ $zip->add_file('test.php', $data, [
+ 'method' => \Pablotron\ZipStream\Methods::STORE,
+ ]);
+ }, function($zip_path) use ($data) {
+ $zip = $this->open_archive($zip_path);
+
+ $this->assertEquals(
+ sha1($data),
+ sha1($zip->getFromName('test.php'))
+ );
+ });
+ }
+
+ public function testAddFileWithMethodDeflate() : void {
+ $data = file_get_contents(__FILE__);
+
+ $this->with_temp_zip(function(ZipStream &$zip) use ($data) {
+ $zip->add_file('test.php', $data, [
+ 'method' => \Pablotron\ZipStream\Methods::DEFLATE,
+ ]);
+ }, function($zip_path) use ($data) {
+ $zip = $this->open_archive($zip_path);
+
+ $this->assertEquals(
+ sha1($data),
+ sha1($zip->getFromName('test.php'))
+ );
});
}
};
diff --git a/tests/ArchiveTest.php b/tests/ArchiveTest.php
index cbac0f1..8345ee4 100644
--- a/tests/ArchiveTest.php
+++ b/tests/ArchiveTest.php
@@ -19,15 +19,15 @@ final class ArchiveTest extends BaseTestCase {
}
public function testFileWriter() {
- $this->with_temp_file(function(string $dst_path) {
- ZipStream::send($dst_path, function(ZipStream &$zip) {
+ $this->with_temp_file(function(string $zip_path) {
+ ZipStream::send($zip_path, function(ZipStream &$zip) {
$zip->add_file('hello.txt', 'hello!');
}, [
'output' => new FileWriter(),
]);
# open archive
- $zip = $this->open_archive($dst_path);
+ $zip = $this->open_archive($zip_path);
# read hello.txt, check text
$this->assertEquals(
@@ -38,13 +38,13 @@ final class ArchiveTest extends BaseTestCase {
}
public function testStreamWriter() {
- $this->with_temp_file(function(string $dst_path) {
- $fh = fopen($dst_path, 'wb');
+ $this->with_temp_file(function(string $zip_path) {
+ $fh = fopen($zip_path, 'wb');
if ($fh === false) {
throw new Exception("fopen() failed");
}
- ZipStream::send($dst_path, function(ZipStream &$zip) {
+ ZipStream::send($zip_path, function(ZipStream &$zip) {
$zip->add_file('hello.txt', 'hello!');
}, [
'output' => new StreamWriter($fh),
@@ -54,7 +54,7 @@ final class ArchiveTest extends BaseTestCase {
fclose($fh);
# open archive
- $zip = $this->open_archive($dst_path);
+ $zip = $this->open_archive($zip_path);
# read hello.txt, check text
$this->assertEquals(
@@ -65,11 +65,11 @@ final class ArchiveTest extends BaseTestCase {
}
public function testArchiveComment() : void {
- $this->with_temp_file(function($dst_path) {
+ $this->with_temp_file(function($zip_path) {
$comment = 'test archive comment';
# write archive
- ZipStream::send($dst_path, function(ZipStream &$zip) {
+ ZipStream::send($zip_path, function(ZipStream &$zip) {
$zip->add_file('hello.txt', 'hello!');
}, [
'comment' => $comment,
@@ -77,7 +77,7 @@ final class ArchiveTest extends BaseTestCase {
]);
# open archive
- $zip = $this->open_archive($dst_path);
+ $zip = $this->open_archive($zip_path);
# read hello.txt, check text
$this->assertEquals(
@@ -86,4 +86,47 @@ final class ArchiveTest extends BaseTestCase {
);
});
}
+
+ public function testLongArchiveComment() : void {
+ $this->expectException(\Pablotron\ZipStream\CommentError::class);
+
+ $this->with_temp_file(function($zip_path) {
+ $comment = str_repeat('x', 0xFFFF);
+
+ # write archive
+ ZipStream::send($zip_path, function(ZipStream &$zip) {
+ $zip->add_file('hello.txt', 'hello!');
+ }, [
+ 'comment' => $comment,
+ 'output' => new FileWriter(),
+ ]);
+ });
+ }
+
+ public function testInvalidOutput() : void {
+ $this->expectException(\Pablotron\ZipStream\Error::class);
+
+ $this->with_temp_file(function($zip_path) {
+ # write archive with invalid writer
+ ZipStream::send($zip_path, function(ZipStream &$zip) {
+ $zip->add_file('hello.txt', 'hello!');
+ }, [
+ 'output' => 'bad writer',
+ ]);
+ });
+ }
+
+ public function testInvalidMethod() : void {
+ $this->expectException(\Pablotron\ZipStream\UnknownMethodError::class);
+
+ $this->with_temp_file(function($zip_path) {
+ # write archive with invalid compression method
+ ZipStream::send($zip_path, function(ZipStream &$zip) {
+ $zip->add_file('hello.txt', 'hello!');
+ }, [
+ 'method' => 100,
+ 'output' => new FileWriter(),
+ ]);
+ });
+ }
};