aboutsummaryrefslogtreecommitdiffstats
path: root/vendor/maennchen/zipstream-php/test
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/maennchen/zipstream-php/test')
-rw-r--r--vendor/maennchen/zipstream-php/test/Assertions.php49
-rw-r--r--vendor/maennchen/zipstream-php/test/CentralDirectoryFileHeaderTest.php60
-rw-r--r--vendor/maennchen/zipstream-php/test/DataDescriptorTest.php26
-rw-r--r--vendor/maennchen/zipstream-php/test/EndOfCentralDirectoryTest.php35
-rw-r--r--vendor/maennchen/zipstream-php/test/EndlessCycleStream.php104
-rw-r--r--vendor/maennchen/zipstream-php/test/FaultInjectionResource.php141
-rw-r--r--vendor/maennchen/zipstream-php/test/LocalFileHeaderTest.php47
-rw-r--r--vendor/maennchen/zipstream-php/test/PackFieldTest.php42
-rw-r--r--vendor/maennchen/zipstream-php/test/ResourceStream.php159
-rw-r--r--vendor/maennchen/zipstream-php/test/Tempfile.php42
-rw-r--r--vendor/maennchen/zipstream-php/test/TimeTest.php44
-rw-r--r--vendor/maennchen/zipstream-php/test/Util.php127
-rw-r--r--vendor/maennchen/zipstream-php/test/Zip64/DataDescriptorTest.php28
-rw-r--r--vendor/maennchen/zipstream-php/test/Zip64/EndOfCentralDirectoryLocatorTest.php28
-rw-r--r--vendor/maennchen/zipstream-php/test/Zip64/EndOfCentralDirectoryTest.php41
-rw-r--r--vendor/maennchen/zipstream-php/test/Zip64/ExtendedInformationExtraFieldTest.php42
-rw-r--r--vendor/maennchen/zipstream-php/test/ZipStreamTest.php1195
-rw-r--r--vendor/maennchen/zipstream-php/test/Zs/ExtendedInformationExtraFieldTest.php22
-rw-r--r--vendor/maennchen/zipstream-php/test/bootstrap.php7
19 files changed, 2239 insertions, 0 deletions
diff --git a/vendor/maennchen/zipstream-php/test/Assertions.php b/vendor/maennchen/zipstream-php/test/Assertions.php
new file mode 100644
index 000000000..8d7670eff
--- /dev/null
+++ b/vendor/maennchen/zipstream-php/test/Assertions.php
@@ -0,0 +1,49 @@
+<?php
+
+declare(strict_types=1);
+
+namespace ZipStream\Test;
+
+trait Assertions
+{
+ protected function assertFileContains(string $filePath, string $needle): void
+ {
+ $last = '';
+
+ $handle = fopen($filePath, 'r');
+ while (!feof($handle)) {
+ $line = fgets($handle, 1024);
+
+ if (str_contains($last . $line, $needle)) {
+ fclose($handle);
+ return;
+ }
+
+ $last = $line;
+ }
+
+ fclose($handle);
+
+ $this->fail("File {$filePath} must contain {$needle}");
+ }
+
+ protected function assertFileDoesNotContain(string $filePath, string $needle): void
+ {
+ $last = '';
+
+ $handle = fopen($filePath, 'r');
+ while (!feof($handle)) {
+ $line = fgets($handle, 1024);
+
+ if (str_contains($last . $line, $needle)) {
+ fclose($handle);
+
+ $this->fail("File {$filePath} must not contain {$needle}");
+ }
+
+ $last = $line;
+ }
+
+ fclose($handle);
+ }
+}
diff --git a/vendor/maennchen/zipstream-php/test/CentralDirectoryFileHeaderTest.php b/vendor/maennchen/zipstream-php/test/CentralDirectoryFileHeaderTest.php
new file mode 100644
index 000000000..5457b4f44
--- /dev/null
+++ b/vendor/maennchen/zipstream-php/test/CentralDirectoryFileHeaderTest.php
@@ -0,0 +1,60 @@
+<?php
+
+declare(strict_types=1);
+
+namespace ZipStream\Test;
+
+use DateTimeImmutable;
+use PHPUnit\Framework\TestCase;
+use ZipStream\CentralDirectoryFileHeader;
+use ZipStream\CompressionMethod;
+
+class CentralDirectoryFileHeaderTest extends TestCase
+{
+ public function testSerializesCorrectly(): void
+ {
+ $dateTime = new DateTimeImmutable('2022-01-01 01:01:01Z');
+
+ $header = CentralDirectoryFileHeader::generate(
+ versionMadeBy: 0x603,
+ versionNeededToExtract: 0x002D,
+ generalPurposeBitFlag: 0x2222,
+ compressionMethod: CompressionMethod::DEFLATE,
+ lastModificationDateTime: $dateTime,
+ crc32: 0x11111111,
+ compressedSize: 0x77777777,
+ uncompressedSize: 0x99999999,
+ fileName: 'test.png',
+ extraField: 'some content',
+ fileComment: 'some comment',
+ diskNumberStart: 0,
+ internalFileAttributes: 0,
+ externalFileAttributes: 32,
+ relativeOffsetOfLocalHeader: 0x1234,
+ );
+
+ $this->assertSame(
+ bin2hex($header),
+ '504b0102' . // 4 bytes; central file header signature
+ '0306' . // 2 bytes; version made by
+ '2d00' . // 2 bytes; version needed to extract
+ '2222' . // 2 bytes; general purpose bit flag
+ '0800' . // 2 bytes; compression method
+ '2008' . // 2 bytes; last mod file time
+ '2154' . // 2 bytes; last mod file date
+ '11111111' . // 4 bytes; crc-32
+ '77777777' . // 4 bytes; compressed size
+ '99999999' . // 4 bytes; uncompressed size
+ '0800' . // 2 bytes; file name length (n)
+ '0c00' . // 2 bytes; extra field length (m)
+ '0c00' . // 2 bytes; file comment length (o)
+ '0000' . // 2 bytes; disk number start
+ '0000' . // 2 bytes; internal file attributes
+ '20000000' . // 4 bytes; external file attributes
+ '34120000' . // 4 bytes; relative offset of local header
+ '746573742e706e67' . // n bytes; file name
+ '736f6d6520636f6e74656e74' . // m bytes; extra field
+ '736f6d6520636f6d6d656e74' // o bytes; file comment
+ );
+ }
+}
diff --git a/vendor/maennchen/zipstream-php/test/DataDescriptorTest.php b/vendor/maennchen/zipstream-php/test/DataDescriptorTest.php
new file mode 100644
index 000000000..cc886c74b
--- /dev/null
+++ b/vendor/maennchen/zipstream-php/test/DataDescriptorTest.php
@@ -0,0 +1,26 @@
+<?php
+
+declare(strict_types=1);
+
+namespace ZipStream\Test;
+
+use PHPUnit\Framework\TestCase;
+use ZipStream\DataDescriptor;
+
+class DataDescriptorTest extends TestCase
+{
+ public function testSerializesCorrectly(): void
+ {
+ $this->assertSame(
+ bin2hex(DataDescriptor::generate(
+ crc32UncompressedData: 0x11111111,
+ compressedSize: 0x77777777,
+ uncompressedSize: 0x99999999,
+ )),
+ '504b0708' . // 4 bytes; Optional data descriptor signature = 0x08074b50
+ '11111111' . // 4 bytes; CRC-32 of uncompressed data
+ '77777777' . // 4 bytes; Compressed size
+ '99999999' // 4 bytes; Uncompressed size
+ );
+ }
+}
diff --git a/vendor/maennchen/zipstream-php/test/EndOfCentralDirectoryTest.php b/vendor/maennchen/zipstream-php/test/EndOfCentralDirectoryTest.php
new file mode 100644
index 000000000..be0a90743
--- /dev/null
+++ b/vendor/maennchen/zipstream-php/test/EndOfCentralDirectoryTest.php
@@ -0,0 +1,35 @@
+<?php
+
+declare(strict_types=1);
+
+namespace ZipStream\Test;
+
+use PHPUnit\Framework\TestCase;
+use ZipStream\EndOfCentralDirectory;
+
+class EndOfCentralDirectoryTest extends TestCase
+{
+ public function testSerializesCorrectly(): void
+ {
+ $this->assertSame(
+ bin2hex(EndOfCentralDirectory::generate(
+ numberOfThisDisk: 0x00,
+ numberOfTheDiskWithCentralDirectoryStart: 0x00,
+ numberOfCentralDirectoryEntriesOnThisDisk: 0x10,
+ numberOfCentralDirectoryEntries: 0x10,
+ sizeOfCentralDirectory: 0x22,
+ centralDirectoryStartOffsetOnDisk: 0x33,
+ zipFileComment: 'foo',
+ )),
+ '504b0506' . // 4 bytes; end of central dir signature 0x06054b50
+ '0000' . // 2 bytes; number of this disk
+ '0000' . // 2 bytes; number of the disk with the start of the central directory
+ '1000' . // 2 bytes; total number of entries in the central directory on this disk
+ '1000' . // 2 bytes; total number of entries in the central directory
+ '22000000' . // 4 bytes; size of the central directory
+ '33000000' . // 4 bytes; offset of start of central directory with respect to the starting disk number
+ '0300' . // 2 bytes; .ZIP file comment length
+ bin2hex('foo')
+ );
+ }
+}
diff --git a/vendor/maennchen/zipstream-php/test/EndlessCycleStream.php b/vendor/maennchen/zipstream-php/test/EndlessCycleStream.php
new file mode 100644
index 000000000..d9e7df1fb
--- /dev/null
+++ b/vendor/maennchen/zipstream-php/test/EndlessCycleStream.php
@@ -0,0 +1,104 @@
+<?php
+
+declare(strict_types=1);
+
+namespace ZipStream\Test;
+
+use Psr\Http\Message\StreamInterface;
+use RuntimeException;
+
+class EndlessCycleStream implements StreamInterface
+{
+ private int $offset = 0;
+
+ public function __construct(private readonly string $toRepeat = '0') {}
+
+ public function __toString(): string
+ {
+ throw new RuntimeException('Infinite Stream!');
+ }
+
+ public function close(): void
+ {
+ $this->detach();
+ }
+
+ /**
+ * @return null
+ */
+ public function detach()
+ {
+ return;
+ }
+
+ public function getSize(): ?int
+ {
+ return null;
+ }
+
+ public function tell(): int
+ {
+ return $this->offset;
+ }
+
+ public function eof(): bool
+ {
+ return false;
+ }
+
+ public function isSeekable(): bool
+ {
+ return true;
+ }
+
+ public function seek(int $offset, int $whence = SEEK_SET): void
+ {
+ switch ($whence) {
+ case SEEK_SET:
+ $this->offset = $offset;
+ break;
+ case SEEK_CUR:
+ $this->offset += $offset;
+ break;
+ case SEEK_END:
+ throw new RuntimeException('Infinite Stream!');
+ break;
+ }
+ }
+
+ public function rewind(): void
+ {
+ $this->seek(0);
+ }
+
+ public function isWritable(): bool
+ {
+ return false;
+ }
+
+ public function write(string $string): int
+ {
+ throw new RuntimeException('Not writeable');
+ }
+
+ public function isReadable(): bool
+ {
+ return true;
+ }
+
+ public function read(int $length): string
+ {
+ $this->offset += $length;
+ return substr(str_repeat($this->toRepeat, (int) ceil($length / strlen($this->toRepeat))), 0, $length);
+ }
+
+ public function getContents(): string
+ {
+ throw new RuntimeException('Infinite Stream!');
+ }
+
+ public function getMetadata(?string $key = null): array|null
+ {
+ return $key !== null ? null : [];
+ }
+}
diff --git a/vendor/maennchen/zipstream-php/test/FaultInjectionResource.php b/vendor/maennchen/zipstream-php/test/FaultInjectionResource.php
new file mode 100644
index 000000000..3d4440e8a
--- /dev/null
+++ b/vendor/maennchen/zipstream-php/test/FaultInjectionResource.php
@@ -0,0 +1,141 @@
+<?php
+
+declare(strict_types=1);
+
+namespace ZipStream\Test;
+
+class FaultInjectionResource
+{
+ public const NAME = 'zipstream-php-test-broken-resource';
+
+ /** @var resource */
+ public $context;
+
+ private array $injectFaults;
+
+ private string $mode;
+
+ /**
+ * @return resource
+ */
+ public static function getResource(array $injectFaults)
+ {
+ self::register();
+
+ return fopen(self::NAME . '://foobar', 'rw+', false, self::createStreamContext($injectFaults));
+ }
+
+ public function stream_open(string $path, string $mode, int $options, string &$opened_path = null): bool
+ {
+ $options = stream_context_get_options($this->context);
+
+ if (!isset($options[self::NAME]['injectFaults'])) {
+ return false;
+ }
+
+ $this->mode = $mode;
+ $this->injectFaults = $options[self::NAME]['injectFaults'];
+
+ if ($this->shouldFail(__FUNCTION__)) {
+ return false;
+ }
+
+ return true;
+ }
+
+ public function stream_write(string $data)
+ {
+ if ($this->shouldFail(__FUNCTION__)) {
+ return false;
+ }
+ return true;
+ }
+
+ public function stream_eof()
+ {
+ return true;
+ }
+
+ public function stream_seek(int $offset, int $whence): bool
+ {
+ if ($this->shouldFail(__FUNCTION__)) {
+ return false;
+ }
+
+ return true;
+ }
+
+ public function stream_tell(): int
+ {
+ if ($this->shouldFail(__FUNCTION__)) {
+ return false;
+ }
+
+ return 0;
+ }
+
+ public static function register(): void
+ {
+ if (!in_array(self::NAME, stream_get_wrappers(), true)) {
+ stream_wrapper_register(self::NAME, __CLASS__);
+ }
+ }
+
+ public function stream_stat(): array
+ {
+ static $modeMap = [
+ 'r' => 33060,
+ 'rb' => 33060,
+ 'r+' => 33206,
+ 'w' => 33188,
+ 'wb' => 33188,
+ ];
+
+ return [
+ 'dev' => 0,
+ 'ino' => 0,
+ 'mode' => $modeMap[$this->mode],
+ 'nlink' => 0,
+ 'uid' => 0,
+ 'gid' => 0,
+ 'rdev' => 0,
+ 'size' => 0,
+ 'atime' => 0,
+ 'mtime' => 0,
+ 'ctime' => 0,
+ 'blksize' => 0,
+ 'blocks' => 0,
+ ];
+ }
+
+ public function url_stat(string $path, int $flags): array
+ {
+ return [
+ 'dev' => 0,
+ 'ino' => 0,
+ 'mode' => 0,
+ 'nlink' => 0,
+ 'uid' => 0,
+ 'gid' => 0,
+ 'rdev' => 0,
+ 'size' => 0,
+ 'atime' => 0,
+ 'mtime' => 0,
+ 'ctime' => 0,
+ 'blksize' => 0,
+ 'blocks' => 0,
+ ];
+ }
+
+ private static function createStreamContext(array $injectFaults)
+ {
+ return stream_context_create([
+ self::NAME => ['injectFaults' => $injectFaults],
+ ]);
+ }
+
+ private function shouldFail(string $function): bool
+ {
+ return in_array($function, $this->injectFaults, true);
+ }
+}
diff --git a/vendor/maennchen/zipstream-php/test/LocalFileHeaderTest.php b/vendor/maennchen/zipstream-php/test/LocalFileHeaderTest.php
new file mode 100644
index 000000000..196dd0fe3
--- /dev/null
+++ b/vendor/maennchen/zipstream-php/test/LocalFileHeaderTest.php
@@ -0,0 +1,47 @@
+<?php
+
+declare(strict_types=1);
+
+namespace ZipStream\Test;
+
+use DateTimeImmutable;
+use PHPUnit\Framework\TestCase;
+use ZipStream\CompressionMethod;
+use ZipStream\LocalFileHeader;
+
+class LocalFileHeaderTest extends TestCase
+{
+ public function testSerializesCorrectly(): void
+ {
+ $dateTime = new DateTimeImmutable('2022-01-01 01:01:01Z');
+
+ $header = LocalFileHeader::generate(
+ versionNeededToExtract: 0x002D,
+ generalPurposeBitFlag: 0x2222,
+ compressionMethod: CompressionMethod::DEFLATE,
+ lastModificationDateTime: $dateTime,
+ crc32UncompressedData: 0x11111111,
+ compressedSize: 0x77777777,
+ uncompressedSize: 0x99999999,
+ fileName: 'test.png',
+ extraField: 'some content'
+ );
+
+ $this->assertSame(
+ bin2hex((string) $header),
+ '504b0304' . // 4 bytes; Local file header signature
+ '2d00' . // 2 bytes; Version needed to extract (minimum)
+ '2222' . // 2 bytes; General purpose bit flag
+ '0800' . // 2 bytes; Compression method; e.g. none = 0, DEFLATE = 8
+ '2008' . // 2 bytes; File last modification time
+ '2154' . // 2 bytes; File last modification date
+ '11111111' . // 4 bytes; CRC-32 of uncompressed data
+ '77777777' . // 4 bytes; Compressed size (or 0xffffffff for ZIP64)
+ '99999999' . // 4 bytes; Uncompressed size (or 0xffffffff for ZIP64)
+ '0800' . // 2 bytes; File name length (n)
+ '0c00' . // 2 bytes; Extra field length (m)
+ '746573742e706e67' . // n bytes; File name
+ '736f6d6520636f6e74656e74' // m bytes; Extra field
+ );
+ }
+}
diff --git a/vendor/maennchen/zipstream-php/test/PackFieldTest.php b/vendor/maennchen/zipstream-php/test/PackFieldTest.php
new file mode 100644
index 000000000..ecd66bac7
--- /dev/null
+++ b/vendor/maennchen/zipstream-php/test/PackFieldTest.php
@@ -0,0 +1,42 @@
+<?php
+
+declare(strict_types=1);
+
+namespace ZipStream\Test;
+
+use PHPUnit\Framework\TestCase;
+use RuntimeException;
+use ZipStream\PackField;
+
+class PackFieldTest extends TestCase
+{
+ public function testPacksFields(): void
+ {
+ $this->assertSame(
+ bin2hex(PackField::pack(new PackField(format: 'v', value: 0x1122))),
+ '2211',
+ );
+ }
+
+ public function testOverflow2(): void
+ {
+ $this->expectException(RuntimeException::class);
+
+ PackField::pack(new PackField(format: 'v', value: 0xFFFFF));
+ }
+
+ public function testOverflow4(): void
+ {
+ $this->expectException(RuntimeException::class);
+
+ PackField::pack(new PackField(format: 'V', value: 0xFFFFFFFFF));
+ }
+
+ public function testUnknownOperator(): void
+ {
+ $this->assertSame(
+ bin2hex(PackField::pack(new PackField(format: 'a', value: 0x1122))),
+ '34',
+ );
+ }
+}
diff --git a/vendor/maennchen/zipstream-php/test/ResourceStream.php b/vendor/maennchen/zipstream-php/test/ResourceStream.php
new file mode 100644
index 000000000..752a1a357
--- /dev/null
+++ b/vendor/maennchen/zipstream-php/test/ResourceStream.php
@@ -0,0 +1,159 @@
+<?php
+
+declare(strict_types=1);
+
+namespace ZipStream\Test;
+
+use Psr\Http\Message\StreamInterface;
+use RuntimeException;
+
+/**
+ * @internal
+ */
+class ResourceStream implements StreamInterface
+{
+ public function __construct(
+ /**
+ * @var resource
+ */
+ private $stream
+ ) {}
+
+ public function __toString(): string
+ {
+ if ($this->isSeekable()) {
+ $this->seek(0);
+ }
+ return (string) stream_get_contents($this->stream);
+ }
+
+ public function close(): void
+ {
+ $stream = $this->detach();
+ if ($stream) {
+ fclose($stream);
+ }
+ }
+
+ public function detach()
+ {
+ $result = $this->stream;
+ // According to the interface, the stream is left in an unusable state;
+ /** @psalm-suppress PossiblyNullPropertyAssignmentValue */
+ $this->stream = null;
+ return $result;
+ }
+
+ public function seek(int $offset, int $whence = SEEK_SET): void
+ {
+ if (!$this->isSeekable()) {
+ throw new RuntimeException();
+ }
+ if (fseek($this->stream, $offset, $whence) !== 0) {
+ // @codeCoverageIgnoreStart
+ throw new RuntimeException();
+ // @codeCoverageIgnoreEnd
+ }
+ }
+
+ public function isSeekable(): bool
+ {
+ return (bool) $this->getMetadata('seekable');
+ }
+
+ public function getMetadata(?string $key = null)
+ {
+ $metadata = stream_get_meta_data($this->stream);
+ return $key !== null ? @$metadata[$key] : $metadata;
+ }
+
+ public function getSize(): ?int
+ {
+ $stats = fstat($this->stream);
+ return $stats['size'];
+ }
+
+ public function tell(): int
+ {
+ $position = ftell($this->stream);
+ if ($position === false) {
+ // @codeCoverageIgnoreStart
+ throw new RuntimeException();
+ // @codeCoverageIgnoreEnd
+ }
+ return $position;
+ }
+
+ public function eof(): bool
+ {
+ return feof($this->stream);
+ }
+
+ public function rewind(): void
+ {
+ $this->seek(0);
+ }
+
+ public function write(string $string): int
+ {
+ if (!$this->isWritable()) {
+ throw new RuntimeException();
+ }
+ if (fwrite($this->stream, $string) === false) {
+ // @codeCoverageIgnoreStart
+ throw new RuntimeException();
+ // @codeCoverageIgnoreEnd
+ }
+ return strlen($string);
+ }
+
+ public function isWritable(): bool
+ {
+ $mode = $this->getMetadata('mode');
+ if (!is_string($mode)) {
+ // @codeCoverageIgnoreStart
+ throw new RuntimeException('Could not get stream mode from metadata!');
+ // @codeCoverageIgnoreEnd
+ }
+ return preg_match('/[waxc+]/', $mode) === 1;
+ }
+
+ public function read(int $length): string
+ {
+ if (!$this->isReadable()) {
+ throw new RuntimeException();
+ }
+ $result = fread($this->stream, $length);
+ if ($result === false) {
+ // @codeCoverageIgnoreStart
+ throw new RuntimeException();
+ // @codeCoverageIgnoreEnd
+ }
+ return $result;
+ }
+
+ public function isReadable(): bool
+ {
+ $mode = $this->getMetadata('mode');
+ if (!is_string($mode)) {
+ // @codeCoverageIgnoreStart
+ throw new RuntimeException('Could not get stream mode from metadata!');
+ // @codeCoverageIgnoreEnd
+ }
+ return preg_match('/[r+]/', $mode) === 1;
+ }
+
+ public function getContents(): string
+ {
+ if (!$this->isReadable()) {
+ throw new RuntimeException();
+ }
+ $result = stream_get_contents($this->stream);
+ if ($result === false) {
+ // @codeCoverageIgnoreStart
+ throw new RuntimeException();
+ // @codeCoverageIgnoreEnd
+ }
+ return $result;
+ }
+}
diff --git a/vendor/maennchen/zipstream-php/test/Tempfile.php b/vendor/maennchen/zipstream-php/test/Tempfile.php
new file mode 100644
index 000000000..7ef9c61f9
--- /dev/null
+++ b/vendor/maennchen/zipstream-php/test/Tempfile.php
@@ -0,0 +1,42 @@
+<?php
+
+declare(strict_types=1);
+
+namespace ZipStream\Test;
+
+trait Tempfile
+{
+ protected string|null $tempfile;
+
+ /**
+ * @var resource
+ */
+ protected $tempfileStream;
+
+ protected function setUp(): void
+ {
+ [$tempfile, $tempfileStream] = $this->getTmpFileStream();
+
+ $this->tempfile = $tempfile;
+ $this->tempfileStream = $tempfileStream;
+ }
+
+ protected function tearDown(): void
+ {
+ unlink($this->tempfile);
+ if (is_resource($this->tempfileStream)) {
+ fclose($this->tempfileStream);
+ }
+
+ $this->tempfile = null;
+ $this->tempfileStream = null;
+ }
+
+ protected function getTmpFileStream(): array
+ {
+ $tmp = tempnam(sys_get_temp_dir(), 'zipstreamtest');
+ $stream = fopen($tmp, 'wb+');
+
+ return [$tmp, $stream];
+ }
+}
diff --git a/vendor/maennchen/zipstream-php/test/TimeTest.php b/vendor/maennchen/zipstream-php/test/TimeTest.php
new file mode 100644
index 000000000..61cfe0388
--- /dev/null
+++ b/vendor/maennchen/zipstream-php/test/TimeTest.php
@@ -0,0 +1,44 @@
+<?php
+
+declare(strict_types=1);
+
+namespace ZipStream\Test;
+
+use DateTimeImmutable;
+use PHPUnit\Framework\TestCase;
+use ZipStream\Exception\DosTimeOverflowException;
+use ZipStream\Time;
+
+class TimeTest extends TestCase
+{
+ public function testNormalDateToDosTime(): void
+ {
+ $this->assertSame(
+ Time::dateTimeToDosTime(new DateTimeImmutable('2014-11-17T17:46:08Z')),
+ 1165069764
+ );
+
+ // January 1 1980 - DOS Epoch.
+ $this->assertSame(
+ Time::dateTimeToDosTime(new DateTimeImmutable('1980-01-01T00:00:00+00:00')),
+ 2162688
+ );
+
+ // Local timezone different than UTC.
+ $prevLocalTimezone = date_default_timezone_get();
+ date_default_timezone_set('Europe/Berlin');
+ $this->assertSame(
+ Time::dateTimeToDosTime(new DateTimeImmutable('1980-01-01T00:00:00+00:00')),
+ 2162688
+ );
+ date_default_timezone_set($prevLocalTimezone);
+ }
+
+ public function testTooEarlyDateToDosTime(): void
+ {
+ $this->expectException(DosTimeOverflowException::class);
+
+ // January 1 1980 is the minimum DOS Epoch.
+ Time::dateTimeToDosTime(new DateTimeImmutable('1970-01-01T00:00:00+00:00'));
+ }
+}
diff --git a/vendor/maennchen/zipstream-php/test/Util.php b/vendor/maennchen/zipstream-php/test/Util.php
new file mode 100644
index 000000000..86592b429
--- /dev/null
+++ b/vendor/maennchen/zipstream-php/test/Util.php
@@ -0,0 +1,127 @@
+<?php
+
+declare(strict_types=1);
+
+namespace ZipStream\Test;
+
+use function fgets;
+use function pclose;
+use function popen;
+use function preg_match;
+
+use RecursiveDirectoryIterator;
+use RecursiveIteratorIterator;
+
+use function strtolower;
+
+use ZipArchive;
+
+trait Util
+{
+ protected function cmdExists(string $command): bool
+ {
+ if (strtolower(\substr(PHP_OS, 0, 3)) === 'win') {
+ $fp = popen("where $command", 'r');
+ $result = fgets($fp, 255);
+ $exists = !preg_match('#Could not find files#', $result);
+ pclose($fp);
+ } else { // non-Windows
+ $fp = popen("which $command", 'r');
+ $result = fgets($fp, 255);
+ $exists = !empty($result);
+ pclose($fp);
+ }
+
+ return $exists;
+ }
+
+ protected function dumpZipContents(string $path): string
+ {
+ if (!$this->cmdExists('hexdump')) {
+ return '';
+ }
+
+ $output = [];
+
+ if (!exec("hexdump -C \"$path\" | head -n 50", $output)) {
+ return '';
+ }
+
+ return "\nHexdump:\n" . implode("\n", $output);
+ }
+
+ protected function validateAndExtractZip(string $zipPath): string
+ {
+ $tmpDir = $this->getTmpDir();
+
+ $zipArchive = new ZipArchive();
+ $result = $zipArchive->open($zipPath);
+
+ if ($result !== true) {
+ $codeName = $this->zipArchiveOpenErrorCodeName($result);
+ $debugInformation = $this->dumpZipContents($zipPath);
+
+ $this->fail("Failed to open {$zipPath}. Code: $result ($codeName)$debugInformation");
+
+ return $tmpDir;
+ }
+
+ $this->assertSame(0, $zipArchive->status);
+ $this->assertSame(0, $zipArchive->statusSys);
+
+ $zipArchive->extractTo($tmpDir);
+ $zipArchive->close();
+
+ return $tmpDir;
+ }
+
+ protected function zipArchiveOpenErrorCodeName(int $code): string
+ {
+ switch ($code) {
+ case ZipArchive::ER_EXISTS: return 'ER_EXISTS';
+ case ZipArchive::ER_INCONS: return 'ER_INCONS';
+ case ZipArchive::ER_INVAL: return 'ER_INVAL';
+ case ZipArchive::ER_MEMORY: return 'ER_MEMORY';
+ case ZipArchive::ER_NOENT: return 'ER_NOENT';
+ case ZipArchive::ER_NOZIP: return 'ER_NOZIP';
+ case ZipArchive::ER_OPEN: return 'ER_OPEN';
+ case ZipArchive::ER_READ: return 'ER_READ';
+ case ZipArchive::ER_SEEK: return 'ER_SEEK';
+ default: return 'unknown';
+ }
+ }
+
+ protected function getTmpDir(): string
+ {
+ $tmp = tempnam(sys_get_temp_dir(), 'zipstreamtest');
+ unlink($tmp);
+ mkdir($tmp) or $this->fail('Failed to make directory');
+
+ return $tmp;
+ }
+
+ /**
+ * @return string[]
+ */
+ protected function getRecursiveFileList(string $path, bool $includeDirectories = false): array
+ {
+ $data = [];
+ $path = (string) realpath($path);
+ $files = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($path));
+
+ $pathLen = strlen($path);
+ foreach ($files as $file) {
+ $filePath = $file->getRealPath();
+
+ if (is_dir($filePath) && !$includeDirectories) {
+ continue;
+ }
+
+ $data[] = substr($filePath, $pathLen + 1);
+ }
+
+ sort($data);
+
+ return $data;
+ }
+}
diff --git a/vendor/maennchen/zipstream-php/test/Zip64/DataDescriptorTest.php b/vendor/maennchen/zipstream-php/test/Zip64/DataDescriptorTest.php
new file mode 100644
index 000000000..49fb2ccb2
--- /dev/null
+++ b/vendor/maennchen/zipstream-php/test/Zip64/DataDescriptorTest.php
@@ -0,0 +1,28 @@
+<?php
+
+declare(strict_types=1);
+
+namespace ZipStream\Test\Zip64;
+
+use PHPUnit\Framework\TestCase;
+use ZipStream\Zip64\DataDescriptor;
+
+class DataDescriptorTest extends TestCase
+{
+ public function testSerializesCorrectly(): void
+ {
+ $descriptor = DataDescriptor::generate(
+ crc32UncompressedData: 0x11111111,
+ compressedSize: (0x77777777 << 32) + 0x66666666,
+ uncompressedSize: (0x99999999 << 32) + 0x88888888,
+ );
+
+ $this->assertSame(
+ bin2hex($descriptor),
+ '504b0708' . // 4 bytes; Optional data descriptor signature = 0x08074b50
+ '11111111' . // 4 bytes; CRC-32 of uncompressed data
+ '6666666677777777' . // 8 bytes; Compressed size
+ '8888888899999999' // 8 bytes; Uncompressed size
+ );
+ }
+}
diff --git a/vendor/maennchen/zipstream-php/test/Zip64/EndOfCentralDirectoryLocatorTest.php b/vendor/maennchen/zipstream-php/test/Zip64/EndOfCentralDirectoryLocatorTest.php
new file mode 100644
index 000000000..271a29862
--- /dev/null
+++ b/vendor/maennchen/zipstream-php/test/Zip64/EndOfCentralDirectoryLocatorTest.php
@@ -0,0 +1,28 @@
+<?php
+
+declare(strict_types=1);
+
+namespace ZipStream\Test\Zip64;
+
+use PHPUnit\Framework\TestCase;
+use ZipStream\Zip64\EndOfCentralDirectoryLocator;
+
+class EndOfCentralDirectoryLocatorTest extends TestCase
+{
+ public function testSerializesCorrectly(): void
+ {
+ $descriptor = EndOfCentralDirectoryLocator::generate(
+ numberOfTheDiskWithZip64CentralDirectoryStart: 0x11111111,
+ zip64centralDirectoryStartOffsetOnDisk: (0x22222222 << 32) + 0x33333333,
+ totalNumberOfDisks: 0x44444444,
+ );
+
+ $this->assertSame(
+ bin2hex($descriptor),
+ '504b0607' . // 4 bytes; zip64 end of central dir locator signature - 0x07064b50
+ '11111111' . // 4 bytes; number of the disk with the start of the zip64 end of central directory
+ '3333333322222222' . // 28 bytes; relative offset of the zip64 end of central directory record
+ '44444444' // 4 bytes;total number of disks
+ );
+ }
+}
diff --git a/vendor/maennchen/zipstream-php/test/Zip64/EndOfCentralDirectoryTest.php b/vendor/maennchen/zipstream-php/test/Zip64/EndOfCentralDirectoryTest.php
new file mode 100644
index 000000000..b86fb1781
--- /dev/null
+++ b/vendor/maennchen/zipstream-php/test/Zip64/EndOfCentralDirectoryTest.php
@@ -0,0 +1,41 @@
+<?php
+
+declare(strict_types=1);
+
+namespace ZipStream\Test\Zip64;
+
+use PHPUnit\Framework\TestCase;
+use ZipStream\Zip64\EndOfCentralDirectory;
+
+class EndOfCentralDirectoryTest extends TestCase
+{
+ public function testSerializesCorrectly(): void
+ {
+ $descriptor = EndOfCentralDirectory::generate(
+ versionMadeBy: 0x3333,
+ versionNeededToExtract: 0x4444,
+ numberOfThisDisk: 0x55555555,
+ numberOfTheDiskWithCentralDirectoryStart: 0x66666666,
+ numberOfCentralDirectoryEntriesOnThisDisk: (0x77777777 << 32) + 0x88888888,
+ numberOfCentralDirectoryEntries: (0x99999999 << 32) + 0xAAAAAAAA,
+ sizeOfCentralDirectory: (0xBBBBBBBB << 32) + 0xCCCCCCCC,
+ centralDirectoryStartOffsetOnDisk: (0xDDDDDDDD << 32) + 0xEEEEEEEE,
+ extensibleDataSector: 'foo',
+ );
+
+ $this->assertSame(
+ bin2hex($descriptor),
+ '504b0606' . // 4 bytes;zip64 end of central dir signature - 0x06064b50
+ '2f00000000000000' . // 8 bytes; size of zip64 end of central directory record
+ '3333' . // 2 bytes; version made by
+ '4444' . // 2 bytes; version needed to extract
+ '55555555' . // 4 bytes; number of this disk
+ '66666666' . // 4 bytes; number of the disk with the start of the central directory
+ '8888888877777777' . // 8 bytes; total number of entries in the central directory on this disk
+ 'aaaaaaaa99999999' . // 8 bytes; total number of entries in the central directory
+ 'ccccccccbbbbbbbb' . // 8 bytes; size of the central directory
+ 'eeeeeeeedddddddd' . // 8 bytes; offset of start of central directory with respect to the starting disk number
+ bin2hex('foo')
+ );
+ }
+}
diff --git a/vendor/maennchen/zipstream-php/test/Zip64/ExtendedInformationExtraFieldTest.php b/vendor/maennchen/zipstream-php/test/Zip64/ExtendedInformationExtraFieldTest.php
new file mode 100644
index 000000000..904783d86
--- /dev/null
+++ b/vendor/maennchen/zipstream-php/test/Zip64/ExtendedInformationExtraFieldTest.php
@@ -0,0 +1,42 @@
+<?php
+
+declare(strict_types=1);
+
+namespace ZipStream\Test\Zip64;
+
+use PHPUnit\Framework\TestCase;
+use ZipStream\Zip64\ExtendedInformationExtraField;
+
+class ExtendedInformationExtraFieldTest extends TestCase
+{
+ public function testSerializesCorrectly(): void
+ {
+ $extraField = ExtendedInformationExtraField::generate(
+ originalSize: (0x77777777 << 32) + 0x66666666,
+ compressedSize: (0x99999999 << 32) + 0x88888888,
+ relativeHeaderOffset: (0x22222222 << 32) + 0x11111111,
+ diskStartNumber: 0x33333333,
+ );
+
+ $this->assertSame(
+ bin2hex($extraField),
+ '0100' . // 2 bytes; Tag for this "extra" block type
+ '1c00' . // 2 bytes; Size of this "extra" block
+ '6666666677777777' . // 8 bytes; Original uncompressed file size
+ '8888888899999999' . // 8 bytes; Size of compressed data
+ '1111111122222222' . // 8 bytes; Offset of local header record
+ '33333333' // 4 bytes; Number of the disk on which this file starts
+ );
+ }
+
+ public function testSerializesEmptyCorrectly(): void
+ {
+ $extraField = ExtendedInformationExtraField::generate();
+
+ $this->assertSame(
+ bin2hex($extraField),
+ '0100' . // 2 bytes; Tag for this "extra" block type
+ '0000' // 2 bytes; Size of this "extra" block
+ );
+ }
+}
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);
+ }
+}
diff --git a/vendor/maennchen/zipstream-php/test/Zs/ExtendedInformationExtraFieldTest.php b/vendor/maennchen/zipstream-php/test/Zs/ExtendedInformationExtraFieldTest.php
new file mode 100644
index 000000000..2b8dbed4a
--- /dev/null
+++ b/vendor/maennchen/zipstream-php/test/Zs/ExtendedInformationExtraFieldTest.php
@@ -0,0 +1,22 @@
+<?php
+
+declare(strict_types=1);
+
+namespace ZipStream\Test\Zs;
+
+use PHPUnit\Framework\TestCase;
+use ZipStream\Zs\ExtendedInformationExtraField;
+
+class ExtendedInformationExtraFieldTest extends TestCase
+{
+ public function testSerializesCorrectly(): void
+ {
+ $extraField = ExtendedInformationExtraField::generate();
+
+ $this->assertSame(
+ bin2hex((string) $extraField),
+ '5356' . // 2 bytes; Tag for this "extra" block type
+ '0000' // 2 bytes; TODO: Document
+ );
+ }
+}
diff --git a/vendor/maennchen/zipstream-php/test/bootstrap.php b/vendor/maennchen/zipstream-php/test/bootstrap.php
new file mode 100644
index 000000000..13c7a0e6c
--- /dev/null
+++ b/vendor/maennchen/zipstream-php/test/bootstrap.php
@@ -0,0 +1,7 @@
+<?php
+
+declare(strict_types=1);
+
+date_default_timezone_set('UTC');
+
+require __DIR__ . '/../vendor/autoload.php';