Parcourir la source

Fixed problem with cloning a zip container.

Ne-Lexa il y a 6 ans
Parent
commit
943cf3e777

+ 45 - 3
src/Model/Data/ZipNewData.php

@@ -4,18 +4,27 @@ namespace PhpZip\Model\Data;
 
 use PhpZip\Model\ZipData;
 use PhpZip\Model\ZipEntry;
+use PhpZip\ZipFile;
 
 /**
- * Class ZipNewData.
+ * The class contains a streaming resource with new content added to the ZIP archive.
  */
 class ZipNewData implements ZipData
 {
-    /** @var resource */
-    private $stream;
+    /**
+     * A static variable allows closing the stream in the destructor
+     * only if it is its sole holder.
+     *
+     * @var array<int, int> array of resource ids and the number of class clones
+     */
+    private static $guardClonedStream = [];
 
     /** @var ZipEntry */
     private $zipEntry;
 
+    /** @var resource */
+    private $stream;
+
     /**
      * ZipStringData constructor.
      *
@@ -38,6 +47,12 @@ class ZipNewData implements ZipData
         } elseif (\is_resource($data)) {
             $this->stream = $data;
         }
+
+        $resourceId = (int) $this->stream;
+        self::$guardClonedStream[$resourceId] =
+            isset(self::$guardClonedStream[$resourceId]) ?
+                self::$guardClonedStream[$resourceId] + 1 :
+                0;
     }
 
     /**
@@ -79,8 +94,35 @@ class ZipNewData implements ZipData
         stream_copy_to_stream($stream, $outStream);
     }
 
+    /**
+     * @see https://php.net/manual/en/language.oop5.cloning.php
+     */
+    public function __clone()
+    {
+        $resourceId = (int) $this->stream;
+        self::$guardClonedStream[$resourceId] =
+            isset(self::$guardClonedStream[$resourceId]) ?
+                self::$guardClonedStream[$resourceId] + 1 :
+                1;
+    }
+
+    /**
+     * The stream will be closed when closing the zip archive.
+     *
+     * The method implements protection against closing the stream of the cloned object.
+     *
+     * @see ZipFile::close()
+     */
     public function __destruct()
     {
+        $resourceId = (int) $this->stream;
+
+        if (isset(self::$guardClonedStream[$resourceId]) && self::$guardClonedStream[$resourceId] > 0) {
+            self::$guardClonedStream[$resourceId]--;
+
+            return;
+        }
+
         if (\is_resource($this->stream)) {
             fclose($this->stream);
         }

+ 16 - 0
src/Model/ImmutableZipContainer.php

@@ -53,4 +53,20 @@ class ImmutableZipContainer implements \Countable
     {
         return \count($this->entries);
     }
+
+    /**
+     * When an object is cloned, PHP 5 will perform a shallow copy of all of the object's properties.
+     * Any properties that are references to other variables, will remain references.
+     * Once the cloning is complete, if a __clone() method is defined,
+     * then the newly created object's __clone() method will be called, to allow any necessary properties that need to
+     * be changed. NOT CALLABLE DIRECTLY.
+     *
+     * @see https://php.net/manual/en/language.oop5.cloning.php
+     */
+    public function __clone()
+    {
+        foreach ($this->entries as $key => $value) {
+            $this->entries[$key] = clone $value;
+        }
+    }
 }

+ 6 - 1
src/Model/ZipContainer.php

@@ -12,7 +12,12 @@ use PhpZip\Exception\ZipException;
  */
 class ZipContainer extends ImmutableZipContainer
 {
-    /** @var ImmutableZipContainer|null */
+    /**
+     * @var ImmutableZipContainer|null The source container contains zip entries from
+     *                                 an open zip archive. The source container makes
+     *                                 it possible to undo changes in the archive.
+     *                                 When cloning, this container is not cloned.
+     */
     private $sourceContainer;
 
     /**

+ 68 - 2
tests/CustomZipFormatTest.php

@@ -4,12 +4,16 @@ namespace PhpZip\Tests;
 
 use PhpZip\Exception\ZipEntryNotFoundException;
 use PhpZip\Exception\ZipException;
+use PhpZip\Model\Extra\Fields\NewUnixExtraField;
+use PhpZip\Model\Extra\Fields\NtfsExtraField;
+use PhpZip\Tests\Internal\CustomZip\ZipFileCustomWriter;
+use PhpZip\Tests\Internal\CustomZip\ZipFileWithBeforeSave;
 use PhpZip\Tests\Internal\Epub\EpubFile;
+use PhpZip\ZipFile;
 
 /**
  * Checks the ability to create own file-type class, reader, writer and container.
- *
- * @see http://www.epubtest.org/test-books source epub files
+ **.
  *
  * @internal
  *
@@ -19,6 +23,8 @@ final class CustomZipFormatTest extends ZipTestCase
 {
     /**
      * @throws ZipException
+     *
+     * @see http://www.epubtest.org/test-books source epub files
      */
     public function testEpub()
     {
@@ -57,4 +63,64 @@ final class CustomZipFormatTest extends ZipTestCase
         self::assertTrue($epubFile->hasEntry('mimetype'));
         $epubFile->close();
     }
+
+    /**
+     * @throws \Exception
+     */
+    public function testBeforeSaveInZipWriter()
+    {
+        $zipFile = new ZipFileCustomWriter();
+        for ($i = 0; $i < 10; $i++) {
+            $zipFile['file ' . $i] = 'contents file ' . $i;
+        }
+        $this->existsExtraFields($zipFile, false);
+        $zipFile->saveAsFile($this->outputFilename);
+        $this->existsExtraFields($zipFile, false);
+        $zipFile->close();
+
+        self::assertCorrectZipArchive($this->outputFilename);
+
+        $zipFile->openFile($this->outputFilename);
+        $this->existsExtraFields($zipFile, true);
+        $zipFile->close();
+    }
+
+    /**
+     * @throws \Exception
+     */
+    public function testBeforeSaveInZipFile()
+    {
+        $zipFile = new ZipFileWithBeforeSave();
+        for ($i = 0; $i < 10; $i++) {
+            $zipFile['file ' . $i] = 'contents file ' . $i;
+        }
+        $this->existsExtraFields($zipFile, false);
+        $zipFile->saveAsFile($this->outputFilename);
+        $this->existsExtraFields($zipFile, true);
+        $zipFile->close();
+
+        self::assertCorrectZipArchive($this->outputFilename);
+
+        $zipFile->openFile($this->outputFilename);
+        $this->existsExtraFields($zipFile, true);
+        $zipFile->close();
+    }
+
+    /**
+     * @param ZipFile $zipFile
+     * @param bool    $exists
+     */
+    private function existsExtraFields(ZipFile $zipFile, $exists)
+    {
+        foreach ($zipFile->getEntries() as $entry) {
+            $localExtras = $entry->getLocalExtraFields();
+            $cdExtras = $entry->getCdExtraFields();
+
+            self::assertSame(isset($localExtras[NtfsExtraField::HEADER_ID]), $exists);
+            self::assertSame(isset($cdExtras[NtfsExtraField::HEADER_ID]), $exists);
+
+            self::assertSame(isset($localExtras[NewUnixExtraField::HEADER_ID]), $exists);
+            self::assertSame(isset($cdExtras[NewUnixExtraField::HEADER_ID]), $exists);
+        }
+    }
 }

+ 39 - 0
tests/Internal/CustomZip/CustomZipWriter.php

@@ -0,0 +1,39 @@
+<?php
+
+namespace PhpZip\Tests\Internal\CustomZip;
+
+use PhpZip\IO\ZipWriter;
+use PhpZip\Model\Extra\Fields\NewUnixExtraField;
+use PhpZip\Model\Extra\Fields\NtfsExtraField;
+use PhpZip\Model\ZipContainer;
+
+/**
+ * Class CustomZipWriter.
+ */
+class CustomZipWriter extends ZipWriter
+{
+    /**
+     * ZipWriter constructor.
+     *
+     * @param ZipContainer $container
+     */
+    public function __construct(ZipContainer $container)
+    {
+//        dump($container);
+        parent::__construct($container);
+//        dd($this->zipContainer);
+    }
+
+    protected function beforeWrite()
+    {
+        parent::beforeWrite();
+        $now = new \DateTimeImmutable();
+        $ntfsTimeExtra = NtfsExtraField::create($now, $now->modify('-1 day'), $now->modify('-10 day'));
+        $unixExtra = new NewUnixExtraField();
+
+        foreach ($this->zipContainer->getEntries() as $entry) {
+            $entry->addExtraField($ntfsTimeExtra);
+            $entry->addExtraField($unixExtra);
+        }
+    }
+}

+ 20 - 0
tests/Internal/CustomZip/ZipFileCustomWriter.php

@@ -0,0 +1,20 @@
+<?php
+
+namespace PhpZip\Tests\Internal\CustomZip;
+
+use PhpZip\IO\ZipWriter;
+use PhpZip\ZipFile;
+
+/**
+ * Class ZipFileCustomWriter.
+ */
+class ZipFileCustomWriter extends ZipFile
+{
+    /**
+     * @return ZipWriter
+     */
+    protected function createZipWriter()
+    {
+        return new CustomZipWriter($this->zipContainer);
+    }
+}

+ 28 - 0
tests/Internal/CustomZip/ZipFileWithBeforeSave.php

@@ -0,0 +1,28 @@
+<?php
+
+namespace PhpZip\Tests\Internal\CustomZip;
+
+use PhpZip\Model\Extra\Fields\NewUnixExtraField;
+use PhpZip\Model\Extra\Fields\NtfsExtraField;
+use PhpZip\ZipFile;
+
+/**
+ * Class ZipFileWithBeforeSave.
+ */
+class ZipFileWithBeforeSave extends ZipFile
+{
+    /**
+     * Event before save or output.
+     */
+    protected function onBeforeSave()
+    {
+        $now = new \DateTimeImmutable();
+        $ntfsTimeExtra = NtfsExtraField::create($now, $now->modify('-1 day'), $now->modify('-10 day'));
+        $unixExtra = new NewUnixExtraField();
+
+        foreach ($this->zipContainer->getEntries() as $entry) {
+            $entry->addExtraField($ntfsTimeExtra);
+            $entry->addExtraField($unixExtra);
+        }
+    }
+}

+ 49 - 0
tests/ZipEntryTest.php

@@ -304,6 +304,55 @@ class ZipEntryTest extends TestCase
         static::assertNull($zipEntry->getData());
     }
 
