From be6e578e2b79a66922418294c88442e15ec003be Mon Sep 17 00:00:00 2001 From: Ruth Ivimey-Cook Date: Thu, 14 Jul 2016 13:00:33 +0100 Subject: [PATCH 01/24] Fix invalid reference to 'this' in static --- test/ZipComponents.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/ZipComponents.php b/test/ZipComponents.php index 445c9ee..134d190 100644 --- a/test/ZipComponents.php +++ b/test/ZipComponents.php @@ -65,7 +65,7 @@ protected static function __constructFromString($str, $pos, $size = -1) { try { $eocdrec->readFromString($str, $pos, $size); } catch (Exception $e) { - $this->fail("error parsing end of central directory record"); + self::fail("error parsing end of central directory record"); } return $eocdrec; From c08fb3b0ce540c0bdef93d2ec83e1a917fd06181 Mon Sep 17 00:00:00 2001 From: Ruth Ivimey-Cook Date: Thu, 14 Jul 2016 13:03:30 +0100 Subject: [PATCH 02/24] Remove trailing close-php (best practice) --- src/lib/Count64.php | 2 -- test/ZipComponents.php | 1 - test/ZipStreamerTest.php | 2 -- test/lib/Count64Test.php | 1 - 4 files changed, 6 deletions(-) diff --git a/src/lib/Count64.php b/src/lib/Count64.php index b52db06..4db8bae 100644 --- a/src/lib/Count64.php +++ b/src/lib/Count64.php @@ -300,5 +300,3 @@ public static function construct($value = 0, $limit32Bit = False) { } } } - -?> diff --git a/test/ZipComponents.php b/test/ZipComponents.php index 134d190..52ef4ae 100644 --- a/test/ZipComponents.php +++ b/test/ZipComponents.php @@ -602,4 +602,3 @@ public function readFromString($str, $pos, $size = -1) { $this->end = $pos - 1; } } -?> diff --git a/test/ZipStreamerTest.php b/test/ZipStreamerTest.php index 64d5d99..1905f2d 100644 --- a/test/ZipStreamerTest.php +++ b/test/ZipStreamerTest.php @@ -418,5 +418,3 @@ public function testIssue29() { $zip->finalize(); } } - -?> diff --git a/test/lib/Count64Test.php b/test/lib/Count64Test.php index 0508506..bb67874 100644 --- a/test/lib/Count64Test.php +++ b/test/lib/Count64Test.php @@ -155,4 +155,3 @@ public function testCount64Add($value, $add, $loBytes, $hiBytes, $description) { $this->assertEquals($hiBytes, $count64->getHiBytes(), $description . " (hiBytes)"); } } -?> From b9e3f752544d280bceefb03aa31737edd97166af Mon Sep 17 00:00:00 2001 From: Ruth Ivimey-Cook Date: Thu, 14 Jul 2016 13:07:40 +0100 Subject: [PATCH 03/24] phpstorm project (no workspace) --- .gitignore | 2 + .idea/PHPZipStreamer.iml | 8 + .idea/copyright/profiles_settings.xml | 3 + .idea/encodings.xml | 6 + .idea/inspectionProfiles/Project_Default.xml | 211 ++++++++++++++++++ .../inspectionProfiles/profiles_settings.xml | 7 + .idea/misc.xml | 19 ++ .idea/modules.xml | 8 + .idea/vcs.xml | 6 + 9 files changed, 270 insertions(+) create mode 100644 .gitignore create mode 100644 .idea/PHPZipStreamer.iml create mode 100644 .idea/copyright/profiles_settings.xml create mode 100644 .idea/encodings.xml create mode 100644 .idea/inspectionProfiles/Project_Default.xml create mode 100644 .idea/inspectionProfiles/profiles_settings.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/vcs.xml diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d0ece88 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/.idea/atlassian-ide-plugin.xml +/.idea/workspace.xml diff --git a/.idea/PHPZipStreamer.iml b/.idea/PHPZipStreamer.iml new file mode 100644 index 0000000..c956989 --- /dev/null +++ b/.idea/PHPZipStreamer.iml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/copyright/profiles_settings.xml b/.idea/copyright/profiles_settings.xml new file mode 100644 index 0000000..e7bedf3 --- /dev/null +++ b/.idea/copyright/profiles_settings.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/.idea/encodings.xml b/.idea/encodings.xml new file mode 100644 index 0000000..97626ba --- /dev/null +++ b/.idea/encodings.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..59df68c --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,211 @@ + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 0000000..3b31283 --- /dev/null +++ b/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..82fc5bf --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..e3e09eb --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file From 5bf19138e52973188af043217191cca07c40ff91 Mon Sep 17 00:00:00 2001 From: Ruth Ivimey-Cook Date: Thu, 14 Jul 2016 13:14:30 +0100 Subject: [PATCH 04/24] Housekeeping --- .gitignore | 6 ++++-- composer.json | 14 ++++++++++++-- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/.gitignore b/.gitignore index d0ece88..0577c9a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ -/.idea/atlassian-ide-plugin.xml -/.idea/workspace.xml +.idea/atlassian-ide-plugin.xml +.idea/workspace.xml +.idea/vcs.xml +.idea/tasks.xml diff --git a/composer.json b/composer.json index d9c0e44..69c4cfb 100644 --- a/composer.json +++ b/composer.json @@ -17,15 +17,25 @@ "name": "Lukas Reschke", "email": "lukas@owncloud.com", "role": "Contributor" + },{ + "name": "Ruth Ivimey-Cook", + "email": "ruth@researchfish.com", + "role": "Contributor" } ], "repositories": [ { "type": "vcs", "url": "https://github.com/McNetic/PHPZipStreamer" } - ], + ], "require": { - "php": ">=5.3.0" + "php": ">=5.3.0", + "php-mbstring": "*", + "php-http": "*" + }, + "require-dev": { +// "phpunit/phpunit": "~4.8", +// "php-xdebug": "*" }, "autoload": { "psr-4": { From c7b7aa3090762f8e02218f87c9d6d6114753a548 Mon Sep 17 00:00:00 2001 From: Ruth Ivimey-Cook Date: Thu, 14 Jul 2016 13:15:12 +0100 Subject: [PATCH 05/24] Teach zipstreamer to create file from data (string) and chunk by chunk. --- src/ZipStreamer.php | 229 +++++++++++++++++++++++++++++++++++++-- test/ZipStreamerTest.php | 38 +++++++ 2 files changed, 257 insertions(+), 10 deletions(-) diff --git a/src/ZipStreamer.php b/src/ZipStreamer.php index 18d41a6..1b12f49 100644 --- a/src/ZipStreamer.php +++ b/src/ZipStreamer.php @@ -77,6 +77,15 @@ class ZipStreamer { /** @var boolean indicates zip is finalized and sent to client; no further addition possible */ private $isFinalized = false; + private $isFileOpen = FALSE; + private $hashCtx = FALSE; + private $filePath; + private $addFileOptions; + private $gpFlags; + private $dataLength; + private $lfhLength; + private $gzLength; + /** * Constructor. Initializes ZipStreamer object for immediate usage. * @param array $options Optional, ZipStreamer and zip file options as key/value pairs. @@ -225,7 +234,7 @@ public function addFileFromStream($stream, $filePath, $options = NULL) { // build cdRec $this->cdRec[] = $this->buildCentralDirectoryHeader($filePath, $options['timestamp'], $gpFlags, $options['compress'], - $dataLength, $gzLength, $dataCRC32, $this->extFileAttrFile, False); + $dataLength, $gzLength, $dataCRC32, $this->extFileAttrFile, FALSE); // calc offset $this->offset->add($ddLength)->add($lfhLength)->add($gzLength); @@ -233,6 +242,184 @@ public function addFileFromStream($stream, $filePath, $options = NULL) { return true; } + /** + * Add a file to the archive at the specified location and file name. + * + * @param string $stream Stream to read data from + * @param string $filePath Filepath and name to be used in the archive. + * @param array $options Optional, additional options + * Valid options are: + * * int timestamp: timestamp for the file (default: current time) + * * string comment: comment to be added for this file (default: none) + * * int compress: compression method (override global option for this file) + * * int level: compression level (override global option for this file) + * @return bool $success + */ + public function addFileOpen($filePath, $options = NULL) { + if ($this->isFinalized || $this->isFileOpen) { + return FALSE; + } + $this->isFileOpen = TRUE; + $defaultOptions = array( + 'timestamp' => NULL, + 'comment' => NULL, + 'compress' => $this->compress, + 'level' => $this->level, + ); + if (is_null($options)) { + $options = array(); + } + $this->addFileOptions = array_merge($defaultOptions, $options); + $this->validateCompressionOptions($this->addFileOptions['compress'], $this->addFileOptions['level']); + + $this->filePath = self::normalizeFilePath($filePath); + $this->gpFlags = GPFLAGS::ADD; + $this->dataLength = Count64::construct(0, !$this->zip64); + $this->gzLength = Count64::construct(0, !$this->zip64); + $this->hashCtx = hash_init('crc32b'); + if (COMPR::DEFLATE === $this->addFileOptions['compress']) { + $this->compStream = DeflateStream::create($this->addFileOptions['level']); + } + + list($this->gpFlags, $this->lfhLength) = + $this->beginFile($filePath, FALSE, + $this->addFileOptions['comment'], $this->addFileOptions['timestamp'], + $this->gpFlags, $this->addFileOptions['compress'], + 0, 0, 0); + return TRUE; + } + + /** + * Add another block of data to the file opened with addFileOpen(). + * + * @param string $block + * Data to write to the file. + * @return bool $success + * FALSE if there is no file open with addFileOpen(), else TRUE. + */ + public function addFileWrite($block) { + if ($this->isFinalized || !$this->isFileOpen) { + return FALSE; + } + + list($this->dataLength, $this->gzLength, $this->dataCRC32) = + $this->writeFile($block, $this->addFileOptions['compress'], $this->addFileOptions['level']); + + return TRUE; + } + + /** + * Close the file in the archive. + * + * @return bool $success + */ + public function addFileClose() { + if ($this->isFinalized || !$this->isFileOpen) { + return FALSE; + } + + if (COMPR::DEFLATE === $compress) { + $data = $this->compStream->finish(); + unset($this->compStream); + + $this->gzLength->add(strlen($data)); + $this->write($data); + } + $this->flush(); + + $ddLength = $this->addDataDescriptor($this->dataLength, $this->gzLength, $this->dataCRC32); + + // build cdRec + $this->cdRec[] = + $this->buildCentralDirectoryHeader($filePath, $this->addFileOptions['timestamp'], + $this->gpFlags, $this->addFileOptions['compress'], + $this->dataLength, $this->gzLength, + $this->dataCRC32, $this->extFileAttrFile, FALSE); + + // calc offset + $this->offset->add($ddLength)->add($this->lfhLength)->add($this->gzLength); + $this->isFileOpen = FALSE; + return TRUE; + } + + /** + * Add a file to the archive at the specified location and file name. + * + * @param string $data + * The (complete) contents of the file. + * @param string $filePath + * Filepath and name to be used in the archive. + * @param array $options Optional, additional options + * Valid options are: + * * int timestamp: timestamp for the file (default: current time) + * * string comment: comment to be added for this file (default: none) + * * it compress: compression method (override global option for this file) + * * nt level: compression level (override global option for this file) + * @return bool $success + */ + public function addFileFromString($data, $filePath, $options = NULL) { + if ($this->isFinalized) { + return FALSE; + } + $defaultOptions = array( + 'timestamp' => NULL, + 'comment' => NULL, + 'compress' => $this->compress, + 'level' => $this->level, + ); + if (is_null($options)) { + $options = array(); + } + $options = array_merge($defaultOptions, $options); + $this->validateCompressionOptions($options['compress'], $options['level']); + + if (!is_string($data)) { + return FALSE; + } + + $this->filePath = self::normalizeFilePath($filePath); + $this->gpFlags = GPFLAGS::ADD; + + $this->dataLength = Count64::construct(0, !$this->zip64); + $this->gzLength = Count64::construct(0, !$this->zip64); + $this->lfhLength = Count64::construct(0, !$this->zip64); + $this->dataCRC32 = hexdec(hash('crc32b', $data)); + if (COMPR::DEFLATE === $options['compress']) { + $compStream = DeflateStream::create($options['level']); + } + + $this->dataLength->add(strlen($data)); + if (COMPR::DEFLATE === $options['compress']) { + $data = $compStream->update($data); + } + $this->gzLength->add(strlen($data)); + + list($this->gpFlags, $this->lfhLength) = $this->beginFile($this->filePath, FALSE, + $options['comment'], $options['timestamp'], + $this->gpFlags, $options['compress'], + $this->dataLength, $this->gzLength, $this->dataCRC32); + + $this->write($data); + + if (COMPR::DEFLATE === $options['compress']) { + $data = $compStream->finish(); + $this->gzLength->add(strlen($data)); + $this->write($data); + } + $this->flush(); + + // build cdRec + $this->cdRec[] = $this->buildCentralDirectoryHeader($filePath, $options['timestamp'], + $this->gpFlags, $options['compress'], + $this->dataLength, $this->gzLength, $this->dataCRC32, + $this->extFileAttrFile, FALSE); + + // calc offset + $this->offset->add($this->lfhLength)->add($this->gzLength); + + return TRUE; + } + /** * Add an empty directory entry to the zip archive. * @@ -262,10 +449,12 @@ public function addEmptyDir($directoryPath, $options = NULL) { $gpFlags = 0x0000; $gzMethod = COMPR::STORE; // Compression type 0 = stored - list($gpFlags, $lfhLength) = $this->beginFile($directoryPath, True, $options['comment'], $options['timestamp'], $gpFlags, $gzMethod); + list($gpFlags, $lfhLength) = $this->beginFile($directoryPath, TRUE, + $options['comment'], $options['timestamp'], + $gpFlags, $gzMethod); // build cdRec $this->cdRec[] = $this->buildCentralDirectoryHeader($directoryPath, $options['timestamp'], $gpFlags, $gzMethod, - Count64::construct(0, !$this->zip64), Count64::construct(0, !$this->zip64), 0, $this->extFileAttrDir, True); + Count64::construct(0, !$this->zip64), Count64::construct(0, !$this->zip64), 0, $this->extFileAttrDir, TRUE); // calc offset $this->offset->add($lfhLength); @@ -339,7 +528,7 @@ private function flush() { } private function beginFile($filePath, $isDir, $fileComment, $timestamp, $gpFlags, $gzMethod, - $dataLength = 0, $gzLength = 0, $dataCRC32 = 0) { + $dataLength = 0, $gzLength = 0, $dataCRC32 = 0) { $isFileUTF8 = mb_check_encoding($filePath, 'UTF-8') && !mb_check_encoding($filePath, 'ASCII'); $isCommentUTF8 = !empty($fileComment) && mb_check_encoding($fileComment, 'UTF-8') @@ -349,14 +538,27 @@ private function beginFile($filePath, $isDir, $fileComment, $timestamp, $gpFlags $gpFlags |= GPFLAGS::EFS; } - $localFileHeader = $this->buildLocalFileHeader($filePath, $timestamp, $gpFlags, $gzMethod, $dataLength, - $gzLength, $isDir, $dataCRC32); + $localFileHeader = $this->buildLocalFileHeader($filePath, $timestamp, + $gpFlags, $gzMethod, $dataLength, + $gzLength, $isDir, $dataCRC32); $this->write($localFileHeader); return array($gpFlags, strlen($localFileHeader)); } + private function writeFile($data, $compress, $level) { + + $this->dataLength->add(strlen($data)); + hash_update($this->hashCtx, $data); + if (COMPR::DEFLATE === $compress) { + $data = $this->compStream->update($data); + } + $this->gzLength->add(strlen($data)); + $this->write($data); + + } + private function streamFileData($stream, $compress, $level) { $dataLength = Count64::construct(0, !$this->zip64); $gzLength = Count64::construct(0, !$this->zip64); @@ -398,15 +600,20 @@ private function buildZip64ExtendedInformationField($dataLength = 0, $gzLength = } private function buildLocalFileHeader($filePath, $timestamp, $gpFlags, - $gzMethod, $dataLength, $gzLength, $isDir = False, $dataCRC32 = 0) { + $gzMethod, $dataLength, $gzLength, $isDir = FALSE, $dataCRC32 = 0) { $versionToExtract = $this->getVersionToExtract($isDir); $dosTime = self::getDosTime($timestamp); if ($this->zip64) { $zip64Ext = $this->buildZip64ExtendedInformationField($dataLength, $gzLength); $dataLength = -1; $gzLength = -1; - } else { + $offset = -1; + } + else { $zip64Ext = ''; + $dataLength = is_numeric($dataLength) ? $dataLength : $dataLength->getLoBytes(); + $gzLength = is_numeric($gzLength) ? $gzLength : $gzLength->getLoBytes(); + $offset = $this->offset->getLoBytes(); } return '' @@ -469,7 +676,8 @@ private function buildZip64EndOfCentralDirectoryRecord($cdRecLength) { } private function buildZip64EndOfCentralDirectoryLocator($cdRecLength) { - $zip64RecStart = Count64::construct($this->offset, !$this->zip64)->add($cdRecLength); + $zip64RecStart = Count64::construct($this->offset, !$this->zip64) + ->add($cdRecLength); return '' . pack32le(self::ZIP64_END_OF_CENTRAL_DIR_LOCATOR) // zip64 end of central dir locator signature 4 bytes (0x07064b50) @@ -481,7 +689,8 @@ private function buildZip64EndOfCentralDirectoryLocator($cdRecLength) { } private function buildCentralDirectoryHeader($filePath, $timestamp, $gpFlags, - $gzMethod, $dataLength, $gzLength, $dataCRC32, $extFileAttr, $isDir) { + $gzMethod, $dataLength, $gzLength, $dataCRC32, + $extFileAttr, $isDir) { $versionToExtract = $this->getVersionToExtract($isDir); $dosTime = self::getDosTime($timestamp); if ($this->zip64) { diff --git a/test/ZipStreamerTest.php b/test/ZipStreamerTest.php index 1905f2d..a4743e6 100644 --- a/test/ZipStreamerTest.php +++ b/test/ZipStreamerTest.php @@ -405,6 +405,44 @@ public function testZipfile($options, $files, $description) { $this->assertOutputZipfileOK($files, $options); } + /** + * @dataProvider providerZipfileOK + */ + public function testZipfileString($options, $files, $description) { + $options = array_merge($options, array('outstream' => $this->outstream)); + $zip = new ZipStreamer($options); + foreach ($files as $file) { + if (File::DIR == $file->type) { + $zip->addEmptyDir($file->filename, array('timestamp' => $file->date)); + } else { + $zip->addFileFromString($file->data, $file->filename, array('timestamp' => $file->date)); + } + } + $zip->finalize(); + + $this->assertOutputZipfileOK($files, $options); + } + + /** + * @dataProvider providerZipfileOK + */ + public function testZipfileStreamed($options, $files, $description) { + $options = array_merge($options, array('outstream' => $this->outstream)); + $zip = new ZipStreamer($options); + foreach ($files as $file) { + if (File::DIR == $file->type) { + $zip->addEmptyDir($file->filename, array('timestamp' => $file->date)); + } else { + $zip->addFileOpen($file->filename, array('timestamp' => $file->date)); + $zip->addFileWrite($file->data); + $zip->addFileClose(); + } + } + $zip->finalize(); + + $this->assertOutputZipfileOK($files, $options); + } + /** https://github.com/McNetic/PHPZipStreamer/issues/29 * ZipStreamer produces an error when the size of a file to be added is a * multiple of the STREAM_CHUNK_SIZE (also for empty files) From 7174e66472a862e2a0f3d9c027f0c7dd783f6e39 Mon Sep 17 00:00:00 2001 From: Ruth Ivimey-Cook Date: Thu, 14 Jul 2016 13:26:11 +0100 Subject: [PATCH 06/24] Docs update --- MANUAL.md | 57 ++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 56 insertions(+), 1 deletion(-) diff --git a/MANUAL.md b/MANUAL.md index 227c135..0391246 100644 --- a/MANUAL.md +++ b/MANUAL.md @@ -39,6 +39,39 @@ $zip->finalize(); ``` +Add a single file (here, html text) in immediate mode: +```php +require("src/ZipStreamer.php"); + +# initialize ZipStreamer object (ZipStreamer has it's own namespace) +$zip = new ZipStreamer\ZipStreamer(); + +$data = "Hello World

Hello World

"; +$zip->addFileFromString($data, 'test1.htm'); + +# finalize zip file. Nothing can be added any more. +$zip->finalize(); + +``` + +Add a single file (here, html text) using the chunk interface, to enable +in-flight file streaming: +```php +require("src/ZipStreamer.php"); + +# initialize ZipStreamer object (ZipStreamer has it's own namespace) +$zip = new ZipStreamer\ZipStreamer(); + +$zip->addFileOpen('test1.htm'); +$data = "Hello World

Hello World

"; +$zip->addFileWrite($data); +$zip->addFileClose(); + +# finalize zip file. Nothing can be added any more. +$zip->finalize(); + +``` + Characteristics --------------- @@ -64,6 +97,10 @@ deflate (the zip standard) compression can be enabled and/or disabled globally and per file. Without pecl_http extension, it is still possible to enable deflate compression, but with compression level 0, so there is no actual compression. +Note: The pecl_http extension is also available in some OS repos as 'php-http'. +* addFileFromString sets the data length and CRC in the file header while +addFileFromStream and addFileOpen/addFileWrite/addFileClose both create a +blank file header and fill in the length afterwards, as allowed by the zip spec. API Documentation ----------------- @@ -125,6 +162,25 @@ Add a file to the archive at the specified location and file name. ######Returns bool Success +``` +addFileFromString(string $data, string $filePath, array $options) : bool +``` + +Add a file to the archive at the specified location and file name. + +######Parameters +* string *$data* The file contents to store. +* string *$filePath* Filepath and name to be used in the archive. +* array *options* (optional) additional options. Valid options are: + * int *$timestamp* Timestamp for the file (default: current time) + * string *$comment* comment to be added for this file (default: none) + * int *compress*: compression method (override global option for this + file) + * int *level*: compression level (override global option for this file) + +######Returns +bool Success + ``` addEmptyDir(string $directoryPath, array $options) : bool ``` @@ -150,4 +206,3 @@ A closed archive can no longer have new files added to it. After closing, the zi ######Returns bool Success - From 4c93aa8e947277b8c74b8ae99e18659cfcaaf05e Mon Sep 17 00:00:00 2001 From: Ruth Ivimey-Cook Date: Thu, 14 Jul 2016 13:29:58 +0100 Subject: [PATCH 07/24] No need for inspection profile info. --- .idea/inspectionProfiles/Project_Default.xml | 211 ------------------- 1 file changed, 211 deletions(-) delete mode 100644 .idea/inspectionProfiles/Project_Default.xml diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml deleted file mode 100644 index 59df68c..0000000 --- a/.idea/inspectionProfiles/Project_Default.xml +++ /dev/null @@ -1,211 +0,0 @@ - - - - \ No newline at end of file From 836079c88bd9e339728b4ae68aee1812201de0f0 Mon Sep 17 00:00:00 2001 From: Ruth Ivimey-Cook Date: Thu, 14 Jul 2016 13:30:45 +0100 Subject: [PATCH 08/24] Add inspection profile to ignores. --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 0577c9a..047f6f2 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ .idea/workspace.xml .idea/vcs.xml .idea/tasks.xml +.idea/inspectionProfiles/Project_Default.xml From a769a319bdc520e7560b9460a990b4641bb8e936 Mon Sep 17 00:00:00 2001 From: Ruth Ivimey-Cook Date: Thu, 14 Jul 2016 13:37:29 +0100 Subject: [PATCH 09/24] composer improvements. --- .idea/composerJson.xml | 9 +++++++++ .idea/misc.xml | 6 ------ composer.json | 8 ++++---- 3 files changed, 13 insertions(+), 10 deletions(-) create mode 100644 .idea/composerJson.xml diff --git a/.idea/composerJson.xml b/.idea/composerJson.xml new file mode 100644 index 0000000..4199499 --- /dev/null +++ b/.idea/composerJson.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml index 82fc5bf..72abef0 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,11 +1,5 @@ - - - - - - diff --git a/composer.json b/composer.json index 69c4cfb..aaf45eb 100644 --- a/composer.json +++ b/composer.json @@ -30,12 +30,12 @@ ], "require": { "php": ">=5.3.0", - "php-mbstring": "*", - "php-http": "*" + "ext-mbstring": "*", + "ext-http": "*" }, "require-dev": { -// "phpunit/phpunit": "~4.8", -// "php-xdebug": "*" + "phpunit/phpunit": "~4.8", + "ext-xdebug": "*" }, "autoload": { "psr-4": { From 71f6ea2304584c9840c97bcfdf2494995e2fb902 Mon Sep 17 00:00:00 2001 From: Ruth Ivimey-Cook Date: Thu, 14 Jul 2016 13:59:15 +0100 Subject: [PATCH 10/24] docs for addFileOpen/Write/Close --- MANUAL.md | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/MANUAL.md b/MANUAL.md index 0391246..dddfc21 100644 --- a/MANUAL.md +++ b/MANUAL.md @@ -181,6 +181,49 @@ Add a file to the archive at the specified location and file name. ######Returns bool Success +``` +addFileOpen(string $filePath, array $options) : bool +``` + +Start the process of adding a file chunk by chunk. + +This call must be followed by 0 or more calls to addFileWrite(), and then one +call to addFileClose(). There is no support for multiple simultaneously active +files. + +######Parameters +* string *$filePath* Filepath and name to be used in the archive. +* array *options* (optional) additional options. Valid options are: + * int *$timestamp* Timestamp for the file (default: current time) + * string *$comment* comment to be added for this file (default: none) + * int *compress*: compression method (override global option for this + file) + * int *level*: compression level (override global option for this file) + +######Returns +bool Success + +``` +addFileWrite(string $data) : bool +``` + +Append more data to a file being written by addFileOpen(). + +######Parameters +* string *$data* The file contents to store. + +######Returns +bool Success + +``` +addFileClose() : bool +``` + +Close a file being written by addFileOpen() and append relevant metadata. + +######Returns +bool Success + ``` addEmptyDir(string $directoryPath, array $options) : bool ``` From a9a551bc1228bac746b45d359440e2301d1b884b Mon Sep 17 00:00:00 2001 From: Ruth Ivimey-Cook Date: Tue, 19 Jul 2016 11:41:24 +0100 Subject: [PATCH 11/24] Name according to my fork --- composer.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/composer.json b/composer.json index aaf45eb..3f8e55a 100644 --- a/composer.json +++ b/composer.json @@ -1,5 +1,5 @@ { - "name": "mcnetic/zipstreamer", + "name": "rivimey/zipstreamer", "type": "library", "description": "Stream zip files without i/o overhead", "keywords": ["zip", "stream"], @@ -25,7 +25,7 @@ ], "repositories": [ { "type": "vcs", - "url": "https://github.com/McNetic/PHPZipStreamer" + "url": "https://github.com/rivimey/PHPZipStreamer" } ], "require": { @@ -39,7 +39,7 @@ }, "autoload": { "psr-4": { - "ZipStreamer\\": "src/" + "ZipStreamer": "src/" } } } From c6c115d371c098221e483e864b1ec245483be8f3 Mon Sep 17 00:00:00 2001 From: Ruth Ivimey-Cook Date: Wed, 20 Jul 2016 00:23:02 +0100 Subject: [PATCH 12/24] Split up into PSR-4 format and fix tests. (Including kill off 'lib' folder, move phpunit.xml and update composer) --- .travis.yml | 4 +- composer.json | 4 +- composer.lock | 1128 +++++++++++++++++ phpunit.xml | 17 + src/Count64/Count64.php | 33 + src/Count64/Count64Base.php | 43 + src/Count64/Count64_32.php | 96 ++ src/Count64/Count64_64.php | 80 ++ src/Count64/PackBits.php | 150 +++ src/DOS.php | 45 + src/Deflate/COMPR.php | 45 + src/Deflate/DeflatePeclStream.php | 73 ++ src/Deflate/DeflateStoreStream.php | 64 + src/Deflate/DeflateStream.php | 48 + src/ExtFileAttr.php | 51 + src/GPFLAGS.php | 48 + src/UNIX.php | 66 + src/ZipStreamer.php | 358 ++---- src/lib/Count64.php | 302 ----- test/phpunit.xml | 11 - {test/lib => tests}/Count64Test.php | 39 +- {test => tests}/ZipComponents.php | 122 +- {test => tests}/ZipStreamerTest.php | 151 ++- tests/bootstrap.php | 6 + {test => tests}/travis/php-modules-install.sh | 0 25 files changed, 2282 insertions(+), 702 deletions(-) create mode 100644 composer.lock create mode 100644 phpunit.xml create mode 100644 src/Count64/Count64.php create mode 100644 src/Count64/Count64Base.php create mode 100644 src/Count64/Count64_32.php create mode 100644 src/Count64/Count64_64.php create mode 100644 src/Count64/PackBits.php create mode 100644 src/DOS.php create mode 100644 src/Deflate/COMPR.php create mode 100644 src/Deflate/DeflatePeclStream.php create mode 100644 src/Deflate/DeflateStoreStream.php create mode 100644 src/Deflate/DeflateStream.php create mode 100644 src/ExtFileAttr.php create mode 100644 src/GPFLAGS.php create mode 100644 src/UNIX.php delete mode 100644 src/lib/Count64.php delete mode 100644 test/phpunit.xml rename {test/lib => tests}/Count64Test.php (76%) rename {test => tests}/ZipComponents.php (81%) rename {test => tests}/ZipStreamerTest.php (78%) create mode 100644 tests/bootstrap.php rename {test => tests}/travis/php-modules-install.sh (100%) diff --git a/.travis.yml b/.travis.yml index 39a9183..01045f1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -17,10 +17,10 @@ before_install: - sh -c "mkdir -p ${TRAVIS_BUILD_DIR}/travis/module-cache/`php-config --vernum`" before_script: - - ./test/travis/php-modules-install.sh + - ./tests/travis/php-modules-install.sh script: - - phpunit --configuration test/phpunit.xml + - phpunit --configuration phpunit.xml cache: directories: diff --git a/composer.json b/composer.json index 3f8e55a..7a0ea1c 100644 --- a/composer.json +++ b/composer.json @@ -31,7 +31,7 @@ "require": { "php": ">=5.3.0", "ext-mbstring": "*", - "ext-http": "*" + "ext-http": "~2.0" }, "require-dev": { "phpunit/phpunit": "~4.8", @@ -39,7 +39,7 @@ }, "autoload": { "psr-4": { - "ZipStreamer": "src/" + "Rivimey\\ZipStreamer\\": "src/" } } } diff --git a/composer.lock b/composer.lock new file mode 100644 index 0000000..8130baa --- /dev/null +++ b/composer.lock @@ -0,0 +1,1128 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", + "This file is @generated automatically" + ], + "hash": "b6671418ddb682f3463d019f15214039", + "content-hash": "932ea8d7a25841713d0d3a4bbed9b277", + "packages": [], + "packages-dev": [ + { + "name": "doctrine/instantiator", + "version": "1.0.5", + "source": { + "type": "git", + "url": "https://github.com/doctrine/instantiator.git", + "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/8e884e78f9f0eb1329e445619e04456e64d8051d", + "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d", + "shasum": "" + }, + "require": { + "php": ">=5.3,<8.0-DEV" + }, + "require-dev": { + "athletic/athletic": "~0.1.8", + "ext-pdo": "*", + "ext-phar": "*", + "phpunit/phpunit": "~4.0", + "squizlabs/php_codesniffer": "~2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com", + "homepage": "http://ocramius.github.com/" + } + ], + "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", + "homepage": "https://github.com/doctrine/instantiator", + "keywords": [ + "constructor", + "instantiate" + ], + "time": "2015-06-14 21:17:01" + }, + { + "name": "phpdocumentor/reflection-common", + "version": "1.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionCommon.git", + "reference": "144c307535e82c8fdcaacbcfc1d6d8eeb896687c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/144c307535e82c8fdcaacbcfc1d6d8eeb896687c", + "reference": "144c307535e82c8fdcaacbcfc1d6d8eeb896687c", + "shasum": "" + }, + "require": { + "php": ">=5.5" + }, + "require-dev": { + "phpunit/phpunit": "^4.6" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": [ + "src" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jaap van Otterdijk", + "email": "opensource@ijaap.nl" + } + ], + "description": "Common reflection classes used by phpdocumentor to reflect the code structure", + "homepage": "http://www.phpdoc.org", + "keywords": [ + "FQSEN", + "phpDocumentor", + "phpdoc", + "reflection", + "static analysis" + ], + "time": "2015-12-27 11:43:31" + }, + { + "name": "phpdocumentor/reflection-docblock", + "version": "3.1.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", + "reference": "9270140b940ff02e58ec577c237274e92cd40cdd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/9270140b940ff02e58ec577c237274e92cd40cdd", + "reference": "9270140b940ff02e58ec577c237274e92cd40cdd", + "shasum": "" + }, + "require": { + "php": ">=5.5", + "phpdocumentor/reflection-common": "^1.0@dev", + "phpdocumentor/type-resolver": "^0.2.0", + "webmozart/assert": "^1.0" + }, + "require-dev": { + "mockery/mockery": "^0.9.4", + "phpunit/phpunit": "^4.4" + }, + "type": "library", + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + } + ], + "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", + "time": "2016-06-10 09:48:41" + }, + { + "name": "phpdocumentor/type-resolver", + "version": "0.2", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/TypeResolver.git", + "reference": "b39c7a5b194f9ed7bd0dd345c751007a41862443" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/b39c7a5b194f9ed7bd0dd345c751007a41862443", + "reference": "b39c7a5b194f9ed7bd0dd345c751007a41862443", + "shasum": "" + }, + "require": { + "php": ">=5.5", + "phpdocumentor/reflection-common": "^1.0" + }, + "require-dev": { + "mockery/mockery": "^0.9.4", + "phpunit/phpunit": "^5.2||^4.8.24" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + } + ], + "time": "2016-06-10 07:14:17" + }, + { + "name": "phpspec/prophecy", + "version": "v1.6.1", + "source": { + "type": "git", + "url": "https://github.com/phpspec/prophecy.git", + "reference": "58a8137754bc24b25740d4281399a4a3596058e0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/58a8137754bc24b25740d4281399a4a3596058e0", + "reference": "58a8137754bc24b25740d4281399a4a3596058e0", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.0.2", + "php": "^5.3|^7.0", + "phpdocumentor/reflection-docblock": "^2.0|^3.0.2", + "sebastian/comparator": "^1.1", + "sebastian/recursion-context": "^1.0" + }, + "require-dev": { + "phpspec/phpspec": "^2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.6.x-dev" + } + }, + "autoload": { + "psr-0": { + "Prophecy\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Konstantin Kudryashov", + "email": "ever.zet@gmail.com", + "homepage": "http://everzet.com" + }, + { + "name": "Marcello Duarte", + "email": "marcello.duarte@gmail.com" + } + ], + "description": "Highly opinionated mocking framework for PHP 5.3+", + "homepage": "https://github.com/phpspec/prophecy", + "keywords": [ + "Double", + "Dummy", + "fake", + "mock", + "spy", + "stub" + ], + "time": "2016-06-07 08:13:47" + }, + { + "name": "phpunit/php-code-coverage", + "version": "2.2.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "eabf68b476ac7d0f73793aada060f1c1a9bf8979" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/eabf68b476ac7d0f73793aada060f1c1a9bf8979", + "reference": "eabf68b476ac7d0f73793aada060f1c1a9bf8979", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "phpunit/php-file-iterator": "~1.3", + "phpunit/php-text-template": "~1.2", + "phpunit/php-token-stream": "~1.3", + "sebastian/environment": "^1.3.2", + "sebastian/version": "~1.0" + }, + "require-dev": { + "ext-xdebug": ">=2.1.4", + "phpunit/phpunit": "~4" + }, + "suggest": { + "ext-dom": "*", + "ext-xdebug": ">=2.2.1", + "ext-xmlwriter": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.2.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", + "homepage": "https://github.com/sebastianbergmann/php-code-coverage", + "keywords": [ + "coverage", + "testing", + "xunit" + ], + "time": "2015-10-06 15:47:00" + }, + { + "name": "phpunit/php-file-iterator", + "version": "1.4.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "6150bf2c35d3fc379e50c7602b75caceaa39dbf0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/6150bf2c35d3fc379e50c7602b75caceaa39dbf0", + "reference": "6150bf2c35d3fc379e50c7602b75caceaa39dbf0", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "FilterIterator implementation that filters files based on a list of suffixes.", + "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", + "keywords": [ + "filesystem", + "iterator" + ], + "time": "2015-06-21 13:08:43" + }, + { + "name": "phpunit/php-text-template", + "version": "1.2.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-text-template.git", + "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/31f8b717e51d9a2afca6c9f046f5d69fc27c8686", + "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Simple template engine.", + "homepage": "https://github.com/sebastianbergmann/php-text-template/", + "keywords": [ + "template" + ], + "time": "2015-06-21 13:50:34" + }, + { + "name": "phpunit/php-timer", + "version": "1.0.8", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "38e9124049cf1a164f1e4537caf19c99bf1eb260" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/38e9124049cf1a164f1e4537caf19c99bf1eb260", + "reference": "38e9124049cf1a164f1e4537caf19c99bf1eb260", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4|~5" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Utility class for timing", + "homepage": "https://github.com/sebastianbergmann/php-timer/", + "keywords": [ + "timer" + ], + "time": "2016-05-12 18:03:57" + }, + { + "name": "phpunit/php-token-stream", + "version": "1.4.8", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-token-stream.git", + "reference": "3144ae21711fb6cac0b1ab4cbe63b75ce3d4e8da" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/3144ae21711fb6cac0b1ab4cbe63b75ce3d4e8da", + "reference": "3144ae21711fb6cac0b1ab4cbe63b75ce3d4e8da", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Wrapper around PHP's tokenizer extension.", + "homepage": "https://github.com/sebastianbergmann/php-token-stream/", + "keywords": [ + "tokenizer" + ], + "time": "2015-09-15 10:49:45" + }, + { + "name": "phpunit/phpunit", + "version": "4.8.26", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "fc1d8cd5b5de11625979125c5639347896ac2c74" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/fc1d8cd5b5de11625979125c5639347896ac2c74", + "reference": "fc1d8cd5b5de11625979125c5639347896ac2c74", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-json": "*", + "ext-pcre": "*", + "ext-reflection": "*", + "ext-spl": "*", + "php": ">=5.3.3", + "phpspec/prophecy": "^1.3.1", + "phpunit/php-code-coverage": "~2.1", + "phpunit/php-file-iterator": "~1.4", + "phpunit/php-text-template": "~1.2", + "phpunit/php-timer": "^1.0.6", + "phpunit/phpunit-mock-objects": "~2.3", + "sebastian/comparator": "~1.1", + "sebastian/diff": "~1.2", + "sebastian/environment": "~1.3", + "sebastian/exporter": "~1.2", + "sebastian/global-state": "~1.0", + "sebastian/version": "~1.0", + "symfony/yaml": "~2.1|~3.0" + }, + "suggest": { + "phpunit/php-invoker": "~1.1" + }, + "bin": [ + "phpunit" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.8.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "The PHP Unit Testing framework.", + "homepage": "https://phpunit.de/", + "keywords": [ + "phpunit", + "testing", + "xunit" + ], + "time": "2016-05-17 03:09:28" + }, + { + "name": "phpunit/phpunit-mock-objects", + "version": "2.3.8", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git", + "reference": "ac8e7a3db35738d56ee9a76e78a4e03d97628983" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/ac8e7a3db35738d56ee9a76e78a4e03d97628983", + "reference": "ac8e7a3db35738d56ee9a76e78a4e03d97628983", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.0.2", + "php": ">=5.3.3", + "phpunit/php-text-template": "~1.2", + "sebastian/exporter": "~1.2" + }, + "require-dev": { + "phpunit/phpunit": "~4.4" + }, + "suggest": { + "ext-soap": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.3.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Mock Object library for PHPUnit", + "homepage": "https://github.com/sebastianbergmann/phpunit-mock-objects/", + "keywords": [ + "mock", + "xunit" + ], + "time": "2015-10-02 06:51:40" + }, + { + "name": "sebastian/comparator", + "version": "1.2.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/comparator.git", + "reference": "937efb279bd37a375bcadf584dec0726f84dbf22" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/937efb279bd37a375bcadf584dec0726f84dbf22", + "reference": "937efb279bd37a375bcadf584dec0726f84dbf22", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "sebastian/diff": "~1.2", + "sebastian/exporter": "~1.2" + }, + "require-dev": { + "phpunit/phpunit": "~4.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides the functionality to compare PHP values for equality", + "homepage": "http://www.github.com/sebastianbergmann/comparator", + "keywords": [ + "comparator", + "compare", + "equality" + ], + "time": "2015-07-26 15:48:44" + }, + { + "name": "sebastian/diff", + "version": "1.4.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "13edfd8706462032c2f52b4b862974dd46b71c9e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/13edfd8706462032c2f52b4b862974dd46b71c9e", + "reference": "13edfd8706462032c2f52b4b862974dd46b71c9e", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.8" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Diff implementation", + "homepage": "https://github.com/sebastianbergmann/diff", + "keywords": [ + "diff" + ], + "time": "2015-12-08 07:14:41" + }, + { + "name": "sebastian/environment", + "version": "1.3.7", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/environment.git", + "reference": "4e8f0da10ac5802913afc151413bc8c53b6c2716" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/4e8f0da10ac5802913afc151413bc8c53b6c2716", + "reference": "4e8f0da10ac5802913afc151413bc8c53b6c2716", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.3.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides functionality to handle HHVM/PHP environments", + "homepage": "http://www.github.com/sebastianbergmann/environment", + "keywords": [ + "Xdebug", + "environment", + "hhvm" + ], + "time": "2016-05-17 03:18:57" + }, + { + "name": "sebastian/exporter", + "version": "1.2.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/exporter.git", + "reference": "42c4c2eec485ee3e159ec9884f95b431287edde4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/42c4c2eec485ee3e159ec9884f95b431287edde4", + "reference": "42c4c2eec485ee3e159ec9884f95b431287edde4", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "sebastian/recursion-context": "~1.0" + }, + "require-dev": { + "ext-mbstring": "*", + "phpunit/phpunit": "~4.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.3.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides the functionality to export PHP variables for visualization", + "homepage": "http://www.github.com/sebastianbergmann/exporter", + "keywords": [ + "export", + "exporter" + ], + "time": "2016-06-17 09:04:28" + }, + { + "name": "sebastian/global-state", + "version": "1.1.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/global-state.git", + "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bc37d50fea7d017d3d340f230811c9f1d7280af4", + "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.2" + }, + "suggest": { + "ext-uopz": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Snapshotting of global state", + "homepage": "http://www.github.com/sebastianbergmann/global-state", + "keywords": [ + "global state" + ], + "time": "2015-10-12 03:26:01" + }, + { + "name": "sebastian/recursion-context", + "version": "1.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/recursion-context.git", + "reference": "913401df809e99e4f47b27cdd781f4a258d58791" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/913401df809e99e4f47b27cdd781f4a258d58791", + "reference": "913401df809e99e4f47b27cdd781f4a258d58791", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides functionality to recursively process PHP variables", + "homepage": "http://www.github.com/sebastianbergmann/recursion-context", + "time": "2015-11-11 19:50:13" + }, + { + "name": "sebastian/version", + "version": "1.0.6", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/version.git", + "reference": "58b3a85e7999757d6ad81c787a1fbf5ff6c628c6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/58b3a85e7999757d6ad81c787a1fbf5ff6c628c6", + "reference": "58b3a85e7999757d6ad81c787a1fbf5ff6c628c6", + "shasum": "" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that helps with managing the version number of Git-hosted PHP projects", + "homepage": "https://github.com/sebastianbergmann/version", + "time": "2015-06-21 13:59:46" + }, + { + "name": "symfony/yaml", + "version": "v3.1.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/yaml.git", + "reference": "2884c26ce4c1d61aebf423a8b912950fe7c764de" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/yaml/zipball/2884c26ce4c1d61aebf423a8b912950fe7c764de", + "reference": "2884c26ce4c1d61aebf423a8b912950fe7c764de", + "shasum": "" + }, + "require": { + "php": ">=5.5.9" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.1-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Yaml\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Yaml Component", + "homepage": "https://symfony.com", + "time": "2016-06-29 05:41:56" + }, + { + "name": "webmozart/assert", + "version": "1.0.2", + "source": { + "type": "git", + "url": "https://github.com/webmozart/assert.git", + "reference": "30eed06dd6bc88410a4ff7f77b6d22f3ce13dbde" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webmozart/assert/zipball/30eed06dd6bc88410a4ff7f77b6d22f3ce13dbde", + "reference": "30eed06dd6bc88410a4ff7f77b6d22f3ce13dbde", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "^4.6" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "psr-4": { + "Webmozart\\Assert\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Assertions to validate method input/output with nice error messages.", + "keywords": [ + "assert", + "check", + "validate" + ], + "time": "2015-08-24 13:29:44" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": ">=5.3.0", + "ext-mbstring": "*", + "ext-http": "~2.0" + }, + "platform-dev": { + "ext-xdebug": "*" + } +} diff --git a/phpunit.xml b/phpunit.xml new file mode 100644 index 0000000..bcc8665 --- /dev/null +++ b/phpunit.xml @@ -0,0 +1,17 @@ + + + + + tests/Count64Test.php + tests/ZipStreamerTest.php + tests/ZipStreamer_NoDeflateTest.php + + + + diff --git a/src/Count64/Count64.php b/src/Count64/Count64.php new file mode 100644 index 0000000..0a7f5d0 --- /dev/null +++ b/src/Count64/Count64.php @@ -0,0 +1,33 @@ +. + * + * @author Nicolai Ehemann + * @copyright Copyright (C) 2013-2014 Nicolai Ehemann and contributors + * @license GNU GPL + */ +namespace Rivimey\ZipStreamer\Count64; + +abstract class Count64 { + public static function construct($value = 0, $limit32Bit = False) { + if (4 == PHP_INT_SIZE) { + return new Count64_32($value, $limit32Bit); + } else { + return new Count64_64($value, $limit32Bit); + } + } +} diff --git a/src/Count64/Count64Base.php b/src/Count64/Count64Base.php new file mode 100644 index 0000000..cefedfb --- /dev/null +++ b/src/Count64/Count64Base.php @@ -0,0 +1,43 @@ +. + * + * @author Nicolai Ehemann + * @copyright Copyright (C) 2013-2014 Nicolai Ehemann and contributors + * @license GNU GPL + */ +namespace Rivimey\ZipStreamer\Count64; + + +abstract class Count64Base { + protected $limit32Bit = False; + + function __construct($value = 0, $limit32Bit = False) { + $this->limit32Bit = $limit32Bit; + $this->set($value); + } + + abstract public function set($value); + abstract public function add($value); + abstract public function getHiBytes(); + abstract public function getLoBytes(); + abstract public function _getValue(); + + const EXCEPTION_SET_INVALID_ARGUMENT = "Count64 object can only be set() to integer or Count64 values"; + const EXCEPTION_ADD_INVALID_ARGUMENT = "Count64 object can only be add()ed integer or Count64 values"; + const EXCEPTION_32BIT_OVERFLOW = "Count64 object limited to 32 bit (overflow)"; +} diff --git a/src/Count64/Count64_32.php b/src/Count64/Count64_32.php new file mode 100644 index 0000000..e04bc8f --- /dev/null +++ b/src/Count64/Count64_32.php @@ -0,0 +1,96 @@ +. + * + * @author Nicolai Ehemann + * @copyright Copyright (C) 2013-2014 Nicolai Ehemann and contributors + * @license GNU GPL + */ +namespace Rivimey\ZipStreamer\Count64; + +class Count64_32 extends Count64Base { + private $loBytes; + private $hiBytes; + + public function getHiBytes() { + return $this->hiBytes; + } + + public function getLoBytes() { + return $this->loBytes; + } + + public function _getValue() { + return array($this->hiBytes, $this->loBytes); + } + + public function set($value) { + if (is_int($value)) { + $this->loBytes = $value; + $this->hiBytes = 0; + } else if (is_array($value) && 2 == sizeof($value)) { + $this->loBytes = $value[0]; + if ($this->limit32Bit && 0 !== $value[1]) { + throw new \OverflowException(self::EXCEPTION_32BIT_OVERFLOW); + } + $this->hiBytes = $value[1]; + } else if (is_object($value) && __CLASS__ == get_class($value)) { + $value = $value->_getValue(); + if ($this->limit32Bit && 0 !== $value[0]) { + throw new \OverflowException(self::EXCEPTION_32BIT_OVERFLOW); + } + $this->hiBytes = $value[0]; + $this->loBytes = $value[1]; + } else { + throw new \InvalidArgumentException(self::EXCEPTION_SET_INVALID_ARGUMENT); + } + return $this; + } + + public function add($value) { + if (is_int($value)) { + $sum = (int) ($this->loBytes + $value); + // overflow! + if (($this->loBytes > -1 && $sum < $this->loBytes && $sum > -1) + || ($this->loBytes < 0 && ($sum < $this->loBytes || $sum > -1))) { + if ($this->limit32Bit) { + throw new \OverflowException(self::EXCEPTION_32BIT_OVERFLOW); + } + $this->hiBytes = (int) ($this->hiBytes + 1); + } + $this->loBytes = $sum; + } else if (is_object($value) && __CLASS__ == get_class($value)) { + $value = $value->_getValue(); + $sum = (int) ($this->loBytes + $value[1]); + if (($this->loBytes > -1 && $sum < $this->loBytes && $sum > -1) + || ($this->loBytes < 0 && ($sum < $this->loBytes || $sum > -1))) { + if ($this->limit32Bit) { + throw new \OverflowException(self::EXCEPTION_32BIT_OVERFLOW); + } + $this->hiBytes = (int) ($this->hiBytes + 1); + } + $this->loBytes = $sum; + if ($this->limit32Bit && 0 !== $value[0]) { + throw new \OverflowException(self::EXCEPTION_32BIT_OVERFLOW); + } + $this->hiBytes = (int) ($this->hiBytes + $value[0]); + } else { + throw new \InvalidArgumentException(self::EXCEPTION_ADD_INVALID_ARGUMENT); + } + return $this; + } +} diff --git a/src/Count64/Count64_64.php b/src/Count64/Count64_64.php new file mode 100644 index 0000000..b0d2640 --- /dev/null +++ b/src/Count64/Count64_64.php @@ -0,0 +1,80 @@ +. + * + * @author Nicolai Ehemann + * @copyright Copyright (C) 2013-2014 Nicolai Ehemann and contributors + * @license GNU GPL + */ +namespace Rivimey\ZipStreamer\Count64; + +class Count64_64 extends Count64Base { + private $value; + + public function getHiBytes() { + return PackBits::urShift($this->value, 32); + } + + public function getLoBytes() { + return $this->value & PackBits::INT64_LOW_MAP; + } + + public function _getValue() { + return $this->value; + } + + public function set($value) { + if (is_int($value)) { + if ($this->limit32Bit && PackBits::INT_MAX_32 < $value) { + throw new \OverFlowException(self::EXCEPTION_32BIT_OVERFLOW); + } + $this->value = $value; + } else if (is_array($value) && 2 == sizeof($value)) { + if ($this->limit32Bit && 0 !== $value[1]) { + throw new \OverFlowException(self::EXCEPTION_32BIT_OVERFLOW); + } + $this->value = $value[1]; + $this->value = $this->value << 32; + $this->value = $this->value + $value[0]; + } else if (is_object($value) && __CLASS__ == get_class($value)) { + $value = $value->_getValue(); + if ($this->limit32Bit && PackBits::INT_MAX_32 < $value) { + throw new \OverFlowException(self::EXCEPTION_32BIT_OVERFLOW); + } + $this->value = $value; + + } else { + throw new \InvalidArgumentException(self::EXCEPTION_SET_INVALID_ARGUMENT); + } + return $this; + } + + public function add($value) { + if (is_int($value)) { + $sum = (int) ($this->value + $value); + } else if (is_object($value) && __CLASS__ == get_class($value)) { + $sum = (int) ($this->value + $value->_getValue()); + } else { + throw new \InvalidArgumentException(self::EXCEPTION_ADD_INVALID_ARGUMENT); + } + if ($this->limit32Bit && PackBits::INT_MAX_32 < $sum) { + throw new \OverFlowException(self::EXCEPTION_32BIT_OVERFLOW); + } + $this->value = $sum; + return $this; + } +} diff --git a/src/Count64/PackBits.php b/src/Count64/PackBits.php new file mode 100644 index 0000000..8dd3667 --- /dev/null +++ b/src/Count64/PackBits.php @@ -0,0 +1,150 @@ +. + * + * @author Nicolai Ehemann + * @copyright Copyright (C) 2013-2014 Nicolai Ehemann and contributors + * @license GNU GPL + */ +namespace Rivimey\ZipStreamer\Count64; + + +class PackBits { + + const INT64_HIGH_MAP = 0xffffffff00000000; + const INT64_LOW_MAP = 0x00000000ffffffff; + const INT_MAX_32 = 0xffffffff; + + /** + * Unsigned right shift + * + * @param int $bits integer to be shifted + * @param int $shift number of bits to be shifted + * @return int shifted integer + */ + static public function urShift($bits, $shift) { + if ($shift == 0) { + return $bits; + } + return ($bits >> $shift) & ~(1 << (8 * PHP_INT_SIZE - 1) >> ($shift - 1)); + } + + /** + * Convert binary data string to readable hex string + * + * @param string $data binary string + * @return string readable hex string + */ + static public function byte2hex($data) { + return unpack("h*", $data); + } + + /** + * Pack 1 byte data into binary string + * + * @param mixed $data data + * @return string 1 byte binary string + */ + static public function pack8($data) { + return pack('C', $data); + } + + /** + * Pack 2 byte data into binary string, little endian format + * + * @param mixed $data data + * @return string 2 byte binary string + */ + static public function pack16le($data) { + return pack('v', $data); + } + + /** + * Unpack 2 byte binary string, little endian format to 2 byte data + * + * @param string $data binary string + * @return integer 2 byte data + */ + static public function unpack16le($data) { + $result = unpack('v', $data); + return $result[1]; + } + + /** + * Pack 4 byte data into binary string, little endian format + * + * @param mixed $data data + * @return 4 byte binary string + */ + static public function pack32le($data) { + return pack('V', $data); + } + + /** + * Unpack 4 byte binary string, little endian format to 4 byte data + * + * @param string $data binary string + * @return integer 4 byte data + */ + static public function unpack32le($data) { + $result = unpack('V', $data); + return $result[1]; + } + + /** + * Pack 8 byte data into binary string, little endian format + * + * @param mixed $data data + * @return string 8 byte binary string + */ + static public function pack64le($data) { + if (is_object($data)) { + if ("Count64_32" == get_class($data)) { + $value = $data->_getValue(); + $hiBytess = $value[0]; + $loBytess = $value[1]; + } + else { + $hiBytess = ($data->_getValue() & self::INT64_HIGH_MAP) >> 32; + $loBytess = $data->_getValue() & self::INT64_LOW_MAP; + } + } + else if (4 == PHP_INT_SIZE) { + $hiBytess = 0; + $loBytess = $data; + } + else { + $hiBytess = ($data & self::INT64_HIGH_MAP) >> 32; + $loBytess = $data & self::INT64_LOW_MAP; + } + return pack('VV', $loBytess, $hiBytess); + } + + /** + * Unpack 8 byte binary string, little endian format to 8 byte data + * + * @param string $data binary string + * @return Count64Base data + */ + static public function unpack64le($data) { + $bytes = unpack('V2', $data); + return Count64::construct(array( + $bytes[1], + $bytes[2] + )); + } +} \ No newline at end of file diff --git a/src/DOS.php b/src/DOS.php new file mode 100644 index 0000000..cd6cdfd --- /dev/null +++ b/src/DOS.php @@ -0,0 +1,45 @@ +. + * + * Inspired by + * CreateZipFile by Rochak Chauhan www.rochakchauhan.com (http://www.phpclasses.org/browse/package/2322.html) + * and + * ZipStream by A. Grandt https://github.com/Grandt/PHPZip (http://www.phpclasses.org/package/6116) + * + * Unix-File attributes according to + * http://unix.stackexchange.com/questions/14705/the-zip-formats-external-file-attribute + * + * @author Nicolai Ehemann + * @author André Rothe + * @author Ruth Ivimey-Cook + * @copyright Copyright (C) 2013-2016 Ruth Ivimey-Cook, Nicolai Ehemann and contributors + * @license GNU GPL + * @version 2.0 + */ +namespace Rivimey\ZipStreamer; + +class DOS extends ExtFileAttr { + + const READ_ONLY = 0x1; + const HIDDEN = 0x2; + const SYSTEM = 0x4; + const VOLUME = 0x8; + const DIR = 0x10; + const ARCHIVE = 0x20; + const RESERVED1 = 0x40; + const RESERVED2 = 0x80; +} diff --git a/src/Deflate/COMPR.php b/src/Deflate/COMPR.php new file mode 100644 index 0000000..2548277 --- /dev/null +++ b/src/Deflate/COMPR.php @@ -0,0 +1,45 @@ +. + * + * Inspired by + * CreateZipFile by Rochak Chauhan www.rochakchauhan.com (http://www.phpclasses.org/browse/package/2322.html) + * and + * ZipStream by A. Grandt https://github.com/Grandt/PHPZip (http://www.phpclasses.org/package/6116) + * + * Unix-File attributes according to + * http://unix.stackexchange.com/questions/14705/the-zip-formats-external-file-attribute + * + * @author Nicolai Ehemann + * @author André Rothe + * @author Ruth Ivimey-Cook + * @copyright Copyright (C) 2013-2016 Ruth Ivimey-Cook, Nicolai Ehemann and contributors + * @license GNU GPL + * @version 2.0 + */ +namespace Rivimey\ZipStreamer\Deflate; + +class COMPR { + // compression method + const STORE = 0x0000; // 0 - The file is stored (no compression) + const DEFLATE = 0x0008; // 8 - The file is deflated + + // compression level (for deflate compression) + const NONE = 0; + const NORMAL = 1; + const MAXIMUM = 2; + const SUPERFAST = 3; +} diff --git a/src/Deflate/DeflatePeclStream.php b/src/Deflate/DeflatePeclStream.php new file mode 100644 index 0000000..e783917 --- /dev/null +++ b/src/Deflate/DeflatePeclStream.php @@ -0,0 +1,73 @@ +. + * + * Inspired by + * CreateZipFile by Rochak Chauhan www.rochakchauhan.com (http://www.phpclasses.org/browse/package/2322.html) + * and + * ZipStream by A. Grandt https://github.com/Grandt/PHPZip (http://www.phpclasses.org/package/6116) + * + * Unix-File attributes according to + * http://unix.stackexchange.com/questions/14705/the-zip-formats-external-file-attribute + * + * @author Nicolai Ehemann + * @author André Rothe + * @author Ruth Ivimey-Cook + * @copyright Copyright (C) 2013-2016 Ruth Ivimey-Cook, Nicolai Ehemann and contributors + * @license GNU GPL + * @version 2.0 + */ + +namespace Rivimey\ZipStreamer\Deflate; + +class DeflatePeclStream extends DeflateStream { + private $peclDeflateStream; + + const PECL1_DEFLATE_STREAM_CLASS = '\HttpDeflateStream'; + const PECL2_DEFLATE_STREAM_CLASS = '\http\encoding\Stream\Deflate'; + + protected function __construct($level) { + $class = self::PECL1_DEFLATE_STREAM_CLASS; + if (!class_exists($class)) { + $class = self::PECL2_DEFLATE_STREAM_CLASS; + } + if (!class_exists($class)) { + new \Exception('unable to instantiate PECL deflate stream (requires pecl_http >= 0.10)'); + } + + $deflateFlags = constant($class . '::TYPE_RAW'); + switch ($level) { + case COMPR::NORMAL: + $deflateFlags |= constant($class . '::LEVEL_DEF'); + break; + case COMPR::MAXIMUM: + $deflateFlags |= constant($class . '::LEVEL_MAX'); + break; + case COMPR::SUPERFAST: + $deflateFlags |= constant($class . '::LEVEL_MIN'); + break; + } + $this->peclDeflateStream = new $class($deflateFlags); + } + + public function update($data) { + return $this->peclDeflateStream->update($data); + } + + public function finish() { + return $this->peclDeflateStream->finish(); + } +} diff --git a/src/Deflate/DeflateStoreStream.php b/src/Deflate/DeflateStoreStream.php new file mode 100644 index 0000000..b87898a --- /dev/null +++ b/src/Deflate/DeflateStoreStream.php @@ -0,0 +1,64 @@ +. + * + * Inspired by + * CreateZipFile by Rochak Chauhan www.rochakchauhan.com (http://www.phpclasses.org/browse/package/2322.html) + * and + * ZipStream by A. Grandt https://github.com/Grandt/PHPZip (http://www.phpclasses.org/package/6116) + * + * Unix-File attributes according to + * http://unix.stackexchange.com/questions/14705/the-zip-formats-external-file-attribute + * + * @author Nicolai Ehemann + * @author André Rothe + * @author Ruth Ivimey-Cook + * @copyright Copyright (C) 2013-2016 Ruth Ivimey-Cook, Nicolai Ehemann and contributors + * @license GNU GPL + * @version 2.0 + */ +namespace Rivimey\ZipStreamer\Deflate; + +use Rivimey\ZipStreamer\Count64\PackBits; + +class DeflateStoreStream extends DeflateStream { + const BLOCK_HEADER_NORMAL = 0x00; + const BLOCK_HEADER_FINAL = 0x01; + const BLOCK_HEADER_ERROR = 0x03; + + const MAX_UNCOMPR_BLOCK_SIZE = 0xffff; + + public function update($data) { + $result = ''; + for ($pos = 0, $len = strlen($data); $pos < $len; $pos += self::MAX_UNCOMPR_BLOCK_SIZE) { + $result .= $this->write_block(self::BLOCK_HEADER_NORMAL, substr($data, $pos, self::MAX_UNCOMPR_BLOCK_SIZE)); + } + return $result; + } + + public function finish() { + return $this->write_block(self::BLOCK_HEADER_FINAL, ''); + } + + private function write_block($header, $data) { + return '' + . PackBits::pack8($header) // block header 3 bits, null padding = 1 byte + . PackBits::pack16le(strlen($data)) // block data length 2 bytes + . PackBits::pack16le(0xffff ^ strlen($data)) // complement of block data size 2 bytes + . $data // data + . ''; + } +} diff --git a/src/Deflate/DeflateStream.php b/src/Deflate/DeflateStream.php new file mode 100644 index 0000000..a1fb0a1 --- /dev/null +++ b/src/Deflate/DeflateStream.php @@ -0,0 +1,48 @@ +. + * + * Inspired by + * CreateZipFile by Rochak Chauhan www.rochakchauhan.com (http://www.phpclasses.org/browse/package/2322.html) + * and + * ZipStream by A. Grandt https://github.com/Grandt/PHPZip (http://www.phpclasses.org/package/6116) + * + * Unix-File attributes according to + * http://unix.stackexchange.com/questions/14705/the-zip-formats-external-file-attribute + * + * @author Nicolai Ehemann + * @author André Rothe + * @author Ruth Ivimey-Cook + * @copyright Copyright (C) 2013-2016 Ruth Ivimey-Cook, Nicolai Ehemann and contributors + * @license GNU GPL + * @version 2.0 + */ +namespace Rivimey\ZipStreamer\Deflate; + +abstract class DeflateStream { + static public function create($level) { + if (COMPR::NONE === $level) { + return new DeflateStoreStream($level); + } else { + return new DeflatePeclStream($level); + } + } + protected function __construct($level) {} + + abstract public function update($data); + abstract public function finish(); +} + diff --git a/src/ExtFileAttr.php b/src/ExtFileAttr.php new file mode 100644 index 0000000..37c243d --- /dev/null +++ b/src/ExtFileAttr.php @@ -0,0 +1,51 @@ +. + * + * Inspired by + * CreateZipFile by Rochak Chauhan www.rochakchauhan.com (http://www.phpclasses.org/browse/package/2322.html) + * and + * ZipStream by A. Grandt https://github.com/Grandt/PHPZip (http://www.phpclasses.org/package/6116) + * + * Unix-File attributes according to + * http://unix.stackexchange.com/questions/14705/the-zip-formats-external-file-attribute + * + * @author Nicolai Ehemann + * @author André Rothe + * @author Ruth Ivimey-Cook + * @copyright Copyright (C) 2013-2016 Ruth Ivimey-Cook, Nicolai Ehemann and contributors + * @license GNU GPL + * @version 2.0 + */ +namespace Rivimey\ZipStreamer; + +abstract class ExtFileAttr { + + /* + ZIP external file attributes layout + TTTTsstrwxrwxrwx0000000000ADVSHR + ^^^^____________________________ UNIX file type + ^^^_________________________ UNIX setuid, setgid, sticky + ^^^^^^^^^________________ UNIX permissions + ^^^^^^^^________ "lower-middle byte" (TODO: what is this?) + ^^^^^^^^ DOS attributes (reserved, reserved, archived, directory, volume, system, hidden, read-only + */ + + public static function getExtFileAttr($attr) { + return $attr; + } +} + diff --git a/src/GPFLAGS.php b/src/GPFLAGS.php new file mode 100644 index 0000000..c6f46a7 --- /dev/null +++ b/src/GPFLAGS.php @@ -0,0 +1,48 @@ +. + * + * Inspired by + * CreateZipFile by Rochak Chauhan www.rochakchauhan.com (http://www.phpclasses.org/browse/package/2322.html) + * and + * ZipStream by A. Grandt https://github.com/Grandt/PHPZip (http://www.phpclasses.org/package/6116) + * + * Unix-File attributes according to + * http://unix.stackexchange.com/questions/14705/the-zip-formats-external-file-attribute + * + * @author Nicolai Ehemann + * @author André Rothe + * @author Ruth Ivimey-Cook + * @copyright Copyright (C) 2013-2016 Ruth Ivimey-Cook, Nicolai Ehemann and contributors + * @license GNU GPL + * @version 2.0 + */ +namespace Rivimey\ZipStreamer; + +class GPFLAGS { + const NONE = 0x0000; // no flags set + const COMP1 = 0x0002; // compression flag 1 (compression settings, see APPNOTE for details) + const COMP2 = 0x0004; // compression flag 2 (compression settings, see APPNOTE for details) + const ADD = 0x0008; // ADD flag (sizes and crc32 are append in data descriptor) + const EFS = 0x0800; // EFS flag (UTF-8 encoded filename and/or comment) + + // compression settings for deflate/deflate64 + const DEFL_NORM = 0x0000; // normal compression (COMP1 and COMP2 not set) + const DEFL_MAX = COMP1; // maximum compression + const DEFL_FAST = COMP2; // fast compression + const DEFL_SFAST = 0x0006; // superfast compression (COMP1 and COMP2 set) +} + diff --git a/src/UNIX.php b/src/UNIX.php new file mode 100644 index 0000000..df3c608 --- /dev/null +++ b/src/UNIX.php @@ -0,0 +1,66 @@ +. + * + * Inspired by + * CreateZipFile by Rochak Chauhan www.rochakchauhan.com (http://www.phpclasses.org/browse/package/2322.html) + * and + * ZipStream by A. Grandt https://github.com/Grandt/PHPZip (http://www.phpclasses.org/package/6116) + * + * Unix-File attributes according to + * http://unix.stackexchange.com/questions/14705/the-zip-formats-external-file-attribute + * + * @author Nicolai Ehemann + * @author André Rothe + * @author Ruth Ivimey-Cook + * @copyright Copyright (C) 2013-2016 Ruth Ivimey-Cook, Nicolai Ehemann and contributors + * @license GNU GPL + * @version 2.0 + */ +namespace Rivimey\ZipStreamer; + +class UNIX extends ExtFileAttr { + + // Octal + const S_IFIFO = 0010000; /* named pipe (fifo) */ + const S_IFCHR = 0020000; /* character special */ + const S_IFDIR = 0040000; /* directory */ + const S_IFBLK = 0060000; /* block special */ + const S_IFREG = 0100000; /* regular */ + const S_IFLNK = 0120000; /* symbolic link */ + const S_IFSOCK = 0140000; /* socket */ + const S_ISUID = 0004000; /* set user id on execution */ + const S_ISGID = 0002000; /* set group id on execution */ + const S_ISTXT = 0001000; /* sticky bit */ + const S_IRWXU = 0000700; /* RWX mask for owner */ + const S_IRUSR = 0000400; /* R for owner */ + const S_IWUSR = 0000200; /* W for owner */ + const S_IXUSR = 0000100; /* X for owner */ + const S_IRWXG = 0000070; /* RWX mask for group */ + const S_IRGRP = 0000040; /* R for group */ + const S_IWGRP = 0000020; /* W for group */ + const S_IXGRP = 0000010; /* X for group */ + const S_IRWXO = 0000007; /* RWX mask for other */ + const S_IROTH = 0000004; /* R for other */ + const S_IWOTH = 0000002; /* W for other */ + const S_IXOTH = 0000001; /* X for other */ + const S_ISVTX = 0001000; /* save swapped text even after use */ + + public static function getExtFileAttr($attr) { + return parent::getExtFileAttr($attr) << 16; + } +} + diff --git a/src/ZipStreamer.php b/src/ZipStreamer.php index 1b12f49..b5ddb73 100644 --- a/src/ZipStreamer.php +++ b/src/ZipStreamer.php @@ -25,25 +25,23 @@ * * @author Nicolai Ehemann * @author André Rothe - * @copyright Copyright (C) 2013-2015 Nicolai Ehemann and contributors + * @author Ruth Ivimey-Cook + * @copyright Copyright (C) 2013-2016 Ruth Ivimey-Cook, Nicolai Ehemann and contributors * @license GNU GPL - * @version 1.0 + * @version 2.0 */ -namespace ZipStreamer; - -require "lib/Count64.php"; - -class COMPR { - // compression method - const STORE = 0x0000; // 0 - The file is stored (no compression) - const DEFLATE = 0x0008; // 8 - The file is deflated - - // compression level (for deflate compression) - const NONE = 0; - const NORMAL = 1; - const MAXIMUM = 2; - const SUPERFAST = 3; -} +namespace Rivimey\ZipStreamer; + +use Rivimey\ZipStreamer\Count64\PackBits; +use Rivimey\ZipStreamer\DOS; +use Rivimey\ZipStreamer\UNIX; +use Rivimey\ZipStreamer\Count64\Count64; +use Rivimey\ZipStreamer\Count64\Count64_32; +use Rivimey\ZipStreamer\Count64\Count64_64; +use Rivimey\ZipStreamer\Deflate\COMPR; +use Rivimey\ZipStreamer\Deflate\DeflateStream; +use Rivimey\ZipStreamer\Deflate\DeflatePeclStream; +use Rivimey\ZipStreamer\Deflate\DeflateStoreStream; class ZipStreamer { const VERSION = "1.0"; @@ -77,6 +75,9 @@ class ZipStreamer { /** @var boolean indicates zip is finalized and sent to client; no further addition possible */ private $isFinalized = false; + /* + * These values are used to persist state during addFileOpen/Write/Close. + */ private $isFileOpen = FALSE; private $hashCtx = FALSE; private $filePath; @@ -282,7 +283,7 @@ public function addFileOpen($filePath, $options = NULL) { } list($this->gpFlags, $this->lfhLength) = - $this->beginFile($filePath, FALSE, + $this->beginFile($this->filePath, FALSE, $this->addFileOptions['comment'], $this->addFileOptions['timestamp'], $this->gpFlags, $this->addFileOptions['compress'], 0, 0, 0); @@ -301,9 +302,7 @@ public function addFileWrite($block) { if ($this->isFinalized || !$this->isFileOpen) { return FALSE; } - - list($this->dataLength, $this->gzLength, $this->dataCRC32) = - $this->writeFile($block, $this->addFileOptions['compress'], $this->addFileOptions['level']); + $this->writeFile($block, $this->addFileOptions['compress'], $this->addFileOptions['level']); return TRUE; } @@ -318,7 +317,7 @@ public function addFileClose() { return FALSE; } - if (COMPR::DEFLATE === $compress) { + if (COMPR::DEFLATE === $this->addFileOptions['compress']) { $data = $this->compStream->finish(); unset($this->compStream); @@ -326,12 +325,13 @@ public function addFileClose() { $this->write($data); } $this->flush(); + $this->dataCRC32 = unpack('N', hash_final($this->hashCtx, true)); $ddLength = $this->addDataDescriptor($this->dataLength, $this->gzLength, $this->dataCRC32); // build cdRec $this->cdRec[] = - $this->buildCentralDirectoryHeader($filePath, $this->addFileOptions['timestamp'], + $this->buildCentralDirectoryHeader($this->filePath, $this->addFileOptions['timestamp'], $this->gpFlags, $this->addFileOptions['compress'], $this->dataLength, $this->gzLength, $this->dataCRC32, $this->extFileAttrFile, FALSE); @@ -591,12 +591,12 @@ private function streamFileData($stream, $compress, $level) { private function buildZip64ExtendedInformationField($dataLength = 0, $gzLength = 0) { return '' - . pack16le(0x0001) // tag for this "extra" block type (ZIP64) 2 bytes (0x0001) - . pack16le(28) // size of this "extra" block 2 bytes - . pack64le($dataLength) // original uncompressed file size 8 bytes - . pack64le($gzLength) // size of compressed data 8 bytes - . pack64le($this->offset) // offset of local header record 8 bytes - . pack32le(0); // number of the disk on which this file starts 4 bytes + . PackBits::pack16le(0x0001) // tag for this "extra" block type (ZIP64) 2 bytes (0x0001) + . PackBits::pack16le(28) // size of this "extra" block 2 bytes + . PackBits::pack64le($dataLength) // original uncompressed file size 8 bytes + . PackBits::pack64le($gzLength) // size of compressed data 8 bytes + . PackBits::pack64le($this->offset) // offset of local header record 8 bytes + . PackBits::pack32le(0); // number of the disk on which this file starts 4 bytes } private function buildLocalFileHeader($filePath, $timestamp, $gpFlags, @@ -617,34 +617,34 @@ private function buildLocalFileHeader($filePath, $timestamp, $gpFlags, } return '' - . pack32le(self::ZIP_LOCAL_FILE_HEADER) // local file header signature 4 bytes (0x04034b50) - . pack16le($versionToExtract) // version needed to extract 2 bytes - . pack16le($gpFlags) // general purpose bit flag 2 bytes - . pack16le($gzMethod) // compression method 2 bytes - . pack32le($dosTime) // last mod file time 2 bytes - // last mod file date 2 bytes - . pack32le($dataCRC32) // crc-32 4 bytes - . pack32le($gzLength) // compressed size 4 bytes - . pack32le($dataLength) // uncompressed size 4 bytes - . pack16le(strlen($filePath)) // file name length 2 bytes - . pack16le(strlen($zip64Ext)) // extra field length 2 bytes - . $filePath // file name (variable size) - . $zip64Ext; // extra field (variable size) + . PackBits::pack32le(self::ZIP_LOCAL_FILE_HEADER) // local file header signature 4 bytes (0x04034b50) + . PackBits::pack16le($versionToExtract) // version needed to extract 2 bytes + . PackBits::pack16le($gpFlags) // general purpose bit flag 2 bytes + . PackBits::pack16le($gzMethod) // compression method 2 bytes + . PackBits::pack32le($dosTime) // last mod file time 2 bytes + // last mod file date 2 bytes + . PackBits::pack32le($dataCRC32) // crc-32 4 bytes + . PackBits::pack32le($gzLength) // compressed size 4 bytes + . PackBits::pack32le($dataLength) // uncompressed size 4 bytes + . PackBits::pack16le(strlen($filePath)) // file name length 2 bytes + . PackBits::pack16le(strlen($zip64Ext)) // extra field length 2 bytes + . $filePath // file name (variable size) + . $zip64Ext; // extra field (variable size) } private function addDataDescriptor($dataLength, $gzLength, $dataCRC32) { if ($this->zip64) { $length = 20; - $packedGzLength = pack64le($gzLength); - $packedDataLength = pack64le($dataLength); + $packedGzLength = PackBits::pack64le($gzLength); + $packedDataLength = PackBits::pack64le($dataLength); } else { $length = 12; - $packedGzLength = pack32le($gzLength->getLoBytes()); - $packedDataLength = pack32le($dataLength->getLoBytes()); + $packedGzLength = PackBits::pack32le($gzLength->getLoBytes()); + $packedDataLength = PackBits::pack32le($dataLength->getLoBytes()); } $this->write('' - . pack32le($dataCRC32) // crc-32 4 bytes + . PackBits::pack32le($dataCRC32) // crc-32 4 bytes . $packedGzLength // compressed size 4/8 bytes (depending on zip64 enabled) . $packedDataLength // uncompressed size 4/8 bytes (depending on zip64 enabled) .''); @@ -656,22 +656,22 @@ private function buildZip64EndOfCentralDirectoryRecord($cdRecLength) { $cdRecCount = sizeof($this->cdRec); return '' - . pack32le(self::ZIP64_END_OF_CENTRAL_DIRECTORY) // zip64 end of central dir signature 4 bytes (0x06064b50) - . pack64le(44) // size of zip64 end of central directory - // record 8 bytes - . pack16le(self::ATTR_MADE_BY_VERSION) //version made by 2 bytes - . pack16le($versionToExtract) // version needed to extract 2 bytes - . pack32le(0) // number of this disk 4 bytes - . pack32le(0) // number of the disk with the start of the - // central directory 4 bytes - . pack64le($cdRecCount) // total number of entries in the central - // directory on this disk 8 bytes - . pack64le($cdRecCount) // total number of entries in the - // central directory 8 bytes - . pack64le($cdRecLength) // size of the central directory 8 bytes - . pack64le($this->offset) // offset of start of central directory - // with respect to the starting disk number 8 bytes - . ''; // zip64 extensible data sector (variable size) + . PackBits::pack32le(self::ZIP64_END_OF_CENTRAL_DIRECTORY) // zip64 end of central dir signature 4 bytes (0x06064b50) + . PackBits::pack64le(44) // size of zip64 end of central directory + // record 8 bytes + . PackBits::pack16le(self::ATTR_MADE_BY_VERSION) //version made by 2 bytes + . PackBits::pack16le($versionToExtract) // version needed to extract 2 bytes + . PackBits::pack32le(0) // number of this disk 4 bytes + . PackBits::pack32le(0) // number of the disk with the start of the + // central directory 4 bytes + . PackBits::pack64le($cdRecCount) // total number of entries in the central + // directory on this disk 8 bytes + . PackBits::pack64le($cdRecCount) // total number of entries in the + // central directory 8 bytes + . PackBits::pack64le($cdRecLength) // size of the central directory 8 bytes + . PackBits::pack64le($this->offset) // offset of start of central directory + // with respect to the starting disk number 8 bytes + . ''; // zip64 extensible data sector (variable size) } @@ -680,12 +680,12 @@ private function buildZip64EndOfCentralDirectoryLocator($cdRecLength) { ->add($cdRecLength); return '' - . pack32le(self::ZIP64_END_OF_CENTRAL_DIR_LOCATOR) // zip64 end of central dir locator signature 4 bytes (0x07064b50) - . pack32le(0) // number of the disk with the start of the - // zip64 end of central directory 4 bytes - . pack64le($zip64RecStart) // relative offset of the zip64 end of - // central directory record 8 bytes - . pack32le(1); // total number of disks 4 bytes + . PackBits::pack32le(self::ZIP64_END_OF_CENTRAL_DIR_LOCATOR) // zip64 end of central dir locator signature 4 bytes (0x07064b50) + . PackBits::pack32le(0) // number of the disk with the start of the + // zip64 end of central directory 4 bytes + . PackBits::pack64le($zip64RecStart) // relative offset of the zip64 end of + // central directory record 8 bytes + . PackBits::pack32le(1); // total number of disks 4 bytes } private function buildCentralDirectoryHeader($filePath, $timestamp, $gpFlags, @@ -708,23 +708,23 @@ private function buildCentralDirectoryHeader($filePath, $timestamp, $gpFlags, } return '' - . pack32le(self::ZIP_CENTRAL_FILE_HEADER) //central file header signature 4 bytes (0x02014b50) - . pack16le(self::ATTR_MADE_BY_VERSION) //version made by 2 bytes - . pack16le($versionToExtract) // version needed to extract 2 bytes - . pack16le($gpFlags) //general purpose bit flag 2 bytes - . pack16le($gzMethod) //compression method 2 bytes - . pack32le($dosTime) //last mod file time 2 bytes - //last mod file date 2 bytes - . pack32le($dataCRC32) //crc-32 4 bytes - . pack32le($gzLength) //compressed size 4 bytes - . pack32le($dataLength) //uncompressed size 4 bytes - . pack16le(strlen($filePath)) //file name length 2 bytes - . pack16le(strlen($zip64Ext)) //extra field length 2 bytes - . pack16le(0) //file comment length 2 bytes - . pack16le($diskNo) //disk number start 2 bytes - . pack16le(0) //internal file attributes 2 bytes - . pack32le($extFileAttr) //external file attributes 4 bytes - . pack32le($offset) //relative offset of local header 4 bytes + . PackBits::pack32le(self::ZIP_CENTRAL_FILE_HEADER) //central file header signature 4 bytes (0x02014b50) + . PackBits::pack16le(self::ATTR_MADE_BY_VERSION) //version made by 2 bytes + . PackBits::pack16le($versionToExtract) // version needed to extract 2 bytes + . PackBits::pack16le($gpFlags) //general purpose bit flag 2 bytes + . PackBits::pack16le($gzMethod) //compression method 2 bytes + . PackBits::pack32le($dosTime) //last mod file time 2 bytes + //last mod file date 2 bytes + . PackBits::pack32le($dataCRC32) //crc-32 4 bytes + . PackBits::pack32le($gzLength) //compressed size 4 bytes + . PackBits::pack32le($dataLength) //uncompressed size 4 bytes + . PackBits::pack16le(strlen($filePath)) //file name length 2 bytes + . PackBits::pack16le(strlen($zip64Ext)) //extra field length 2 bytes + . PackBits::pack16le(0) //file comment length 2 bytes + . PackBits::pack16le($diskNo) //disk number start 2 bytes + . PackBits::pack16le(0) //internal file attributes 2 bytes + . PackBits::pack32le($extFileAttr) //external file attributes 4 bytes + . PackBits::pack32le($offset) //relative offset of local header 4 bytes . $filePath //file name (variable size) . $zip64Ext //extra field (variable size) //TODO: implement? @@ -745,21 +745,21 @@ private function buildEndOfCentralDirectoryRecord($cdRecLength) { //throw new \Exception(sprintf("zip64 %d diskno %d", $this->zip64, $diskNumber)); return '' - . pack32le(self::ZIP_END_OF_CENTRAL_DIRECTORY) // end of central dir signature 4 bytes (0x06064b50) - . pack16le($diskNumber) // number of this disk 2 bytes - . pack16le($diskNumber) // number of the disk with the - // start of the central directory 2 bytes - . pack16le($cdRecCount) // total number of entries in the - // central directory on this disk 2 bytes - . pack16le($cdRecCount) // total number of entries in the - // central directory 2 bytes - . pack32le($cdRecLength) // size of the central directory 4 bytes - . pack32le($offset) // offset of start of central - // directory with respect to the - // starting disk number 4 bytes - . pack16le(0) // .ZIP file comment length 2 bytes + . PackBits::pack32le(self::ZIP_END_OF_CENTRAL_DIRECTORY) // end of central dir signature 4 bytes (0x06064b50) + . PackBits::pack16le($diskNumber) // number of this disk 2 bytes + . PackBits::pack16le($diskNumber) // number of the disk with the + // start of the central directory 2 bytes + . PackBits::pack16le($cdRecCount) // total number of entries in the + // central directory on this disk 2 bytes + . PackBits::pack16le($cdRecCount) // total number of entries in the + // central directory 2 bytes + . PackBits::pack32le($cdRecLength) // size of the central directory 4 bytes + . PackBits::pack32le($offset) // offset of start of central + // directory with respect to the + // starting disk number 4 bytes + . PackBits::pack16le(0) // .ZIP file comment length 2 bytes //TODO: implement? - . ''; // .ZIP file comment (variable size) + . ''; // .ZIP file comment (variable size) } // Utility methods //////////////////////////////////////////////////////// @@ -787,161 +787,3 @@ public static function getDosTime($timestamp = 0) { return 0x0000; } } - -abstract class ExtFileAttr { - - /* - ZIP external file attributes layout - TTTTsstrwxrwxrwx0000000000ADVSHR - ^^^^____________________________ UNIX file type - ^^^_________________________ UNIX setuid, setgid, sticky - ^^^^^^^^^________________ UNIX permissions - ^^^^^^^^________ "lower-middle byte" (TODO: what is this?) - ^^^^^^^^ DOS attributes (reserved, reserved, archived, directory, volume, system, hidden, read-only - */ - - public static function getExtFileAttr($attr) { - return $attr; - } -} - -class UNIX extends ExtFileAttr { - - // Octal - const S_IFIFO = 0010000; /* named pipe (fifo) */ - const S_IFCHR = 0020000; /* character special */ - const S_IFDIR = 0040000; /* directory */ - const S_IFBLK = 0060000; /* block special */ - const S_IFREG = 0100000; /* regular */ - const S_IFLNK = 0120000; /* symbolic link */ - const S_IFSOCK = 0140000; /* socket */ - const S_ISUID = 0004000; /* set user id on execution */ - const S_ISGID = 0002000; /* set group id on execution */ - const S_ISTXT = 0001000; /* sticky bit */ - const S_IRWXU = 0000700; /* RWX mask for owner */ - const S_IRUSR = 0000400; /* R for owner */ - const S_IWUSR = 0000200; /* W for owner */ - const S_IXUSR = 0000100; /* X for owner */ - const S_IRWXG = 0000070; /* RWX mask for group */ - const S_IRGRP = 0000040; /* R for group */ - const S_IWGRP = 0000020; /* W for group */ - const S_IXGRP = 0000010; /* X for group */ - const S_IRWXO = 0000007; /* RWX mask for other */ - const S_IROTH = 0000004; /* R for other */ - const S_IWOTH = 0000002; /* W for other */ - const S_IXOTH = 0000001; /* X for other */ - const S_ISVTX = 0001000; /* save swapped text even after use */ - - public static function getExtFileAttr($attr) { - return parent::getExtFileAttr($attr) << 16; - } -} - -abstract class DeflateStream { - static public function create($level) { - if (COMPR::NONE === $level) { - return new DeflateStoreStream($level); - } else { - return new DeflatePeclStream($level); - } - } - protected function __construct($level) {} - - abstract public function update($data); - abstract public function finish(); -} - -class DeflatePeclStream extends DeflateStream { - private $peclDeflateStream; - - const PECL1_DEFLATE_STREAM_CLASS = '\HttpDeflateStream'; - const PECL2_DEFLATE_STREAM_CLASS = '\http\encoding\Stream\Deflate'; - - protected function __construct($level) { - $class = self::PECL1_DEFLATE_STREAM_CLASS; - if (!class_exists($class)) { - $class = self::PECL2_DEFLATE_STREAM_CLASS; - } - if (!class_exists($class)) { - new \Exception('unable to instantiate PECL deflate stream (requires pecl_http >= 0.10)'); - } - - $deflateFlags = constant($class . '::TYPE_RAW'); - switch ($level) { - case COMPR::NORMAL: - $deflateFlags |= constant($class . '::LEVEL_DEF'); - break; - case COMPR::MAXIMUM: - $deflateFlags |= constant($class . '::LEVEL_MAX'); - break; - case COMPR::SUPERFAST: - $deflateFlags |= constant($class . '::LEVEL_MIN'); - break; - } - $this->peclDeflateStream = new $class($deflateFlags); - } - - public function update($data) { - return $this->peclDeflateStream->update($data); - } - - public function finish() { - return $this->peclDeflateStream->finish(); - } -} - -class DeflateStoreStream extends DeflateStream { - const BLOCK_HEADER_NORMAL = 0x00; - const BLOCK_HEADER_FINAL = 0x01; - const BLOCK_HEADER_ERROR = 0x03; - - const MAX_UNCOMPR_BLOCK_SIZE = 0xffff; - - public function update($data) { - $result = ''; - for ($pos = 0, $len = strlen($data); $pos < $len; $pos += self::MAX_UNCOMPR_BLOCK_SIZE) { - $result .= $this->write_block(self::BLOCK_HEADER_NORMAL, substr($data, $pos, self::MAX_UNCOMPR_BLOCK_SIZE)); - } - return $result; - } - - public function finish() { - return $this->write_block(self::BLOCK_HEADER_FINAL, ''); - } - - private function write_block($header, $data) { - return '' - . pack8($header) // block header 3 bits, null padding = 1 byte - . pack16le(strlen($data)) // block data length 2 bytes - . pack16le(0xffff ^ strlen($data)) // complement of block data size 2 bytes - . $data // data - . ''; - } -} - -class DOS extends ExtFileAttr { - - const READ_ONLY = 0x1; - const HIDDEN = 0x2; - const SYSTEM = 0x4; - const VOLUME = 0x8; - const DIR = 0x10; - const ARCHIVE = 0x20; - const RESERVED1 = 0x40; - const RESERVED2 = 0x80; -} - -class GPFLAGS { - const NONE = 0x0000; // no flags set - const COMP1 = 0x0002; // compression flag 1 (compression settings, see APPNOTE for details) - const COMP2 = 0x0004; // compression flag 2 (compression settings, see APPNOTE for details) - const ADD = 0x0008; // ADD flag (sizes and crc32 are append in data descriptor) - const EFS = 0x0800; // EFS flag (UTF-8 encoded filename and/or comment) - - // compression settings for deflate/deflate64 - const DEFL_NORM = 0x0000; // normal compression (COMP1 and COMP2 not set) - const DEFL_MAX = COMP1; // maximum compression - const DEFL_FAST = COMP2; // fast compression - const DEFL_SFAST = 0x0006; // superfast compression (COMP1 and COMP2 set) -} - diff --git a/src/lib/Count64.php b/src/lib/Count64.php deleted file mode 100644 index 4db8bae..0000000 --- a/src/lib/Count64.php +++ /dev/null @@ -1,302 +0,0 @@ -. - * - * @author Nicolai Ehemann - * @copyright Copyright (C) 2013-2014 Nicolai Ehemann and contributors - * @license GNU GPL - */ -namespace ZipStreamer; - -const INT64_HIGH_MAP = 0xffffffff00000000; -const INT64_LOW_MAP = 0x00000000ffffffff; -const INT_MAX_32 = 0xffffffff; - -/** - * Unsigned right shift - * - * @param int $bits integer to be shifted - * @param int $shift number of bits to be shifted - * @return int shifted integer - */ -function urShift($bits, $shift) { - if ($shift == 0) { - return $bits; - } - return ($bits >> $shift) & ~(1 << (8 * PHP_INT_SIZE - 1) >> ($shift - 1)); -} - -/** - * Convert binary data string to readable hex string - * - * @param string $data binary string - * @return string readable hex string - */ -function byte2hex($data) { - return unpack("h*", $data); -} - -/** - * Pack 1 byte data into binary string - * - * @param mixed $data data - * @return string 1 byte binary string - */ -function pack8($data) { - return pack('C', $data); -} - -/** - * Pack 2 byte data into binary string, little endian format - * - * @param mixed $data data - * @return string 2 byte binary string - */ -function pack16le($data) { - return pack('v', $data); -} - -/** - * Unpack 2 byte binary string, little endian format to 2 byte data - * - * @param string $data binary string - * @return integer 2 byte data - */ -function unpack16le($data) { - $result = unpack('v', $data); - return $result[1]; -} - -/** - * Pack 4 byte data into binary string, little endian format - * - * @param mixed $data data - * @return 4 byte binary string - */ -function pack32le($data) { - return pack('V', $data); -} - -/** - * Unpack 4 byte binary string, little endian format to 4 byte data - * - * @param string $data binary string - * @return integer 4 byte data - */ -function unpack32le($data) { - $result = unpack('V', $data); - return $result[1]; -} - -/** - * Pack 8 byte data into binary string, little endian format - * - * @param mixed $data data - * @return string 8 byte binary string - */ -function pack64le($data) { - if (is_object($data)) { - if ("Count64_32" == get_class($data)) { - $value = $data->_getValue(); - $hiBytess = $value[0]; - $loBytess = $value[1]; - } else { - $hiBytess = ($data->_getValue() & INT64_HIGH_MAP) >> 32; - $loBytess = $data->_getValue() & INT64_LOW_MAP; - } - } else if (4 == PHP_INT_SIZE) { - $hiBytess = 0; - $loBytess = $data; - } else { - $hiBytess = ($data & INT64_HIGH_MAP) >> 32; - $loBytess = $data & INT64_LOW_MAP; - } - return pack('VV', $loBytess, $hiBytess); -} - -/** - * Unpack 8 byte binary string, little endian format to 8 byte data - * - * @param string $data binary string - * @return Count64Base data - */ -function unpack64le($data) { - $bytes = unpack('V2', $data); - return Count64::construct(array( - $bytes[1], - $bytes[2] - )); -} - -abstract class Count64Base { - protected $limit32Bit = False; - - function __construct($value = 0, $limit32Bit = False) { - $this->limit32Bit = $limit32Bit; - $this->set($value); - } - - abstract public function set($value); - abstract public function add($value); - abstract public function getHiBytes(); - abstract public function getLoBytes(); - abstract public function _getValue(); - - const EXCEPTION_SET_INVALID_ARGUMENT = "Count64 object can only be set() to integer or Count64 values"; - const EXCEPTION_ADD_INVALID_ARGUMENT = "Count64 object can only be add()ed integer or Count64 values"; - const EXCEPTION_32BIT_OVERFLOW = "Count64 object limited to 32 bit (overflow)"; -} - -class Count64_32 extends Count64Base { - private $loBytes; - private $hiBytes; - - public function getHiBytes() { - return $this->hiBytes; - } - - public function getLoBytes() { - return $this->loBytes; - } - - public function _getValue() { - return array($this->hiBytes, $this->loBytes); - } - - public function set($value) { - if (is_int($value)) { - $this->loBytes = $value; - $this->hiBytes = 0; - } else if (is_array($value) && 2 == sizeof($value)) { - $this->loBytes = $value[0]; - if ($this->limit32Bit && 0 !== $value[1]) { - throw new \OverflowException(self::EXCEPTION_32BIT_OVERFLOW); - } - $this->hiBytes = $value[1]; - } else if (is_object($value) && __CLASS__ == get_class($value)) { - $value = $value->_getValue(); - if ($this->limit32Bit && 0 !== $value[0]) { - throw new \OverflowException(self::EXCEPTION_32BIT_OVERFLOW); - } - $this->hiBytes = $value[0]; - $this->loBytes = $value[1]; - } else { - throw new \InvalidArgumentException(self::EXCEPTION_SET_INVALID_ARGUMENT); - } - return $this; - } - - public function add($value) { - if (is_int($value)) { - $sum = (int) ($this->loBytes + $value); - // overflow! - if (($this->loBytes > -1 && $sum < $this->loBytes && $sum > -1) - || ($this->loBytes < 0 && ($sum < $this->loBytes || $sum > -1))) { - if ($this->limit32Bit) { - throw new \OverflowException(self::EXCEPTION_32BIT_OVERFLOW); - } - $this->hiBytes = (int) ($this->hiBytes + 1); - } - $this->loBytes = $sum; - } else if (is_object($value) && __CLASS__ == get_class($value)) { - $value = $value->_getValue(); - $sum = (int) ($this->loBytes + $value[1]); - if (($this->loBytes > -1 && $sum < $this->loBytes && $sum > -1) - || ($this->loBytes < 0 && ($sum < $this->loBytes || $sum > -1))) { - if ($this->limit32Bit) { - throw new \OverflowException(self::EXCEPTION_32BIT_OVERFLOW); - } - $this->hiBytes = (int) ($this->hiBytes + 1); - } - $this->loBytes = $sum; - if ($this->limit32Bit && 0 !== $value[0]) { - throw new \OverflowException(self::EXCEPTION_32BIT_OVERFLOW); - } - $this->hiBytes = (int) ($this->hiBytes + $value[0]); - } else { - throw new \InvalidArgumentException(self::EXCEPTION_ADD_INVALID_ARGUMENT); - } - return $this; - } -} - -class Count64_64 extends Count64Base { - private $value; - - public function getHiBytes() { - return urShift($this->value, 32); - } - - public function getLoBytes() { - return $this->value & INT64_LOW_MAP; - } - - public function _getValue() { - return $this->value; - } - - public function set($value) { - if (is_int($value)) { - if ($this->limit32Bit && INT_MAX_32 < $value) { - throw new \OverFlowException(self::EXCEPTION_32BIT_OVERFLOW); - } - $this->value = $value; - } else if (is_array($value) && 2 == sizeof($value)) { - if ($this->limit32Bit && 0 !== $value[1]) { - throw new \OverFlowException(self::EXCEPTION_32BIT_OVERFLOW); - } - $this->value = $value[1]; - $this->value = $this->value << 32; - $this->value = $this->value + $value[0]; - } else if (is_object($value) && __CLASS__ == get_class($value)) { - $value = $value->_getValue(); - if ($this->limit32Bit && INT_MAX_32 < $value) { - throw new \OverFlowException(self::EXCEPTION_32BIT_OVERFLOW); - } - $this->value = $value; - - } else { - throw new \InvalidArgumentException(self::EXCEPTION_SET_INVALID_ARGUMENT); - } - return $this; - } - - public function add($value) { - if (is_int($value)) { - $sum = (int) ($this->value + $value); - } else if (is_object($value) && __CLASS__ == get_class($value)) { - $sum = (int) ($this->value + $value->_getValue()); - } else { - throw new \InvalidArgumentException(self::EXCEPTION_ADD_INVALID_ARGUMENT); - } - if ($this->limit32Bit && INT_MAX_32 < $sum) { - throw new \OverFlowException(self::EXCEPTION_32BIT_OVERFLOW); - } - $this->value = $sum; - return $this; - } -} - -abstract class Count64 { - public static function construct($value = 0, $limit32Bit = False) { - if (4 == PHP_INT_SIZE) { - return new Count64_32($value, $limit32Bit); - } else { - return new Count64_64($value, $limit32Bit); - } - } -} diff --git a/test/phpunit.xml b/test/phpunit.xml deleted file mode 100644 index f367297..0000000 --- a/test/phpunit.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - ./ - - diff --git a/test/lib/Count64Test.php b/tests/Count64Test.php similarity index 76% rename from test/lib/Count64Test.php rename to tests/Count64Test.php index bb67874..a395c00 100644 --- a/test/lib/Count64Test.php +++ b/tests/Count64Test.php @@ -5,6 +5,13 @@ * This file is licensed under the GNU GPL version 3 or later. * See COPYING for details. */ + +use Rivimey\ZipStreamer\Count64\Count64Base; +use Rivimey\ZipStreamer\Count64\Count64; +use Rivimey\ZipStreamer\Count64\Count64_32; +use Rivimey\ZipStreamer\Count64\Count64_64; +use Rivimey\ZipStreamer\Count64\PackBits; + class TestPack extends PHPUnit_Framework_TestCase { public function providerPack16leValues() { @@ -25,7 +32,7 @@ public function providerPack16leValues() { * @dataProvider providerPack16leValues */ public function testPack16le($value, $description) { - $this->assertEquals(ZipStreamer\pack16le($value), pack('v', $value), $description); + $this->assertEquals(PackBits::pack16le($value), pack('v', $value), $description); } public function providerPack32leValues() { @@ -46,18 +53,18 @@ public function providerPack32leValues() { * @dataProvider providerPack32leValues */ public function testPack32le($value, $description) { - $this->assertEquals(ZipStreamer\pack32le($value), pack('V', $value), $description); + $this->assertEquals(PackBits::pack32le($value), pack('V', $value), $description); } public function providerPack64leValues() { # input value, expected high bytes, expected low bytes, description return array( array(0, 0, 0, "packing 0"), - array(ZipStreamer\Count64::construct(array(0xffffffff, 0x00000000)), 0xffffffff, 0x00000000, "packing pattern 0x00000000ffffffff"), - array(ZipStreamer\Count64::construct(array(0x00000000, 0xffffffff)), 0x00000000, 0xffffffff, "packing pattern 0xffffffff00000000"), - array(ZipStreamer\Count64::construct(array(0x0f0f0f0f, 0x0f0f0f0f)), 0x0f0f0f0f, 0x0f0f0f0f, "packing pattern 0x0f0f0f0f0f0f0f0f"), - array(ZipStreamer\Count64::construct(array(0xf0f0f0f0, 0xf0f0f0f0)), 0xf0f0f0f0, 0xf0f0f0f0, "packing pattern 0x00f0f0f0f0f0f0f0"), - array(ZipStreamer\Count64::construct(array(0xffffffff, 0xffffffff)), 0xffffffff, 0xffffffff, "packing maximum 64 bit value (0xffffffffffffffff)") + array(Count64::construct(array(0xffffffff, 0x00000000)), 0xffffffff, 0x00000000, "packing pattern 0x00000000ffffffff"), + array(Count64::construct(array(0x00000000, 0xffffffff)), 0x00000000, 0xffffffff, "packing pattern 0xffffffff00000000"), + array(Count64::construct(array(0x0f0f0f0f, 0x0f0f0f0f)), 0x0f0f0f0f, 0x0f0f0f0f, "packing pattern 0x0f0f0f0f0f0f0f0f"), + array(Count64::construct(array(0xf0f0f0f0, 0xf0f0f0f0)), 0xf0f0f0f0, 0xf0f0f0f0, "packing pattern 0x00f0f0f0f0f0f0f0"), + array(Count64::construct(array(0xffffffff, 0xffffffff)), 0xffffffff, 0xffffffff, "packing maximum 64 bit value (0xffffffffffffffff)") ); } @@ -65,7 +72,7 @@ public function providerPack64leValues() { * @dataProvider providerPack64leValues */ public function testPack64le($inVal, $cmpVal1, $cmpVal2, $description) { - $this->assertEquals(ZipStreamer\pack64le($inVal), pack('VV', $cmpVal1, $cmpVal2), $description); + $this->assertEquals(PackBits::pack64le($inVal), pack('VV', $cmpVal1, $cmpVal2), $description); } public function providerGoodCount64InitializationValues() { @@ -78,7 +85,7 @@ public function providerGoodCount64InitializationValues() { array(0xffffffff, 0x00000000, array(0xffffffff, 0x00000000), "bit pattern array(0xffffffff, 0x00000000)"), array(0x0f0f0f0f, 0x0f0f0f0f, array(0x0f0f0f0f, 0x0f0f0f0f), "bit pattern array(0x0f0f0f0f, 0x0f0f0f0f)"), array(0xf0f0f0f0, 0xf0f0f0f0, array(0xf0f0f0f0, 0xf0f0f0f0), "bit pattern array(0xf0f0f0f0, 0xf0f0f0f0)"), - array(0x00000000, 0x00000000, ZipStreamer\Count64::construct(0), "Count64Base object (value 0)") + array(0x00000000, 0x00000000, Count64::construct(0), "Count64Base object (value 0)") ); } @@ -86,8 +93,8 @@ public function providerGoodCount64InitializationValues() { * @dataProvider providerGoodCount64InitializationValues */ public function testCount64Construct($loBytes, $hiBytes, $value, $description) { - $count64 = ZipStreamer\Count64::construct($value); - $this->assertInstanceOf('ZipStreamer\Count64Base', $count64, $description . ' (instanceof)'); + $count64 = Count64::construct($value); + $this->assertTrue($count64 instanceof Count64Base, $description . ' (instanceof)'); $this->assertEquals($loBytes, $count64->getLoBytes(), $description . " (loBytes)"); $this->assertEquals($hiBytes, $count64->getHiBytes(), $description . " (hiBytes)"); } @@ -107,16 +114,16 @@ public function providerBadCount64InitializationValues() { * @expectedException InvalidArgumentException */ public function testCount64ConstructFail($badValue) { - $count64 = ZipStreamer\Count64::construct($badValue); + $count64 = Count64::construct($badValue); } /** * @dataProvider providerGoodCount64InitializationValues */ public function testCount64Set($loBytes, $hiBytes, $value, $description) { - $count64 = ZipStreamer\Count64::construct(); + $count64 = Count64::construct(); $count64->set($value); - $this->assertInstanceOf('ZipStreamer\Count64Base', $count64, $description . ' (instanceof)'); + $this->assertTrue($count64 instanceof Count64Base); $this->assertEquals($loBytes, $count64->getLoBytes(), $description . " (loBytes)"); $this->assertEquals($hiBytes, $count64->getHiBytes(), $description . " (hiBytes)"); } @@ -126,7 +133,7 @@ public function testCount64Set($loBytes, $hiBytes, $value, $description) { * @expectedException InvalidArgumentException */ public function testCount64SetFail($badValue) { - $count64 = ZipStreamer\Count64::construct(); + $count64 = Count64::construct(); $count64->set($badValue); } @@ -149,7 +156,7 @@ public function providerCount64AddValues() { * @dataProvider providerCount64AddValues */ public function testCount64Add($value, $add, $loBytes, $hiBytes, $description) { - $count64 = ZipStreamer\Count64::construct($value); + $count64 = Count64::construct($value); $count64->add($add); $this->assertEquals($loBytes, $count64->getLoBytes(), $description . " (loBytes)".sprintf("%x=%x", $loBytes, $count64->getLoBytes())); $this->assertEquals($hiBytes, $count64->getHiBytes(), $description . " (hiBytes)"); diff --git a/test/ZipComponents.php b/tests/ZipComponents.php similarity index 81% rename from test/ZipComponents.php rename to tests/ZipComponents.php index 52ef4ae..ee148a7 100644 --- a/test/ZipComponents.php +++ b/tests/ZipComponents.php @@ -6,9 +6,11 @@ * This file is licensed under the GNU GPL version 3 or later. * See COPYING for details. */ -namespace ZipStreamer; - -require_once "src/ZipStreamer.php"; +namespace Rivimey\ZipStreamer\Tests; +use Rivimey\ZipStreamer\Deflate\COMPR; +use Rivimey\ZipStreamer\GPFLAGS; +use Rivimey\ZipStreamer\Count64\Count64; +use Rivimey\ZipStreamer\Count64\PackBits; /** * @codeCoverageIgnore @@ -30,6 +32,26 @@ function hexIfFFFFFFFF($value) { return $value == 0xffffffff ? '0x' . dechex($value) : $value; } +class File { + const FILE = 1; + const DIR = 2; + public $filename; + public $date; + public $type; + public $data; + + public function __construct($filename, $type, $date, $data = "") { + $this->filename = $filename; + $this->type = $type; + $this->date = $date; + $this->data = $data; + } + + public function getSize() { + return strlen($this->data); + } +} + /** * @codeCoverageIgnore */ @@ -52,9 +74,9 @@ public static function setUnitTest($unitTest) { public static function getMagicBytes() { if (!array_key_exists(static::$MAGIC, self::$magicBytes)) { if (2 == static::$magicLength) { - self::$magicBytes[static::$MAGIC] = pack16le(static::$MAGIC); + self::$magicBytes[static::$MAGIC] = PackBits::pack16le(static::$MAGIC); } else { - self::$magicBytes[static::$MAGIC] = pack32le(static::$MAGIC); + self::$magicBytes[static::$MAGIC] = PackBits::pack32le(static::$MAGIC); } } return self::$magicBytes[static::$MAGIC]; @@ -137,13 +159,13 @@ public function readFromString($str, $pos, $size = -1) { if (self::getMagicBytes() != $magic) { throw new ParseException("invalid magic"); } - $this->numberDisk = (int) unpack16le(readstr($str, $pos, 2)); - $this->numberDiskStartCD = (int) unpack16le(readstr($str, $pos, 2)); - $this->numberEntriesDisk = (int) unpack16le(readstr($str, $pos, 2)); - $this->numberEntriesCD = (int) unpack16le(readstr($str, $pos, 2)); - $this->size = (int) unpack32le(readstr($str, $pos, 4)); - $this->offsetStart = (int) unpack32le(readstr($str, $pos, 4)); - $this->lengthComment = unpack16le(readstr($str, $pos, 2)); + $this->numberDisk = (int) PackBits::unpack16le(readstr($str, $pos, 2)); + $this->numberDiskStartCD = (int) PackBits::unpack16le(readstr($str, $pos, 2)); + $this->numberEntriesDisk = (int) PackBits::unpack16le(readstr($str, $pos, 2)); + $this->numberEntriesCD = (int) PackBits::unpack16le(readstr($str, $pos, 2)); + $this->size = (int) PackBits::unpack32le(readstr($str, $pos, 4)); + $this->offsetStart = (int) PackBits::unpack32le(readstr($str, $pos, 4)); + $this->lengthComment = PackBits::unpack16le(readstr($str, $pos, 2)); if (0 < $this->lengthComment) { $this->comment = (string) readstr($str, $pos, $this->lengthComment); } else { @@ -195,9 +217,9 @@ public function readFromString($str, $pos, $size = -1) { if (static::getMagicBytes() != $magic) { throw new ParseException("invalid magic"); } - $this->numberDiskStartZ64EOCDL = (int) unpack32le(readstr($str, $pos, 4)); - $this->offsetStart = unpack64le(readstr($str, $pos, 8)); - $this->numberDisks = (int) unpack32le(readstr($str, $pos, 4)); + $this->numberDiskStartZ64EOCDL = (int) PackBits::unpack32le(readstr($str, $pos, 4)); + $this->offsetStart = PackBits::unpack64le(readstr($str, $pos, 8)); + $this->numberDisks = (int) PackBits::unpack32le(readstr($str, $pos, 4)); $this->end = $pos - 1; } } @@ -262,15 +284,15 @@ public function readFromString($str, $pos, $size = -1) { if (static::getMagicBytes() != $magic) { throw new ParseException("invalid magic"); } - $this->size = unpack64le(readstr($str, $pos, 8)); + $this->size = PackBits::unpack64le(readstr($str, $pos, 8)); $this->madeByVersion = readstr($str, $pos, 2); $this->versionToExtract = readstr($str, $pos, 2); - $this->numberDisk = (int) unpack32le(readstr($str, $pos, 4)); - $this->numberDiskStartCDR = (int) unpack32le(readstr($str, $pos, 4)); - $this->numberEntriesDisk = unpack64le(readstr($str, $pos, 8)); - $this->numberEntriesCD = unpack64le(readstr($str, $pos, 8)); - $this->sizeCD = unpack64le(readstr($str, $pos, 8)); - $this->offsetStart = unpack64le(readstr($str, $pos, 8)); + $this->numberDisk = (int) PackBits::unpack32le(readstr($str, $pos, 4)); + $this->numberDiskStartCDR = (int) PackBits::unpack32le(readstr($str, $pos, 4)); + $this->numberEntriesDisk = PackBits::unpack64le(readstr($str, $pos, 8)); + $this->numberEntriesCD = PackBits::unpack64le(readstr($str, $pos, 8)); + $this->sizeCD = PackBits::unpack64le(readstr($str, $pos, 8)); + $this->offsetStart = PackBits::unpack64le(readstr($str, $pos, 8)); $this->end = $pos - 1; } } @@ -359,16 +381,16 @@ public function readFromString($str, $pos, $size = -1) { $this->gpFlags = readstr($str, $pos, 2); $this->gzMethod = readstr($str, $pos, 2); $this->dosTime = readstr($str, $pos, 4); - $this->dataCRC32 = (int) unpack32le(readstr($str, $pos, 4)); - $this->sizeCompressed = (int) unpack32le(readstr($str, $pos, 4)); - $this->size = (int) unpack32le(readstr($str, $pos, 4)); - $this->lengthFilename = (int) unpack16le(readstr($str, $pos, 2)); - $this->lengthExtraField = (int) unpack16le(readstr($str, $pos, 2)); - $this->lengthComment = (int) unpack16le(readstr($str, $pos, 2)); - $this->diskNumberStart = (int) unpack16le(readstr($str, $pos, 2)); + $this->dataCRC32 = (int) PackBits::unpack32le(readstr($str, $pos, 4)); + $this->sizeCompressed = (int) PackBits::unpack32le(readstr($str, $pos, 4)); + $this->size = (int) PackBits::unpack32le(readstr($str, $pos, 4)); + $this->lengthFilename = (int) PackBits::unpack16le(readstr($str, $pos, 2)); + $this->lengthExtraField = (int) PackBits::unpack16le(readstr($str, $pos, 2)); + $this->lengthComment = (int) PackBits::unpack16le(readstr($str, $pos, 2)); + $this->diskNumberStart = (int) PackBits::unpack16le(readstr($str, $pos, 2)); $this->fileAttrInternal = readstr($str, $pos, 2); $this->fileAttrExternal = readstr($str, $pos, 4); - $this->offsetStart = (int) unpack32le(readstr($str, $pos, 4)); + $this->offsetStart = (int) PackBits::unpack32le(readstr($str, $pos, 4)); if (0 < $this->lengthFilename) { $this->filename = (string) readstr($str, $pos, $this->lengthFilename); } else { @@ -433,11 +455,11 @@ public function readFromString($str, $pos, $size = -1) { if (static::getMagicBytes() != $magic) { throw new ParseException("invalid magic"); } - $this->sizeField = (int) unpack16le(readstr($str, $pos, 2)); - $this->size = unpack64le(readstr($str, $pos, 8)); - $this->sizeCompressed = unpack64le(readstr($str, $pos, 8)); - $this->offsetStart = unpack64le(readstr($str, $pos, 8)); - $this->diskNumberStart = (int) unpack16le(readstr($str, $pos, 4)); + $this->sizeField = (int) PackBits::unpack16le(readstr($str, $pos, 2)); + $this->size = PackBits::unpack64le(readstr($str, $pos, 8)); + $this->sizeCompressed = PackBits::unpack64le(readstr($str, $pos, 8)); + $this->offsetStart = PackBits::unpack64le(readstr($str, $pos, 8)); + $this->diskNumberStart = (int) PackBits::unpack16le(readstr($str, $pos, 4)); $this->end = $pos - 1; } @@ -513,9 +535,9 @@ public function __toString() { "Filename length: %d\n" . "Extra field length: %d\n" . "Filename: %s\n" , - bin2hex($this->versionToExtract), - bin2hex($this->gpFlags), - bin2hex($this->gzMethod), + PackBits::bin2hex($this->versionToExtract), + PackBits::bin2hex($this->gpFlags), + PackBits::bin2hex($this->gzMethod), $this->dosTime, $this->dataCRC32, hexIfFFFFFFFF($this->sizeCompressed), @@ -542,14 +564,14 @@ public function readFromString($str, $pos, $size = -1) { throw new ParseException("invalid magic"); } $this->versionToExtract = readstr($str, $pos, 2); - $this->gpFlags = (int) unpack16le(readstr($str, $pos, 2)); - $this->gzMethod = (int) unpack16le(readstr($str, $pos, 2)); + $this->gpFlags = (int) PackBits::unpack16le(readstr($str, $pos, 2)); + $this->gzMethod = (int) PackBits::unpack16le(readstr($str, $pos, 2)); $this->dosTime = readstr($str, $pos, 4); - $this->dataCRC32 = (int) unpack32le(readstr($str, $pos, 4)); - $this->sizeCompressed = (int) unpack32le(readstr($str, $pos, 4)); - $this->size = (int) unpack32le(readstr($str, $pos, 4)); - $this->lengthFilename = (int) unpack16le(readstr($str, $pos, 2)); - $this->lengthExtraField = (int) unpack16le(readstr($str, $pos, 2)); + $this->dataCRC32 = (int) PackBits::unpack32le(readstr($str, $pos, 4)); + $this->sizeCompressed = (int) PackBits::unpack32le(readstr($str, $pos, 4)); + $this->size = (int) PackBits::unpack32le(readstr($str, $pos, 4)); + $this->lengthFilename = (int) PackBits::unpack16le(readstr($str, $pos, 2)); + $this->lengthExtraField = (int) PackBits::unpack16le(readstr($str, $pos, 2)); if (0 < $this->lengthFilename) { $this->filename = (string) readstr($str, $pos, $this->lengthFilename); } else { @@ -591,13 +613,13 @@ public static function constructFromString($str, $offset = 0, $size = -1) { public function readFromString($str, $pos, $size = -1) { $this->begin = $pos; - $this->dataCRC32 = (int) unpack32le(readstr($str, $pos, 4)); + $this->dataCRC32 = (int) PackBits::unpack32le(readstr($str, $pos, 4)); if (20 == $size) { - $this->sizeCompressed = unpack64le(readstr($str, $pos, 8)); - $this->size = unpack64le(readstr($str, $pos, 8)); + $this->sizeCompressed = PackBits::unpack64le(readstr($str, $pos, 8)); + $this->size = PackBits::unpack64le(readstr($str, $pos, 8)); } else { - $this->sizeCompressed = Count64::construct((int) unpack32le(readstr($str, $pos, 4))); - $this->size = Count64::construct((int) unpack32le(readstr($str, $pos, 4))); + $this->sizeCompressed = Count64::construct((int) PackBits::unpack32le(readstr($str, $pos, 4))); + $this->size = Count64::construct((int) PackBits::unpack32le(readstr($str, $pos, 4))); } $this->end = $pos - 1; } diff --git a/test/ZipStreamerTest.php b/tests/ZipStreamerTest.php similarity index 78% rename from test/ZipStreamerTest.php rename to tests/ZipStreamerTest.php index a4743e6..1f905de 100644 --- a/test/ZipStreamerTest.php +++ b/tests/ZipStreamerTest.php @@ -6,30 +6,22 @@ * This file is licensed under the GNU GPL version 3 or later. * See COPYING for details. */ -namespace ZipStreamer; - -require "src/ZipStreamer.php"; -require "test/ZipComponents.php"; - -class File { - const FILE = 1; - const DIR = 2; - public $filename; - public $date; - public $type; - public $data; - - public function __construct($filename, $type, $date, $data = "") { - $this->filename = $filename; - $this->type = $type; - $this->date = $date; - $this->data = $data; - } - - public function getSize() { - return strlen($this->data); - } -} +namespace Rivimey\ZipStreamer\Tests; + +use Rivimey\ZipStreamer; +use Rivimey\ZipStreamer\Count64\PackBits; +use Rivimey\ZipStreamer\DOS; +use Rivimey\ZipStreamer\UNIX; +use Rivimey\ZipStreamer\GPFLAGS; +use Rivimey\ZipStreamer\Count64\Count64; +use Rivimey\ZipStreamer\Count64\Count64_32; +use Rivimey\ZipStreamer\Count64\Count64_64; +use Rivimey\ZipStreamer\Deflate\COMPR; +use Rivimey\ZipStreamer\Deflate\DeflateStream; +use Rivimey\ZipStreamer\Deflate\DeflatePeclStream; +use Rivimey\ZipStreamer\Deflate\DeflateStoreStream; + +require "tests/ZipComponents.php"; class TestZipStreamer extends \PHPUnit_Framework_TestCase { const ATTR_MADE_BY_VERSION = 0x032d; // made by version (upper byte: UNIX, lower byte v4.5) @@ -110,8 +102,8 @@ protected function assertOutputZipfileOK($files, $options) { $this->assertEquals($z64eocdrec->end + 1, $z64eocdloc->begin, "Z64EOCDR directly before Z64EOCDL"); $z64eocdrec->assertValues(array( "size" => Count64::construct(44), - "madeByVersion" => pack16le(self::ATTR_MADE_BY_VERSION), - "versionToExtract" => pack16le($this->getVersionToExtract($options['zip64'], False)), + "madeByVersion" => PackBits::pack16le(self::ATTR_MADE_BY_VERSION), + "versionToExtract" => PackBits::pack16le($this->getVersionToExtract($options['zip64'], False)), "numberDisk" => 0, "numberDiskStartCDR" => 0, "numberEntriesDisk" => Count64::construct(sizeof($files)), @@ -146,15 +138,15 @@ protected function assertOutputZipfileOK($files, $options) { $this->assertArrayHasKey($filename, $files, "CDH entry has valid name"); $cdhead->assertValues(array( - "madeByVersion" => pack16le(self::ATTR_MADE_BY_VERSION), - "versionToExtract" => pack16le($this->getVersionToExtract($options['zip64'], File::DIR == $files[$filename]->type)), - "gpFlags" => (File::FILE == $files[$filename]->type ? pack16le(GPFLAGS::ADD) : pack16le(GPFLAGS::NONE)), - "gzMethod" => (File::FILE == $files[$filename]->type ? pack16le($options['compress']) : pack16le(COMPR::STORE)), - "dosTime" => pack32le(ZipStreamer::getDosTime($files[$filename]->date)), + "madeByVersion" => PackBits::pack16le(self::ATTR_MADE_BY_VERSION), + "versionToExtract" => PackBits::pack16le($this->getVersionToExtract($options['zip64'], File::DIR == $files[$filename]->type)), + "gpFlags" => (File::FILE == $files[$filename]->type ? PackBits::pack16le(GPFLAGS::ADD) : PackBits::pack16le(GPFLAGS::NONE)), + "gzMethod" => (File::FILE == $files[$filename]->type ? PackBits::pack16le($options['compress']) : PackBits::pack16le(COMPR::STORE)), + "dosTime" => PackBits::pack32le(ZipStreamer\ZipStreamer::getDosTime($files[$filename]->date)), "lengthFilename" => strlen($filename), "lengthComment" => 0, - "fileAttrInternal" => pack16le(0x0000), - "fileAttrExternal" => (File::FILE == $files[$filename]->type ? pack32le(self::EXT_FILE_ATTR_FILE) : pack32le(self::EXT_FILE_ATTR_DIR)) + "fileAttrInternal" => PackBits::pack16le(0x0000), + "fileAttrExternal" => (File::FILE == $files[$filename]->type ? PackBits::pack32le(self::EXT_FILE_ATTR_FILE) : PackBits::pack32le(self::EXT_FILE_ATTR_DIR)) )); if ($options['zip64']) { $cdhead->assertValues(array( @@ -205,32 +197,66 @@ protected function assertOutputZipfileOK($files, $options) { if (GPFLAGS::ADD & $file->lfh->gpFlags) { $this->assertNotNull($file->dd, "data descriptor present (flag ADD set)"); } - if ($options['zip64']) { + // Flag fileheader_nonzero is set by test harness when we expect lfh to + // have size & crc values, rather than zeros with a following DataDescriptor. + if (isset($options['fileheader_nonzero']) && $options['fileheader_nonzero']) { + if ($options['zip64']) { + // if zip64, the 32 bit headers are unused: + $file->lfh->assertValues(array( + "sizeCompressed" => 0xffffffff, + "size" => 0xffffffff, + )); + // The 64 bit headers + $file->lfh->z64Ext->assertValues(array( + "sizeField" => 28, + "diskNumberStart" => 0 + )); + $this->assertGreaterThan(0, $file->lfh->z64Ext->size->getLoBytes()); + $this->assertGreaterThan(0, $file->lfh->z64Ext->sizeCompressed->getLoBytes()); + } + else { + // if 32 bit headers, it's easier: + $this->assertGreaterThan(0, $file->lfh->size); + $this->assertGreaterThan(0, $file->lfh->sizeCompressed); + } $file->lfh->assertValues(array( - "sizeCompressed" => 0xffffffff, - "size" => 0xffffffff, - )); - $file->lfh->z64Ext->assertValues(array( - "sizeField" => 28, - "size" => Count64::construct(0), - "sizeCompressed" => Count64::construct(0), - "diskNumberStart" => 0 - )); - } else { + "versionToExtract" => PackBits::pack16le($this->getVersionToExtract($options['zip64'], File::DIR == $files[$filename]->type)), + "gpFlags" => (File::FILE == $files[$filename]->type ? GPFLAGS::ADD : GPFLAGS::NONE), + "gzMethod" => (File::FILE == $files[$filename]->type ? $options['compress'] : COMPR::STORE), + "dosTime" => PackBits::pack32le(ZipStreamer\ZipStreamer::getDosTime($files[$filename]->date)), + "lengthFilename" => strlen($filename), + "filename" => $filename + )); + } + else { + if ($options['zip64']) { + $file->lfh->assertValues(array( + "sizeCompressed" => 0xffffffff, + "size" => 0xffffffff, + )); + $file->lfh->z64Ext->assertValues(array( + "sizeField" => 28, + "size" => Count64::construct(0), + "sizeCompressed" => Count64::construct(0), + "diskNumberStart" => 0 + )); + } + else { + $file->lfh->assertValues(array( + "sizeCompressed" => 0, + "size" => 0, + )); + } $file->lfh->assertValues(array( - "sizeCompressed" => 0, - "size" => 0, - )); + "versionToExtract" => PackBits::pack16le($this->getVersionToExtract($options['zip64'], File::DIR == $files[$filename]->type)), + "gpFlags" => (File::FILE == $files[$filename]->type ? GPFLAGS::ADD : GPFLAGS::NONE), + "gzMethod" => (File::FILE == $files[$filename]->type ? $options['compress'] : COMPR::STORE), + "dosTime" => PackBits::pack32le(ZipStreamer\ZipStreamer::getDosTime($files[$filename]->date)), + "dataCRC32" => 0x0000, + "lengthFilename" => strlen($filename), + "filename" => $filename + )); } - $file->lfh->assertValues(array( - "versionToExtract" => pack16le($this->getVersionToExtract($options['zip64'], File::DIR == $files[$filename]->type)), - "gpFlags" => (File::FILE == $files[$filename]->type ? GPFLAGS::ADD : GPFLAGS::NONE), - "gzMethod" => (File::FILE == $files[$filename]->type ? $options['compress'] : COMPR::STORE), - "dosTime" => pack32le(ZipStreamer::getDosTime($files[$filename]->date)), - "dataCRC32" => 0x0000, - "lengthFilename" => strlen($filename), - "filename" => $filename - )); $endLastFile = $file->end; $first = False; @@ -308,7 +334,7 @@ public function testSendHeadersOKWithRegularBrowser(array $arguments, $description, $browser, $expectedDisposition) { - $zip = new ZipStreamer(array( + $zip = new ZipStreamer\ZipStreamer(array( 'outstream' => $this->outstream )); $_SERVER['HTTP_USER_AGENT'] = $browser; @@ -388,7 +414,7 @@ public function providerZipfileOK() { */ public function testZipfile($options, $files, $description) { $options = array_merge($options, array('outstream' => $this->outstream)); - $zip = new ZipStreamer($options); + $zip = new ZipStreamer\ZipStreamer($options); foreach ($files as $file) { if (File::DIR == $file->type) { $zip->addEmptyDir($file->filename, array('timestamp' => $file->date)); @@ -402,6 +428,7 @@ public function testZipfile($options, $files, $description) { } $zip->finalize(); + $options['fileheader_nonzero'] = FALSE; $this->assertOutputZipfileOK($files, $options); } @@ -410,7 +437,7 @@ public function testZipfile($options, $files, $description) { */ public function testZipfileString($options, $files, $description) { $options = array_merge($options, array('outstream' => $this->outstream)); - $zip = new ZipStreamer($options); + $zip = new ZipStreamer\ZipStreamer($options); foreach ($files as $file) { if (File::DIR == $file->type) { $zip->addEmptyDir($file->filename, array('timestamp' => $file->date)); @@ -420,6 +447,7 @@ public function testZipfileString($options, $files, $description) { } $zip->finalize(); + $options['fileheader_nonzero'] = TRUE; $this->assertOutputZipfileOK($files, $options); } @@ -428,7 +456,7 @@ public function testZipfileString($options, $files, $description) { */ public function testZipfileStreamed($options, $files, $description) { $options = array_merge($options, array('outstream' => $this->outstream)); - $zip = new ZipStreamer($options); + $zip = new ZipStreamer\ZipStreamer($options); foreach ($files as $file) { if (File::DIR == $file->type) { $zip->addEmptyDir($file->filename, array('timestamp' => $file->date)); @@ -440,6 +468,7 @@ public function testZipfileStreamed($options, $files, $description) { } $zip->finalize(); + $options['fileheader_nonzero'] = FALSE; $this->assertOutputZipfileOK($files, $options); } @@ -449,7 +478,7 @@ public function testZipfileStreamed($options, $files, $description) { */ public function testIssue29() { $options = array('zip64' => True,'compress' => COMPR::DEFLATE, 'outstream' => $this->outstream); - $zip = new ZipStreamer($options); + $zip = new ZipStreamer\ZipStreamer($options); $stream = fopen('php://memory', 'r+'); $zip->addFileFromStream($stream, "test.bin"); fclose($stream); diff --git a/tests/bootstrap.php b/tests/bootstrap.php new file mode 100644 index 0000000..cc79889 --- /dev/null +++ b/tests/bootstrap.php @@ -0,0 +1,6 @@ + Date: Wed, 20 Jul 2016 15:55:15 +0100 Subject: [PATCH 13/24] Add in many of the other compression method constants, and use decimal (it is not a bitmask) --- src/Deflate/COMPR.php | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/Deflate/COMPR.php b/src/Deflate/COMPR.php index 2548277..e9106d3 100644 --- a/src/Deflate/COMPR.php +++ b/src/Deflate/COMPR.php @@ -33,9 +33,14 @@ namespace Rivimey\ZipStreamer\Deflate; class COMPR { - // compression method - const STORE = 0x0000; // 0 - The file is stored (no compression) - const DEFLATE = 0x0008; // 8 - The file is deflated + // Compression method: APPNOTE: 4.4.5 + const STORE = 0; // 0 - The file is stored (== no compression) + const SHRUNK = 1; // 1 - The file is Shrunk + const IMPLODE = 6; // 6 - Compressed using Implode + const DEFLATE = 8; // 8 - Compressed using Deflate + const DEFLATE64 = 9; // 9 - Enhanced Deflate using Deflate64 + const BZIP2 = 12; // 12 - Compressed using BZIP2 + const LZMA = 14; // 14 - Compressed using LMZA (EFS) // compression level (for deflate compression) const NONE = 0; From ac1036230f7dc31f97840ad328e76e454f6ef392 Mon Sep 17 00:00:00 2001 From: Ruth Ivimey-Cook Date: Wed, 20 Jul 2016 15:56:47 +0100 Subject: [PATCH 14/24] Switch to using 1< Date: Wed, 20 Jul 2016 15:57:10 +0100 Subject: [PATCH 15/24] Fix handling of GPFLAGS::ADD and crc calculation. --- src/ZipStreamer.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/ZipStreamer.php b/src/ZipStreamer.php index b5ddb73..7123114 100644 --- a/src/ZipStreamer.php +++ b/src/ZipStreamer.php @@ -325,7 +325,7 @@ public function addFileClose() { $this->write($data); } $this->flush(); - $this->dataCRC32 = unpack('N', hash_final($this->hashCtx, true)); + $this->dataCRC32 = hexdec(hash_final($this->hashCtx)); $ddLength = $this->addDataDescriptor($this->dataLength, $this->gzLength, $this->dataCRC32); @@ -378,7 +378,7 @@ public function addFileFromString($data, $filePath, $options = NULL) { } $this->filePath = self::normalizeFilePath($filePath); - $this->gpFlags = GPFLAGS::ADD; + $this->gpFlags = GPFLAGS::NONE; $this->dataLength = Count64::construct(0, !$this->zip64); $this->gzLength = Count64::construct(0, !$this->zip64); @@ -585,8 +585,8 @@ private function streamFileData($stream, $compress, $level) { $this->flush(); } - $crc = unpack('N', hash_final($hashCtx, true)); - return array($dataLength, $gzLength, $crc[1]); + $crc = hexdec(hash_final($hashCtx)); + return array($dataLength, $gzLength, $crc); } private function buildZip64ExtendedInformationField($dataLength = 0, $gzLength = 0) { From 13ff0f59045439712186b8b06140da14216ef05f Mon Sep 17 00:00:00 2001 From: Ruth Ivimey-Cook Date: Wed, 20 Jul 2016 15:57:44 +0100 Subject: [PATCH 16/24] Fix test code to test with the psr-4 layout and with files that don't use DD. --- tests/ZipStreamerTest.php | 178 +++++++++++++++++++++++++++++--------- 1 file changed, 136 insertions(+), 42 deletions(-) diff --git a/tests/ZipStreamerTest.php b/tests/ZipStreamerTest.php index 1f905de..2f8bda2 100644 --- a/tests/ZipStreamerTest.php +++ b/tests/ZipStreamerTest.php @@ -1,5 +1,4 @@ * @@ -71,6 +70,7 @@ protected function assertOutputZipfileOK($files, $options) { return $element->filename; }, $files), $files); } + $fileheader_nonzero = (isset($options['fileheader_nonzero']) && $options['fileheader_nonzero']); $output = $this->getOutput(); $eocdrec = EndOfCentralDirectoryRecord::constructFromString($output); @@ -140,7 +140,7 @@ protected function assertOutputZipfileOK($files, $options) { $cdhead->assertValues(array( "madeByVersion" => PackBits::pack16le(self::ATTR_MADE_BY_VERSION), "versionToExtract" => PackBits::pack16le($this->getVersionToExtract($options['zip64'], File::DIR == $files[$filename]->type)), - "gpFlags" => (File::FILE == $files[$filename]->type ? PackBits::pack16le(GPFLAGS::ADD) : PackBits::pack16le(GPFLAGS::NONE)), + "gpFlags" => ((!$fileheader_nonzero && File::FILE == $files[$filename]->type) ? PackBits::pack16le(GPFLAGS::ADD) : PackBits::pack16le(GPFLAGS::NONE)), "gzMethod" => (File::FILE == $files[$filename]->type ? PackBits::pack16le($options['compress']) : PackBits::pack16le(COMPR::STORE)), "dosTime" => PackBits::pack32le(ZipStreamer\ZipStreamer::getDosTime($files[$filename]->date)), "lengthFilename" => strlen($filename), @@ -179,6 +179,9 @@ protected function assertOutputZipfileOK($files, $options) { $first = True; foreach ($cdheaders as $filename => $cdhead) { + $file = $files[$filename]; + $origFileSize = $file->getSize(); + if ($options['zip64']) { $sizeCompressed = $cdhead->z64Ext->sizeCompressed->getLoBytes(); $offsetStart = $cdhead->z64Ext->offsetStart->getLoBytes(); @@ -191,37 +194,48 @@ protected function assertOutputZipfileOK($files, $options) { } else { $this->assertEquals($endLastFile + 1, $offsetStart, "file immediately after last file"); } - $file = FileEntry::constructFromString($output, $offsetStart, $sizeCompressed); - $this->assertEquals($files[$filename]->data, $file->data); - $this->assertEquals(crc32($files[$filename]->data), $cdhead->dataCRC32); - if (GPFLAGS::ADD & $file->lfh->gpFlags) { - $this->assertNotNull($file->dd, "data descriptor present (flag ADD set)"); + $fileentry = FileEntry::constructFromString($output, $offsetStart, $sizeCompressed); + $this->assertEquals($file->data, $fileentry->data, 'CDH Data'); + $this->assertEquals(hexdec(hash('crc32b', $file->data)), $cdhead->dataCRC32, 'CDH CRC32'); + if (GPFLAGS::ADD & $fileentry->lfh->gpFlags) { + $this->assertNotNull($fileentry->dd, "data descriptor present (flag ADD set)"); } - // Flag fileheader_nonzero is set by test harness when we expect lfh to + else { + $this->assertNull($fileentry->dd, "data descriptor NOT present (flag ADD unset)"); + } + + // Flag fileheader_nonzero is set by test harness when we expect LocalFileHeader to // have size & crc values, rather than zeros with a following DataDescriptor. - if (isset($options['fileheader_nonzero']) && $options['fileheader_nonzero']) { + if ($fileheader_nonzero) { if ($options['zip64']) { // if zip64, the 32 bit headers are unused: - $file->lfh->assertValues(array( + $fileentry->lfh->assertValues(array( "sizeCompressed" => 0xffffffff, "size" => 0xffffffff, )); // The 64 bit headers - $file->lfh->z64Ext->assertValues(array( + $fileentry->lfh->z64Ext->assertValues(array( "sizeField" => 28, - "diskNumberStart" => 0 + "diskNumberStart" => 0, + "size" => Count64::construct($origFileSize) )); - $this->assertGreaterThan(0, $file->lfh->z64Ext->size->getLoBytes()); - $this->assertGreaterThan(0, $file->lfh->z64Ext->sizeCompressed->getLoBytes()); + + if ($fileentry->lfh->z64Ext->sizeCompressed->getLoBytes() > 0) { + $this->assertGreaterThanOrEqual($fileentry->lfh->z64Ext->size->getLoBytes(), + $fileentry->lfh->z64Ext->sizeCompressed->getLoBytes()); + } } else { - // if 32 bit headers, it's easier: - $this->assertGreaterThan(0, $file->lfh->size); - $this->assertGreaterThan(0, $file->lfh->sizeCompressed); + // 32 bit headers, so it's easier: + if ($fileentry->lfh->sizeCompressed > 0) { + $fileentry->lfh->assertValues(array("size" => $origFileSize)); + $this->assertGreaterThanOrEqual($fileentry->lfh->size, + $fileentry->lfh->sizeCompressed); + } } - $file->lfh->assertValues(array( + $fileentry->lfh->assertValues(array( "versionToExtract" => PackBits::pack16le($this->getVersionToExtract($options['zip64'], File::DIR == $files[$filename]->type)), - "gpFlags" => (File::FILE == $files[$filename]->type ? GPFLAGS::ADD : GPFLAGS::NONE), + "gpFlags" => ((!$fileheader_nonzero && File::FILE == $files[$filename]->type) ? GPFLAGS::ADD : GPFLAGS::NONE), "gzMethod" => (File::FILE == $files[$filename]->type ? $options['compress'] : COMPR::STORE), "dosTime" => PackBits::pack32le(ZipStreamer\ZipStreamer::getDosTime($files[$filename]->date)), "lengthFilename" => strlen($filename), @@ -230,11 +244,11 @@ protected function assertOutputZipfileOK($files, $options) { } else { if ($options['zip64']) { - $file->lfh->assertValues(array( + $fileentry->lfh->assertValues(array( "sizeCompressed" => 0xffffffff, "size" => 0xffffffff, )); - $file->lfh->z64Ext->assertValues(array( + $fileentry->lfh->z64Ext->assertValues(array( "sizeField" => 28, "size" => Count64::construct(0), "sizeCompressed" => Count64::construct(0), @@ -242,14 +256,14 @@ protected function assertOutputZipfileOK($files, $options) { )); } else { - $file->lfh->assertValues(array( + $fileentry->lfh->assertValues(array( "sizeCompressed" => 0, "size" => 0, )); } - $file->lfh->assertValues(array( + $fileentry->lfh->assertValues(array( "versionToExtract" => PackBits::pack16le($this->getVersionToExtract($options['zip64'], File::DIR == $files[$filename]->type)), - "gpFlags" => (File::FILE == $files[$filename]->type ? GPFLAGS::ADD : GPFLAGS::NONE), + "gpFlags" => ((!$fileheader_nonzero && File::FILE == $files[$filename]->type) ? GPFLAGS::ADD : GPFLAGS::NONE), "gzMethod" => (File::FILE == $files[$filename]->type ? $options['compress'] : COMPR::STORE), "dosTime" => PackBits::pack32le(ZipStreamer\ZipStreamer::getDosTime($files[$filename]->date)), "dataCRC32" => 0x0000, @@ -258,7 +272,7 @@ protected function assertOutputZipfileOK($files, $options) { )); } - $endLastFile = $file->end; + $endLastFile = $fileentry->end; $first = False; } if (0 < sizeof($files)) { @@ -350,35 +364,112 @@ public function testSendHeadersOKWithRegularBrowser(array $arguments, $this->assertContainsOneMatch('/^Last-Modified: /', $headers); } + /** + * A test data source for the actual test functions (tagged with dataProvider). + * + * @return array + */ public function providerZipfileOK() { $zip64Options = array(array(True, 'True'), array(False, 'False')); $defaultLevelOption = array(array(COMPR::NORMAL, 'COMPR::NORMAL')); $compressOptions = array(array(COMPR::STORE, 'COMPR::STORE'), array(COMPR::DEFLATE, 'COMPR::DEFLATE')); $levelOptions = array(array(COMPR::NONE, 'COMPR::NONE'), array(COMPR::SUPERFAST, 'COMPR::SUPERFAST'), array(COMPR::MAXIMUM, 'COMPR::MAXIMUM')); $fileSets = array( - array( - array(), - "empty" + 'empty' => array( + 'content' => array(), ), - array( - array( + 'one empty dir' => array( + 'content' => array( new File('test/', File::DIR, 1) ), - "one empty dir" ), - array( - array( + 'one file' => array( + 'content' => array( new File('test1.txt', File::FILE, 1, 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed elit diam, posuere vel aliquet et, malesuada quis purus. Aliquam mattis aliquet massa, a semper sem porta in. Aliquam consectetur ligula a nulla vestibulum dictum. Interdum et malesuada fames ac ante ipsum primis in faucibus. Nullam luctus faucibus urna, accumsan cursus neque laoreet eu. Suspendisse potenti. Nulla ut feugiat neque. Maecenas molestie felis non purus tempor, in blandit ligula tincidunt. Ut in tortor sit amet nisi rutrum vestibulum vel quis tortor. Sed bibendum mauris sit amet gravida tristique. Ut hendrerit sapien vel tellus dapibus, eu pharetra nulla adipiscing. Donec in quam faucibus, cursus lacus sed, elementum ligula. Morbi volutpat vel lacus malesuada condimentum. Fusce consectetur nisl euismod justo volutpat sodales.') ), - "one file" ), - array( - array( + 'one larger file' => array( + 'content' => array( + new File('test1.txt', File::FILE, 1, ' +Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec lectus risus, tempor eu tortor quis, luctus tempus leo. Maecenas pretium eget velit id lacinia. Morbi eget consequat urna. Quisque imperdiet lorem odio, vitae maximus tellus imperdiet et. Duis non malesuada nunc. Vestibulum non augue ut magna vehicula elementum. Maecenas imperdiet commodo augue in tempor. Etiam eget nulla ut elit dictum rhoncus sit amet eget nisi. Vivamus ac lacinia neque. Cras semper est sed arcu suscipit, eu pretium ipsum consectetur. Aenean ultrices venenatis mauris. Aenean pellentesque elit et imperdiet dignissim. +Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Donec id hendrerit odio. Etiam imperdiet vulputate aliquam. Aliquam sit amet felis lacinia, pretium ante id, porta nisl. Duis tristique placerat massa, quis tristique magna. Proin cursus vel nibh et pulvinar. Aenean venenatis ligula at pretium pharetra. Mauris convallis dictum enim venenatis suscipit. Nulla nec lacus sed sem sagittis imperdiet ac et ipsum. Aenean vitae urna ac diam aliquam bibendum ac ut leo. Aenean condimentum erat purus, sit amet placerat ante vulputate at. +Vestibulum imperdiet felis dui, iaculis aliquam enim imperdiet et. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Integer neque mi, placerat sed gravida in, tempor ut sem. Quisque pharetra diam eget turpis mattis, quis convallis turpis sollicitudin. Donec nec elementum lacus. Quisque rhoncus vel lectus ac efficitur. Integer lorem urna, ultrices a elementum vitae, hendrerit sit amet tellus. Phasellus quis maximus nibh. Mauris ante nibh, lacinia et nulla ut, fringilla fermentum ante. Vestibulum porttitor et justo sit amet molestie. +Nam bibendum in ipsum ac dictum. Pellentesque diam sapien, congue vitae varius et, suscipit vitae justo. In purus ex, vehicula vel tristique non, faucibus eget diam. Integer nec leo facilisis magna efficitur luctus semper at sapien. Nunc non purus ut libero facilisis dignissim. Duis luctus nec enim quis imperdiet. Etiam pretium lorem et orci auctor, eget hendrerit ante bibendum. Nullam imperdiet quam nec dui malesuada lobortis. Maecenas id leo nibh. Nunc non ante vel arcu porttitor scelerisque ut eu sem. Integer a facilisis nisi. Duis non fringilla arcu. Nullam viverra tincidunt ex eget faucibus. Morbi rhoncus consequat orci, non volutpat tortor blandit ac. Suspendisse tincidunt ante eu leo pretium pellentesque. +Etiam ac arcu porta, dictum nisi at, interdum enim. Sed sit amet scelerisque libero, et lobortis libero. Phasellus sed facilisis libero, a volutpat orci. Nunc quis rhoncus eros. Fusce facilisis leo a volutpat ornare. Donec sit amet leo porta erat mollis hendrerit. Pellentesque eget ante iaculis, condimentum nisl a, dictum lectus. Fusce bibendum posuere nibh eget eleifend. Duis ut ex ante. Mauris ut tortor nec felis vulputate dapibus. Morbi quis nibh quis libero volutpat viverra at vestibulum libero. Curabitur feugiat vel arcu nec gravida. Etiam sed dui ut elit volutpat congue. Duis ac feugiat justo. Donec sed nibh mollis, scelerisque enim sed, cursus enim. +Quisque consectetur, dui eu pretium ultricies, mauris odio tempus nulla, eget consectetur lectus justo vehicula quam. Mauris tristique odio suscipit arcu faucibus euismod. Duis vel nunc sem. Integer felis nisi, varius id laoreet vitae, dignissim egestas ipsum. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Cras id rhoncus magna, ut bibendum ante. Suspendisse mattis suscipit justo, a tempus dolor cursus et. Quisque diam tortor, tempus rhoncus nisi quis, malesuada rhoncus leo. Aenean vitae ex urna. Nam interdum sapien cursus ultrices scelerisque. Mauris diam diam, rhoncus vitae vulputate eget, rhoncus eget quam. Proin pretium lorem vitae purus euismod, in rhoncus mi pulvinar. Phasellus tincidunt dignissim rhoncus. +Pellentesque vehicula metus non cursus vehicula. Pellentesque faucibus, dolor ac vehicula condimentum, urna eros varius nulla, eu vulputate libero lacus quis metus. Nulla sodales urna sit amet velit dictum, eu ullamcorper odio finibus. Donec vitae nulla consectetur, accumsan tortor ac, ultrices eros. Phasellus magna ante, egestas vel posuere nec, vulputate sit amet libero. Morbi et imperdiet neque. Proin cursus nibh quis neque lobortis, at placerat quam facilisis. Vivamus ultrices egestas sodales. Nunc nec posuere dolor, sit amet pharetra lacus. In hac habitasse platea dictumst. +Sed sed magna et odio bibendum ornare. Etiam auctor odio velit, non scelerisque nibh aliquam et. Vestibulum accumsan vehicula velit vitae tempus. Duis non congue ante, non commodo lorem. Integer eleifend dui sed eros bibendum, pulvinar auctor ipsum consequat. Mauris sit amet justo ac nulla interdum semper. Aenean volutpat nibh non nisl consectetur, non gravida magna facilisis. Cras at volutpat nibh. Quisque egestas mollis augue, quis fermentum augue blandit quis. Cras fringilla congue nisi, eget hendrerit leo interdum id. Nulla facilisi. Aliquam nec mi id justo sagittis tristique. Quisque sit amet tellus sit amet turpis tincidunt convallis et et nunc. Nunc tristique non nibh vitae tristique. Aliquam tortor lacus, facilisis a velit non, vehicula egestas quam. Donec non urna vel nunc maximus condimentum. +Praesent a velit eu neque sodales rhoncus. Proin fermentum ac diam et tempor. Suspendisse hendrerit lacinia nibh id hendrerit. Integer vel risus est. Donec condimentum, lacus id accumsan interdum, arcu lectus gravida tellus, eu pellentesque urna ligula vel tortor. Nulla congue mi eget ipsum dapibus cursus. Curabitur vehicula tristique nulla a auctor. Vestibulum ut neque in turpis accumsan tincidunt. Nunc fermentum enim a felis tristique, sit amet tempor justo tempus. Ut ac tortor porttitor, sodales libero a, sodales libero. Ut varius, nulla ac placerat viverra, nunc sapien volutpat libero, ac rutrum velit felis a odio. Pellentesque ut odio ut sem vulputate volutpat. Proin a egestas augue. Vestibulum eget tellus purus. +In sit amet tristique metus, vitae eleifend nibh. Pellentesque quis mauris purus. Etiam lacinia lorem eget nisl sodales porttitor. In ac urna non sem efficitur aliquam. Nullam suscipit, lorem eget scelerisque posuere, diam augue vulputate elit, sit amet sagittis ipsum nulla quis odio. Proin felis justo, congue et nisi non, sagittis imperdiet dolor. Cras vestibulum sem libero, sed gravida felis tempor sit amet. Donec faucibus tellus ut diam laoreet molestie. +Nam ex erat, iaculis maximus ipsum non, blandit tempor risus. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris vitae ultrices turpis. In nec vehicula nibh. Sed sed pretium nulla. In hac habitasse platea dictumst. Integer posuere ipsum sed feugiat facilisis. Suspendisse non quam quis lacus venenatis venenatis. Suspendisse scelerisque, ipsum et venenatis ullamcorper, mauris velit consequat magna, et laoreet nunc augue nec elit. Morbi aliquam, felis eget ornare ullamcorper, nulla tortor mollis erat, et pellentesque est urna et urna. Ut scelerisque non augue blandit iaculis. Phasellus rutrum auctor ex, et pellentesque mauris lacinia vel. Praesent bibendum, quam id interdum molestie, sem magna pulvinar tellus, eget porta justo libero a elit. Fusce magna ante, sollicitudin vitae odio nec, elementum accumsan turpis. Lorem ipsum dolor sit amet, consectetur adipiscing elit. +Integer sodales consequat tellus eu iaculis. Quisque accumsan ex in nulla eleifend lobortis. Nullam cursus sed diam eleifend pharetra. Aenean consectetur finibus mi, id dapibus sem elementum sed. Nunc ac quam orci. Etiam sollicitudin leo elit, sit amet dictum odio luctus nec. Sed interdum condimentum vulputate. Sed eu nisi ut nibh pulvinar cursus. Praesent at magna urna. Sed non tempus erat, nec posuere tortor. In at turpis porta, suscipit libero ut, placerat ex. Suspendisse eu blandit quam, eget eleifend metus. Donec sollicitudin viverra fermentum. Sed pretium pulvinar diam ut iaculis. +Quisque a elementum tellus. Fusce nec enim ullamcorper, egestas turpis quis, ullamcorper ligula. Vivamus convallis velit id nunc malesuada ullamcorper. Donec pharetra felis fermentum nulla dignissim, dapibus viverra dui volutpat. Duis ornare ante tellus, id porttitor erat pellentesque eget. Sed dignissim orci sit amet risus placerat, vel dignissim odio commodo. Nullam eros sem, porta vel egestas nec, cursus id tortor. Duis vitae quam quam. Maecenas commodo turpis tempus efficitur eleifend. Etiam fringilla scelerisque magna, ut rutrum ex aliquet a. Duis vulputate nisl at tincidunt posuere. Donec dignissim augue tellus, et ultricies velit maximus ac. Vestibulum a est ante. +Aliquam erat volutpat. Aenean non turpis diam. Cras auctor felis purus. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Phasellus ullamcorper ipsum eu vehicula auctor. Praesent efficitur tempus dolor non scelerisque. Donec vel sapien at dolor porttitor tempor. +Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Pellentesque dapibus est a mi vulputate suscipit. Sed dignissim nibh eget metus maximus sagittis. Suspendisse potenti. Nullam id luctus urna, id iaculis sem. Nunc dui nulla, euismod et cursus a, aliquet ac odio. Maecenas bibendum varius urna. Nullam convallis mi vitae condimentum molestie. Sed rhoncus pellentesque tellus, eget hendrerit tortor consectetur vel. Sed posuere turpis sed facilisis dictum. Suspendisse non leo dui. Donec interdum bibendum ipsum, sed iaculis tortor malesuada id. Donec tincidunt tortor vel porta tincidunt. Quisque scelerisque nisi nec sem pharetra, sed porta ipsum elementum.' + ) + ), + ), + 'large file' => array( + 'content' => array( + new File('test1.txt', File::FILE, 1, ' +Duis fermentum egestas enim eget ullamcorper. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Suspendisse fringilla quam id tortor sagittis ultricies. Praesent vel maximus odio, quis interdum ligula. Duis metus ante, sollicitudin a condimentum viverra, ultrices id sapien. Fusce nec urna quam. Donec blandit molestie tempus. Etiam tempor vitae leo a lacinia. Praesent aliquet non leo sit amet imperdiet. Etiam maximus, ipsum vitae porttitor tincidunt, elit quam ultrices enim, a malesuada justo diam vitae purus. Vestibulum rhoncus tristique tincidunt. Nunc et blandit ante, eu blandit lectus. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; +Donec non ligula cursus, tincidunt neque at, auctor lacus. Suspendisse varius orci placerat, posuere felis id, sagittis orci. Nullam varius hendrerit lectus non vulputate. Duis sagittis sodales urna at aliquet. Quisque vitae turpis ex. Pellentesque vitae interdum lacus, ac rutrum metus. In vestibulum turpis dolor, non elementum lorem finibus ac. Sed metus libero, convallis et vulputate elementum, feugiat et diam. Phasellus fringilla fermentum ante, a imperdiet justo tempor sit amet. Mauris consectetur sit amet odio nec venenatis. Integer malesuada elit ipsum, ut auctor diam auctor a. +Nulla facilisi. Phasellus volutpat fermentum lectus ac consectetur. Aliquam nulla augue, accumsan eget orci vitae, euismod ultrices eros. Pellentesque ultrices urna lacus, eu iaculis urna cursus sit amet. Aliquam gravida non sapien sed cursus. Sed viverra urna facilisis, aliquam nisl id, ultrices sapien. Nam mollis ultrices nunc sit amet placerat. Etiam id lorem a tortor viverra porta. Suspendisse finibus ultrices tristique. Vestibulum arcu ex, facilisis a ante sed, ultrices bibendum massa. Suspendisse justo quam, pellentesque eget orci sed, varius sagittis purus. Duis in maximus magna, a facilisis mi. Morbi elementum pulvinar justo. Nulla facilisi. Sed ac finibus urna. Ut felis sem, sagittis a justo id, feugiat interdum nibh. +Curabitur tristique bibendum ligula ut congue. In faucibus arcu sit amet diam iaculis posuere. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec nec libero blandit, porta augue quis, ultrices est. Aenean ultrices risus erat, vitae semper odio imperdiet eget. Phasellus dapibus id nulla iaculis aliquet. Aliquam sagittis quis diam vel pretium. Fusce ac egestas massa. Nunc tristique tincidunt quam et finibus. +In eu elit sed sapien pulvinar maximus. Suspendisse fermentum consequat sapien, vitae bibendum ante pharetra vitae. In placerat magna ac neque convallis aliquam eu nec turpis. Sed ut nisl eget ante blandit tempor vel id nisl. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Vivamus id mauris a neque sodales volutpat non et est. Vestibulum rutrum pharetra mauris sed aliquet. Morbi auctor elementum justo ut vestibulum. Pellentesque ac tellus auctor, laoreet tellus eget, luctus magna. Donec dolor ligula, ornare eget urna a, euismod fringilla ipsum. Interdum et malesuada fames ac ante ipsum primis in faucibus. Ut eget magna a nunc imperdiet accumsan. Donec vitae mollis lacus. Cras faucibus viverra purus. Donec nec quam in quam finibus bibendum. Ut eget quam sem. +Sed sed ligula sed nisi sollicitudin accumsan vitae quis augue. Nunc sollicitudin ut augue sed hendrerit. Quisque turpis felis, hendrerit non quam et, mattis varius leo. Duis enim ipsum, porttitor vitae dui nec, pulvinar pharetra odio. Ut gravida blandit fermentum. Maecenas et commodo enim, in dignissim sem. Quisque vestibulum, metus ut aliquet sodales, quam tellus vehicula dui, vitae tincidunt neque tortor sit amet risus. Etiam ac cursus ligula. +Donec suscipit ipsum eu justo luctus consequat. Phasellus pellentesque tincidunt orci vel lobortis. Nam bibendum ex aliquam nunc varius molestie. Nullam quis enim faucibus orci accumsan congue. Nulla facilisi. Proin quis diam quis mauris consectetur auctor. Nam scelerisque diam eu diam vulputate, id fermentum risus pretium. Sed mauris ipsum, consectetur a odio ut, rutrum tempus velit. +Nunc faucibus consectetur cursus. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Praesent quis sem ac massa fringilla aliquet. Maecenas sed lectus pharetra, iaculis libero sit amet, lobortis mauris. Quisque sodales arcu id enim commodo ultricies. Phasellus accumsan, ante in dapibus tempus, velit dolor luctus turpis, at consequat ligula leo vel libero. Nullam gravida ante eu ligula facilisis fringilla. Sed auctor vitae sapien ultrices luctus. +Nam viverra turpis eros, eget rutrum orci commodo quis. Vestibulum sit amet lacus vel metus tincidunt euismod ac eget magna. Nullam bibendum velit libero, eget auctor libero ullamcorper non. Nullam sagittis id ligula et tincidunt. Sed dolor ante, mattis ac varius et, laoreet rutrum turpis. Cras sollicitudin nulla a dapibus finibus. Fusce tempor sed dui at vulputate. In efficitur luctus leo. Nam aliquam elementum justo. Donec eget iaculis justo. Etiam dapibus nulla sed libero rutrum ullamcorper. Aliquam tristique aliquet sapien et ultrices. Vivamus ut ex maximus, semper ex eget, condimentum velit. Mauris eget augue sit amet leo ultrices euismod ut ac nulla. Aenean eget mauris orci. Morbi congue enim orci, non maximus augue luctus quis. +Vestibulum rutrum lectus lacus, vel condimentum justo ultrices quis. Donec ac erat eu nisi sodales cursus. Nullam eu dolor ante. Nulla fringilla tortor ut dolor ullamcorper laoreet sed et tellus. Phasellus sapien odio, ornare et malesuada ac, semper id lorem. Duis malesuada iaculis sapien, sit amet fringilla magna vehicula posuere. Cras pellentesque est tellus, id pretium massa efficitur in. Duis id egestas leo. Etiam id aliquet lectus. +Nulla magna velit, dictum id dui quis, aliquam fringilla risus. Sed venenatis, mi sit amet consequat mattis, nisi velit imperdiet ipsum, maximus facilisis dui ipsum quis eros. Pellentesque eleifend lacus vitae quam auctor, non aliquet lacus consequat. Nam euismod venenatis vestibulum. Nunc sodales erat et diam accumsan ornare. Fusce vestibulum consectetur porttitor. Aenean maximus non tellus a sodales. +Cras et luctus arcu. Maecenas id ultrices dui. Proin mollis urna nec ligula tempor, ac ullamcorper odio placerat. Cras mi metus, condimentum ac urna in, egestas auctor neque. Vestibulum vel elementum mauris. Ut placerat quam elit, vitae ornare nisl laoreet in. Suspendisse venenatis ullamcorper nunc, eget tristique purus interdum eu. Cras ac est ac velit tincidunt varius. Suspendisse finibus ornare arcu at accumsan. Nulla a mollis est, sit amet viverra ipsum. Suspendisse a felis mattis, tincidunt felis quis, gravida justo. Suspendisse potenti. Cras consequat scelerisque lectus a imperdiet. +Duis tincidunt urna et leo tempus, a dapibus ante suscipit. Aenean pretium gravida nisl, in faucibus nibh aliquam id. Proin vel quam aliquet, sollicitudin orci in, interdum dolor. Mauris egestas non velit vitae sollicitudin. Pellentesque dignissim enim massa, et iaculis orci vehicula nec. Mauris sit amet blandit ex. Donec ac ullamcorper nisi, eu mollis tortor. Donec varius eros sit amet nunc vulputate tincidunt. +Proin molestie imperdiet dui at ultrices. Fusce blandit elit in arcu pellentesque varius. Aenean egestas arcu neque, non ornare ipsum euismod maximus. Proin egestas venenatis diam, quis feugiat diam pulvinar eget. Vestibulum ut sodales libero. Sed dapibus metus in aliquam pretium. Nulla et rutrum quam. Nullam nec nisi at velit molestie porttitor eget imperdiet lacus. +Sed tellus dui, scelerisque ac odio nec, pretium aliquet massa. Pellentesque nunc libero, malesuada efficitur urna at, congue dapibus lorem. Nunc scelerisque sed neque at faucibus. Sed posuere velit sem, vel sodales magna euismod a. Maecenas quis vehicula mi, malesuada tincidunt quam. Maecenas finibus vulputate tellus, non rutrum tortor imperdiet vel. Nunc tristique, lectus viverra mollis volutpat, turpis ante lobortis sem, nec vehicula dui ex in nisl. Vestibulum non vestibulum elit. Donec efficitur risus non massa aliquet hendrerit. Mauris convallis ultricies elit egestas porttitor. Vestibulum sed eleifend mi, et tincidunt mi. Etiam imperdiet placerat ex quis venenatis. +Aenean maximus sem lorem, finibus cursus ante tincidunt et. Vivamus hendrerit ligula velit, lacinia porta neque rutrum a. Nullam eu placerat nisl. Donec eget nulla quis sem malesuada efficitur. Aenean maximus turpis a lacus volutpat auctor. Phasellus dictum pharetra egestas. Maecenas non fringilla neque. Nunc non lectus erat. Phasellus pulvinar viverra egestas. Suspendisse semper nisi at purus tincidunt consectetur. Vestibulum sodales sapien sit amet diam ullamcorper, vitae laoreet lorem consequat. Ut felis quam, pellentesque a ultricies consequat, ornare eget quam. Donec quis interdum metus. Integer scelerisque, massa ac semper porta, magna purus pharetra libero, non malesuada nibh lectus quis orci. Praesent egestas dignissim pharetra. Duis eu eros velit. +Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nam pulvinar porttitor molestie. Donec ornare tincidunt tellus. Morbi pulvinar bibendum eleifend. Nam et auctor sem, at dignissim nulla. Quisque facilisis commodo suscipit. Sed pretium, felis ac mollis tristique, elit tellus vulputate odio, eget tincidunt sapien lacus sed dui. Donec porta, ipsum vel rhoncus congue, leo justo tristique lorem, ac rutrum quam odio vel enim. Suspendisse ut leo nec ante hendrerit pulvinar eu et risus. Ut consequat arcu ac mauris viverra, facilisis tristique urna consectetur. Ut a lectus quis magna commodo luctus. Duis et dolor turpis. +Ut ante est, fermentum sed dolor et, tempus euismod nisi. Etiam porta augue at dictum dictum. Quisque mollis vitae justo at gravida. Vestibulum ipsum nunc, accumsan vel blandit ullamcorper, sollicitudin feugiat dolor. Donec pretium rutrum diam ac efficitur. In metus libero, vulputate in rhoncus sed, aliquet a mauris. Cras diam leo, tempor nec ante ut, tincidunt blandit nunc. Duis eu dolor porttitor, ullamcorper nulla non, suscipit dolor. Ut tristique viverra sem, vel malesuada nisi euismod iaculis. Aenean molestie tristique sodales. Duis quis dui justo. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec placerat, tortor ut suscipit aliquet, enim eros pulvinar sapien, quis porta quam odio et augue. +Cras bibendum interdum massa vitae posuere. Duis ut ipsum nec ipsum viverra tincidunt sed a urna. Mauris bibendum libero sit amet nisi tempus dignissim. Suspendisse sed lorem pulvinar, scelerisque ipsum non, feugiat nunc. Quisque ut purus libero. Curabitur gravida, nisi vel malesuada egestas, est sem venenatis libero, eget dapibus turpis tellus id ligula. Vivamus eget neque viverra, mollis diam ac, consectetur massa. +Nam eu posuere quam, non tincidunt libero. Fusce cursus sapien ac est vestibulum gravida. Cras sollicitudin ornare placerat. Maecenas eu sem gravida, tempus purus sit amet, accumsan ex. Donec vehicula, sem eu ultrices mollis, nisi eros condimentum enim, at dictum mauris est non libero. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Vestibulum et bibendum felis. Proin suscipit enim in hendrerit mollis. Aenean ornare fringilla purus, sit amet dapibus orci varius sit amet. Quisque posuere libero quis velit pharetra, sed euismod nisi scelerisque. Quisque varius ipsum eu neque dictum pharetra. Fusce hendrerit mi turpis, id eleifend metus egestas ac. Donec rhoncus nisi nunc. Sed vitae neque lacinia, mollis arcu ac, molestie ante. +Sed tortor nisi, eleifend sit amet lorem eget, pretium rutrum enim. Cras mi nulla, elementum vel bibendum at, consequat dictum lectus. Suspendisse dapibus, orci at cursus efficitur, lorem magna suscipit neque, ac congue leo nibh et odio. Mauris nulla felis, vehicula eget dolor vel, interdum ultricies risus. Cras risus ligula, semper nec nisi vel, aliquam molestie neque. Donec sollicitudin vestibulum auctor. Curabitur ullamcorper ligula vitae lacus efficitur elementum. Vestibulum imperdiet id enim id rutrum. Suspendisse condimentum ut ligula vel pellentesque. +Curabitur et ligula risus. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Interdum et malesuada fames ac ante ipsum primis in faucibus. Ut finibus nibh sit amet ipsum euismod consectetur. Morbi sed nunc ac nunc pretium pellentesque in ultrices elit. Sed ornare sodales velit eget maximus. Aenean non malesuada augue, pulvinar ultricies felis. Integer id ex ut eros auctor maximus. +Fusce at porta eros, lacinia finibus lacus. In eu odio ut nulla ullamcorper rhoncus vel a augue. Vestibulum vitae purus mollis mauris dignissim venenatis. Cras massa ante, pharetra non mollis in, interdum in felis. Nulla at metus et urna aliquam viverra. Curabitur egestas ante et ultrices cursus. Nulla convallis justo non dolor ornare vehicula. Nam non pellentesque ipsum. Etiam tristique ac elit vitae semper. +Donec et sem felis. Morbi tristique nulla eu ex semper mattis. Fusce cursus id tortor imperdiet convallis. Duis sagittis felis at magna laoreet pulvinar. Donec interdum felis nunc, nec ultrices turpis tincidunt a. Nunc scelerisque tellus at mi rhoncus, eu elementum lacus malesuada. Sed felis felis, tincidunt id metus nec, accumsan dictum magna. Integer ullamcorper purus leo, sed efficitur felis cursus eu. +Aenean vel nulla ac augue elementum pretium ornare non libero. Sed dictum dolor non quam scelerisque, a fringilla erat vehicula. Vivamus at erat ac sapien porta porttitor sed non mauris. Integer lacinia lacus vehicula, pellentesque nunc eget, sollicitudin odio. Vivamus dictum pharetra iaculis. Ut eu blandit arcu. Nullam euismod pretium euismod. Morbi eget lacus efficitur, dignissim lorem eu, elementum neque. Donec turpis mi, mattis a dignissim quis, facilisis nec nibh. Nullam ut lacinia arcu. Integer et leo lectus. Proin lobortis elit ut urna mollis fringilla. Nulla in elit non enim dignissim ultrices feugiat nec lorem. Integer tristique iaculis pulvinar. Fusce facilisis rhoncus neque. +Phasellus tincidunt diam id ipsum convallis, sed imperdiet diam sollicitudin. Nunc porta rhoncus condimentum. Nullam facilisis efficitur risus nec semper. Maecenas ipsum velit, porta ut luctus vel, varius ac metus. Aliquam cursus ultricies scelerisque. Fusce imperdiet lorem ultricies, mattis sem non, finibus augue. Nulla vestibulum nisl mi, id egestas tellus consectetur eget. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Donec aliquam bibendum felis sed placerat. Suspendisse pellentesque mauris lacus, sit amet blandit lorem lobortis at. Sed et quam eget nibh imperdiet consequat in at metus. +Phasellus pellentesque tempor pharetra. Suspendisse id sagittis est, sit amet rhoncus neque. Aenean vitae magna ut arcu luctus auctor. Phasellus fermentum egestas tristique. Donec eu vestibulum mi. Nam dignissim lobortis tortor eu pulvinar. Etiam dapibus in lorem eget facilisis. Sed bibendum, turpis in rutrum rutrum, neque lorem tincidunt erat, eu commodo enim nisi a est. Maecenas vel laoreet magna. Etiam tempor augue posuere maximus ultrices. +Etiam ut placerat leo. Aenean ornare sapien eget lacus cursus sollicitudin. Pellentesque a elit consequat, viverra justo a, porta augue. Nam vitae ligula nec lectus eleifend auctor vitae vitae tellus. Nulla facilisi. Ut finibus commodo tristique. Integer vulputate augue a tellus finibus facilisis. Fusce a eleifend magna. Aenean eu auctor urna, sit amet consequat odio. +Suspendisse potenti. Curabitur ultricies hendrerit leo, sed mollis mauris hendrerit quis. Nulla facilisi. Aliquam non lectus a augue pulvinar scelerisque vitae euismod tellus. Ut tristique sem sit amet efficitur gravida. Nullam ac pulvinar arcu, sit amet porttitor lectus. Proin pulvinar neque ultrices, viverra urna sed, ullamcorper neque. Etiam sollicitudin volutpat aliquet. +Praesent vestibulum vulputate nunc vitae tincidunt. Cras euismod lectus sit amet est iaculis bibendum. Donec quis sodales ligula. Duis gravida lacinia nulla, vitae dapibus est blandit dignissim. Suspendisse sed vehicula eros, at pellentesque orci. Fusce blandit venenatis ipsum quis facilisis. Integer sollicitudin, diam sed mollis semper, ligula urna bibendum leo, et sagittis metus elit vitae sem. Integer auctor aliquam erat eu laoreet. Sed in metus odio. Mauris vel blandit arcu, et semper felis. Aliquam id fringilla dolor, nec volutpat sem. Cras ac erat erat. Duis vel justo laoreet, convallis velit sit amet, tristique nulla. Etiam vel pharetra ligula, ac ultricies est. Proin accumsan urna nunc, in cursus ligula consectetur sed. Nullam interdum, dui sit amet accumsan mattis, nisi massa iaculis turpis, eu condimentum nibh libero scelerisque metus. +Pellentesque vitae diam purus. Ut fermentum ex sed diam placerat auctor. Phasellus sollicitudin ante purus, id interdum magna lobortis sit amet. Fusce molestie, purus vel pharetra iaculis, elit ex egestas lorem, sit amet maximus odio eros eu dolor. Vivamus quis dapibus nisl, elementum sodales dolor. Nulla ac tortor quis libero interdum lobortis. Vivamus aliquam urna ac tortor scelerisque viverra id quis tortor. Vestibulum lacus sem, vestibulum eget lorem a, ullamcorper tristique ligula. Pellentesque et orci dictum tortor imperdiet luctus eu nec diam. Sed a libero in diam luctus dapibus non eu odio. Vivamus malesuada mauris et tristique hendrerit. Pellentesque fringilla et mauris vitae finibus. Aenean pretium faucibus nibh, ut mattis orci convallis vel. Fusce semper ex ex, id luctus est cursus in. +Sed ut accumsan erat, a aliquam nisi. Mauris rhoncus leo ut semper laoreet. Integer lobortis dui ac urna tempor ultricies sed consectetur tortor. Suspendisse a gravida diam, vel hendrerit ex. Suspendisse cursus libero non turpis pharetra, eget laoreet risus maximus. Nam at ullamcorper magna, quis molestie eros. Suspendisse interdum neque leo, ullamcorper ultrices ante iaculis ac. Aliquam luctus euismod libero, sed semper arcu viverra ut. Sed pretium nisi justo, ut dignissim dolor luctus vitae. Vivamus egestas neque vel accumsan volutpat. Etiam sed fermentum enim. Proin ac augue dui. +Nunc ac nulla eget elit viverra vehicula ac vel dolor. Quisque in dignissim ipsum, in fermentum mauris. In hac habitasse platea dictumst. Phasellus ut orci nec enim vehicula vehicula. Phasellus facilisis efficitur libero, nec pellentesque augue vehicula a. Nam id gravida tellus, quis semper lorem. Proin fermentum orci orci, vitae ultricies magna malesuada quis. Cras maximus tempus magna, eget bibendum orci varius ac. Sed sollicitudin fermentum magna at tempus. Maecenas euismod bibendum sem a gravida. Curabitur ultricies dapibus orci, vitae auctor ipsum bibendum vel. Nunc lobortis scelerisque risus quis mollis. Sed cursus nibh ac metus finibus consequat. Proin mattis leo at ante tincidunt, id semper metus malesuada. Quisque et enim nec quam ornare tempor ut vel neque. Donec commodo leo quis urna fermentum lacinia. +Sed dictum ex vitae pellentesque viverra. Vivamus mi metus, auctor non sodales quis, dignissim et massa. Quisque at enim rhoncus, placerat nunc in, sollicitudin purus. Aenean dictum, ex quis suscipit aliquam, mauris felis efficitur nisl, vel vestibulum sapien libero at urna. Sed at sem eget dui imperdiet faucibus. Duis purus tortor, viverra et faucibus at, interdum at ipsum. Duis iaculis in leo vel consectetur. +Morbi dignissim, turpis a porttitor pretium, lectus urna sollicitudin massa, sed pharetra leo ex et ligula. Cras augue ex, porttitor eget tempor non, faucibus in augue. Nunc sit amet lacus nec neque iaculis hendrerit eget in leo. Mauris maximus tristique quam sit amet rutrum. Praesent porta dui ligula, eu lacinia eros fermentum et. Nullam in ante eu tellus bibendum vestibulum. Phasellus magna ligula, venenatis egestas dapibus non, lacinia ac turpis. Praesent a ipsum maximus, egestas neque eget, dictum nunc. Quisque bibendum eros vitae ipsum ullamcorper, non gravida ante auctor. Fusce rhoncus malesuada augue et auctor. +Nunc magna ligula, facilisis in luctus vel, vehicula sit amet nibh. Sed semper, tellus sit amet efficitur eleifend, lorem nunc vehicula ante, et tristique massa enim vel quam. Morbi urna urna, pulvinar id ex sit amet, facilisis dapibus neque. Morbi sollicitudin arcu ex, ut ultrices mauris ornare sed. Pellentesque pellentesque, orci et consectetur scelerisque, elit nunc aliquet nibh, vitae ultricies purus nunc eu arcu. Nunc justo turpis, elementum ut nulla eu, vestibulum blandit orci. Donec eu nisi non erat aliquam sagittis. Sed posuere lacus sed imperdiet tincidunt. Proin viverra leo at felis accumsan vestibulum. Pellentesque sollicitudin vehicula est, non placerat lacus dapibus at. Curabitur nec est eros. Etiam eget malesuada nisl. Nam maximus egestas eros, nec ullamcorper mauris dictum a. Sed massa tellus, hendrerit quis arcu ut, ultricies aliquam quam. Vivamus fermentum euismod diam, vitae elementum enim tincidunt et. Integer vel felis ut ipsum scelerisque volutpat sed quis ex. +Curabitur sagittis gravida nibh, eget pharetra nulla ornare a. Mauris dictum congue neque. Aenean a tempus mi. Donec id neque aliquet, luctus mauris et, dignissim est. Proin consectetur consequat mattis. Phasellus interdum dui ut tortor porta convallis. Duis laoreet id arcu vitae ullamcorper. Cras malesuada tellus sed justo tincidunt ultricies. Nunc vel mi libero. Sed non aliquet metus, varius varius diam. Cras nec elit nisl. Etiam vestibulum ligula magna, quis hendrerit ligula vehicula nec. Aliquam faucibus diam eu lacus tincidunt, sollicitudin rutrum mauris maximus. +Nullam sollicitudin eget elit eget dapibus. Nunc congue porttitor dapibus. Nam pretium laoreet semper. Vivamus et nisi eu justo bibendum tristique. Aenean at efficitur tortor. Curabitur eu velit vulputate arcu consequat consectetur in sed ante. Integer rutrum laoreet ipsum, eget lacinia tortor elementum nec. Proin ut euismod velit. Nunc finibus, augue sed venenatis mattis, dui enim mollis quam, vel blandit mi sem non odio. Mauris nec enim eros. Morbi dictum malesuada purus, et vehicula nunc efficitur sit amet. Praesent sit amet tortor a enim tincidunt faucibus. Pellentesque imperdiet commodo vestibulum. +Maecenas dui dui, mattis non elit a, rhoncus tincidunt sem. Quisque non congue orci. Sed sit amet suscipit augue. Vestibulum lacus velit, condimentum eget est in, facilisis volutpat sapien. Nam mattis sed sem aliquet venenatis. Integer tempus gravida leo in sodales. Etiam ac orci vel metus auctor fermentum. Vestibulum sit amet mauris tortor. Donec vulputate, velit sed blandit tincidunt, ante lorem mattis purus, in hendrerit ligula lacus a arcu. Etiam consequat ut nulla nec iaculis. Proin porttitor ornare risus, non congue augue faucibus quis. Morbi et urna vestibulum, dignissim libero et, vestibulum enim. Maecenas sit amet mi sed ex blandit semper nec quis leo. +Nullam libero velit, tincidunt vitae arcu sit amet, bibendum interdum mauris. Morbi pretium felis quis lorem feugiat tincidunt nec et velit. Phasellus scelerisque elit facilisis interdum dapibus. Quisque at dui non nulla lacinia volutpat. Phasellus nunc tortor, elementum id commodo at, faucibus gravida nibh. Phasellus id risus rhoncus, condimentum urna sit amet, aliquam lacus. Nullam molestie nulla convallis urna mollis, suscipit sagittis tellus gravida. Mauris aliquet ut lectus sed fermentum. Aenean porttitor tempor nisi, et facilisis odio ornare non. Quisque rhoncus diam sollicitudin metus ultrices, et tempor quam bibendum. Aliquam commodo augue ligula, eu aliquam purus mattis sit amet. Curabitur rutrum laoreet elit at pulvinar. Proin in aliquet ante. Nulla tempor mi magna, at blandit neque ultrices sit amet. +Sed vel lorem eu nisi volutpat placerat id ut lacus. Sed a facilisis quam. Vestibulum ac lacus in nisl mattis porta. Praesent iaculis rhoncus sem at hendrerit. Quisque elementum augue at gravida maximus. Interdum et malesuada fames ac ante ipsum primis in faucibus. Pellentesque auctor neque sed enim sollicitudin, sit amet scelerisque purus tempor. Integer eget nibh ante. Cras rhoncus hendrerit aliquet. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Aenean mollis ante vel libero cursus, nec scelerisque ligula tincidunt. Maecenas at ultricies nulla. Sed malesuada odio id arcu accumsan, nec gravida urna tincidunt. +Ut id lacus ut dolor rutrum iaculis accumsan id est. Sed consectetur varius lectus et hendrerit. Quisque consequat ante a purus aliquet sodales. Maecenas pretium, mauris sit amet dignissim tempor, quam tortor consectetur eros, vitae imperdiet metus lectus a leo. Mauris sed egestas metus. Vestibulum ut gravida ante, nec malesuada justo. Quisque id quam in purus viverra dapibus. Vestibulum at nisi ex. Interdum et malesuada fames ac ante ipsum primis in faucibus. Pellentesque purus justo, finibus id tortor nec, rhoncus aliquet justo. Ut lobortis, turpis vel molestie tincidunt, justo mi sodales metus, eu convallis lorem purus in nisi. Aenean vel sapien sodales, feugiat diam a, cursus nulla. Maecenas vel venenatis libero. Praesent dapibus eget massa in efficitur. Suspendisse vitae leo eget nunc gravida elementum nec a nulla. Mauris fringilla mauris vitae pellentesque accumsan. +Quisque eu mi sed enim efficitur fringilla. Ut vel congue erat. Duis vel turpis dolor. Nam sit amet nulla suscipit, commodo quam sed, tincidunt dolor. Nam sed placerat lorem. Sed mi metus, hendrerit in posuere eget, vestibulum vitae tellus. Sed eu augue ante. Vivamus leo dolor, congue eget blandit sed, vestibulum vitae quam. Morbi eget mi et purus vulputate malesuada commodo nec odio. Nullam vel sagittis est, vitae tincidunt metus. Aliquam in tincidunt metus, id congue turpis. Donec vitae nibh ac leo consectetur faucibus vel nec lorem. Ut mollis interdum laoreet. Vivamus eget metus felis. Nulla sit amet sapien nibh. Donec mollis congue nisi, vitae congue mi iaculis cursus. +Sed fermentum metus non erat tristique vestibulum. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Fusce tortor tellus, lacinia at posuere in, condimentum id nulla. Duis et molestie est. Vestibulum sit amet iaculis nulla, faucibus accumsan eros. Nunc rutrum ipsum vitae ex luctus, ut rhoncus justo volutpat. Etiam quis quam aliquam diam viverra fermentum. Sed et sollicitudin turpis. Nam neque justo, rutrum ac aliquet vel, placerat sed mi. Vestibulum porttitor lacus et odio lobortis, at euismod purus mollis. Sed auctor suscipit ipsum ut egestas. Aliquam sit amet velit semper, cursus arcu id, mattis ipsum. Ut congue, erat venenatis dapibus bibendum, nibh est dictum dolor, eu mollis orci lacus eu elit. +Mauris eget dolor at nisl maximus sollicitudin. In tincidunt efficitur turpis et mattis. Proin sit amet arcu commodo, pharetra est vitae, ultrices dui. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Sed facilisis metus a nisi viverra sollicitudin. Pellentesque eget tortor a leo rhoncus tempor non ac risus. Mauris vulputate dolor et magna ornare commodo et ac erat. Nunc molestie sagittis tempor. Cras quis molestie magna. Cras tempor quis nulla quis cursus. Quisque sed nisl non orci viverra volutpat et vitae libero. Duis sagittis bibendum accumsan. Integer pellentesque orci in nulla euismod, et porttitor eros lacinia. Fusce ultricies consectetur lorem ut tincidunt. Nullam nec turpis nec urna malesuada faucibus. +Proin ac pharetra arcu. Vivamus rhoncus eros eget elit condimentum varius. Nunc blandit semper dolor. Ut lacinia mattis leo, et cursus leo condimentum et. Suspendisse potenti. Vestibulum et sagittis lacus. Suspendisse mollis facilisis velit quis semper. Morbi malesuada odio a posuere sollicitudin. In sed elit non velit commodo condimentum sit amet ut nisi. +Maecenas ornare purus lorem, sed rhoncus ex bibendum aliquet. Sed malesuada, turpis at luctus rhoncus, ipsum enim egestas urna, ut posuere mi nulla gravida lectus. Morbi malesuada est vel sagittis finibus. Vestibulum eget odio nulla. Duis mi mauris, pharetra ac elit in, ornare porta diam. Nullam erat diam, hendrerit ut pretium ut, dapibus vitae dolor. Nullam vel tempor risus. Nulla blandit sodales viverra. +Vivamus sit amet venenatis velit. Fusce imperdiet efficitur felis, non porttitor est sagittis vitae. Mauris ac faucibus lectus. Nullam in tortor purus. Praesent nunc leo, suscipit sodales rutrum eget, sodales quis felis. Praesent et nulla imperdiet, posuere enim vitae, placerat turpis. Integer ornare ipsum ac mi dictum, id iaculis lectus ultrices. Phasellus feugiat dictum quam ac placerat. Etiam a nulla elit. Donec elementum, ex eu fermentum ornare, eros nulla tincidunt est, ac scelerisque velit enim ultrices augue. +Praesent id quam ut ex gravida commodo. Etiam tristique felis arcu, tempor accumsan libero faucibus eu. Donec in libero a tortor scelerisque maximus. Fusce justo tortor, condimentum et lacus in, vulputate efficitur ante. Nunc feugiat scelerisque tempus. Integer libero diam, fringilla at diam congue, placerat maximus nunc. Vivamus lobortis id felis elementum tempor. Aenean mattis massa ligula, sit amet rutrum risus viverra sit amet. Phasellus a lobortis purus. Nunc vel ex tristique, eleifend mauris sed, vestibulum eros. Vestibulum ac dolor volutpat neque suscipit posuere. Ut nibh turpis, fermentum sit amet mollis vel, tincidunt in turpis. Vivamus non interdum magna. Sed lobortis nisi urna, eget mollis felis ornare ac. Duis eu lacinia tortor, sit amet pharetra nisi.' + ) + ), + ), + 'simple structure' => array( + 'content' => array( new File('test1.txt', File::FILE, 1, 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed elit diam, posuere vel aliquet et, malesuada quis purus. Aliquam mattis aliquet massa, a semper sem porta in. Aliquam consectetur ligula a nulla vestibulum dictum. Interdum et malesuada fames ac ante ipsum primis in faucibus. Nullam luctus faucibus urna, accumsan cursus neque laoreet eu. Suspendisse potenti. Nulla ut feugiat neque. Maecenas molestie felis non purus tempor, in blandit ligula tincidunt. Ut in tortor sit amet nisi rutrum vestibulum vel quis tortor. Sed bibendum mauris sit amet gravida tristique. Ut hendrerit sapien vel tellus dapibus, eu pharetra nulla adipiscing. Donec in quam faucibus, cursus lacus sed, elementum ligula. Morbi volutpat vel lacus malesuada condimentum. Fusce consectetur nisl euismod justo volutpat sodales.'), new File('test/', File::DIR, 1), new File('test/test12.txt', File::FILE, 1, 'Duis malesuada lorem lorem, id sodales sapien sagittis ac. Donec in porttitor tellus, eu aliquam elit. Curabitur eu aliquam eros. Nulla accumsan augue quam, et consectetur quam eleifend eget. Donec cursus dolor lacus, eget pellentesque risus tincidunt at. Pellentesque rhoncus purus eget semper porta. Duis in magna tincidunt, fermentum orci non, consectetur nibh. Aliquam tortor eros, dignissim a posuere ac, rhoncus a justo. Sed sagittis velit ac massa pulvinar, ac pharetra ipsum fermentum. Etiam commodo lorem a scelerisque facilisis.') ), - "simple structure" ) ); @@ -390,17 +481,20 @@ public function providerZipfileOK() { $levels = array_merge($levels, $levelOptions); } foreach ($levels as $level) { - foreach ($fileSets as $fileSet) { + foreach ($fileSets as $descr => $fileSet) { $options = array( 'zip64' => $zip64[0], 'compress' => $compress[0], - 'level' => $level[0] + 'level' => $level[0], ); - $description = $fileSet[1] . ' (options = array(zip64=' . $zip64[1] . ', compress=' . $compress[1] . ', level=' . $level[1] . '))'; + $description = $descr . ' (options[zip64=' . $zip64[1] . ',compress=' . $compress[1] . ',level=' . $level[1] . '])'; + + // The dataProvider flag means these rows are passed as args to the + // testZipFile() et al test functions. array_push($data, array( $options, - $fileSet[0], - $description + $fileSet['content'], + $description, )); } } From 7375e214e6ca40f53d631ddb26909d69ec32aed1 Mon Sep 17 00:00:00 2001 From: Ruth Ivimey-Cook Date: Wed, 20 Jul 2016 15:59:15 +0100 Subject: [PATCH 17/24] Housekeeping --- .gitignore | 1 + .idea/php.xml | 8 ++++++++ 2 files changed, 9 insertions(+) create mode 100644 .idea/php.xml diff --git a/.gitignore b/.gitignore index 047f6f2..3bff9d8 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ .idea/vcs.xml .idea/tasks.xml .idea/inspectionProfiles/Project_Default.xml +/vendor diff --git a/.idea/php.xml b/.idea/php.xml new file mode 100644 index 0000000..d7b6eb3 --- /dev/null +++ b/.idea/php.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file From 7bbd94751c700aedd5e3aa5fbb493f2de42943d9 Mon Sep 17 00:00:00 2001 From: Ruth Ivimey-Cook Date: Wed, 20 Jul 2016 17:06:05 +0100 Subject: [PATCH 18/24] Fix repo url --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 7a0ea1c..c65ca30 100644 --- a/composer.json +++ b/composer.json @@ -25,7 +25,7 @@ ], "repositories": [ { "type": "vcs", - "url": "https://github.com/rivimey/PHPZipStreamer" + "url": "https://github.com/rivimey/PHPZipStreamer.git" } ], "require": { From 2f00629899c8c17c4f050c5e7aa986d05986dd9c Mon Sep 17 00:00:00 2001 From: Ruth Ivimey-Cook Date: Wed, 27 Jul 2016 14:20:12 +0100 Subject: [PATCH 19/24] Variable name outStream caps here but not elsewhere. --- src/ZipStreamer.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ZipStreamer.php b/src/ZipStreamer.php index 7123114..34ae37f 100644 --- a/src/ZipStreamer.php +++ b/src/ZipStreamer.php @@ -60,7 +60,7 @@ class ZipStreamer { private $extFileAttrDir; /** @var stream output stream zip file is written to */ - private $outStream; + private $outstream; /** @var boolean zip64 enabled */ private $zip64 = True; /** @var int compression method */ From 4d4af7297ebb28305d12030a1c47211f1cbed373 Mon Sep 17 00:00:00 2001 From: Ruth Ivimey-Cook Date: Wed, 27 Jul 2016 14:25:44 +0100 Subject: [PATCH 20/24] TRUE/True/true consistency; some re-spacing. --- src/ZipStreamer.php | 153 ++++++++++++++++++++++++-------------------- 1 file changed, 84 insertions(+), 69 deletions(-) diff --git a/src/ZipStreamer.php b/src/ZipStreamer.php index 34ae37f..75d224b 100644 --- a/src/ZipStreamer.php +++ b/src/ZipStreamer.php @@ -73,13 +73,13 @@ class ZipStreamer { /** @var int offset of next file to be added */ private $offset; /** @var boolean indicates zip is finalized and sent to client; no further addition possible */ - private $isFinalized = false; + private $isFinalized = False; /* * These values are used to persist state during addFileOpen/Write/Close. */ - private $isFileOpen = FALSE; - private $hashCtx = FALSE; + private $isFileOpen = False; + private $hashCtx = False; private $filePath; private $addFileOptions; private $gpFlags; @@ -101,10 +101,10 @@ class ZipStreamer { */ function __construct($options = NULL) { $defaultOptions = array( - 'outstream' => NULL, - 'zip64' => True, - 'compress' => COMPR::STORE, - 'level' => COMPR::NORMAL, + 'outstream' => NULL, + 'zip64' => True, + 'compress' => COMPR::STORE, + 'level' => COMPR::NORMAL, ); if (is_null($options)) { $options = array(); @@ -113,7 +113,8 @@ function __construct($options = NULL) { if ($options['outstream']) { $this->outstream = $options['outstream']; - } else { + } + else { $this->outstream = fopen('php://output', 'w'); } $this->zip64 = $options['zip64']; @@ -136,16 +137,18 @@ function __construct($options = NULL) { } function __destruct() { - $this->isFinalized = true; - $this->cdRec = null; + $this->isFinalized = True; + $this->cdRec = NULL; } private function getVersionToExtract($isDir) { if ($this->zip64) { $version = 0x2d; // 4.5 - File uses ZIP64 format extensions - } else if ($isDir) { + } + else if ($isDir) { $version = 0x14; // 2.0 - File is a folder (directory) - } else { + } + else { $version = 0x0a; // 1.0 - Default value } return $version; @@ -159,8 +162,8 @@ private function getVersionToExtract($isDir) { * @param string $contentType Content mime type to be set (optional, default 'application/zip') */ public function sendHeaders($archiveName = 'archive.zip', $contentType = 'application/zip') { - $headerFile = null; - $headerLine = null; + $headerFile = NULL; + $headerLine = NULL; if (!headers_sent($headerFile, $headerLine) or die("

Error: Unable to send file " . "$archiveName. HTML Headers have already been sent from " . @@ -177,11 +180,12 @@ public function sendHeaders($archiveName = 'archive.zip', $contentType = 'applic header('Connection: Keep-Alive'); header('Content-Type: ' . $contentType); // Use UTF-8 filenames when not using Internet Explorer - if(strpos($_SERVER['HTTP_USER_AGENT'], 'MSIE') > 0) { - header('Content-Disposition: attachment; filename="' . rawurlencode($archiveName) . '"' ); - } else { - header( 'Content-Disposition: attachment; filename*=UTF-8\'\'' . rawurlencode($archiveName) - . '; filename="' . rawurlencode($archiveName) . '"' ); + if (strpos($_SERVER['HTTP_USER_AGENT'], 'MSIE') > 0) { + header('Content-Disposition: attachment; filename="' . rawurlencode($archiveName) . '"'); + } + else { + header('Content-Disposition: attachment; filename*=UTF-8\'\'' . rawurlencode($archiveName) + . '; filename="' . rawurlencode($archiveName) . '"'); } header('Content-Transfer-Encoding: binary'); } @@ -206,41 +210,48 @@ public function sendHeaders($archiveName = 'archive.zip', $contentType = 'applic */ public function addFileFromStream($stream, $filePath, $options = NULL) { if ($this->isFinalized) { - return false; + return False; } $defaultOptions = array( - 'timestamp' => NULL, - 'comment' => NULL, - 'compress' => $this->compress, - 'level' => $this->level, + 'timestamp' => NULL, + 'comment' => NULL, + 'compress' => $this->compress, + 'level' => $this->level, ); if (is_null($options)) { - $options = array(); + $options = array(); } $options = array_merge($defaultOptions, $options); $this->validateCompressionOptions($options['compress'], $options['level']); if (!is_resource($stream) || get_resource_type($stream) != 'stream') { - return false; + return False; } $filePath = self::normalizeFilePath($filePath); $gpFlags = GPFLAGS::ADD; - list($gpFlags, $lfhLength) = $this->beginFile($filePath, False, $options['comment'], $options['timestamp'], $gpFlags, $options['compress']); - list($dataLength, $gzLength, $dataCRC32) = $this->streamFileData($stream, $options['compress'], $options['level']); + list($gpFlags, $lfhLength) = + $this->beginFile($filePath, False, $options['comment'], + $options['timestamp'], $gpFlags, $options['compress']); + + list($dataLength, $gzLength, $dataCRC32) = + $this->streamFileData($stream, $options['compress'], $options['level']); $ddLength = $this->addDataDescriptor($dataLength, $gzLength, $dataCRC32); // build cdRec - $this->cdRec[] = $this->buildCentralDirectoryHeader($filePath, $options['timestamp'], $gpFlags, $options['compress'], - $dataLength, $gzLength, $dataCRC32, $this->extFileAttrFile, FALSE); + $this->cdRec[] = + $this->buildCentralDirectoryHeader($filePath, $options['timestamp'], + $gpFlags, $options['compress'], + $dataLength, $gzLength, $dataCRC32, + $this->extFileAttrFile, False); // calc offset $this->offset->add($ddLength)->add($lfhLength)->add($gzLength); - return true; + return True; } /** @@ -258,9 +269,9 @@ public function addFileFromStream($stream, $filePath, $options = NULL) { */ public function addFileOpen($filePath, $options = NULL) { if ($this->isFinalized || $this->isFileOpen) { - return FALSE; + return False; } - $this->isFileOpen = TRUE; + $this->isFileOpen = True; $defaultOptions = array( 'timestamp' => NULL, 'comment' => NULL, @@ -283,11 +294,11 @@ public function addFileOpen($filePath, $options = NULL) { } list($this->gpFlags, $this->lfhLength) = - $this->beginFile($this->filePath, FALSE, + $this->beginFile($this->filePath, False, $this->addFileOptions['comment'], $this->addFileOptions['timestamp'], $this->gpFlags, $this->addFileOptions['compress'], 0, 0, 0); - return TRUE; + return True; } /** @@ -296,15 +307,15 @@ public function addFileOpen($filePath, $options = NULL) { * @param string $block * Data to write to the file. * @return bool $success - * FALSE if there is no file open with addFileOpen(), else TRUE. + * False if there is no file open with addFileOpen(), else True. */ public function addFileWrite($block) { if ($this->isFinalized || !$this->isFileOpen) { - return FALSE; + return False; } $this->writeFile($block, $this->addFileOptions['compress'], $this->addFileOptions['level']); - return TRUE; + return True; } /** @@ -314,7 +325,7 @@ public function addFileWrite($block) { */ public function addFileClose() { if ($this->isFinalized || !$this->isFileOpen) { - return FALSE; + return False; } if (COMPR::DEFLATE === $this->addFileOptions['compress']) { @@ -334,12 +345,12 @@ public function addFileClose() { $this->buildCentralDirectoryHeader($this->filePath, $this->addFileOptions['timestamp'], $this->gpFlags, $this->addFileOptions['compress'], $this->dataLength, $this->gzLength, - $this->dataCRC32, $this->extFileAttrFile, FALSE); + $this->dataCRC32, $this->extFileAttrFile, False); // calc offset $this->offset->add($ddLength)->add($this->lfhLength)->add($this->gzLength); - $this->isFileOpen = FALSE; - return TRUE; + $this->isFileOpen = False; + return True; } /** @@ -359,8 +370,9 @@ public function addFileClose() { */ public function addFileFromString($data, $filePath, $options = NULL) { if ($this->isFinalized) { - return FALSE; + return False; } + $defaultOptions = array( 'timestamp' => NULL, 'comment' => NULL, @@ -374,7 +386,7 @@ public function addFileFromString($data, $filePath, $options = NULL) { $this->validateCompressionOptions($options['compress'], $options['level']); if (!is_string($data)) { - return FALSE; + return False; } $this->filePath = self::normalizeFilePath($filePath); @@ -394,10 +406,11 @@ public function addFileFromString($data, $filePath, $options = NULL) { } $this->gzLength->add(strlen($data)); - list($this->gpFlags, $this->lfhLength) = $this->beginFile($this->filePath, FALSE, - $options['comment'], $options['timestamp'], - $this->gpFlags, $options['compress'], - $this->dataLength, $this->gzLength, $this->dataCRC32); + list($this->gpFlags, $this->lfhLength) = + $this->beginFile($this->filePath, False, + $options['comment'], $options['timestamp'], + $this->gpFlags, $options['compress'], + $this->dataLength, $this->gzLength, $this->dataCRC32); $this->write($data); @@ -409,15 +422,16 @@ public function addFileFromString($data, $filePath, $options = NULL) { $this->flush(); // build cdRec - $this->cdRec[] = $this->buildCentralDirectoryHeader($filePath, $options['timestamp'], - $this->gpFlags, $options['compress'], - $this->dataLength, $this->gzLength, $this->dataCRC32, - $this->extFileAttrFile, FALSE); + $this->cdRec[] = + $this->buildCentralDirectoryHeader($filePath, $options['timestamp'], + $this->gpFlags, $options['compress'], + $this->dataLength, $this->gzLength, $this->dataCRC32, + $this->extFileAttrFile, False); // calc offset $this->offset->add($this->lfhLength)->add($this->gzLength); - return TRUE; + return True; } /** @@ -432,14 +446,14 @@ public function addFileFromString($data, $filePath, $options = NULL) { */ public function addEmptyDir($directoryPath, $options = NULL) { if ($this->isFinalized) { - return false; + return False; } $defaultOptions = array( - 'timestamp' => NULL, - 'comment' => NULL, + 'timestamp' => NULL, + 'comment' => NULL, ); if (is_null($options)) { - $options = array(); + $options = array(); } $options = array_merge($defaultOptions, $options); @@ -449,19 +463,19 @@ public function addEmptyDir($directoryPath, $options = NULL) { $gpFlags = 0x0000; $gzMethod = COMPR::STORE; // Compression type 0 = stored - list($gpFlags, $lfhLength) = $this->beginFile($directoryPath, TRUE, + list($gpFlags, $lfhLength) = $this->beginFile($directoryPath, True, $options['comment'], $options['timestamp'], $gpFlags, $gzMethod); // build cdRec $this->cdRec[] = $this->buildCentralDirectoryHeader($directoryPath, $options['timestamp'], $gpFlags, $gzMethod, - Count64::construct(0, !$this->zip64), Count64::construct(0, !$this->zip64), 0, $this->extFileAttrDir, TRUE); + Count64::construct(0, !$this->zip64), Count64::construct(0, !$this->zip64), 0, $this->extFileAttrDir, True); // calc offset $this->offset->add($lfhLength); - return true; + return True; } - return false; + return False; } /** @@ -490,13 +504,13 @@ public function finalize() { $this->flush(); - $this->isFinalized = true; - $cd = null; - $this->cdRec = null; + $this->isFinalized = True; + $cd = NULL; + $this->cdRec = NULL; - return true; + return True; } - return false; + return False; } private function validateCompressionOptions($compress, $level) { @@ -512,9 +526,10 @@ private function validateCompressionOptions($compress, $level) { } if (!(COMPR::NONE === $level || - COMPR::NORMAL === $level || - COMPR::MAXIMUM === $level || - COMPR::SUPERFAST === $level)) { + COMPR::NORMAL === $level || + COMPR::MAXIMUM === $level || + COMPR::SUPERFAST === $level) + ) { throw new \Exception('invalid option ' . $level . ' (compression level'); } } From df2990d283bd065e9ed225ad3bbe08b6666718a9 Mon Sep 17 00:00:00 2001 From: Ruth Ivimey-Cook Date: Wed, 27 Jul 2016 14:27:34 +0100 Subject: [PATCH 21/24] Work on Docblocks --- src/ZipStreamer.php | 188 +++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 177 insertions(+), 11 deletions(-) diff --git a/src/ZipStreamer.php b/src/ZipStreamer.php index 75d224b..8ecf6cd 100644 --- a/src/ZipStreamer.php +++ b/src/ZipStreamer.php @@ -198,14 +198,18 @@ public function sendHeaders($archiveName = 'archive.zip', $contentType = 'applic /** * Add a file to the archive at the specified location and file name. * - * @param string $stream Stream to read data from - * @param string $filePath Filepath and name to be used in the archive. - * @param array $options Optional, additional options - * Valid options are: - * * int timestamp: timestamp for the file (default: current time) - * * string comment: comment to be added for this file (default: none) - * * int compress: compression method (override global option for this file) - * * int level: compression level (override global option for this file) + * @param string $stream + * Stream to read data from + * @param string $filePath + * Filepath and name to be used in the archive. + * @param array $options + * Optional, additional options + * Valid options are: + * * int timestamp: timestamp for the file (default: current time) + * * string comment: comment to be added for this file (default: none) + * * int compress: compression method (override global option for this file) + * * int level: compression level (override global option for this file) + * * @return bool $success */ public function addFileFromStream($stream, $filePath, $options = NULL) { @@ -437,8 +441,8 @@ public function addFileFromString($data, $filePath, $options = NULL) { /** * Add an empty directory entry to the zip archive. * - * @param string $directoryPath Directory Path and name to be added to the archive. - * @param array $options Optional, additional options + * @param string $directoryPath Directory Path and name to be added to the archive. + * @param array $options Optional, additional options * Valid options are: * * int timestamp: timestamp for the file (default: current time) * * string comment: comment to be added for this file (default: none) @@ -480,8 +484,10 @@ public function addEmptyDir($directoryPath, $options = NULL) { /** * Close the archive. + * * A closed archive can no longer have new files added to it. After * closing, the zip file is completely written to the output stream. + * * @return bool $success */ public function finalize() { @@ -513,6 +519,13 @@ public function finalize() { return False; } + /** + * Check that the indicated compression method and level is acceptable. + * + * @param $compress + * @param $level + * @throws \Exception + */ private function validateCompressionOptions($compress, $level) { if (COMPR::STORE === $compress) { } else if (COMPR::DEFLATE === $compress) { @@ -534,15 +547,60 @@ private function validateCompressionOptions($compress, $level) { } } + /** + * Write a block of data to the output stream. + * + * @param $data + * @return int + */ private function write($data) { return fwrite($this->outstream, $data); } + /** + * Flush the output stream. + */ private function flush() { return fflush($this->outstream); } - private function beginFile($filePath, $isDir, $fileComment, $timestamp, $gpFlags, $gzMethod, + /** + * Build and write out a local file header for a new file. + * + * Character encoding checks are done on both the filename and comment; if + * either requires UTF-8, then the record is marked as using UTF-8. + * + * @param $filePath + * The file pathname for the new file; checked for UTF-8 vs ASCII encoding. + * @param $isDir + * Flag: if True this is actually a directory entry not a file. + * @param $fileComment + * The file comment describing the file; can be (often is) empty. Also + * checked for encoding. + * @param $timestamp + * The last modification / update date timestamp (unix style) to store for + * this file. + * @param $gpFlags + * The general flags for this file, using the GPFLAGS constants. + * @param $gzMethod + * The compression method, using the COMPR constants. + * @param int $dataLength + * The length of the file in bytes, or 0 if this is not known at this point, + * in which case GPFLAGS must include ADD and a DataDirectory record must + * appear after the data including this and the CRC. + * @param int $gzLength + * The compressed length of the file in bytes, or 0 if not known: as for + * dataLength. + * @param int $dataCRC32 + * The CRC32b checksum of the file, or 0 if not known: as for dataLength. + * + * @return array[2] + * Two values: + * - gpFlags - updated Flags to store. + * - lfhLength - the length of the file header in bytes. + */ + private function beginFile($filePath, $isDir, $fileComment, $timestamp, + $gpFlags, $gzMethod, $dataLength = 0, $gzLength = 0, $dataCRC32 = 0) { $isFileUTF8 = mb_check_encoding($filePath, 'UTF-8') && !mb_check_encoding($filePath, 'ASCII'); @@ -562,6 +620,21 @@ private function beginFile($filePath, $isDir, $fileComment, $timestamp, $gpFlags return array($gpFlags, strlen($localFileHeader)); } + /** + * Write data to the current output stream. + * + * Track the total data length, update the compressed data and checksums, + * and write the block to the php output stream. + * + * @param $data + * The data to (compress and) write to the file. + * @param $compress + * The compression method. One of the COMPR constants. + * - DEFLATE is the only compression supported at present; + * - STORE is supported for 'do not compress'. + * @param $level + * Not used. + */ private function writeFile($data, $compress, $level) { $this->dataLength->add(strlen($data)); @@ -574,6 +647,25 @@ private function writeFile($data, $compress, $level) { } + /** + * Copy the data from the input stream to a file started with beginFile. + * + * Initialise CRC32, read and write chunks of data through to the output + * stream, and calculate the final CRC, compressing the data as required. + * + * @param $stream + * A php fopen() resource stream to read. + * @param $compress + * The compression method to use. Only DEFLATE is supported. + * @param $level + * The compression level to use for DEFLATE compression. + * + * @return array[3] + * Three values: + * - dataLength - the number of bytes copied from the input stream. + * - gpzLength - the number of bytes written tothe output stream. + * - crc - the CRC32b checksum of the input data. + */ private function streamFileData($stream, $compress, $level) { $dataLength = Count64::construct(0, !$this->zip64); $gzLength = Count64::construct(0, !$this->zip64); @@ -604,6 +696,13 @@ private function streamFileData($stream, $compress, $level) { return array($dataLength, $gzLength, $crc); } + /** + * Create a Zip64 extended information record. + * + * @param int $dataLength + * @param int $gzLength + * @return string + */ private function buildZip64ExtendedInformationField($dataLength = 0, $gzLength = 0) { return '' . PackBits::pack16le(0x0001) // tag for this "extra" block type (ZIP64) 2 bytes (0x0001) @@ -614,6 +713,33 @@ private function buildZip64ExtendedInformationField($dataLength = 0, $gzLength = . PackBits::pack32le(0); // number of the disk on which this file starts 4 bytes } + /** + * Create a local file header record. + * + * Uses flag $this->zip64 to indicate whether to use 64 bit data structures. + * + * @param $filePath + * The file pathname for the new file. + * @param $timestamp + * The last modification / update date timestamp (Unix style) to store for + * this file. + * @param $gpFlags + * The general flags for this file, using the GPFLAGS constants. + * @param $gzMethod + * The compression method, using the COMPR constants. + * @param $dataLength + * The length of the file in bytes, or 0 if this is not known at this point, + * in which case GPFLAGS must include ADD and a DataDirectory record must + * appear after the data including this and the CRC. + * @param $gzLength + * The compressed length of the file in bytes, or 0 if not known: as for + * @param $isDir + * Flag: if True this is actually a directory entry not a file. + * @param int $dataCRC32 + * The CRC32b checksum of the file, or 0 if not known: as for dataLength. + * + * @return string + */ private function buildLocalFileHeader($filePath, $timestamp, $gpFlags, $gzMethod, $dataLength, $gzLength, $isDir = FALSE, $dataCRC32 = 0) { $versionToExtract = $this->getVersionToExtract($isDir); @@ -647,6 +773,14 @@ private function buildLocalFileHeader($filePath, $timestamp, $gpFlags, . $zip64Ext; // extra field (variable size) } + /** + * Create a data descriptor record. + * + * @param $dataLength + * @param $gzLength + * @param $dataCRC32 + * @return int + */ private function addDataDescriptor($dataLength, $gzLength, $dataCRC32) { if ($this->zip64) { $length = 20; @@ -666,6 +800,12 @@ private function addDataDescriptor($dataLength, $gzLength, $dataCRC32) { return $length; } + /** + * Create a Zip64 End of Central Directory record. + * + * @param $cdRecLength + * @return string + */ private function buildZip64EndOfCentralDirectoryRecord($cdRecLength) { $versionToExtract = $this->getVersionToExtract(False); $cdRecCount = sizeof($this->cdRec); @@ -690,6 +830,12 @@ private function buildZip64EndOfCentralDirectoryRecord($cdRecLength) { } + /** + * Create a Zip64 End of Central Directory Locator record. + * + * @param $cdRecLength + * @return string + */ private function buildZip64EndOfCentralDirectoryLocator($cdRecLength) { $zip64RecStart = Count64::construct($this->offset, !$this->zip64) ->add($cdRecLength); @@ -703,6 +849,20 @@ private function buildZip64EndOfCentralDirectoryLocator($cdRecLength) { . PackBits::pack32le(1); // total number of disks 4 bytes } + /** + * Build a Central Directory Header record. + * + * @param $filePath + * @param $timestamp + * @param $gpFlags + * @param $gzMethod + * @param $dataLength + * @param $gzLength + * @param $dataCRC32 + * @param $extFileAttr + * @param $isDir + * @return string + */ private function buildCentralDirectoryHeader($filePath, $timestamp, $gpFlags, $gzMethod, $dataLength, $gzLength, $dataCRC32, $extFileAttr, $isDir) { @@ -746,6 +906,12 @@ private function buildCentralDirectoryHeader($filePath, $timestamp, $gpFlags, . ''; //file comment (variable size) } + /** + * Build an End of Central Directory record. + * + * @param $cdRecLength + * @return string + */ private function buildEndOfCentralDirectoryRecord($cdRecLength) { if ($this->zip64) { $diskNumber = -1; From a17bd0a00acf6a11c81ba6fd22e3a9ae10234f39 Mon Sep 17 00:00:00 2001 From: Ruth Ivimey-Cook Date: Wed, 27 Jul 2016 14:30:05 +0100 Subject: [PATCH 22/24] Recast nested 'if' as a switch --- src/ZipStreamer.php | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/src/ZipStreamer.php b/src/ZipStreamer.php index 8ecf6cd..6743e9f 100644 --- a/src/ZipStreamer.php +++ b/src/ZipStreamer.php @@ -527,15 +527,23 @@ public function finalize() { * @throws \Exception */ private function validateCompressionOptions($compress, $level) { - if (COMPR::STORE === $compress) { - } else if (COMPR::DEFLATE === $compress) { - if (COMPR::NONE !== $level - && !class_exists(DeflatePeclStream::PECL1_DEFLATE_STREAM_CLASS) - && !class_exists(DeflatePeclStream::PECL2_DEFLATE_STREAM_CLASS)) { - throw new \Exception('unable to use compression method DEFLATE with level other than NONE (requires pecl_http >= 0.10)'); - } - } else { - throw new \Exception('invalid option ' . $compress . ' (compression method)'); + + switch($compress) { + case COMPR::STORE: + break; + + case COMPR::DEFLATE: + if (COMPR::NONE !== $level + && !class_exists(DeflatePeclStream::PECL1_DEFLATE_STREAM_CLASS) + && !class_exists(DeflatePeclStream::PECL2_DEFLATE_STREAM_CLASS) + ) { + throw new \Exception('unable to use compression method DEFLATE with level other than NONE (requires pecl_http >= 0.10)'); + } + break; + + default: + throw new \Exception('invalid option ' . $compress . ' (compression method)'); + break; } if (!(COMPR::NONE === $level || From c122dadf6240ee298f0c9e339b8df0c954aa1463 Mon Sep 17 00:00:00 2001 From: Ruth Ivimey-Cook Date: Wed, 27 Jul 2016 14:31:44 +0100 Subject: [PATCH 23/24] Code block reindenting. --- src/ZipStreamer.php | 212 +++++++++++++++++++++++--------------------- 1 file changed, 109 insertions(+), 103 deletions(-) diff --git a/src/ZipStreamer.php b/src/ZipStreamer.php index 6743e9f..205ee47 100644 --- a/src/ZipStreamer.php +++ b/src/ZipStreamer.php @@ -155,24 +155,28 @@ private function getVersionToExtract($isDir) { } /** - * Send appropriate http headers before streaming the zip file and disable output buffering. - * This method, if used, has to be called before adding anything to the zip file. - * - * @param string $archiveName Filename of archive to be created (optional, default 'archive.zip') - * @param string $contentType Content mime type to be set (optional, default 'application/zip') - */ + * Send appropriate http headers before streaming the zip file and disable output buffering. + * This method, if used, has to be called before adding anything to the zip file. + * + * @param string $archiveName + * Filename of archive to be created (optional, default 'archive.zip') + * @param string $contentType + * Content mime type to be set (optional, default 'application/zip') + */ public function sendHeaders($archiveName = 'archive.zip', $contentType = 'application/zip') { $headerFile = NULL; $headerLine = NULL; if (!headers_sent($headerFile, $headerLine) - or die("

Error: Unable to send file " . - "$archiveName. HTML Headers have already been sent from " . - "$headerFile in line $headerLine" . - "

")) { - if ((ob_get_contents() === false || ob_get_contents() == '') - or die("\n

Error: Unable to send file " . - "$archiveName.epub. Output buffer " . - "already contains text (typically warnings or errors).

")) { + or die("

Error: Unable to send file " . + "$archiveName. HTML Headers have already been sent from " . + "$headerFile in line $headerLine" . + "

") + ) { + if ((ob_get_contents() === False || ob_get_contents() == '') + or die("\n

Error: Unable to send file " . + "$archiveName.epub. Output buffer " . + "already contains text (typically warnings or errors).

") + ) { header('Pragma: public'); header('Last-Modified: ' . gmdate('D, d M Y H:i:s T')); header('Expires: 0'); @@ -613,7 +617,7 @@ private function beginFile($filePath, $isDir, $fileComment, $timestamp, $isFileUTF8 = mb_check_encoding($filePath, 'UTF-8') && !mb_check_encoding($filePath, 'ASCII'); $isCommentUTF8 = !empty($fileComment) && mb_check_encoding($fileComment, 'UTF-8') - && !mb_check_encoding($fileComment, 'ASCII'); + && !mb_check_encoding($fileComment, 'ASCII'); if ($isFileUTF8 || $isCommentUTF8) { $gpFlags |= GPFLAGS::EFS; @@ -713,12 +717,12 @@ private function streamFileData($stream, $compress, $level) { */ private function buildZip64ExtendedInformationField($dataLength = 0, $gzLength = 0) { return '' - . PackBits::pack16le(0x0001) // tag for this "extra" block type (ZIP64) 2 bytes (0x0001) - . PackBits::pack16le(28) // size of this "extra" block 2 bytes - . PackBits::pack64le($dataLength) // original uncompressed file size 8 bytes - . PackBits::pack64le($gzLength) // size of compressed data 8 bytes - . PackBits::pack64le($this->offset) // offset of local header record 8 bytes - . PackBits::pack32le(0); // number of the disk on which this file starts 4 bytes + . PackBits::pack16le(0x0001) // tag for this "extra" block type (ZIP64) 2 bytes (0x0001) + . PackBits::pack16le(28) // size of this "extra" block 2 bytes + . PackBits::pack64le($dataLength) // original uncompressed file size 8 bytes + . PackBits::pack64le($gzLength) // size of compressed data 8 bytes + . PackBits::pack64le($this->offset) // offset of local header record 8 bytes + . PackBits::pack32le(0); // number of the disk on which this file starts 4 bytes } /** @@ -749,7 +753,8 @@ private function buildZip64ExtendedInformationField($dataLength = 0, $gzLength = * @return string */ private function buildLocalFileHeader($filePath, $timestamp, $gpFlags, - $gzMethod, $dataLength, $gzLength, $isDir = FALSE, $dataCRC32 = 0) { + $gzMethod, $dataLength, $gzLength, + $isDir = False, $dataCRC32 = 0) { $versionToExtract = $this->getVersionToExtract($isDir); $dosTime = self::getDosTime($timestamp); if ($this->zip64) { @@ -766,19 +771,19 @@ private function buildLocalFileHeader($filePath, $timestamp, $gpFlags, } return '' - . PackBits::pack32le(self::ZIP_LOCAL_FILE_HEADER) // local file header signature 4 bytes (0x04034b50) - . PackBits::pack16le($versionToExtract) // version needed to extract 2 bytes - . PackBits::pack16le($gpFlags) // general purpose bit flag 2 bytes - . PackBits::pack16le($gzMethod) // compression method 2 bytes - . PackBits::pack32le($dosTime) // last mod file time 2 bytes - // last mod file date 2 bytes - . PackBits::pack32le($dataCRC32) // crc-32 4 bytes - . PackBits::pack32le($gzLength) // compressed size 4 bytes - . PackBits::pack32le($dataLength) // uncompressed size 4 bytes - . PackBits::pack16le(strlen($filePath)) // file name length 2 bytes - . PackBits::pack16le(strlen($zip64Ext)) // extra field length 2 bytes - . $filePath // file name (variable size) - . $zip64Ext; // extra field (variable size) + . PackBits::pack32le(self::ZIP_LOCAL_FILE_HEADER) // local file header signature 4 bytes (0x04034b50) + . PackBits::pack16le($versionToExtract) // version needed to extract 2 bytes + . PackBits::pack16le($gpFlags) // general purpose bit flag 2 bytes + . PackBits::pack16le($gzMethod) // compression method 2 bytes + . PackBits::pack32le($dosTime) // last mod file time 2 bytes + // last mod file date 2 bytes + . PackBits::pack32le($dataCRC32) // crc-32 4 bytes + . PackBits::pack32le($gzLength) // compressed size 4 bytes + . PackBits::pack32le($dataLength) // uncompressed size 4 bytes + . PackBits::pack16le(strlen($filePath)) // file name length 2 bytes + . PackBits::pack16le(strlen($zip64Ext)) // extra field length 2 bytes + . $filePath // file name (variable size) + . $zip64Ext; // extra field (variable size) } /** @@ -794,17 +799,18 @@ private function addDataDescriptor($dataLength, $gzLength, $dataCRC32) { $length = 20; $packedGzLength = PackBits::pack64le($gzLength); $packedDataLength = PackBits::pack64le($dataLength); - } else { + } + else { $length = 12; $packedGzLength = PackBits::pack32le($gzLength->getLoBytes()); $packedDataLength = PackBits::pack32le($dataLength->getLoBytes()); - } + } $this->write('' - . PackBits::pack32le($dataCRC32) // crc-32 4 bytes - . $packedGzLength // compressed size 4/8 bytes (depending on zip64 enabled) - . $packedDataLength // uncompressed size 4/8 bytes (depending on zip64 enabled) - .''); + . PackBits::pack32le($dataCRC32) // crc-32 4 bytes + . $packedGzLength // compressed size 4/8 bytes (depending on zip64 enabled) + . $packedDataLength // uncompressed size 4/8 bytes (depending on zip64 enabled) + . ''); return $length; } @@ -819,22 +825,22 @@ private function buildZip64EndOfCentralDirectoryRecord($cdRecLength) { $cdRecCount = sizeof($this->cdRec); return '' - . PackBits::pack32le(self::ZIP64_END_OF_CENTRAL_DIRECTORY) // zip64 end of central dir signature 4 bytes (0x06064b50) - . PackBits::pack64le(44) // size of zip64 end of central directory - // record 8 bytes - . PackBits::pack16le(self::ATTR_MADE_BY_VERSION) //version made by 2 bytes - . PackBits::pack16le($versionToExtract) // version needed to extract 2 bytes - . PackBits::pack32le(0) // number of this disk 4 bytes - . PackBits::pack32le(0) // number of the disk with the start of the - // central directory 4 bytes - . PackBits::pack64le($cdRecCount) // total number of entries in the central - // directory on this disk 8 bytes - . PackBits::pack64le($cdRecCount) // total number of entries in the - // central directory 8 bytes - . PackBits::pack64le($cdRecLength) // size of the central directory 8 bytes - . PackBits::pack64le($this->offset) // offset of start of central directory - // with respect to the starting disk number 8 bytes - . ''; // zip64 extensible data sector (variable size) + . PackBits::pack32le(self::ZIP64_END_OF_CENTRAL_DIRECTORY) // zip64 end of central dir signature 4 bytes (0x06064b50) + . PackBits::pack64le(44) // size of zip64 end of central directory + // record 8 bytes + . PackBits::pack16le(self::ATTR_MADE_BY_VERSION) //version made by 2 bytes + . PackBits::pack16le($versionToExtract) // version needed to extract 2 bytes + . PackBits::pack32le(0) // number of this disk 4 bytes + . PackBits::pack32le(0) // number of the disk with the start of the + // central directory 4 bytes + . PackBits::pack64le($cdRecCount) // total number of entries in the central + // directory on this disk 8 bytes + . PackBits::pack64le($cdRecCount) // total number of entries in the + // central directory 8 bytes + . PackBits::pack64le($cdRecLength) // size of the central directory 8 bytes + . PackBits::pack64le($this->offset) // offset of start of central directory + // with respect to the starting disk number 8 bytes + . ''; // zip64 extensible data sector (variable size) } @@ -848,13 +854,13 @@ private function buildZip64EndOfCentralDirectoryLocator($cdRecLength) { $zip64RecStart = Count64::construct($this->offset, !$this->zip64) ->add($cdRecLength); - return '' - . PackBits::pack32le(self::ZIP64_END_OF_CENTRAL_DIR_LOCATOR) // zip64 end of central dir locator signature 4 bytes (0x07064b50) - . PackBits::pack32le(0) // number of the disk with the start of the - // zip64 end of central directory 4 bytes - . PackBits::pack64le($zip64RecStart) // relative offset of the zip64 end of - // central directory record 8 bytes - . PackBits::pack32le(1); // total number of disks 4 bytes + return '' + . PackBits::pack32le(self::ZIP64_END_OF_CENTRAL_DIR_LOCATOR) // zip64 end of central dir locator signature 4 bytes (0x07064b50) + . PackBits::pack32le(0) // number of the disk with the start of the + // zip64 end of central directory 4 bytes + . PackBits::pack64le($zip64RecStart) // relative offset of the zip64 end of + // central directory record 8 bytes + . PackBits::pack32le(1); // total number of disks 4 bytes } /** @@ -882,7 +888,8 @@ private function buildCentralDirectoryHeader($filePath, $timestamp, $gpFlags, $gzLength = -1; $diskNo = -1; $offset = -1; - } else { + } + else { $zip64Ext = ''; $dataLength = $dataLength->getLoBytes(); $gzLength = $gzLength->getLoBytes(); @@ -891,27 +898,26 @@ private function buildCentralDirectoryHeader($filePath, $timestamp, $gpFlags, } return '' - . PackBits::pack32le(self::ZIP_CENTRAL_FILE_HEADER) //central file header signature 4 bytes (0x02014b50) - . PackBits::pack16le(self::ATTR_MADE_BY_VERSION) //version made by 2 bytes - . PackBits::pack16le($versionToExtract) // version needed to extract 2 bytes - . PackBits::pack16le($gpFlags) //general purpose bit flag 2 bytes - . PackBits::pack16le($gzMethod) //compression method 2 bytes - . PackBits::pack32le($dosTime) //last mod file time 2 bytes - //last mod file date 2 bytes - . PackBits::pack32le($dataCRC32) //crc-32 4 bytes - . PackBits::pack32le($gzLength) //compressed size 4 bytes - . PackBits::pack32le($dataLength) //uncompressed size 4 bytes - . PackBits::pack16le(strlen($filePath)) //file name length 2 bytes - . PackBits::pack16le(strlen($zip64Ext)) //extra field length 2 bytes - . PackBits::pack16le(0) //file comment length 2 bytes - . PackBits::pack16le($diskNo) //disk number start 2 bytes - . PackBits::pack16le(0) //internal file attributes 2 bytes - . PackBits::pack32le($extFileAttr) //external file attributes 4 bytes - . PackBits::pack32le($offset) //relative offset of local header 4 bytes - . $filePath //file name (variable size) - . $zip64Ext //extra field (variable size) - //TODO: implement? - . ''; //file comment (variable size) + . PackBits::pack32le(self::ZIP_CENTRAL_FILE_HEADER) // Central file header signature 4 bytes (0x02014b50) + . PackBits::pack16le(self::ATTR_MADE_BY_VERSION) // Version made by 2 bytes + . PackBits::pack16le($versionToExtract) // Version needed to extract 2 bytes + . PackBits::pack16le($gpFlags) // General purpose bit flag 2 bytes + . PackBits::pack16le($gzMethod) // Compression method 2 bytes + . PackBits::pack32le($dosTime) // Last mod file time 2 bytes + // Last mod file date 2 bytes + . PackBits::pack32le($dataCRC32) // Crc-32 4 bytes + . PackBits::pack32le($gzLength) // Compressed size 4 bytes + . PackBits::pack32le($dataLength) // Uncompressed size 4 bytes + . PackBits::pack16le(strlen($filePath)) // File name length 2 bytes + . PackBits::pack16le(strlen($zip64Ext)) // Extra field length 2 bytes + . PackBits::pack16le(0) // File comment length 2 bytes + . PackBits::pack16le($diskNo) // Disk number start 2 bytes + . PackBits::pack16le(0) // Internal file attributes 2 bytes + . PackBits::pack32le($extFileAttr) // External file attributes 4 bytes + . PackBits::pack32le($offset) // Relative offset of local header 4 bytes + . $filePath // File name (variable size) + . $zip64Ext // Extra field (variable size) + . ''; //TODO: implement? // File comment (variable size) } /** @@ -926,7 +932,8 @@ private function buildEndOfCentralDirectoryRecord($cdRecLength) { $cdRecCount = -1; $cdRecLength = -1; $offset = -1; - } else { + } + else { $diskNumber = 0; $cdRecCount = sizeof($this->cdRec); $offset = $this->offset->getLoBytes(); @@ -934,21 +941,20 @@ private function buildEndOfCentralDirectoryRecord($cdRecLength) { //throw new \Exception(sprintf("zip64 %d diskno %d", $this->zip64, $diskNumber)); return '' - . PackBits::pack32le(self::ZIP_END_OF_CENTRAL_DIRECTORY) // end of central dir signature 4 bytes (0x06064b50) - . PackBits::pack16le($diskNumber) // number of this disk 2 bytes - . PackBits::pack16le($diskNumber) // number of the disk with the - // start of the central directory 2 bytes - . PackBits::pack16le($cdRecCount) // total number of entries in the - // central directory on this disk 2 bytes - . PackBits::pack16le($cdRecCount) // total number of entries in the - // central directory 2 bytes - . PackBits::pack32le($cdRecLength) // size of the central directory 4 bytes - . PackBits::pack32le($offset) // offset of start of central - // directory with respect to the - // starting disk number 4 bytes - . PackBits::pack16le(0) // .ZIP file comment length 2 bytes - //TODO: implement? - . ''; // .ZIP file comment (variable size) + . PackBits::pack32le(self::ZIP_END_OF_CENTRAL_DIRECTORY) // end of central dir signature 4 bytes (0x06064b50) + . PackBits::pack16le($diskNumber) // number of this disk 2 bytes + . PackBits::pack16le($diskNumber) // number of the disk with the + // start of the central directory 2 bytes + . PackBits::pack16le($cdRecCount) // total number of entries in the + // central directory on this disk 2 bytes + . PackBits::pack16le($cdRecCount) // total number of entries in the + // central directory 2 bytes + . PackBits::pack32le($cdRecLength) // size of the central directory 4 bytes + . PackBits::pack32le($offset) // offset of start of central + // directory with respect to the + // starting disk number 4 bytes + . PackBits::pack16le(0) // .ZIP file comment length 2 bytes + . ''; // TODO: implement? // .ZIP file comment (variable size) } // Utility methods //////////////////////////////////////////////////////// @@ -971,7 +977,7 @@ public static function getDosTime($timestamp = 0) { date_default_timezone_set($oldTZ); if ($date['year'] >= 1980) { return (($date['mday'] + ($date['mon'] << 5) + (($date['year'] - 1980) << 9)) << 16) - | (($date['seconds'] >> 1) + ($date['minutes'] << 5) + ($date['hours'] << 11)); + | (($date['seconds'] >> 1) + ($date['minutes'] << 5) + ($date['hours'] << 11)); } return 0x0000; } From 8253118837fe65e29a181f06269e5cbd33be747e Mon Sep 17 00:00:00 2001 From: Ruth Ivimey-Cook Date: Wed, 27 Jul 2016 14:32:19 +0100 Subject: [PATCH 24/24] FLush the output stream more completely. --- src/ZipStreamer.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/ZipStreamer.php b/src/ZipStreamer.php index 205ee47..57e62e1 100644 --- a/src/ZipStreamer.php +++ b/src/ZipStreamer.php @@ -322,6 +322,7 @@ public function addFileWrite($block) { return False; } $this->writeFile($block, $this->addFileOptions['compress'], $this->addFileOptions['level']); + $this->flush(); return True; } @@ -573,7 +574,9 @@ private function write($data) { * Flush the output stream. */ private function flush() { - return fflush($this->outstream); + fflush($this->outstream); + ob_flush(); + flush(); } /**