From 3efd96d822c35bb53ccf4de9a7ae3bc07883256e Mon Sep 17 00:00:00 2001 From: rahul_ <rahul_gupta@3278723.no-reply.drupal.org> Date: Thu, 29 Sep 2022 20:03:18 +0000 Subject: [PATCH] Issue #3310901 by rahul_, phenaproxima, TravisCarden: Stage::require() should validate the incoming package names --- package_manager/src/Stage.php | 33 ++++++++++ ...OverwriteExistingPackagesValidatorTest.php | 2 +- .../tests/src/Kernel/StageTest.php | 60 ++++++++++++++++++- 3 files changed, 91 insertions(+), 4 deletions(-) diff --git a/package_manager/src/Stage.php b/package_manager/src/Stage.php index e34a400252..ef386f951c 100644 --- a/package_manager/src/Stage.php +++ b/package_manager/src/Stage.php @@ -356,10 +356,12 @@ class Stage implements LoggerAwareInterface { // Change the runtime and dev requirements as needed, but don't update // the installed packages yet. if ($runtime) { + $this->validatePackageNames($runtime); $command = array_merge(['require', '--no-update'], $runtime); $this->stager->stage($command, $active_dir, $stage_dir, NULL, $timeout); } if ($dev) { + $this->validatePackageNames($dev); $command = array_merge(['require', '--dev', '--no-update'], $dev); $this->stager->stage($command, $active_dir, $stage_dir, NULL, $timeout); } @@ -705,4 +707,35 @@ class Stage implements LoggerAwareInterface { return $this->t('Staged changes failed to apply, and the site is in an indeterminate state. It is strongly recommended to restore the code and database from a backup.'); } + /** + * Validates a set of package names. + * + * Package names are considered invalid if they look like Drupal project + * names. The only exceptions to this are `php` and `composer`, which Composer + * treats as legitimate requirements. + * + * @param string[] $package_versions + * A set of package names (with or without version constraints), as passed + * to ::require(). + * + * @throws \InvalidArgumentException + * Thrown if any of the given package names are invalid. + * + * @see https://getcomposer.org/doc/articles/composer-platform-dependencies.md + */ + protected function validatePackageNames(array $package_versions): void { + foreach ($package_versions as $package_name) { + $package_name = trim($package_name); + + // Don't mistake the legitimate `php` and `composer` platform requirements + // for Drupal projects. + if ($package_name === 'php' || $package_name === 'composer') { + continue; + } + elseif (preg_match('/^[a-z0-9_]+$/i', $package_name)) { + throw new \InvalidArgumentException("Invalid package name '$package_name'."); + } + } + } + } diff --git a/package_manager/tests/src/Kernel/OverwriteExistingPackagesValidatorTest.php b/package_manager/tests/src/Kernel/OverwriteExistingPackagesValidatorTest.php index 7eae32bb71..b8ba5daa15 100644 --- a/package_manager/tests/src/Kernel/OverwriteExistingPackagesValidatorTest.php +++ b/package_manager/tests/src/Kernel/OverwriteExistingPackagesValidatorTest.php @@ -51,7 +51,7 @@ class OverwriteExistingPackagesValidatorTest extends PackageManagerKernelTestBas $stage = $this->createStage(); $stage->create(); - $stage->require(['drupal' => '9.8.1']); + $stage->require(['drupal/core:9.8.1']); $expected_results = [ ValidationResult::createError([ diff --git a/package_manager/tests/src/Kernel/StageTest.php b/package_manager/tests/src/Kernel/StageTest.php index 2dd2dd82fe..7edc016dc9 100644 --- a/package_manager/tests/src/Kernel/StageTest.php +++ b/package_manager/tests/src/Kernel/StageTest.php @@ -300,7 +300,7 @@ class StageTest extends PackageManagerKernelTestBase { public function testCommitException(string $thrown_class, string $expected_class): void { $stage = $this->createStage(); $stage->create(); - $stage->require(['drupal/core' => '9.8.1']); + $stage->require(['drupal/core:9.8.1']); $thrown_message = 'A very bad thing happened'; // PreconditionException requires a preconditions object. @@ -421,7 +421,7 @@ class StageTest extends PackageManagerKernelTestBase { // Run apply and post-apply in the same request (i.e., the same request // time), and ensure the warning is logged. $stage->create(); - $stage->require(['drupal/core' => '9.8.1']); + $stage->require(['drupal/core:9.8.1']); $stage->apply(); $stage->postApply(); $this->assertTrue($logger->hasRecord($warning_message, LogLevel::WARNING)); @@ -429,7 +429,7 @@ class StageTest extends PackageManagerKernelTestBase { $logger->reset(); $stage->create(); - $stage->require(['drupal/core' => '9.8.2']); + $stage->require(['drupal/core:9.8.2']); $stage->apply(); // Simulate post-apply taking place in another request by simulating a // request time 30 seconds after apply started. @@ -438,6 +438,60 @@ class StageTest extends PackageManagerKernelTestBase { $this->assertFalse($logger->hasRecord($warning_message, LogLevel::WARNING)); } + /** + * @covers ::validatePackageNames + * + * @param string $package_name + * The package name. + * @param bool $is_invalid + * TRUE if the gien package name is invalid and will cause an exception, + * FALSE otherwise. + * + * @dataProvider providerValidatePackageNames + */ + public function testValidatePackageNames(string $package_name, bool $is_invalid): void { + $stage = $this->createStage(); + $stage->create(); + if ($is_invalid) { + $this->expectException('InvalidArgumentException'); + $this->expectExceptionMessage("Invalid package name '$package_name'."); + } + $stage->require([$package_name]); + // If we got here, the package name is valid and we just need to assert something so PHPUnit doesn't complain. + $this->assertTrue(TRUE); + } + + /** + * Data provider for testValidatePackageNames. + * + * @return array[] + * The test cases. + */ + public function providerValidatePackageNames(): array { + return [ + // White space is trimmed out of package names during validation. + 'empty string' => ['', FALSE], + 'white space' => [' ', FALSE], + // The `composer` and `php` requirements are special, since they could + // otherwise be mistaken for Drupal project names. + 'Composer runtime, unconstrained' => ['composer', FALSE], + 'Composer runtime, constrained' => ['composer:^2.4', FALSE], + 'Composer runtime API, unconstrained' => ['composer-runtime-api', FALSE], + 'Composer runtime API, constrained' => ['composer-runtime-api:~2.2.0', FALSE], + 'PHP runtime, unconstrained' => ['php', FALSE], + 'PHP runtime, constrained' => ['php:>=7.4', FALSE], + 'PHP runtime variant, unconstrained' => ['php-zts', FALSE], + 'PHP runtime variant, constrained' => ['php-zts:8.1', FALSE], + 'PHP extension, unconstrained' => ['ext-json', FALSE], + 'PHP extension, constrained' => ['ext-json:7.4.1', FALSE], + 'PHP library, unconstrained' => ['lib-curl', FALSE], + 'PHP library, constrained' => ['lib-curl:^7', FALSE], + 'Drupal package, unconstrained' => ['drupal/semver_test', FALSE], + 'Drupal package, constrained' => ['drupal/semver_test:^1.10', FALSE], + 'Drupal project' => ['semver_test', TRUE], + ]; + } + } /** -- GitLab