diff options
Diffstat (limited to 'vendor/maennchen/zipstream-php/test')
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'; |