diff --git a/src/Composer/Util/Zip.php b/src/Composer/Util/Zip.php index ab10d5bbf..3ebdcdd85 100644 --- a/src/Composer/Util/Zip.php +++ b/src/Composer/Util/Zip.php @@ -71,37 +71,41 @@ class Zip */ private static function locateFile(\ZipArchive $zip, $filename) { - $indexOfShortestMatch = false; - $lengthOfShortestMatch = -1; + // return root composer.json if it is there and is a file + if (false !== ($index = $zip->locateName($filename)) && $zip->getFromIndex($index) !== false) { + return $index; + } + $topLevelPaths = array(); 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; - } + $name = $zip->getNameIndex($i); + $dirname = dirname($name); - if (strpos($directoryName, '\\') !== false || - strpos($directoryName, '/') !== false) { - //composer.json files below first directory are rejected - continue; + // handle archives with proper TOC + if ($dirname === '.') { + $topLevelPaths[$name] = true; + if (\count($topLevelPaths) > 1) { + // archive can only contain one top level directory + return false; } + 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; - } + // handle archives which do not have a TOC record for the directory itself + if (false === strpos('\\', $dirname) && false === strpos('/', $dirname)) { + $topLevelPaths[$dirname.'/'] = true; + if (\count($topLevelPaths) > 1) { + // archive can only contain one top level directory + return false; } } } - return $indexOfShortestMatch; + if ($topLevelPaths && false !== ($index = $zip->locateName(key($topLevelPaths).$filename)) && $zip->getFromIndex($index) !== false) { + return $index; + } + + // no composer.json found either at the top level or within the topmost directory + return false; } } diff --git a/tests/Composer/Test/Repository/Fixtures/artifacts/composer-1.0.0-alpha6.zip b/tests/Composer/Test/Repository/Fixtures/artifacts/composer-1.0.0-alpha6.zip index e94843eb6..585b4f7ea 100644 Binary files a/tests/Composer/Test/Repository/Fixtures/artifacts/composer-1.0.0-alpha6.zip and b/tests/Composer/Test/Repository/Fixtures/artifacts/composer-1.0.0-alpha6.zip differ diff --git a/tests/Composer/Test/Repository/Fixtures/artifacts/jsonInFirstLevel.zip b/tests/Composer/Test/Repository/Fixtures/artifacts/jsonInFirstLevel.zip index 498037464..653b60095 100644 Binary files a/tests/Composer/Test/Repository/Fixtures/artifacts/jsonInFirstLevel.zip and b/tests/Composer/Test/Repository/Fixtures/artifacts/jsonInFirstLevel.zip differ diff --git a/tests/Composer/Test/Repository/Fixtures/artifacts/jsonInFirstLevelWithExtraFirstLevel.zip b/tests/Composer/Test/Repository/Fixtures/artifacts/jsonInFirstLevelWithExtraFirstLevel.zip new file mode 100644 index 000000000..b400918b9 Binary files /dev/null and b/tests/Composer/Test/Repository/Fixtures/artifacts/jsonInFirstLevelWithExtraFirstLevel.zip differ diff --git a/tests/Composer/Test/Repository/Fixtures/artifacts/not-an-artifact.zip b/tests/Composer/Test/Repository/Fixtures/artifacts/not-an-artifact.zip index 3e788dcc2..0979dcb16 100644 Binary files a/tests/Composer/Test/Repository/Fixtures/artifacts/not-an-artifact.zip and b/tests/Composer/Test/Repository/Fixtures/artifacts/not-an-artifact.zip differ diff --git a/tests/Composer/Test/Util/Fixtures/Zip/multiple.zip b/tests/Composer/Test/Util/Fixtures/Zip/multiple.zip index db8c50302..96c0959bb 100644 Binary files a/tests/Composer/Test/Util/Fixtures/Zip/multiple.zip and b/tests/Composer/Test/Util/Fixtures/Zip/multiple.zip differ diff --git a/tests/Composer/Test/Util/Fixtures/Zip/single-sub.zip b/tests/Composer/Test/Util/Fixtures/Zip/single-sub.zip new file mode 100644 index 000000000..b8ccd4c76 Binary files /dev/null and b/tests/Composer/Test/Util/Fixtures/Zip/single-sub.zip differ diff --git a/tests/Composer/Test/Util/Fixtures/Zip/subfolder.zip b/tests/Composer/Test/Util/Fixtures/Zip/subfolder.zip deleted file mode 100644 index 93060bea2..000000000 Binary files a/tests/Composer/Test/Util/Fixtures/Zip/subfolder.zip and /dev/null differ diff --git a/tests/Composer/Test/Util/Fixtures/Zip/subfolders.zip b/tests/Composer/Test/Util/Fixtures/Zip/subfolders.zip new file mode 100644 index 000000000..06827d6a4 Binary files /dev/null and b/tests/Composer/Test/Util/Fixtures/Zip/subfolders.zip differ diff --git a/tests/Composer/Test/Util/ZipTest.php b/tests/Composer/Test/Util/ZipTest.php index 19dc54fa1..78c7e9657 100644 --- a/tests/Composer/Test/Util/ZipTest.php +++ b/tests/Composer/Test/Util/ZipTest.php @@ -20,7 +20,7 @@ use Composer\Test\TestCase; */ class ZipTest extends TestCase { - public function testThrowsExceptionIfZipExcentionIsNotLoaded() + public function testThrowsExceptionIfZipExtensionIsNotLoaded() { if (extension_loaded('zip')) { $this->markTestSkipped('The PHP zip extension is loaded.'); @@ -74,7 +74,7 @@ class ZipTest extends TestCase return; } - $result = Zip::getComposerJson(__DIR__.'/Fixtures/Zip/subfolder.zip'); + $result = Zip::getComposerJson(__DIR__.'/Fixtures/Zip/subfolders.zip'); $this->assertNull($result); } @@ -103,7 +103,7 @@ class ZipTest extends TestCase $this->assertEquals("{\n \"name\": \"foo/bar\"\n}\n", $result); } - public function testReturnsRootComposerJsonAndSkipsSubfolders() + public function testMultipleTopLevelDirsIsInvalid() { if (!extension_loaded('zip')) { $this->markTestSkipped('The PHP zip extension is not loaded.'); @@ -112,6 +112,18 @@ class ZipTest extends TestCase $result = Zip::getComposerJson(__DIR__.'/Fixtures/Zip/multiple.zip'); + $this->assertNull($result); + } + + public function testReturnsComposerJsonFromFirstSubfolder() + { + if (!extension_loaded('zip')) { + $this->markTestSkipped('The PHP zip extension is not loaded.'); + return; + } + + $result = Zip::getComposerJson(__DIR__.'/Fixtures/Zip/single-sub.zip'); + $this->assertEquals("{\n \"name\": \"foo/bar\"\n}\n", $result); } }