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