| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216 |
- <?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\Attributes\Group;
- 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();
- }
- #[Group('slow')]
- public function testSimulationWithLargeZip64AndZeroHeader(): void
- {
- $zip = new ZipStream(
- outputStream: $this->tempfileStream,
- sendHttpHeaders: false,
- operationMode: OperationMode::SIMULATE_STRICT,
- defaultCompressionMethod: CompressionMethod::STORE,
- outputName: 'archive.zip',
- enableZip64: true,
- defaultEnableZeroHeader: true
- );
- $zip->addFileFromPsr7Stream(
- fileName: 'large',
- stream: new EndlessCycleStream('large'),
- exactSize: 0x120000000, // ~5gb
- compressionMethod: CompressionMethod::STORE,
- lastModificationDateTime: new DateTimeImmutable('2022-01-01 01:01:01Z'),
- );
- $zip->addFileFromPsr7Stream(
- fileName: 'small',
- stream: new EndlessCycleStream('small'),
- exactSize: 0x20,
- compressionMethod: CompressionMethod::STORE,
- lastModificationDateTime: new DateTimeImmutable('2022-01-01 01:01:01Z'),
- );
- $forecastedSize = $zip->finish();
- $zip->executeSimulation();
- $this->assertSame($forecastedSize, filesize($this->tempfile));
- $this->validateAndExtractZip($this->tempfile);
- }
- 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);
- }
- }
|