1
0
Fork 0

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

pull/9058/head
Jordi Boggiano 2020-07-16 17:00:29 +02:00
parent 37b1e0fffd
commit 942562c382
No known key found for this signature in database
GPG Key ID: 7BBD42C429EC80BC
10 changed files with 42 additions and 26 deletions

View File

@ -71,37 +71,41 @@ class Zip
*/
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;
// return root composer.json if it is there and is a file
if (false !== ($index = $zip->locateName($filename)) && $zip->getFromIndex($index) !== false) {
return $index;
}
if (strpos($directoryName, '\\') !== false ||
strpos($directoryName, '/') !== false) {
//composer.json files below first directory are rejected
$topLevelPaths = array();
for ($i = 0; $i < $zip->numFiles; $i++) {
$name = $zip->getNameIndex($i);
$dirname = dirname($name);
// 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;
}
}

Binary file not shown.

Binary file not shown.

View File

@ -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);
}
}