aboutsummaryrefslogtreecommitdiffstats
path: root/vendor/maennchen/zipstream-php/test/ZipStreamTest.php
diff options
context:
space:
mode:
authorHarald Eilertsen <haraldei@anduin.net>2024-11-07 19:23:35 +0100
committerHarald Eilertsen <haraldei@anduin.net>2024-11-08 16:43:29 +0100
commitfe30b5497593dcfb4445d72c99fa357011cebf46 (patch)
tree6d4d89fed5dcd52e26dd1e07e7cae04206c737d4 /vendor/maennchen/zipstream-php/test/ZipStreamTest.php
parentb00ae997a5dab923a99e1f1cccf35bb52eba9a62 (diff)
downloadvolse-hubzilla-fe30b5497593dcfb4445d72c99fa357011cebf46.tar.gz
volse-hubzilla-fe30b5497593dcfb4445d72c99fa357011cebf46.tar.bz2
volse-hubzilla-fe30b5497593dcfb4445d72c99fa357011cebf46.zip
Update php-epub-meta and use composer for dep handling.
Note that we upgrade to the 2.x branch of the dependency, as the 3.x branch requires PHP version 8.2 or later. There's no reason for us to move our minimum supported version of PHP just yet.
Diffstat (limited to 'vendor/maennchen/zipstream-php/test/ZipStreamTest.php')
-rw-r--r--vendor/maennchen/zipstream-php/test/ZipStreamTest.php1195
1 files changed, 1195 insertions, 0 deletions
diff --git a/vendor/maennchen/zipstream-php/test/ZipStreamTest.php b/vendor/maennchen/zipstream-php/test/ZipStreamTest.php
new file mode 100644
index 000000000..9b10ba65d
--- /dev/null
+++ b/vendor/maennchen/zipstream-php/test/ZipStreamTest.php
@@ -0,0 +1,1195 @@
+<?php
+
+declare(strict_types=1);
+
+namespace ZipStream\Test;
+
+use DateTimeImmutable;
+use GuzzleHttp\Psr7\Response;
+use GuzzleHttp\Psr7\StreamWrapper;
+use org\bovigo\vfs\vfsStream;
+use PHPUnit\Framework\TestCase;
+use Psr\Http\Message\StreamInterface;
+use RuntimeException;
+use ZipArchive;
+use ZipStream\CompressionMethod;
+use ZipStream\Exception\FileNotFoundException;
+use ZipStream\Exception\FileNotReadableException;
+use ZipStream\Exception\FileSizeIncorrectException;
+use ZipStream\Exception\OverflowException;
+use ZipStream\Exception\ResourceActionException;
+use ZipStream\Exception\SimulationFileUnknownException;
+use ZipStream\Exception\StreamNotReadableException;
+use ZipStream\Exception\StreamNotSeekableException;
+use ZipStream\OperationMode;
+use ZipStream\PackField;
+use ZipStream\ZipStream;
+
+class ZipStreamTest extends TestCase
+{
+ use Util;
+ use Assertions;
+ use Tempfile;
+
+ public function testAddFile(): void
+ {
+ $zip = new ZipStream(
+ outputStream: $this->tempfileStream,
+ sendHttpHeaders: false,
+ );
+
+ $zip->addFile('sample.txt', 'Sample String Data');
+ $zip->addFile('test/sample.txt', 'More Simple Sample Data');
+
+ $zip->finish();
+
+ $tmpDir = $this->validateAndExtractZip($this->tempfile);
+
+ $files = $this->getRecursiveFileList($tmpDir);
+ $this->assertSame(['sample.txt', 'test' . DIRECTORY_SEPARATOR . 'sample.txt'], $files);
+
+ $this->assertStringEqualsFile($tmpDir . '/sample.txt', 'Sample String Data');
+ $this->assertStringEqualsFile($tmpDir . '/test/sample.txt', 'More Simple Sample Data');
+ }
+
+ public function testAddFileUtf8NameComment(): void
+ {
+ $zip = new ZipStream(
+ outputStream: $this->tempfileStream,
+ sendHttpHeaders: false,
+ );
+
+ $name = 'árvíztűrő tükörfúrógép.txt';
+ $content = 'Sample String Data';
+ $comment =
+ 'Filename has every special characters ' .
+ 'from Hungarian language in lowercase. ' .
+ 'In uppercase: ÁÍŰŐÜÖÚÓÉ';
+
+ $zip->addFile(fileName: $name, data: $content, comment: $comment);
+ $zip->finish();
+
+ $tmpDir = $this->validateAndExtractZip($this->tempfile);
+
+ $files = $this->getRecursiveFileList($tmpDir);
+ $this->assertSame([$name], $files);
+ $this->assertStringEqualsFile($tmpDir . '/' . $name, $content);
+
+ $zipArchive = new ZipArchive();
+ $zipArchive->open($this->tempfile);
+ $this->assertSame($comment, $zipArchive->getCommentName($name));
+ }
+
+ public function testAddFileUtf8NameNonUtfComment(): void
+ {
+ $zip = new ZipStream(
+ outputStream: $this->tempfileStream,
+ sendHttpHeaders: false,
+ );
+
+ $name = 'á.txt';
+ $content = 'any';
+ $comment = mb_convert_encoding('á', 'ISO-8859-2', 'UTF-8');
+
+ // @see https://libzip.org/documentation/zip_file_get_comment.html
+ //
+ // mb_convert_encoding hasn't CP437.
+ // nearly CP850 (DOS-Latin-1)
+ $guessComment = mb_convert_encoding($comment, 'UTF-8', 'CP850');
+
+ $zip->addFile(fileName: $name, data: $content, comment: $comment);
+
+ $zip->finish();
+
+ $zipArch = new ZipArchive();
+ $zipArch->open($this->tempfile);
+ $this->assertSame($guessComment, $zipArch->getCommentName($name));
+ $this->assertSame($comment, $zipArch->getCommentName($name, ZipArchive::FL_ENC_RAW));
+ }
+
+ public function testAddFileWithStorageMethod(): void
+ {
+ $zip = new ZipStream(
+ outputStream: $this->tempfileStream,
+ sendHttpHeaders: false,
+ );
+
+ $zip->addFile(fileName: 'sample.txt', data: 'Sample String Data', compressionMethod: CompressionMethod::STORE);
+ $zip->addFile(fileName: 'test/sample.txt', data: 'More Simple Sample Data');
+ $zip->finish();
+
+ $zipArchive = new ZipArchive();
+ $zipArchive->open($this->tempfile);
+
+ $sample1 = $zipArchive->statName('sample.txt');
+ $sample12 = $zipArchive->statName('test/sample.txt');
+ $this->assertSame($sample1['comp_method'], CompressionMethod::STORE->value);
+ $this->assertSame($sample12['comp_method'], CompressionMethod::DEFLATE->value);
+
+ $zipArchive->close();
+ }
+
+ public function testAddFileFromPath(): void
+ {
+ $zip = new ZipStream(
+ outputStream: $this->tempfileStream,
+ sendHttpHeaders: false,
+ );
+
+ [$tmpExample, $streamExample] = $this->getTmpFileStream();
+ fwrite($streamExample, 'Sample String Data');
+ fclose($streamExample);
+ $zip->addFileFromPath(fileName: 'sample.txt', path: $tmpExample);
+
+ [$tmpExample, $streamExample] = $this->getTmpFileStream();
+ fwrite($streamExample, 'More Simple Sample Data');
+ fclose($streamExample);
+ $zip->addFileFromPath(fileName: 'test/sample.txt', path: $tmpExample);
+
+ $zip->finish();
+
+ $tmpDir = $this->validateAndExtractZip($this->tempfile);
+
+ $files = $this->getRecursiveFileList($tmpDir);
+ $this->assertSame(['sample.txt', 'test' . DIRECTORY_SEPARATOR . 'sample.txt'], $files);
+
+ $this->assertStringEqualsFile($tmpDir . '/sample.txt', 'Sample String Data');
+ $this->assertStringEqualsFile($tmpDir . '/test/sample.txt', 'More Simple Sample Data');
+
+ unlink($tmpExample);
+ }
+
+ public function testAddFileFromPathFileNotFoundException(): void
+ {
+ $this->expectException(FileNotFoundException::class);
+
+ // Get ZipStream Object
+ $zip = new ZipStream(
+ outputStream: $this->tempfileStream,
+ sendHttpHeaders: false,
+ );
+
+ // Trigger error by adding a file which doesn't exist
+ $zip->addFileFromPath(fileName: 'foobar.php', path: '/foo/bar/foobar.php');
+ }
+
+ public function testAddFileFromPathFileNotReadableException(): void
+ {
+ $this->expectException(FileNotReadableException::class);
+
+ // create new virtual filesystem
+ $root = vfsStream::setup('vfs');
+ // create a virtual file with no permissions
+ $file = vfsStream::newFile('foo.txt', 0)->at($root)->setContent('bar');
+
+ // Get ZipStream Object
+ $zip = new ZipStream(
+ outputStream: $this->tempfileStream,
+ sendHttpHeaders: false,
+ );
+
+ $zip->addFileFromPath('foo.txt', $file->url());
+ }
+
+ public function testAddFileFromPathWithStorageMethod(): void
+ {
+ $zip = new ZipStream(
+ outputStream: $this->tempfileStream,
+ sendHttpHeaders: false,
+ );
+
+ [$tmpExample, $streamExample] = $this->getTmpFileStream();
+ fwrite($streamExample, 'Sample String Data');
+ fclose($streamExample);
+ $zip->addFileFromPath(fileName: 'sample.txt', path: $tmpExample, compressionMethod: CompressionMethod::STORE);
+
+ [$tmpExample, $streamExample] = $this->getTmpFileStream();
+ fwrite($streamExample, 'More Simple Sample Data');
+ fclose($streamExample);
+ $zip->addFileFromPath('test/sample.txt', $tmpExample);
+
+ $zip->finish();
+
+ $zipArchive = new ZipArchive();
+ $zipArchive->open($this->tempfile);
+
+ $sample1 = $zipArchive->statName('sample.txt');
+ $this->assertSame(CompressionMethod::STORE->value, $sample1['comp_method']);
+
+ $sample2 = $zipArchive->statName('test/sample.txt');
+ $this->assertSame(CompressionMethod::DEFLATE->value, $sample2['comp_method']);
+
+ $zipArchive->close();
+ }
+
+ public function testAddLargeFileFromPath(): void
+ {
+ foreach ([CompressionMethod::DEFLATE, CompressionMethod::STORE] as $compressionMethod) {
+ foreach ([false, true] as $zeroHeader) {
+ foreach ([false, true] as $zip64) {
+ if ($zeroHeader && $compressionMethod === CompressionMethod::DEFLATE) {
+ continue;
+ }
+ $this->addLargeFileFileFromPath(
+ compressionMethod: $compressionMethod,
+ zeroHeader: $zeroHeader,
+ zip64: $zip64
+ );
+ }
+ }
+ }
+ }
+
+ public function testAddFileFromStream(): void
+ {
+ $zip = new ZipStream(
+ outputStream: $this->tempfileStream,
+ sendHttpHeaders: false,
+ );
+
+ // In this test we can't use temporary stream to feed data
+ // because zlib.deflate filter gives empty string before PHP 7
+ // it works fine with file stream
+ $streamExample = fopen(__FILE__, 'rb');
+ $zip->addFileFromStream('sample.txt', $streamExample);
+ fclose($streamExample);
+
+ $streamExample2 = fopen('php://temp', 'wb+');
+ fwrite($streamExample2, 'More Simple Sample Data');
+ rewind($streamExample2); // move the pointer back to the beginning of file.
+ $zip->addFileFromStream('test/sample.txt', $streamExample2); //, $fileOptions);
+ fclose($streamExample2);
+
+ $zip->finish();
+
+ $tmpDir = $this->validateAndExtractZip($this->tempfile);
+
+ $files = $this->getRecursiveFileList($tmpDir);
+ $this->assertSame(['sample.txt', 'test' . DIRECTORY_SEPARATOR . 'sample.txt'], $files);
+
+ $this->assertStringEqualsFile(__FILE__, file_get_contents($tmpDir . '/sample.txt'));
+ $this->assertStringEqualsFile($tmpDir . '/test/sample.txt', 'More Simple Sample Data');
+ }
+
+ public function testAddFileFromStreamUnreadableInput(): void
+ {
+ $this->expectException(StreamNotReadableException::class);
+
+ [$tmpInput] = $this->getTmpFileStream();
+
+ $zip = new ZipStream(
+ outputStream: $this->tempfileStream,
+ sendHttpHeaders: false,
+ );
+
+ $streamUnreadable = fopen($tmpInput, 'w');
+
+ $zip->addFileFromStream('sample.json', $streamUnreadable);
+ }
+
+ public function testAddFileFromStreamBrokenOutputWrite(): void
+ {
+ $this->expectException(ResourceActionException::class);
+
+ $outputStream = FaultInjectionResource::getResource(['stream_write']);
+
+ $zip = new ZipStream(
+ outputStream: $outputStream,
+ sendHttpHeaders: false,
+ );
+
+ $zip->addFile('sample.txt', 'foobar');
+ }
+
+ public function testAddFileFromStreamBrokenInputRewind(): void
+ {
+ $this->expectException(ResourceActionException::class);
+
+ $zip = new ZipStream(
+ outputStream: $this->tempfileStream,
+ sendHttpHeaders: false,
+ defaultEnableZeroHeader: false,
+ );
+
+ $fileStream = FaultInjectionResource::getResource(['stream_seek']);
+
+ $zip->addFileFromStream('sample.txt', $fileStream, maxSize: 0);
+ }
+
+ public function testAddFileFromStreamUnseekableInputWithoutZeroHeader(): void
+ {
+ $this->expectException(StreamNotSeekableException::class);
+
+ $zip = new ZipStream(
+ outputStream: $this->tempfileStream,
+ sendHttpHeaders: false,
+ defaultEnableZeroHeader: false,
+ );
+
+ if (file_exists('/dev/null')) {
+ $streamUnseekable = fopen('/dev/null', 'w+');
+ } elseif (file_exists('NUL')) {
+ $streamUnseekable = fopen('NUL', 'w+');
+ } else {
+ $this->markTestSkipped('Needs file /dev/null');
+ }
+
+ $zip->addFileFromStream('sample.txt', $streamUnseekable, maxSize: 2);
+ }
+
+ public function testAddFileFromStreamUnseekableInputWithZeroHeader(): void
+ {
+ $zip = new ZipStream(
+ outputStream: $this->tempfileStream,
+ sendHttpHeaders: false,
+ defaultEnableZeroHeader: true,
+ defaultCompressionMethod: CompressionMethod::STORE,
+ );
+
+ $streamUnseekable = StreamWrapper::getResource(new class ('test') extends EndlessCycleStream {
+ public function isSeekable(): bool
+ {
+ return false;
+ }
+
+ public function seek(int $offset, int $whence = SEEK_SET): void
+ {
+ throw new RuntimeException('Not seekable');
+ }
+ });
+
+ $zip->addFileFromStream('sample.txt', $streamUnseekable, maxSize: 7);
+
+ $zip->finish();
+
+ $tmpDir = $this->validateAndExtractZip($this->tempfile);
+
+ $files = $this->getRecursiveFileList($tmpDir);
+ $this->assertSame(['sample.txt'], $files);
+
+ $this->assertSame(filesize($tmpDir . '/sample.txt'), 7);
+ }
+
+ public function testAddFileFromStreamWithStorageMethod(): void
+ {
+ $zip = new ZipStream(
+ outputStream: $this->tempfileStream,
+ sendHttpHeaders: false,
+ );
+
+ $streamExample = fopen('php://temp', 'wb+');
+ fwrite($streamExample, 'Sample String Data');
+ rewind($streamExample); // move the pointer back to the beginning of file.
+ $zip->addFileFromStream('sample.txt', $streamExample, compressionMethod: CompressionMethod::STORE);
+ fclose($streamExample);
+
+ $streamExample2 = fopen('php://temp', 'bw+');
+ fwrite($streamExample2, 'More Simple Sample Data');
+ rewind($streamExample2); // move the pointer back to the beginning of file.
+ $zip->addFileFromStream('test/sample.txt', $streamExample2, compressionMethod: CompressionMethod::DEFLATE);
+ fclose($streamExample2);
+
+ $zip->finish();
+
+ $zipArchive = new ZipArchive();
+ $zipArchive->open($this->tempfile);
+
+ $sample1 = $zipArchive->statName('sample.txt');
+ $this->assertSame(CompressionMethod::STORE->value, $sample1['comp_method']);
+
+ $sample2 = $zipArchive->statName('test/sample.txt');
+ $this->assertSame(CompressionMethod::DEFLATE->value, $sample2['comp_method']);
+
+ $zipArchive->close();
+ }
+
+ public function testAddFileFromPsr7Stream(): void
+ {
+ $zip = new ZipStream(
+ outputStream: $this->tempfileStream,
+ sendHttpHeaders: false,
+ );
+
+ $body = 'Sample String Data';
+ $response = new Response(200, [], $body);
+
+ $zip->addFileFromPsr7Stream('sample.json', $response->getBody());
+ $zip->finish();
+
+ $tmpDir = $this->validateAndExtractZip($this->tempfile);
+
+ $files = $this->getRecursiveFileList($tmpDir);
+ $this->assertSame(['sample.json'], $files);
+ $this->assertStringEqualsFile($tmpDir . '/sample.json', $body);
+ }
+
+ /**
+ * @group slow
+ */
+ public function testAddLargeFileFromPsr7Stream(): void
+ {
+ $zip = new ZipStream(
+ outputStream: $this->tempfileStream,
+ sendHttpHeaders: false,
+ enableZip64: true,
+ );
+
+ $zip->addFileFromPsr7Stream(
+ fileName: 'sample.json',
+ stream: new EndlessCycleStream('0'),
+ maxSize: 0x100000000,
+ compressionMethod: CompressionMethod::STORE,
+ lastModificationDateTime: new DateTimeImmutable('2022-01-01 01:01:01Z'),
+ );
+ $zip->finish();
+
+ $tmpDir = $this->validateAndExtractZip($this->tempfile);
+
+ $files = $this->getRecursiveFileList($tmpDir);
+ $this->assertSame(['sample.json'], $files);
+ $this->assertFileIsReadable($tmpDir . '/sample.json');
+ $this->assertStringStartsWith('000000', file_get_contents(filename: $tmpDir . '/sample.json', length: 20));
+ }
+
+ public function testContinueFinishedZip(): void
+ {
+ $this->expectException(RuntimeException::class);
+
+ $zip = new ZipStream(
+ outputStream: $this->tempfileStream,
+ sendHttpHeaders: false,
+ );
+ $zip->finish();
+
+ $zip->addFile('sample.txt', '1234');
+ }
+
+ /**
+ * @group slow
+ */
+ public function testManyFilesWithoutZip64(): void
+ {
+ $this->expectException(OverflowException::class);
+
+ $zip = new ZipStream(
+ outputStream: $this->tempfileStream,
+ sendHttpHeaders: false,
+ enableZip64: false,
+ );
+
+ for ($i = 0; $i <= 0xFFFF; $i++) {
+ $zip->addFile('sample' . $i, '');
+ }
+
+ $zip->finish();
+ }
+
+ /**
+ * @group slow
+ */
+ public function testManyFilesWithZip64(): void
+ {
+ $zip = new ZipStream(
+ outputStream: $this->tempfileStream,
+ sendHttpHeaders: false,
+ enableZip64: true,
+ );
+
+ for ($i = 0; $i <= 0xFFFF; $i++) {
+ $zip->addFile('sample' . $i, '');
+ }
+
+ $zip->finish();
+
+ $tmpDir = $this->validateAndExtractZip($this->tempfile);
+
+ $files = $this->getRecursiveFileList($tmpDir);
+
+ $this->assertSame(count($files), 0x10000);
+ }
+
+ /**
+ * @group slow
+ */
+ public function testLongZipWithout64(): void
+ {
+ $this->expectException(OverflowException::class);
+
+ $zip = new ZipStream(
+ outputStream: $this->tempfileStream,
+ sendHttpHeaders: false,
+ enableZip64: false,
+ defaultCompressionMethod: CompressionMethod::STORE,
+ );
+
+ for ($i = 0; $i < 4; $i++) {
+ $zip->addFileFromPsr7Stream(
+ fileName: 'sample' . $i,
+ stream: new EndlessCycleStream('0'),
+ maxSize: 0xFFFFFFFF,
+ compressionMethod: CompressionMethod::STORE,
+ lastModificationDateTime: new DateTimeImmutable('2022-01-01 01:01:01Z'),
+ );
+ }
+ }
+
+ /**
+ * @group slow
+ */
+ public function testLongZipWith64(): void
+ {
+ $zip = new ZipStream(
+ outputStream: $this->tempfileStream,
+ sendHttpHeaders: false,
+ enableZip64: true,
+ defaultCompressionMethod: CompressionMethod::STORE,
+ );
+
+ for ($i = 0; $i < 4; $i++) {
+ $zip->addFileFromPsr7Stream(
+ fileName: 'sample' . $i,
+ stream: new EndlessCycleStream('0'),
+ maxSize: 0x5FFFFFFF,
+ compressionMethod: CompressionMethod::STORE,
+ lastModificationDateTime: new DateTimeImmutable('2022-01-01 01:01:01Z'),
+ );
+ }
+
+ $zip->finish();
+
+ $tmpDir = $this->validateAndExtractZip($this->tempfile);
+
+ $files = $this->getRecursiveFileList($tmpDir);
+ $this->assertSame(['sample0', 'sample1', 'sample2', 'sample3'], $files);
+ }
+
+ /**
+ * @group slow
+ */
+ public function testAddLargeFileWithoutZip64WithZeroHeader(): void
+ {
+ $this->expectException(OverflowException::class);
+
+ $zip = new ZipStream(
+ outputStream: $this->tempfileStream,
+ sendHttpHeaders: false,
+ enableZip64: false,
+ defaultEnableZeroHeader: true,
+ );
+
+ $zip->addFileFromPsr7Stream(
+ fileName: 'sample.json',
+ stream: new EndlessCycleStream('0'),
+ maxSize: 0x100000000,
+ compressionMethod: CompressionMethod::STORE,
+ lastModificationDateTime: new DateTimeImmutable('2022-01-01 01:01:01Z'),
+ );
+ }
+
+ /**
+ * @group slow
+ */
+ public function testAddsZip64HeaderWhenNeeded(): void
+ {
+ $zip = new ZipStream(
+ outputStream: $this->tempfileStream,
+ sendHttpHeaders: false,
+ enableZip64: true,
+ defaultEnableZeroHeader: false,
+ );
+
+ $zip->addFileFromPsr7Stream(
+ fileName: 'sample.json',
+ stream: new EndlessCycleStream('0'),
+ maxSize: 0x100000000,
+ compressionMethod: CompressionMethod::STORE,
+ lastModificationDateTime: new DateTimeImmutable('2022-01-01 01:01:01Z'),
+ );
+
+ $zip->finish();
+
+ $tmpDir = $this->validateAndExtractZip($this->tempfile);
+ $files = $this->getRecursiveFileList($tmpDir);
+
+ $this->assertSame(['sample.json'], $files);
+ $this->assertFileContains($this->tempfile, PackField::pack(
+ new PackField(format: 'V', value: 0x06064b50)
+ ));
+ }
+
+ /**
+ * @group slow
+ */
+ public function testDoesNotAddZip64HeaderWhenNotNeeded(): void
+ {
+ $zip = new ZipStream(
+ outputStream: $this->tempfileStream,
+ sendHttpHeaders: false,
+ enableZip64: true,
+ defaultEnableZeroHeader: false,
+ );
+
+ $zip->addFileFromPsr7Stream(
+ fileName: 'sample.json',
+ stream: new EndlessCycleStream('0'),
+ maxSize: 0x10,
+ compressionMethod: CompressionMethod::STORE,
+ lastModificationDateTime: new DateTimeImmutable('2022-01-01 01:01:01Z'),
+ );
+
+ $zip->finish();
+
+ $tmpDir = $this->validateAndExtractZip($this->tempfile);
+ $files = $this->getRecursiveFileList($tmpDir);
+
+ $this->assertSame(['sample.json'], $files);
+ $this->assertFileDoesNotContain($this->tempfile, PackField::pack(
+ new PackField(format: 'V', value: 0x06064b50)
+ ));
+ }
+
+ /**
+ * @group slow
+ */
+ public function testAddLargeFileWithoutZip64WithoutZeroHeader(): void
+ {
+ $this->expectException(OverflowException::class);
+
+ $zip = new ZipStream(
+ outputStream: $this->tempfileStream,
+ sendHttpHeaders: false,
+ enableZip64: false,
+ defaultEnableZeroHeader: false,
+ );
+
+ $zip->addFileFromPsr7Stream(
+ fileName: 'sample.json',
+ stream: new EndlessCycleStream('0'),
+ maxSize: 0x100000000,
+ compressionMethod: CompressionMethod::STORE,
+ lastModificationDateTime: new DateTimeImmutable('2022-01-01 01:01:01Z'),
+ );
+ }
+
+ public function testAddFileFromPsr7StreamWithOutputToPsr7Stream(): void
+ {
+ $psr7OutputStream = new ResourceStream($this->tempfileStream);
+
+ $zip = new ZipStream(
+ outputStream: $psr7OutputStream,
+ sendHttpHeaders: false,
+ );
+
+ $body = 'Sample String Data';
+ $response = new Response(200, [], $body);
+
+ $zip->addFileFromPsr7Stream(
+ fileName: 'sample.json',
+ stream: $response->getBody(),
+ compressionMethod: CompressionMethod::STORE,
+ );
+ $zip->finish();
+ $psr7OutputStream->close();
+
+ $tmpDir = $this->validateAndExtractZip($this->tempfile);
+ $files = $this->getRecursiveFileList($tmpDir);
+
+ $this->assertSame(['sample.json'], $files);
+ $this->assertStringEqualsFile($tmpDir . '/sample.json', $body);
+ }
+
+ public function testAddFileFromPsr7StreamWithFileSizeSet(): void
+ {
+ $zip = new ZipStream(
+ outputStream: $this->tempfileStream,
+ sendHttpHeaders: false,
+ );
+
+ $body = 'Sample String Data';
+ $fileSize = strlen($body);
+ // Add fake padding
+ $fakePadding = "\0\0\0\0\0\0";
+ $response = new Response(200, [], $body . $fakePadding);
+
+ $zip->addFileFromPsr7Stream(
+ fileName: 'sample.json',
+ stream: $response->getBody(),
+ compressionMethod: CompressionMethod::STORE,
+ maxSize: $fileSize
+ );
+ $zip->finish();
+
+ $tmpDir = $this->validateAndExtractZip($this->tempfile);
+
+ $files = $this->getRecursiveFileList($tmpDir);
+ $this->assertSame(['sample.json'], $files);
+ $this->assertStringEqualsFile($tmpDir . '/sample.json', $body);
+ }
+
+ public function testCreateArchiveHeaders(): void
+ {
+ $headers = [];
+
+ $httpHeaderCallback = function (string $header) use (&$headers) {
+ $headers[] = $header;
+ };
+
+ $zip = new ZipStream(
+ outputStream: $this->tempfileStream,
+ sendHttpHeaders: true,
+ outputName: 'example.zip',
+ httpHeaderCallback: $httpHeaderCallback,
+ );
+
+ $zip->addFile(
+ fileName: 'sample.json',
+ data: 'foo',
+ );
+ $zip->finish();
+
+ $this->assertContains('Content-Type: application/x-zip', $headers);
+ $this->assertContains("Content-Disposition: attachment; filename*=UTF-8''example.zip", $headers);
+ $this->assertContains('Pragma: public', $headers);
+ $this->assertContains('Cache-Control: public, must-revalidate', $headers);
+ $this->assertContains('Content-Transfer-Encoding: binary', $headers);
+ }
+
+ public function testCreateArchiveWithFlushOptionSet(): void
+ {
+ $zip = new ZipStream(
+ outputStream: $this->tempfileStream,
+ flushOutput: true,
+ sendHttpHeaders: false,
+ );
+
+ $zip->addFile('sample.txt', 'Sample String Data');
+ $zip->addFile('test/sample.txt', 'More Simple Sample Data');
+
+ $zip->finish();
+
+ $tmpDir = $this->validateAndExtractZip($this->tempfile);
+
+ $files = $this->getRecursiveFileList($tmpDir);
+ $this->assertSame(['sample.txt', 'test' . DIRECTORY_SEPARATOR . 'sample.txt'], $files);
+
+ $this->assertStringEqualsFile($tmpDir . '/sample.txt', 'Sample String Data');
+ $this->assertStringEqualsFile($tmpDir . '/test/sample.txt', 'More Simple Sample Data');
+ }
+
+ public function testCreateArchiveWithOutputBufferingOffAndFlushOptionSet(): void
+ {
+ // WORKAROUND (1/2): remove phpunit's output buffer in order to run test without any buffering
+ ob_end_flush();
+ $this->assertSame(0, ob_get_level());
+
+ $zip = new ZipStream(
+ outputStream: $this->tempfileStream,
+ flushOutput: true,
+ sendHttpHeaders: false,
+ );
+
+ $zip->addFile('sample.txt', 'Sample String Data');
+
+ $zip->finish();
+
+ $tmpDir = $this->validateAndExtractZip($this->tempfile);
+ $this->assertStringEqualsFile($tmpDir . '/sample.txt', 'Sample String Data');
+
+ // WORKAROUND (2/2): add back output buffering so that PHPUnit doesn't complain that it is missing
+ ob_start();
+ }
+
+ public function testAddEmptyDirectory(): void
+ {
+ $zip = new ZipStream(
+ outputStream: $this->tempfileStream,
+ sendHttpHeaders: false,
+ );
+
+ $zip->addDirectory('foo');
+
+ $zip->finish();
+
+ $tmpDir = $this->validateAndExtractZip($this->tempfile);
+
+ $files = $this->getRecursiveFileList($tmpDir, includeDirectories: true);
+
+ $this->assertContains('foo', $files);
+
+ $this->assertFileExists($tmpDir . DIRECTORY_SEPARATOR . 'foo');
+ $this->assertDirectoryExists($tmpDir . DIRECTORY_SEPARATOR . 'foo');
+ }
+
+ public function testAddFileSimulate(): void
+ {
+ $create = function (OperationMode $operationMode): int {
+ $zip = new ZipStream(
+ sendHttpHeaders: false,
+ operationMode: $operationMode,
+ defaultEnableZeroHeader: true,
+ outputStream: $this->tempfileStream,
+ );
+
+ $zip->addFile('sample.txt', 'Sample String Data');
+ $zip->addFile('test/sample.txt', 'More Simple Sample Data');
+
+ return $zip->finish();
+ };
+
+
+ $sizeExpected = $create(OperationMode::NORMAL);
+ $sizeActual = $create(OperationMode::SIMULATE_LAX);
+
+ $this->assertEquals($sizeExpected, $sizeActual);
+ }
+
+ public function testAddFileSimulateWithMaxSize(): void
+ {
+ $create = function (OperationMode $operationMode): int {
+ $zip = new ZipStream(
+ sendHttpHeaders: false,
+ operationMode: $operationMode,
+ defaultCompressionMethod: CompressionMethod::STORE,
+ defaultEnableZeroHeader: true,
+ outputStream: $this->tempfileStream,
+ );
+
+ $zip->addFile('sample.txt', 'Sample String Data', maxSize: 0);
+
+ return $zip->finish();
+ };
+
+
+ $sizeExpected = $create(OperationMode::NORMAL);
+ $sizeActual = $create(OperationMode::SIMULATE_LAX);
+
+ $this->assertEquals($sizeExpected, $sizeActual);
+ }
+
+ public function testAddFileSimulateWithFstat(): void
+ {
+ $create = function (OperationMode $operationMode): int {
+ $zip = new ZipStream(
+ sendHttpHeaders: false,
+ operationMode: $operationMode,
+ defaultCompressionMethod: CompressionMethod::STORE,
+ defaultEnableZeroHeader: true,
+ outputStream: $this->tempfileStream,
+ );
+
+ $zip->addFile('sample.txt', 'Sample String Data');
+ $zip->addFile('test/sample.txt', 'More Simple Sample Data');
+
+ return $zip->finish();
+ };
+
+
+ $sizeExpected = $create(OperationMode::NORMAL);
+ $sizeActual = $create(OperationMode::SIMULATE_LAX);
+
+ $this->assertEquals($sizeExpected, $sizeActual);
+ }
+
+ public function testAddFileSimulateWithExactSizeZero(): void
+ {
+ $create = function (OperationMode $operationMode): int {
+ $zip = new ZipStream(
+ sendHttpHeaders: false,
+ operationMode: $operationMode,
+ defaultCompressionMethod: CompressionMethod::STORE,
+ defaultEnableZeroHeader: true,
+ outputStream: $this->tempfileStream,
+ );
+
+ $zip->addFile('sample.txt', 'Sample String Data', exactSize: 18);
+
+ return $zip->finish();
+ };
+
+
+ $sizeExpected = $create(OperationMode::NORMAL);
+ $sizeActual = $create(OperationMode::SIMULATE_LAX);
+
+ $this->assertEquals($sizeExpected, $sizeActual);
+ }
+
+ public function testAddFileSimulateWithExactSizeInitial(): void
+ {
+ $create = function (OperationMode $operationMode): int {
+ $zip = new ZipStream(
+ sendHttpHeaders: false,
+ operationMode: $operationMode,
+ defaultCompressionMethod: CompressionMethod::STORE,
+ defaultEnableZeroHeader: false,
+ outputStream: $this->tempfileStream,
+ );
+
+ $zip->addFile('sample.txt', 'Sample String Data', exactSize: 18);
+
+ return $zip->finish();
+ };
+
+ $sizeExpected = $create(OperationMode::NORMAL);
+ $sizeActual = $create(OperationMode::SIMULATE_LAX);
+
+ $this->assertEquals($sizeExpected, $sizeActual);
+ }
+
+ public function testAddFileSimulateWithZeroSizeInFstat(): void
+ {
+ $create = function (OperationMode $operationMode): int {
+ $zip = new ZipStream(
+ sendHttpHeaders: false,
+ operationMode: $operationMode,
+ defaultCompressionMethod: CompressionMethod::STORE,
+ defaultEnableZeroHeader: false,
+ outputStream: $this->tempfileStream,
+ );
+
+ $zip->addFileFromPsr7Stream('sample.txt', new class implements StreamInterface {
+ public $pos = 0;
+
+ public function __toString(): string
+ {
+ return 'test';
+ }
+
+ public function close(): void {}
+
+ public function detach() {}
+
+ public function getSize(): ?int
+ {
+ return null;
+ }
+
+ public function tell(): int
+ {
+ return $this->pos;
+ }
+
+ public function eof(): bool
+ {
+ return $this->pos >= 4;
+ }
+
+ public function isSeekable(): bool
+ {
+ return true;
+ }
+
+ public function seek(int $offset, int $whence = SEEK_SET): void
+ {
+ $this->pos = $offset;
+ }
+
+ public function rewind(): void
+ {
+ $this->pos = 0;
+ }
+
+ public function isWritable(): bool
+ {
+ return false;
+ }
+
+ public function write(string $string): int
+ {
+ return 0;
+ }
+
+ public function isReadable(): bool
+ {
+ return true;
+ }
+
+ public function read(int $length): string
+ {
+ $data = substr('test', $this->pos, $length);
+ $this->pos += strlen($data);
+ return $data;
+ }
+
+ public function getContents(): string
+ {
+ return $this->read(4);
+ }
+
+ public function getMetadata(?string $key = null)
+ {
+ return $key !== null ? null : [];
+ }
+ });
+
+ return $zip->finish();
+ };
+
+ $sizeExpected = $create(OperationMode::NORMAL);
+ $sizeActual = $create(OperationMode::SIMULATE_LAX);
+
+
+ $this->assertEquals($sizeExpected, $sizeActual);
+ }
+
+ public function testAddFileSimulateWithWrongExactSize(): void
+ {
+ $this->expectException(FileSizeIncorrectException::class);
+
+ $zip = new ZipStream(
+ sendHttpHeaders: false,
+ operationMode: OperationMode::SIMULATE_LAX,
+ );
+
+ $zip->addFile('sample.txt', 'Sample String Data', exactSize: 1000);
+ }
+
+ public function testAddFileSimulateStrictZero(): void
+ {
+ $this->expectException(SimulationFileUnknownException::class);
+
+ $zip = new ZipStream(
+ sendHttpHeaders: false,
+ operationMode: OperationMode::SIMULATE_STRICT,
+ defaultEnableZeroHeader: true
+ );
+
+ $zip->addFile('sample.txt', 'Sample String Data');
+ }
+
+ public function testAddFileSimulateStrictInitial(): void
+ {
+ $this->expectException(SimulationFileUnknownException::class);
+
+ $zip = new ZipStream(
+ sendHttpHeaders: false,
+ operationMode: OperationMode::SIMULATE_STRICT,
+ defaultEnableZeroHeader: false
+ );
+
+ $zip->addFile('sample.txt', 'Sample String Data');
+ }
+
+ public function testAddFileCallbackStrict(): void
+ {
+ $this->expectException(SimulationFileUnknownException::class);
+
+ $zip = new ZipStream(
+ sendHttpHeaders: false,
+ operationMode: OperationMode::SIMULATE_STRICT,
+ defaultEnableZeroHeader: false
+ );
+
+ $zip->addFileFromCallback('sample.txt', callback: function () {
+ return '';
+ });
+ }
+
+ public function testAddFileCallbackLax(): void
+ {
+ $zip = new ZipStream(
+ operationMode: OperationMode::SIMULATE_LAX,
+ defaultEnableZeroHeader: false,
+ sendHttpHeaders: false,
+ );
+
+ $zip->addFileFromCallback('sample.txt', callback: function () {
+ return 'Sample String Data';
+ });
+
+ $size = $zip->finish();
+
+ $this->assertEquals($size, 142);
+ }
+
+ public function testExecuteSimulation(): void
+ {
+ $zip = new ZipStream(
+ operationMode: OperationMode::SIMULATE_STRICT,
+ defaultCompressionMethod: CompressionMethod::STORE,
+ defaultEnableZeroHeader: false,
+ sendHttpHeaders: false,
+ outputStream: $this->tempfileStream,
+ );
+
+ $zip->addFileFromCallback(
+ 'sample.txt',
+ exactSize: 18,
+ callback: function () {
+ return 'Sample String Data';
+ }
+ );
+
+ $zip->addFileFromCallback(
+ '.gitkeep',
+ exactSize: 0,
+ callback: function () {
+ return '';
+ }
+ );
+
+ $size = $zip->finish();
+
+ $this->assertEquals(filesize($this->tempfile), 0);
+
+ $zip->executeSimulation();
+
+ clearstatcache();
+
+ $this->assertEquals(filesize($this->tempfile), $size);
+
+ $tmpDir = $this->validateAndExtractZip($this->tempfile);
+
+ $files = $this->getRecursiveFileList($tmpDir);
+ $this->assertSame(['.gitkeep', 'sample.txt'], $files);
+ }
+
+ public function testExecuteSimulationBeforeFinish(): void
+ {
+ $this->expectException(RuntimeException::class);
+
+ $zip = new ZipStream(
+ operationMode: OperationMode::SIMULATE_LAX,
+ defaultEnableZeroHeader: false,
+ sendHttpHeaders: false,
+ outputStream: $this->tempfileStream,
+ );
+
+ $zip->executeSimulation();
+ }
+
+ private function addLargeFileFileFromPath(CompressionMethod $compressionMethod, $zeroHeader, $zip64): void
+ {
+ [$tmp, $stream] = $this->getTmpFileStream();
+
+ $zip = new ZipStream(
+ outputStream: $stream,
+ sendHttpHeaders: false,
+ defaultEnableZeroHeader: $zeroHeader,
+ enableZip64: $zip64,
+ );
+
+ [$tmpExample, $streamExample] = $this->getTmpFileStream();
+ for ($i = 0; $i <= 10000; $i++) {
+ fwrite($streamExample, sha1((string) $i));
+ if ($i % 100 === 0) {
+ fwrite($streamExample, "\n");
+ }
+ }
+ fclose($streamExample);
+ $shaExample = sha1_file($tmpExample);
+ $zip->addFileFromPath('sample.txt', $tmpExample);
+ unlink($tmpExample);
+
+ $zip->finish();
+ fclose($stream);
+
+ $tmpDir = $this->validateAndExtractZip($tmp);
+
+ $files = $this->getRecursiveFileList($tmpDir);
+ $this->assertSame(['sample.txt'], $files);
+
+ $this->assertSame(sha1_file($tmpDir . '/sample.txt'), $shaExample, "SHA-1 Mismatch Method: {$compressionMethod->value}");
+
+ unlink($tmp);
+ }
+}