Kaynağa Gözat

Merge pull request #7994 from aschempp/feature/zip-util

Extract the ZIP utility functions from ArtifactRepository
Jordi Boggiano 5 yıl önce
ebeveyn
işleme
26a3e12c96

+ 4 - 64
src/Composer/Repository/ArtifactRepository.php

@@ -16,6 +16,7 @@ use Composer\IO\IOInterface;
 use Composer\Json\JsonFile;
 use Composer\Package\Loader\ArrayLoader;
 use Composer\Package\Loader\LoaderInterface;
+use Composer\Util\Zip;
 
 /**
  * @author Serge Smertin <serg.smertin@gmail.com>
@@ -80,76 +81,15 @@ class ArtifactRepository extends ArrayRepository implements ConfigurableReposito
         }
     }
 
-    /**
-     * Find a file by name, returning the one that has the shortest path.
-     *
-     * @param \ZipArchive $zip
-     * @param string $filename
-     * @return bool|int
-     */
-    private function locateFile(\ZipArchive $zip, $filename)
-    {
-        $indexOfShortestMatch = false;
-        $lengthOfShortestMatch = -1;
-
-        for ($i = 0; $i < $zip->numFiles; $i++) {
-            $stat = $zip->statIndex($i);
-            if (strcmp(basename($stat['name']), $filename) === 0) {
-                $directoryName = dirname($stat['name']);
-                if ($directoryName == '.') {
-                    //if composer.json is in root directory
-                    //it has to be the one to use.
-                    return $i;
-                }
-
-                if (strpos($directoryName, '\\') !== false ||
-                   strpos($directoryName, '/') !== false) {
-                    //composer.json files below first directory are rejected
-                    continue;
-                }
-
-                $length = strlen($stat['name']);
-                if ($indexOfShortestMatch === false || $length < $lengthOfShortestMatch) {
-                    //Check it's not a directory.
-                    $contents = $zip->getFromIndex($i);
-                    if ($contents !== false) {
-                        $indexOfShortestMatch = $i;
-                        $lengthOfShortestMatch = $length;
-                    }
-                }
-            }
-        }
-
-        return $indexOfShortestMatch;
-    }
-
     private function getComposerInformation(\SplFileInfo $file)
     {
-        $zip = new \ZipArchive();
-        if ($zip->open($file->getPathname()) !== true) {
-            return false;
-        }
-
-        if (0 == $zip->numFiles) {
-            $zip->close();
+        $json = Zip::getComposerJson($file->getPathname());
 
+        if (null === $json) {
             return false;
         }
 
-        $foundFileIndex = $this->locateFile($zip, 'composer.json');
-        if (false === $foundFileIndex) {
-            $zip->close();
-
-            return false;
-        }
-
-        $configurationFileName = $zip->getNameIndex($foundFileIndex);
-        $zip->close();
-
-        $composerFile = "zip://{$file->getPathname()}#$configurationFileName";
-        $json = file_get_contents($composerFile);
-
-        $package = JsonFile::parseJson($json, $composerFile);
+        $package = JsonFile::parseJson($json, $file->getPathname().'#composer.json');
         $package['dist'] = array(
             'type' => 'zip',
             'url' => strtr($file->getPathname(), '\\', '/'),

+ 108 - 0
src/Composer/Util/Zip.php

@@ -0,0 +1,108 @@
+<?php
+
+/*
+ * This file is part of Composer.
+ *
+ * (c) Nils Adermann <naderman@naderman.de>
+ *     Jordi Boggiano <j.boggiano@seld.be>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Composer\Util;
+
+/**
+ * @author Andreas Schempp <andreas.schempp@terminal42.ch>
+ */
+class Zip
+{
+    /**
+     * Gets content of the root composer.json inside a ZIP archive.
+     *
+     * @param string $pathToZip
+     * @param string $filename
+     *
+     * @return string|null
+     */
+    public static function getComposerJson($pathToZip)
+    {
+        if (!extension_loaded('zip')) {
+            throw new \RuntimeException('The Zip Util requires PHP\'s zip extension');
+        }
+
+        $zip = new \ZipArchive();
+        if ($zip->open($pathToZip) !== true) {
+            return null;
+        }
+
+        if (0 == $zip->numFiles) {
+            $zip->close();
+
+            return null;
+        }
+
+        $foundFileIndex = self::locateFile($zip, 'composer.json');
+        if (false === $foundFileIndex) {
+            $zip->close();
+
+            return null;
+        }
+
+        $content = null;
+        $configurationFileName = $zip->getNameIndex($foundFileIndex);
+        $stream = $zip->getStream($configurationFileName);
+
+        if (false !== $stream) {
+            $content = stream_get_contents($stream);
+        }
+
+        $zip->close();
+
+        return $content;
+    }
+
+    /**
+     * Find a file by name, returning the one that has the shortest path.
+     *
+     * @param \ZipArchive $zip
+     * @param string      $filename
+     *
+     * @return bool|int
+     */
+    private static function locateFile(\ZipArchive $zip, $filename)
+    {
+        $indexOfShortestMatch = false;
+        $lengthOfShortestMatch = -1;
+
+        for ($i = 0; $i < $zip->numFiles; $i++) {
+            $stat = $zip->statIndex($i);
+            if (strcmp(basename($stat['name']), $filename) === 0) {
+                $directoryName = dirname($stat['name']);
+                if ($directoryName === '.') {
+                    //if composer.json is in root directory
+                    //it has to be the one to use.
+                    return $i;
+                }
+
+                if (strpos($directoryName, '\\') !== false ||
+                    strpos($directoryName, '/') !== false) {
+                    //composer.json files below first directory are rejected
+                    continue;
+                }
+
+                $length = strlen($stat['name']);
+                if ($indexOfShortestMatch === false || $length < $lengthOfShortestMatch) {
+                    //Check it's not a directory.
+                    $contents = $zip->getFromIndex($i);
+                    if ($contents !== false) {
+                        $indexOfShortestMatch = $i;
+                        $lengthOfShortestMatch = $length;
+                    }
+                }
+            }
+        }
+
+        return $indexOfShortestMatch;
+    }
+}

BIN
tests/Composer/Test/Util/Fixtures/Zip/empty.zip


BIN
tests/Composer/Test/Util/Fixtures/Zip/folder.zip


BIN
tests/Composer/Test/Util/Fixtures/Zip/multiple.zip


BIN
tests/Composer/Test/Util/Fixtures/Zip/nojson.zip


BIN
tests/Composer/Test/Util/Fixtures/Zip/root.zip


BIN
tests/Composer/Test/Util/Fixtures/Zip/subfolder.zip


+ 117 - 0
tests/Composer/Test/Util/ZipTest.php

@@ -0,0 +1,117 @@
+<?php
+
+/*
+ * This file is part of Composer.
+ *
+ * (c) Nils Adermann <naderman@naderman.de>
+ *     Jordi Boggiano <j.boggiano@seld.be>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Composer\Test\Util;
+
+use Composer\Util\Zip;
+use PHPUnit\Framework\TestCase;
+
+/**
+ * @author Andreas Schempp <andreas.schempp@terminal42.ch>
+ */
+class ZipTest extends TestCase
+{
+    public function testThrowsExceptionIfZipExcentionIsNotLoaded()
+    {
+        if (extension_loaded('zip')) {
+            $this->markTestSkipped('The PHP zip extension is loaded.');
+        }
+
+        $this->setExpectedException('\RuntimeException', 'The Zip Util requires PHP\'s zip extension');
+
+        Zip::getComposerJson('');
+    }
+
+    public function testReturnsNullifTheZipIsNotFound()
+    {
+        if (!extension_loaded('zip')) {
+            $this->markTestSkipped('The PHP zip extension is not loaded.');
+            return;
+        }
+
+        $result = Zip::getComposerJson(__DIR__.'/Fixtures/Zip/invalid.zip');
+
+        $this->assertNull($result);
+    }
+
+    public function testReturnsNullIfTheZipIsEmpty()
+    {
+        if (!extension_loaded('zip')) {
+            $this->markTestSkipped('The PHP zip extension is not loaded.');
+            return;
+        }
+
+        $result = Zip::getComposerJson(__DIR__.'/Fixtures/Zip/empty.zip');
+
+        $this->assertNull($result);
+    }
+
+    public function testReturnsNullIfTheZipHasNoComposerJson()
+    {
+        if (!extension_loaded('zip')) {
+            $this->markTestSkipped('The PHP zip extension is not loaded.');
+            return;
+        }
+
+        $result = Zip::getComposerJson(__DIR__.'/Fixtures/Zip/nojson.zip');
+
+        $this->assertNull($result);
+    }
+
+    public function testReturnsNullIfTheComposerJsonIsInASubSubfolder()
+    {
+        if (!extension_loaded('zip')) {
+            $this->markTestSkipped('The PHP zip extension is not loaded.');
+            return;
+        }
+
+        $result = Zip::getComposerJson(__DIR__.'/Fixtures/Zip/subfolder.zip');
+
+        $this->assertNull($result);
+    }
+
+    public function testReturnsComposerJsonInZipRoot()
+    {
+        if (!extension_loaded('zip')) {
+            $this->markTestSkipped('The PHP zip extension is not loaded.');
+            return;
+        }
+
+        $result = Zip::getComposerJson(__DIR__.'/Fixtures/Zip/root.zip');
+
+        $this->assertEquals("{\n    \"name\": \"foo/bar\"\n}\n", $result);
+    }
+
+    public function testReturnsComposerJsonInFirstFolder()
+    {
+        if (!extension_loaded('zip')) {
+            $this->markTestSkipped('The PHP zip extension is not loaded.');
+            return;
+        }
+
+        $result = Zip::getComposerJson(__DIR__.'/Fixtures/Zip/folder.zip');
+
+        $this->assertEquals("{\n    \"name\": \"foo/bar\"\n}\n", $result);
+    }
+
+    public function testReturnsRootComposerJsonAndSkipsSubfolders()
+    {
+        if (!extension_loaded('zip')) {
+            $this->markTestSkipped('The PHP zip extension is not loaded.');
+            return;
+        }
+
+        $result = Zip::getComposerJson(__DIR__.'/Fixtures/Zip/multiple.zip');
+
+        $this->assertEquals("{\n    \"name\": \"foo/bar\"\n}\n", $result);
+    }
+}