+    /**
+     * @throws \Exception
+     */
+    public function testZipNewDataGuardClone()
+    {
+        $resource = fopen('php://temp', 'r+b');
+        static::assertNotFalse($resource);
+        fwrite($resource, random_bytes(1024));
+        rewind($resource);
+
+        $zipEntry = new ZipEntry('entry');
+        $zipEntry2 = new ZipEntry('entry2');
+
+        $zipData = new ZipNewData($zipEntry, $resource);
+        $zipData2 = new ZipNewData($zipEntry2, $resource);
+        $cloneData = clone $zipData;
+        $cloneData2 = clone $cloneData;
+
+        static::assertSame($zipData->getDataAsStream(), $resource);
+        static::assertSame($zipData2->getDataAsStream(), $resource);
+        static::assertSame($cloneData->getDataAsStream(), $resource);
+        static::assertSame($cloneData2->getDataAsStream(), $resource);
+
+        $validResource = \is_resource($resource);
+        static::assertTrue($validResource);
+
+        unset($cloneData);
+        $validResource = \is_resource($resource);
+        static::assertTrue($validResource);
+
+        unset($zipData);
+        $validResource = \is_resource($resource);
+        static::assertTrue($validResource);
+
+        unset($zipData2);
+        $validResource = \is_resource($resource);
+        static::assertTrue($validResource);
+
+        $reflectionClass = new \ReflectionClass($cloneData2);
+        static::assertSame(
+            $reflectionClass->getStaticProperties()['guardClonedStream'][(int) $resource],
+            0
+        );
+
+        unset($cloneData2);
+        $validResource = \is_resource($resource);
+        static::assertFalse($validResource);
+    }
+
     /**
      * @dataProvider providePlatform
      *

+ 0 - 41
tests/ZipEventTest.php

@@ -1,41 +0,0 @@
-<?php
-
-namespace PhpZip\Tests;
-
-use PhpZip\Exception\ZipException;
-use PhpZip\Tests\Internal\ZipFileExtended;
-
-/**
- * @internal
- *
- * @small
- */
-class ZipEventTest extends ZipTestCase
-{
-    /**
-     * @throws ZipException
-     */
-    public function testBeforeSave()
-    {
-        $zipFile = new ZipFileExtended();
-        $zipFile->openFile(__DIR__ . '/resources/apk.zip');
-        static::assertTrue(isset($zipFile['META-INF/MANIFEST.MF']));
-        static::assertTrue(isset($zipFile['META-INF/CERT.SF']));
-        static::assertTrue(isset($zipFile['META-INF/CERT.RSA']));
-        // the "META-INF/" folder will be deleted when saved
-        // in the ZipFileExtended::onBeforeSave() method
-        $zipFile->saveAsFile($this->outputFilename);
-        static::assertFalse(isset($zipFile['META-INF/MANIFEST.MF']));
-        static::assertFalse(isset($zipFile['META-INF/CERT.SF']));
-        static::assertFalse(isset($zipFile['META-INF/CERT.RSA']));
-        $zipFile->close();
-
-        static::assertCorrectZipArchive($this->outputFilename);
-
-        $zipFile->openFile($this->outputFilename);
-        static::assertFalse(isset($zipFile['META-INF/MANIFEST.MF']));
-        static::assertFalse(isset($zipFile['META-INF/CERT.SF']));
-        static::assertFalse(isset($zipFile['META-INF/CERT.RSA']));
-        $zipFile->close();
-    }
-}

+ 14 - 0
tests/ZipFileTest.php

@@ -2435,4 +2435,18 @@ class ZipFileTest extends ZipTestCase
 
         $zipFile->close();
     }
+
+    /**
+     * @throws ZipException
+     */
+    public function testMultiSave()
+    {
+        $zipFile = new ZipFile();
+        $zipFile['file 1'] = 'contents';
+        for ($i = 0; $i < 10; $i++) {
+            $zipFile->saveAsFile($this->outputFilename);
+            self::assertCorrectZipArchive($this->outputFilename);
+        }
+        $zipFile->close();
+    }
 }