Clean up Zip Util to be more strict about what is a valid package archive, fixes #8931

main
Jordi Boggiano 4 years ago
parent 37b1e0fffd
commit 942562c382
No known key found for this signature in database
GPG Key ID: 7BBD42C429EC80BC

@ -71,37 +71,41 @@ class Zip
*/ */
private static function locateFile(\ZipArchive $zip, $filename) private static function locateFile(\ZipArchive $zip, $filename)
{ {
$indexOfShortestMatch = false; // return root composer.json if it is there and is a file
$lengthOfShortestMatch = -1; if (false !== ($index = $zip->locateName($filename)) && $zip->getFromIndex($index) !== false) {
return $index;
}
$topLevelPaths = array();
for ($i = 0; $i < $zip->numFiles; $i++) { for ($i = 0; $i < $zip->numFiles; $i++) {
$stat = $zip->statIndex($i); $name = $zip->getNameIndex($i);
if (strcmp(basename($stat['name']), $filename) === 0) { $dirname = dirname($name);
$directoryName = dirname($stat['name']);
if ($directoryName === '.') { // handle archives with proper TOC
//if composer.json is in root directory if ($dirname === '.') {
//it has to be the one to use. $topLevelPaths[$name] = true;
return $i; if (\count($topLevelPaths) > 1) {
} // archive can only contain one top level directory
return false;
if (strpos($directoryName, '\\') !== false ||
strpos($directoryName, '/') !== false) {
//composer.json files below first directory are rejected
continue;
} }
continue;
}
$length = strlen($stat['name']); // handle archives which do not have a TOC record for the directory itself
if ($indexOfShortestMatch === false || $length < $lengthOfShortestMatch) { if (false === strpos('\\', $dirname) && false === strpos('/', $dirname)) {
//Check it's not a directory. $topLevelPaths[$dirname.'/'] = true;
$contents = $zip->getFromIndex($i); if (\count($topLevelPaths) > 1) {
if ($contents !== false) { // archive can only contain one top level directory
$indexOfShortestMatch = $i; return false;
$lengthOfShortestMatch = $length;
}
} }
} }
} }
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;
} }
} }

@ -20,7 +20,7 @@ use Composer\Test\TestCase;
*/ */
class ZipTest extends TestCase class ZipTest extends TestCase
{ {
public function testThrowsExceptionIfZipExcentionIsNotLoaded() public function testThrowsExceptionIfZipExtensionIsNotLoaded()
{ {
if (extension_loaded('zip')) { if (extension_loaded('zip')) {
$this->markTestSkipped('The PHP zip extension is loaded.'); $this->markTestSkipped('The PHP zip extension is loaded.');
@ -74,7 +74,7 @@ class ZipTest extends TestCase
return; return;
} }
$result = Zip::getComposerJson(__DIR__.'/Fixtures/Zip/subfolder.zip'); $result = Zip::getComposerJson(__DIR__.'/Fixtures/Zip/subfolders.zip');
$this->assertNull($result); $this->assertNull($result);
} }
@ -103,7 +103,7 @@ class ZipTest extends TestCase
$this->assertEquals("{\n \"name\": \"foo/bar\"\n}\n", $result); $this->assertEquals("{\n \"name\": \"foo/bar\"\n}\n", $result);
} }
public function testReturnsRootComposerJsonAndSkipsSubfolders() public function testMultipleTopLevelDirsIsInvalid()
{ {
if (!extension_loaded('zip')) { if (!extension_loaded('zip')) {
$this->markTestSkipped('The PHP zip extension is not loaded.'); $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'); $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); $this->assertEquals("{\n \"name\": \"foo/bar\"\n}\n", $result);
} }
} }

Loading…
Cancel
Save