From 3eb12efae5b588eb03e3f14787cc510198e8d0f8 Mon Sep 17 00:00:00 2001 From: Stephan Date: Wed, 16 Feb 2022 08:37:36 +0000 Subject: [PATCH 1/7] VcsRepositories: handle initialize with invalid repository URL (#10525) --- .../Repository/Vcs/GitBitbucketDriver.php | 5 +- src/Composer/Repository/Vcs/GitHubDriver.php | 5 +- src/Composer/Repository/Vcs/GitLabDriver.php | 2 +- .../Repository/Vcs/GitBitbucketDriverTest.php | 8 ++++ .../Test/Repository/Vcs/GitHubDriverTest.php | 48 ++++++++++++++++++- 5 files changed, 63 insertions(+), 5 deletions(-) diff --git a/src/Composer/Repository/Vcs/GitBitbucketDriver.php b/src/Composer/Repository/Vcs/GitBitbucketDriver.php index 694204899..dd4033991 100644 --- a/src/Composer/Repository/Vcs/GitBitbucketDriver.php +++ b/src/Composer/Repository/Vcs/GitBitbucketDriver.php @@ -61,7 +61,10 @@ class GitBitbucketDriver extends VcsDriver */ public function initialize() { - Preg::match('#^https?://bitbucket\.org/([^/]+)/([^/]+?)(\.git|/?)?$#i', $this->url, $match); + if (!Preg::isMatch('#^https?://bitbucket\.org/([^/]+)/([^/]+?)(\.git|/?)?$#i', $this->url, $match)) { + throw new \InvalidArgumentException(sprintf('The Bitbucket repository URL %s is invalid. It must be the HTTPS URL of a Bitbucket repository.', $this->url)); + } + $this->owner = $match[1]; $this->repository = $match[2]; $this->originUrl = 'bitbucket.org'; diff --git a/src/Composer/Repository/Vcs/GitHubDriver.php b/src/Composer/Repository/Vcs/GitHubDriver.php index e95492a66..33d7e8cd3 100644 --- a/src/Composer/Repository/Vcs/GitHubDriver.php +++ b/src/Composer/Repository/Vcs/GitHubDriver.php @@ -59,7 +59,10 @@ class GitHubDriver extends VcsDriver */ public function initialize() { - Preg::match('#^(?:(?:https?|git)://([^/]+)/|git@([^:]+):/?)([^/]+)/(.+?)(?:\.git|/)?$#', $this->url, $match); + if (!Preg::isMatch('#^(?:(?:https?|git)://([^/]+)/|git@([^:]+):/?)([^/]+)/(.+?)(?:\.git|/)?$#', $this->url, $match)) { + throw new \InvalidArgumentException(sprintf('The GitHub repository URL %s is invalid.', $this->url)); + } + $this->owner = $match[3]; $this->repository = $match[4]; $this->originUrl = strtolower(!empty($match[1]) ? $match[1] : $match[2]); diff --git a/src/Composer/Repository/Vcs/GitLabDriver.php b/src/Composer/Repository/Vcs/GitLabDriver.php index 3c45b4e73..2e7d36cc2 100644 --- a/src/Composer/Repository/Vcs/GitLabDriver.php +++ b/src/Composer/Repository/Vcs/GitLabDriver.php @@ -94,7 +94,7 @@ class GitLabDriver extends VcsDriver public function initialize() { if (!Preg::isMatch(self::URL_REGEX, $this->url, $match)) { - throw new \InvalidArgumentException('The URL provided is invalid. It must be the HTTP URL of a GitLab project.'); + throw new \InvalidArgumentException(sprintf('The GitLab repository URL %s is invalid. It must be the HTTP URL of a GitLab project.', $this->url)); } $guessedDomain = !empty($match['domain']) ? $match['domain'] : $match['domain2']; diff --git a/tests/Composer/Test/Repository/Vcs/GitBitbucketDriverTest.php b/tests/Composer/Test/Repository/Vcs/GitBitbucketDriverTest.php index e685b9dfd..b8fd2d2a5 100644 --- a/tests/Composer/Test/Repository/Vcs/GitBitbucketDriverTest.php +++ b/tests/Composer/Test/Repository/Vcs/GitBitbucketDriverTest.php @@ -218,6 +218,14 @@ class GitBitbucketDriverTest extends TestCase ); } + public function testInitializeInvalidRepositoryUrl() + { + $this->setExpectedException('\InvalidArgumentException'); + + $driver = $this->getDriver(array('url' => 'https://bitbucket.org/acme')); + $driver->initialize(); + } + public function testSupports() { $this->assertTrue( diff --git a/tests/Composer/Test/Repository/Vcs/GitHubDriverTest.php b/tests/Composer/Test/Repository/Vcs/GitHubDriverTest.php index d48a8fd1c..fcffb583b 100644 --- a/tests/Composer/Test/Repository/Vcs/GitHubDriverTest.php +++ b/tests/Composer/Test/Repository/Vcs/GitHubDriverTest.php @@ -14,11 +14,11 @@ namespace Composer\Test\Repository\Vcs; use Composer\Downloader\TransportException; use Composer\Repository\Vcs\GitHubDriver; +use Composer\Test\Mock\ProcessExecutorMock; use Composer\Test\TestCase; use Composer\Util\Filesystem; -use Composer\Util\Http\Response; -use Composer\Test\Mock\ProcessExecutorMock; use Composer\Config; +use Composer\Util\Http\Response; use Composer\Util\ProcessExecutor; class GitHubDriverTest extends TestCase @@ -341,6 +341,50 @@ class GitHubDriverTest extends TestCase $process->assertComplete($this); } + /** + * @return void + */ + public function initializeInvalidReoUrl() + { + $this->setExpectedException('\InvalidArgumentException'); + + $repoConfig = array( + 'url' => 'https://github.com/acme', + ); + + $io = $this->getMockBuilder('Composer\IO\IOInterface')->getMock(); + $httpDownloader = $this->getMockBuilder('Composer\Util\HttpDownloader') + ->setConstructorArgs(array($io, $this->config)) + ->getMock(); + + $gitHubDriver = new GitHubDriver($repoConfig, $io, $this->config, $httpDownloader, new ProcessExecutorMock); + $gitHubDriver->initialize(); + } + + /** + * @dataProvider supportsProvider + * @param bool $expected + * @param string $repoUrl + */ + public function testSupports($expected, $repoUrl) + { + $io = $this->getMockBuilder('Composer\IO\IOInterface')->getMock(); + + $this->assertSame($expected, GitHubDriver::supports($io, $this->config, $repoUrl)); + } + + /** + * @return list + */ + public function supportsProvider() + { + return array( + array(false, 'https://github.com/acme'), + array(true, 'https://github.com/acme/repository'), + array(true, 'git@github.com:acme/repository.git'), + ); + } + /** * @param string|object $object * @param string $attribute From a3e6af54f981804b085380cb4edf420ca7a7622f Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Wed, 16 Feb 2022 10:43:21 +0100 Subject: [PATCH 2/7] Allow xdebug-handler 3.x, fixes #10528 --- composer.json | 2 +- composer.lock | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/composer.json b/composer.json index 308b01100..a5f217143 100644 --- a/composer.json +++ b/composer.json @@ -27,7 +27,7 @@ "composer/metadata-minifier": "^1.0", "composer/semver": "^3.0", "composer/spdx-licenses": "^1.2", - "composer/xdebug-handler": "^2.0", + "composer/xdebug-handler": "^2.0 || ^3.0", "justinrainbow/json-schema": "^5.2.11", "psr/log": "^1.0 || ^2.0", "seld/jsonlint": "^1.4", diff --git a/composer.lock b/composer.lock index 6b44b0234..e7e7f7d76 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "659a81a68363780c2e9fb35a1116808c", + "content-hash": "7a6428e0d2adbbc09048fb85d7d6ca3e", "packages": [ { "name": "composer/ca-bundle", @@ -1067,12 +1067,12 @@ } }, "autoload": { - "psr-4": { - "Symfony\\Polyfill\\Mbstring\\": "" - }, "files": [ "bootstrap.php" - ] + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ From 8756f54da09cbdcade84ed1c6c7e7a4a7e81686f Mon Sep 17 00:00:00 2001 From: "Marko H. Tamminen" Date: Wed, 16 Feb 2022 13:17:49 +0200 Subject: [PATCH 3/7] Handle missing license when showing license summary (#10537) Fixes the issue of license summary command aborting when a package is missing license information. --- src/Composer/Command/LicensesCommand.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Composer/Command/LicensesCommand.php b/src/Composer/Command/LicensesCommand.php index 5375ab08d..a8aadd021 100644 --- a/src/Composer/Command/LicensesCommand.php +++ b/src/Composer/Command/LicensesCommand.php @@ -125,7 +125,8 @@ EOT $usedLicenses = array(); foreach ($packages as $package) { $license = $package instanceof CompletePackageInterface ? $package->getLicense() : array(); - $licenseName = $license[0]; + $licenseName = array_key_exists(0, $license) ? $license[0] : 'none'; + if (!isset($usedLicenses[$licenseName])) { $usedLicenses[$licenseName] = 0; } From 6ea5b84bd9143a0a084e6e6439d26982e44c16e6 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Wed, 16 Feb 2022 12:26:49 +0100 Subject: [PATCH 4/7] Fix licenses command summary to count all licenses of a package --- src/Composer/Command/LicensesCommand.php | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/Composer/Command/LicensesCommand.php b/src/Composer/Command/LicensesCommand.php index a8aadd021..86ca08d72 100644 --- a/src/Composer/Command/LicensesCommand.php +++ b/src/Composer/Command/LicensesCommand.php @@ -93,7 +93,7 @@ EOT $tableStyle->setVerticalBorderChar(''); } $tableStyle->setCellRowContentFormat('%s '); - $table->setHeaders(array('Name', 'Version', 'License')); + $table->setHeaders(array('Name', 'Version', 'Licenses')); foreach ($packages as $package) { $table->addRow(array( $package->getPrettyName(), @@ -124,13 +124,16 @@ EOT case 'summary': $usedLicenses = array(); foreach ($packages as $package) { - $license = $package instanceof CompletePackageInterface ? $package->getLicense() : array(); - $licenseName = array_key_exists(0, $license) ? $license[0] : 'none'; - - if (!isset($usedLicenses[$licenseName])) { - $usedLicenses[$licenseName] = 0; + $licenses = $package instanceof CompletePackageInterface ? $package->getLicense() : array(); + if (count($licenses) === 0) { + $licenses[] = 'none'; + } + foreach ($licenses as $licenseName) { + if (!isset($usedLicenses[$licenseName])) { + $usedLicenses[$licenseName] = 0; + } + $usedLicenses[$licenseName]++; } - $usedLicenses[$licenseName]++; } // Sort licenses so that the most used license will appear first From f1ebc1d2b606a3c8386a89d7d65b3e986ab9961b Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Wed, 16 Feb 2022 13:08:54 +0100 Subject: [PATCH 5/7] Fix init author support to make email optional, fixes #10538 --- src/Composer/Command/InitCommand.php | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/src/Composer/Command/InitCommand.php b/src/Composer/Command/InitCommand.php index a5cf0fee4..c792d12f7 100644 --- a/src/Composer/Command/InitCommand.php +++ b/src/Composer/Command/InitCommand.php @@ -350,7 +350,7 @@ EOT $self = $this; $author = $io->askAndValidate( - 'Author ['.$author.', n to skip]: ', + 'Author ['.(is_string($author) ? ''.$author.', ' : '') . 'n to skip]: ', function ($value) use ($self, $author) { if ($value === 'n' || $value === 'no') { return; @@ -358,6 +358,10 @@ EOT $value = $value ?: $author; $author = $self->parseAuthorString($value); + if ($author['email'] === null) { + return $author['name']; + } + return sprintf('%s <%s>', $author['name'], $author['email']); }, null, @@ -471,22 +475,20 @@ EOT /** * @private * @param string $author - * @return array{name: string, email: string} + * @return array{name: string, email: string|null} */ public function parseAuthorString($author) { - if (Preg::isMatch('/^(?P[- .,\p{L}\p{N}\p{Mn}\'’"()]+) <(?P.+?)>$/u', $author, $match)) { - if ($this->isValidEmail($match['email'])) { - return array( - 'name' => trim($match['name']), - 'email' => $match['email'], - ); - } + if (Preg::isMatch('/^(?P[- .,\p{L}\p{N}\p{Mn}\'’"()]+)(?: <(?P.+?)>)?$/u', $author, $match)) { + return array( + 'name' => trim($match['name']), + 'email' => (isset($match['email']) && '' !== $match['email'] && $this->isValidEmail($match['email'])) ? $match['email'] : null, + ); } throw new \InvalidArgumentException( - 'Invalid author string. Must be in the format: '. - 'John Smith ' + 'Invalid author string. Must be in the formats: '. + 'Jane Doe or John Smith ' ); } @@ -684,11 +686,11 @@ EOT /** * @param string $author * - * @return array + * @return array */ protected function formatAuthors($author) { - return array($this->parseAuthorString($author)); + return array(array_filter($this->parseAuthorString($author))); } /** From 28ec4fa7b032586df098546dede9b60a19e3c4cc Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Wed, 16 Feb 2022 13:24:15 +0100 Subject: [PATCH 6/7] Fix handling of invalid emails --- src/Composer/Command/InitCommand.php | 11 ++++++++--- tests/Composer/Test/Command/InitCommandTest.php | 8 ++++++++ 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/src/Composer/Command/InitCommand.php b/src/Composer/Command/InitCommand.php index c792d12f7..11a19de26 100644 --- a/src/Composer/Command/InitCommand.php +++ b/src/Composer/Command/InitCommand.php @@ -479,10 +479,15 @@ EOT */ public function parseAuthorString($author) { - if (Preg::isMatch('/^(?P[- .,\p{L}\p{N}\p{Mn}\'’"()]+)(?: <(?P.+?)>)?$/u', $author, $match)) { + if (Preg::isMatch('/^(?P[- .,\p{L}\p{N}\p{Mn}\'’"()]+)(?:\s+<(?P.+?)>)?$/u', $author, $match)) { + $hasEmail = isset($match['email']) && '' !== $match['email']; + if ($hasEmail && !$this->isValidEmail($match['email'])) { + throw new \InvalidArgumentException('Invalid email "'.$match['email'].'"'); + } + return array( 'name' => trim($match['name']), - 'email' => (isset($match['email']) && '' !== $match['email'] && $this->isValidEmail($match['email'])) ? $match['email'] : null, + 'email' => $hasEmail ? $match['email'] : null, ); } @@ -690,7 +695,7 @@ EOT */ protected function formatAuthors($author) { - return array(array_filter($this->parseAuthorString($author))); + return array(array_filter($this->parseAuthorString($author), 'is_string')); } /** diff --git a/tests/Composer/Test/Command/InitCommandTest.php b/tests/Composer/Test/Command/InitCommandTest.php index 8006fe106..d9a4f57c4 100644 --- a/tests/Composer/Test/Command/InitCommandTest.php +++ b/tests/Composer/Test/Command/InitCommandTest.php @@ -25,6 +25,14 @@ class InitCommandTest extends TestCase $this->assertEquals('john@example.com', $author['email']); } + public function testParseValidAuthorStringWithoutEmail() + { + $command = new InitCommand; + $author = $command->parseAuthorString('John Smith'); + $this->assertEquals('John Smith', $author['name']); + $this->assertNull($author['email']); + } + public function testParseValidUtf8AuthorString() { $command = new InitCommand; From 53810b0cfbdd8127efeac956b70f3d4a029a83c1 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Wed, 16 Feb 2022 13:30:54 +0100 Subject: [PATCH 7/7] Fix return type --- src/Composer/Command/InitCommand.php | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/Composer/Command/InitCommand.php b/src/Composer/Command/InitCommand.php index 11a19de26..30ef1fd0c 100644 --- a/src/Composer/Command/InitCommand.php +++ b/src/Composer/Command/InitCommand.php @@ -691,11 +691,16 @@ EOT /** * @param string $author * - * @return array + * @return array */ protected function formatAuthors($author) { - return array(array_filter($this->parseAuthorString($author), 'is_string')); + $author = $this->parseAuthorString($author); + if (null === $author['email']) { + unset($author['email']); + } + + return array($author); } /**