From c34f90ac18a13ffea66ad3ef3f440ab48a145024 Mon Sep 17 00:00:00 2001 From: Ne-Lexa <alexey@nelexa.ru> Date: Wed, 6 Dec 2017 15:09:50 +0300 Subject: [PATCH 1/2] fix bug issues #9 --- src/PhpZip/Model/Entry/ZipAbstractEntry.php | 17 ++--- src/PhpZip/Model/ZipEntry.php | 2 +- src/PhpZip/Stream/ZipOutputStream.php | 82 ++++++--------------- tests/PhpZip/ZipPasswordTest.php | 24 ++++++ 4 files changed, 56 insertions(+), 69 deletions(-) diff --git a/src/PhpZip/Model/Entry/ZipAbstractEntry.php b/src/PhpZip/Model/Entry/ZipAbstractEntry.php index 6fb64d8..72d1722 100644 --- a/src/PhpZip/Model/Entry/ZipAbstractEntry.php +++ b/src/PhpZip/Model/Entry/ZipAbstractEntry.php @@ -255,11 +255,8 @@ abstract class ZipAbstractEntry implements ZipEntry */ public function isZip64ExtensionsRequired() { - // Offset MUST be considered in decision about ZIP64 format - see - // description of Data Descriptor in ZIP File Format Specification! return 0xffffffff <= $this->getCompressedSize() - || 0xffffffff <= $this->getSize() - || 0xffffffff <= sprintf('%u', $this->getOffset()); + || 0xffffffff <= $this->getSize(); } /** @@ -432,7 +429,10 @@ abstract class ZipAbstractEntry implements ZipEntry */ public function getMethod() { - return $this->isInit(self::BIT_METHOD) ? $this->method & 0xffff : self::UNKNOWN; + $isInit = $this->isInit(self::BIT_METHOD); + return $isInit ? + $this->method & 0xffff : + self::UNKNOWN; } /** @@ -446,17 +446,14 @@ abstract class ZipAbstractEntry implements ZipEntry { if (self::UNKNOWN === $method) { $this->method = $method; + $this->setInit(self::BIT_METHOD, false); return $this; } if (0x0000 > $method || $method > 0xffff) { - throw new ZipException('method out of range'); + throw new ZipException('method out of range: ' . $method); } switch ($method) { case self::METHOD_WINZIP_AES: - $this->method = $method; - $this->setInit(self::BIT_METHOD, true); - break; - case ZipFileInterface::METHOD_STORED: case ZipFileInterface::METHOD_DEFLATED: case ZipFileInterface::METHOD_BZIP2: diff --git a/src/PhpZip/Model/ZipEntry.php b/src/PhpZip/Model/ZipEntry.php index 19dbd13..015b4ac 100644 --- a/src/PhpZip/Model/ZipEntry.php +++ b/src/PhpZip/Model/ZipEntry.php @@ -18,7 +18,7 @@ interface ZipEntry // Bit masks for initialized fields. const BIT_PLATFORM = 1, BIT_METHOD = 2 /* 1 << 1 */, - BIT_CRC = 2 /* 1 << 2 */, + BIT_CRC = 4 /* 1 << 2 */, BIT_DATE_TIME = 64 /* 1 << 6 */, BIT_EXTERNAL_ATTR = 128 /* 1 << 7*/ ; diff --git a/src/PhpZip/Stream/ZipOutputStream.php b/src/PhpZip/Stream/ZipOutputStream.php index d692e97..13cf2c5 100644 --- a/src/PhpZip/Stream/ZipOutputStream.php +++ b/src/PhpZip/Stream/ZipOutputStream.php @@ -223,7 +223,6 @@ class ZipOutputStream implements ZipOutputStreamInterface | ($entry->isDataDescriptorRequired() ? ZipEntry::GPBF_DATA_DESCRIPTOR : 0) | ($utf8 ? ZipEntry::GPBF_UTF8 : 0); - $skipCrc = false; $entryContent = null; $extraFieldsCollection = $entry->getExtraFieldsCollection(); if (!($entry instanceof ZipChangesEntry && !$entry->isChangedContent())) { @@ -233,57 +232,14 @@ class ZipOutputStream implements ZipOutputStreamInterface $entry->setSize(strlen($entryContent)); $entry->setCrc(crc32($entryContent)); - if ( - $encrypted && - ( - ZipEntry::METHOD_WINZIP_AES === $method || - $entry->getEncryptionMethod() === ZipFileInterface::ENCRYPTION_METHOD_WINZIP_AES_128 || - $entry->getEncryptionMethod() === ZipFileInterface::ENCRYPTION_METHOD_WINZIP_AES_192 || - $entry->getEncryptionMethod() === ZipFileInterface::ENCRYPTION_METHOD_WINZIP_AES_256 - ) - ) { - $field = null; - $method = $entry->getMethod(); - $keyStrength = WinZipAesEntryExtraField::getKeyStrangeFromEncryptionMethod($entry->getEncryptionMethod()); // size bits - - $compressedSize = $entry->getCompressedSize(); - - if (ZipEntry::METHOD_WINZIP_AES === $method) { - /** - * @var WinZipAesEntryExtraField $field - */ - $field = $extraFieldsCollection->get(WinZipAesEntryExtraField::getHeaderId()); - if (null !== $field) { - $method = $field->getMethod(); - if (ZipEntry::UNKNOWN !== $compressedSize) { - $compressedSize -= $field->getKeyStrength() / 2 // salt value - + 2 // password verification value - + 10; // authentication code - } - $entry->setMethod($method); - } - } - if (null === $field) { - $field = ExtraFieldsFactory::createWinZipAesEntryExtra(); - } - $field->setKeyStrength($keyStrength); - $field->setMethod($method); - $size = $entry->getSize(); - if (20 <= $size && ZipFileInterface::METHOD_BZIP2 !== $method) { - $field->setVendorVersion(WinZipAesEntryExtraField::VV_AE_1); - } else { - $field->setVendorVersion(WinZipAesEntryExtraField::VV_AE_2); - $skipCrc = true; - } - $extraFieldsCollection->add($field); - if (ZipEntry::UNKNOWN !== $compressedSize) { - $compressedSize += $field->getKeyStrength() / 2 // salt value - + 2 // password verification value - + 10; // authentication code - $entry->setCompressedSize($compressedSize); - } - if ($skipCrc) { - $entry->setCrc(0); + if ($encrypted && ZipEntry::METHOD_WINZIP_AES === $method) { + /** + * @var WinZipAesEntryExtraField $field + */ + $field = $extraFieldsCollection->get(WinZipAesEntryExtraField::getHeaderId()); + if (null !== $field) { + $method = $field->getMethod(); + $entry->setMethod($method); } } @@ -337,14 +293,23 @@ class ZipOutputStream implements ZipOutputStreamInterface } if ($encrypted) { - if ( - $entry->getEncryptionMethod() === ZipFileInterface::ENCRYPTION_METHOD_WINZIP_AES_128 || - $entry->getEncryptionMethod() === ZipFileInterface::ENCRYPTION_METHOD_WINZIP_AES_192 || - $entry->getEncryptionMethod() === ZipFileInterface::ENCRYPTION_METHOD_WINZIP_AES_256 - ) { - if ($skipCrc) { + if (in_array($entry->getEncryptionMethod(), [ + ZipFileInterface::ENCRYPTION_METHOD_WINZIP_AES_128, + ZipFileInterface::ENCRYPTION_METHOD_WINZIP_AES_192, + ZipFileInterface::ENCRYPTION_METHOD_WINZIP_AES_256, + ], true)) { + $keyStrength = WinZipAesEntryExtraField::getKeyStrangeFromEncryptionMethod($entry->getEncryptionMethod()); // size bits + $field = ExtraFieldsFactory::createWinZipAesEntryExtra(); + $field->setKeyStrength($keyStrength); + $field->setMethod($method); + $size = $entry->getSize(); + if (20 <= $size && ZipFileInterface::METHOD_BZIP2 !== $method) { + $field->setVendorVersion(WinZipAesEntryExtraField::VV_AE_1); + } else { + $field->setVendorVersion(WinZipAesEntryExtraField::VV_AE_2); $entry->setCrc(0); } + $extraFieldsCollection->add($field); $entry->setMethod(ZipEntry::METHOD_WINZIP_AES); $winZipAesEngine = new WinZipAesEngine($entry); @@ -375,6 +340,7 @@ class ZipOutputStream implements ZipOutputStreamInterface * @param ZipEntry $entry * @param string $content * @return string + * @throws ZipException */ protected function determineBestCompressionMethod(ZipEntry $entry, $content) { diff --git a/tests/PhpZip/ZipPasswordTest.php b/tests/PhpZip/ZipPasswordTest.php index ac96f10..46a969c 100644 --- a/tests/PhpZip/ZipPasswordTest.php +++ b/tests/PhpZip/ZipPasswordTest.php @@ -346,4 +346,28 @@ class ZipPasswordTest extends ZipFileAddDirTest $zipFile->close(); } + + /** + * @see https://github.com/Ne-Lexa/php-zip/issues/9 + */ + public function testIssues9() + { + $contents = str_pad('', 1000, 'test;test2;test3' . PHP_EOL, STR_PAD_RIGHT); + $password = base64_encode(CryptoUtil::randomBytes(20)); + + $encryptMethod = ZipFile::ENCRYPTION_METHOD_WINZIP_AES_256; + $zipFile = new ZipFile(); + $zipFile + ->addFromString('codes.csv', $contents) + ->setPassword($password, $encryptMethod) + ->saveAsFile($this->outputFilename) + ->close(); + + $this->assertCorrectZipArchive($this->outputFilename, $password); + + $zipFile->openFile($this->outputFilename); + $zipFile->setReadPassword($password); + $this->assertEquals($zipFile['codes.csv'], $contents); + $zipFile->close(); + } } From aa09b24d02a8be1a2b7ab7946d3261edef4ee8c1 Mon Sep 17 00:00:00 2001 From: Ne-Lexa <alexey@nelexa.ru> Date: Wed, 6 Dec 2017 15:28:17 +0300 Subject: [PATCH 2/2] added an additional test of the encrypted archive --- src/PhpZip/Model/ZipModel.php | 2 +- src/PhpZip/Stream/ZipOutputStream.php | 1 - tests/PhpZip/ZipFileTest.php | 2 +- tests/PhpZip/ZipPasswordTest.php | 26 ++++++++++++++++++ .../PhpZip/resources/aes_password_archive.zip | Bin 0 -> 193 bytes 5 files changed, 28 insertions(+), 3 deletions(-) create mode 100644 tests/PhpZip/resources/aes_password_archive.zip diff --git a/src/PhpZip/Model/ZipModel.php b/src/PhpZip/Model/ZipModel.php index 9adcf4e..ac62b17 100644 --- a/src/PhpZip/Model/ZipModel.php +++ b/src/PhpZip/Model/ZipModel.php @@ -224,7 +224,7 @@ class ZipModel implements \Countable if (isset($this->outEntries[$entryName])) { return $this->outEntries[$entryName]; } - throw new ZipNotFoundEntry('Zip entry ' . $entryName . ' not found'); + throw new ZipNotFoundEntry('Zip entry "' . $entryName . '" not found'); } /** diff --git a/src/PhpZip/Stream/ZipOutputStream.php b/src/PhpZip/Stream/ZipOutputStream.php index 13cf2c5..c1c875d 100644 --- a/src/PhpZip/Stream/ZipOutputStream.php +++ b/src/PhpZip/Stream/ZipOutputStream.php @@ -239,7 +239,6 @@ class ZipOutputStream implements ZipOutputStreamInterface $field = $extraFieldsCollection->get(WinZipAesEntryExtraField::getHeaderId()); if (null !== $field) { $method = $field->getMethod(); - $entry->setMethod($method); } } diff --git a/tests/PhpZip/ZipFileTest.php b/tests/PhpZip/ZipFileTest.php index 774288d..2adf1ee 100644 --- a/tests/PhpZip/ZipFileTest.php +++ b/tests/PhpZip/ZipFileTest.php @@ -1672,7 +1672,7 @@ class ZipFileTest extends ZipTestCase /** * @expectedException \PhpZip\Exception\ZipNotFoundEntry - * @expectedExceptionMessage Zip entry bad entry name not found + * @expectedExceptionMessage Zip entry "bad entry name" not found */ public function testNotFoundEntry() { diff --git a/tests/PhpZip/ZipPasswordTest.php b/tests/PhpZip/ZipPasswordTest.php index 46a969c..1321530 100644 --- a/tests/PhpZip/ZipPasswordTest.php +++ b/tests/PhpZip/ZipPasswordTest.php @@ -370,4 +370,30 @@ class ZipPasswordTest extends ZipFileAddDirTest $this->assertEquals($zipFile['codes.csv'], $contents); $zipFile->close(); } + + public function testReadAesEncryptedAndRewriteArchive() + { + $file = __DIR__ . '/resources/aes_password_archive.zip'; + $password = '1234567890'; + + $zipFile = new ZipFile(); + $zipFile->openFile($file); + $zipFile->setReadPassword($password); + $zipFile->setEntryComment('contents.txt', 'comment'); // change entry, but not changed contents + $zipFile->saveAsFile($this->outputFilename); + + $zipFile2 = new ZipFile(); + $zipFile2->openFile($this->outputFilename); + $zipFile2->setReadPassword($password); + $this->assertEquals($zipFile2->getListFiles(), $zipFile->getListFiles()); + foreach ($zipFile as $name => $contents) { + $this->assertNotEmpty($name); + $this->assertNotEmpty($contents); + $this->assertContains('test contents', $contents); + $this->assertEquals($zipFile2[$name], $contents); + } + $zipFile2->close(); + + $zipFile->close(); + } } diff --git a/tests/PhpZip/resources/aes_password_archive.zip b/tests/PhpZip/resources/aes_password_archive.zip new file mode 100644 index 0000000000000000000000000000000000000000..7de71ae7e9de6fda86ad53915846563a50e1c497 GIT binary patch literal 193 zcmWIWW@a&FW@Jca*j(A>%>V?3KrGG9z`(=6&5)d*SCX1nQmj`}QNlQroq>tL(Up;d zVdaA?Dff(`)VTcX!nLc*FHZDiywU1#VBwGTOO#g|s85-t|E9_RMu_+KWhSRjzT(t7 o5a7+oWY3J-9+15Z3<?cP8bLG;TLZjV*+9~aK<Emj(?A>s02-e!(*OVf literal 0 HcmV?d00001