diff --git a/CHANGELOG.md b/CHANGELOG.md index decb5b05c..21f52c39e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -71,6 +71,15 @@ * Fixed symlink creation in linux VM guest filesystems to be recognized by Windows (#10592) * Performance improvement in pool optimization step (#10585) +### [2.2.13] 2022-05-25 + + * Fixed invalid credentials loop when setting up GitLab token (#10748) + * Fixed PHP 8.2 deprecations (#10766) + * Fixed lock file changes being output even when the lock file creation is disabled + * Fixed race condition when multiple requests asking for auth on the same hostname fired concurrently (#10763) + * Fixed quoting of commas on Windows (#10775) + * Fixed issue installing path repos with a disabled symlink function (#10786) + ### [2.2.12] 2022-04-13 * Security: Fixed command injection vulnerability in HgDriver/GitDriver (GHSA-x7cr-6qr6-2hh6 / CVE-2022-24828) @@ -1508,6 +1517,7 @@ [2.3.0]: https://github.com/composer/composer/compare/2.3.0-RC2...2.3.0 [2.3.0-RC2]: https://github.com/composer/composer/compare/2.3.0-RC1...2.3.0-RC2 [2.3.0-RC1]: https://github.com/composer/composer/compare/2.2.9...2.3.0-RC1 +[2.2.13]: https://github.com/composer/composer/compare/2.2.12...2.2.13 [2.2.12]: https://github.com/composer/composer/compare/2.2.11...2.2.12 [2.2.11]: https://github.com/composer/composer/compare/2.2.10...2.2.11 [2.2.10]: https://github.com/composer/composer/compare/2.2.9...2.2.10 diff --git a/composer.lock b/composer.lock index 28eda92d6..b97256314 100644 --- a/composer.lock +++ b/composer.lock @@ -8,16 +8,16 @@ "packages": [ { "name": "composer/ca-bundle", - "version": "1.3.1", + "version": "1.3.2", "source": { "type": "git", "url": "https://github.com/composer/ca-bundle.git", - "reference": "4c679186f2aca4ab6a0f1b0b9cf9252decb44d0b" + "reference": "fd5dd441932a7e10ca6e5b490e272d34c8430640" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/ca-bundle/zipball/4c679186f2aca4ab6a0f1b0b9cf9252decb44d0b", - "reference": "4c679186f2aca4ab6a0f1b0b9cf9252decb44d0b", + "url": "https://api.github.com/repos/composer/ca-bundle/zipball/fd5dd441932a7e10ca6e5b490e272d34c8430640", + "reference": "fd5dd441932a7e10ca6e5b490e272d34c8430640", "shasum": "" }, "require": { @@ -64,7 +64,7 @@ "support": { "irc": "irc://irc.freenode.org/composer", "issues": "https://github.com/composer/ca-bundle/issues", - "source": "https://github.com/composer/ca-bundle/tree/1.3.1" + "source": "https://github.com/composer/ca-bundle/tree/1.3.2" }, "funding": [ { @@ -80,7 +80,7 @@ "type": "tidelift" } ], - "time": "2021-10-28T20:44:15+00:00" + "time": "2022-05-24T11:56:16+00:00" }, { "name": "composer/metadata-minifier", @@ -305,16 +305,16 @@ }, { "name": "composer/spdx-licenses", - "version": "1.5.6", + "version": "1.5.7", "source": { "type": "git", "url": "https://github.com/composer/spdx-licenses.git", - "reference": "a30d487169d799745ca7280bc90fdfa693536901" + "reference": "c848241796da2abf65837d51dce1fae55a960149" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/spdx-licenses/zipball/a30d487169d799745ca7280bc90fdfa693536901", - "reference": "a30d487169d799745ca7280bc90fdfa693536901", + "url": "https://api.github.com/repos/composer/spdx-licenses/zipball/c848241796da2abf65837d51dce1fae55a960149", + "reference": "c848241796da2abf65837d51dce1fae55a960149", "shasum": "" }, "require": { @@ -365,7 +365,7 @@ "support": { "irc": "irc://irc.freenode.org/composer", "issues": "https://github.com/composer/spdx-licenses/issues", - "source": "https://github.com/composer/spdx-licenses/tree/1.5.6" + "source": "https://github.com/composer/spdx-licenses/tree/1.5.7" }, "funding": [ { @@ -381,7 +381,7 @@ "type": "tidelift" } ], - "time": "2021-11-18T10:14:14+00:00" + "time": "2022-05-23T07:37:50+00:00" }, { "name": "composer/xdebug-handler", @@ -1825,16 +1825,16 @@ "packages-dev": [ { "name": "phpstan/phpstan", - "version": "1.6.8", + "version": "1.7.1", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "d76498c5531232cb8386ceb6004f7e013138d3ba" + "reference": "e3baed2ee2ef322e0f9b8fe8f87fdbe024c7c719" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/d76498c5531232cb8386ceb6004f7e013138d3ba", - "reference": "d76498c5531232cb8386ceb6004f7e013138d3ba", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/e3baed2ee2ef322e0f9b8fe8f87fdbe024c7c719", + "reference": "e3baed2ee2ef322e0f9b8fe8f87fdbe024c7c719", "shasum": "" }, "require": { @@ -1860,7 +1860,7 @@ "description": "PHPStan - PHP Static Analysis Tool", "support": { "issues": "https://github.com/phpstan/phpstan/issues", - "source": "https://github.com/phpstan/phpstan/tree/1.6.8" + "source": "https://github.com/phpstan/phpstan/tree/1.7.1" }, "funding": [ { @@ -1880,7 +1880,7 @@ "type": "tidelift" } ], - "time": "2022-05-10T06:54:21+00:00" + "time": "2022-05-24T09:05:09+00:00" }, { "name": "phpstan/phpstan-deprecation-rules", diff --git a/phpstan/baseline.neon b/phpstan/baseline.neon index 9fd8b8af7..32b12b276 100644 --- a/phpstan/baseline.neon +++ b/phpstan/baseline.neon @@ -2167,7 +2167,7 @@ parameters: - message: "#^Call to function in_array\\(\\) with arguments 20, array\\{0\\: 10\\|20, 1\\?\\: 20\\} and true will always evaluate to true\\.$#" - count: 1 + count: 2 path: ../src/Composer/Downloader/PathDownloader.php - diff --git a/res/composer-schema.json b/res/composer-schema.json index 2c410b262..6e85af00d 100644 --- a/res/composer-schema.json +++ b/res/composer-schema.json @@ -6,7 +6,7 @@ "name": { "type": "string", "description": "Package name, including 'vendor-name/' prefix.", - "pattern": "^[a-z0-9]([_.-]?[a-z0-9]+)*/[a-z0-9](([_.]?|-{0,2})[a-z0-9]+)*$" + "pattern": "^[a-z0-9]([_.-]?[a-z0-9]++)*+/[a-z0-9](([_.]|-{1,2})?[a-z0-9]++)*+$" }, "description": { "type": "string", diff --git a/src/Composer/Command/ArchiveCommand.php b/src/Composer/Command/ArchiveCommand.php index fa450a6da..a35f3aecf 100644 --- a/src/Composer/Command/ArchiveCommand.php +++ b/src/Composer/Command/ArchiveCommand.php @@ -159,7 +159,7 @@ EOT $localRepo = $composer->getRepositoryManager()->getLocalRepository(); $repo = new CompositeRepository(array_merge(array($localRepo), $composer->getRepositoryManager()->getRepositories())); } else { - $defaultRepos = RepositoryFactory::defaultRepos($this->getIO()); + $defaultRepos = RepositoryFactory::defaultReposWithDefaultManager($io); $io->writeError('No composer.json found in the current directory, searching packages from ' . implode(', ', array_keys($defaultRepos))); $repo = new CompositeRepository($defaultRepos); } diff --git a/src/Composer/Command/BaseDependencyCommand.php b/src/Composer/Command/BaseDependencyCommand.php index 7222319ff..2d4dea40f 100644 --- a/src/Composer/Command/BaseDependencyCommand.php +++ b/src/Composer/Command/BaseDependencyCommand.php @@ -18,6 +18,7 @@ use Composer\Package\CompletePackageInterface; use Composer\Package\RootPackage; use Composer\Repository\InstalledArrayRepository; use Composer\Repository\CompositeRepository; +use Composer\Repository\RepositoryInterface; use Composer\Repository\RootPackageRepository; use Composer\Repository\InstalledRepository; use Composer\Repository\PlatformRepository; @@ -80,7 +81,7 @@ abstract class BaseDependencyCommand extends BaseCommand // If the version we ask for is not installed then we need to locate it in remote repos and add it. // This is needed for why-not to resolve conflicts from an uninstalled version against installed packages. if (!$installedRepo->findPackage($needle, $textConstraint)) { - $defaultRepos = new CompositeRepository(RepositoryFactory::defaultRepos($this->getIO())); + $defaultRepos = new CompositeRepository(RepositoryFactory::defaultRepos($this->getIO(), $composer->getConfig(), $composer->getRepositoryManager())); if ($match = $defaultRepos->findPackage($needle, $textConstraint)) { $installedRepo->addRepository(new InstalledArrayRepository(array(clone $match))); } diff --git a/src/Composer/Command/HomeCommand.php b/src/Composer/Command/HomeCommand.php index dad4e3a75..c4457ac4c 100644 --- a/src/Composer/Command/HomeCommand.php +++ b/src/Composer/Command/HomeCommand.php @@ -170,6 +170,6 @@ EOT ); } - return RepositoryFactory::defaultRepos($this->getIO()); + return RepositoryFactory::defaultReposWithDefaultManager($this->getIO()); } } diff --git a/src/Composer/Command/InitCommand.php b/src/Composer/Command/InitCommand.php index f923f6929..ae8bc6c9b 100644 --- a/src/Composer/Command/InitCommand.php +++ b/src/Composer/Command/InitCommand.php @@ -225,6 +225,9 @@ EOT $repositories = $input->getOption('repository'); if (count($repositories) > 0) { $config = Factory::createConfig($io); + $io->loadConfiguration($config); + $repoManager = RepositoryFactory::manager($io, $config); + $repos = array(new PlatformRepository); $createDefaultPackagistRepo = true; foreach ($repositories as $repo) { @@ -236,14 +239,14 @@ EOT $createDefaultPackagistRepo = false; continue; } - $repos[] = RepositoryFactory::createRepo($io, $config, $repoConfig); + $repos[] = RepositoryFactory::createRepo($io, $config, $repoConfig, $repoManager); } if ($createDefaultPackagistRepo) { $repos[] = RepositoryFactory::createRepo($io, $config, array( 'type' => 'composer', 'url' => 'https://repo.packagist.org', - )); + ), $repoManager); } $this->repos = new CompositeRepository($repos); diff --git a/src/Composer/Command/PackageDiscoveryTrait.php b/src/Composer/Command/PackageDiscoveryTrait.php index 99fb0f39b..5e210bd7c 100644 --- a/src/Composer/Command/PackageDiscoveryTrait.php +++ b/src/Composer/Command/PackageDiscoveryTrait.php @@ -47,7 +47,7 @@ trait PackageDiscoveryTrait if (null === $this->repos) { $this->repos = new CompositeRepository(array_merge( array(new PlatformRepository), - RepositoryFactory::defaultRepos($this->getIO()) + RepositoryFactory::defaultReposWithDefaultManager($this->getIO()) )); } diff --git a/src/Composer/Command/ShowCommand.php b/src/Composer/Command/ShowCommand.php index 0194662b7..72242073b 100644 --- a/src/Composer/Command/ShowCommand.php +++ b/src/Composer/Command/ShowCommand.php @@ -194,7 +194,7 @@ EOT $repos = new CompositeRepository($composer->getRepositoryManager()->getRepositories()); $installedRepo->addRepository($composer->getRepositoryManager()->getLocalRepository()); } else { - $defaultRepos = RepositoryFactory::defaultRepos($io); + $defaultRepos = RepositoryFactory::defaultReposWithDefaultManager($io); $repos = new CompositeRepository($defaultRepos); $io->writeError('No composer.json found in the current directory, showing available packages from ' . implode(', ', array_keys($defaultRepos))); } @@ -209,7 +209,7 @@ EOT } $repos = new CompositeRepository(array_merge(array(new FilterRepository($installedRepo, array('canonical' => false))), $composer->getRepositoryManager()->getRepositories())); } elseif ($input->getOption('all')) { - $defaultRepos = RepositoryFactory::defaultRepos($io); + $defaultRepos = RepositoryFactory::defaultReposWithDefaultManager($io); $io->writeError('No composer.json found in the current directory, showing available packages from ' . implode(', ', array_keys($defaultRepos))); $installedRepo = new InstalledRepository(array($platformRepo)); $repos = new CompositeRepository(array_merge(array($installedRepo), $defaultRepos)); diff --git a/src/Composer/Console/Application.php b/src/Composer/Console/Application.php index ba140a149..dac353a94 100644 --- a/src/Composer/Console/Application.php +++ b/src/Composer/Console/Application.php @@ -135,7 +135,8 @@ class Application extends BaseApplication $this->disablePluginsByDefault = $input->hasParameterOption('--no-plugins'); $this->disableScriptsByDefault = $input->hasParameterOption('--no-scripts'); - if (Platform::getEnv('COMPOSER_NO_INTERACTION') || !Platform::isTty(defined('STDIN') ? STDIN : fopen('php://stdin', 'r'))) { + $stdin = defined('STDIN') ? STDIN : fopen('php://stdin', 'r'); + if (Platform::getEnv('COMPOSER_NO_INTERACTION') || $stdin === false || !Platform::isTty($stdin)) { $input->setInteractive(false); } diff --git a/src/Composer/DependencyResolver/RuleSetIterator.php b/src/Composer/DependencyResolver/RuleSetIterator.php index 3d30c5d7e..2bf67f55b 100644 --- a/src/Composer/DependencyResolver/RuleSetIterator.php +++ b/src/Composer/DependencyResolver/RuleSetIterator.php @@ -75,7 +75,7 @@ class RuleSetIterator implements \Iterator } $this->currentType = $this->types[$this->currentTypeOffset]; - } while (isset($this->types[$this->currentTypeOffset]) && !\count($this->rules[$this->currentType])); + } while (0 === \count($this->rules[$this->currentType])); } } @@ -95,7 +95,7 @@ class RuleSetIterator implements \Iterator } $this->currentType = $this->types[$this->currentTypeOffset]; - } while (isset($this->types[$this->currentTypeOffset]) && !\count($this->rules[$this->currentType])); + } while (0 === \count($this->rules[$this->currentType])); } public function valid(): bool diff --git a/src/Composer/Downloader/PathDownloader.php b/src/Composer/Downloader/PathDownloader.php index d2d2e2bc9..a998f4774 100644 --- a/src/Composer/Downloader/PathDownloader.php +++ b/src/Composer/Downloader/PathDownloader.php @@ -280,6 +280,15 @@ class PathDownloader extends FileDownloader implements VcsCapableDownloaderInter $allowedStrategies = array(self::STRATEGY_MIRROR); } + // Check we can use symlink() otherwise + if (!Platform::isWindows() && self::STRATEGY_SYMLINK === $currentStrategy && !function_exists('symlink')) { + if (!in_array(self::STRATEGY_MIRROR, $allowedStrategies, true)) { + throw new \RuntimeException('Your PHP has the symlink() function disabled which does not allow Composer to use symlinks and this path repository has symlink:true in its options so copying is not allowed'); + } + $currentStrategy = self::STRATEGY_MIRROR; + $allowedStrategies = array(self::STRATEGY_MIRROR); + } + return array($currentStrategy, $allowedStrategies); } diff --git a/src/Composer/Package/BasePackage.php b/src/Composer/Package/BasePackage.php index b815aec53..df9b3ce29 100644 --- a/src/Composer/Package/BasePackage.php +++ b/src/Composer/Package/BasePackage.php @@ -53,7 +53,6 @@ abstract class BasePackage implements PackageInterface * READ-ONLY: The package id, public for fast access in dependency solver * @var int * @internal - * @readonly */ public $id; /** @var string */ diff --git a/src/Composer/Package/Loader/ValidatingArrayLoader.php b/src/Composer/Package/Loader/ValidatingArrayLoader.php index ac7fff385..e5d513c41 100644 --- a/src/Composer/Package/Loader/ValidatingArrayLoader.php +++ b/src/Composer/Package/Loader/ValidatingArrayLoader.php @@ -456,7 +456,7 @@ class ValidatingArrayLoader implements LoaderInterface return null; } - if (!Preg::isMatch('{^[a-z0-9](?:[_.-]?[a-z0-9]+)*/[a-z0-9](?:(?:[_.]?|-{0,2})[a-z0-9]+)*$}iD', $name)) { + if (!Preg::isMatch('{^[a-z0-9](?:[_.-]?[a-z0-9]++)*+/[a-z0-9](?:(?:[_.]|-{1,2})?[a-z0-9]++)*+$}iD', $name)) { return $name.' is invalid, it should have a vendor name, a forward slash, and a package name. The vendor and package name can be words separated by -, . or _. The complete name should match "^[a-z0-9]([_.-]?[a-z0-9]+)*/[a-z0-9](([_.]?|-{0,2})[a-z0-9]+)*$".'; } diff --git a/src/Composer/Repository/RepositoryFactory.php b/src/Composer/Repository/RepositoryFactory.php index b31f77530..e24fa1596 100644 --- a/src/Composer/Repository/RepositoryFactory.php +++ b/src/Composer/Repository/RepositoryFactory.php @@ -80,7 +80,8 @@ class RepositoryFactory public static function createRepo(IOInterface $io, Config $config, array $repoConfig, RepositoryManager $rm = null): RepositoryInterface { if (!$rm) { - $rm = static::manager($io, $config, Factory::createHttpDownloader($io, $config)); + @trigger_error('Not passing a repository manager when calling createRepo is deprecated since Composer 2.3.6', E_USER_DEPRECATED); + $rm = static::manager($io, $config); } $repos = self::createRepos($rm, array($repoConfig)); @@ -95,14 +96,18 @@ class RepositoryFactory */ public static function defaultRepos(IOInterface $io = null, Config $config = null, RepositoryManager $rm = null): array { - if (!$config) { + if (null === $rm) { + @trigger_error('Not passing a repository manager when calling defaultRepos is deprecated since Composer 2.3.6, use defaultReposWithDefaultManager() instead if you cannot get a manager.', E_USER_DEPRECATED); + } + + if (null === $config) { $config = Factory::createConfig($io); } - if ($io) { + if (null !== $io) { $io->loadConfiguration($config); } - if (!$rm) { - if (!$io) { + if (null === $rm) { + if (null === $io) { throw new \InvalidArgumentException('This function requires either an IOInterface or a RepositoryManager'); } $rm = static::manager($io, $config, Factory::createHttpDownloader($io, $config)); @@ -118,8 +123,16 @@ class RepositoryFactory * @param HttpDownloader $httpDownloader * @return RepositoryManager */ - public static function manager(IOInterface $io, Config $config, HttpDownloader $httpDownloader, EventDispatcher $eventDispatcher = null, ProcessExecutor $process = null): RepositoryManager + public static function manager(IOInterface $io, Config $config, HttpDownloader $httpDownloader = null, EventDispatcher $eventDispatcher = null, ProcessExecutor $process = null): RepositoryManager { + if ($httpDownloader === null) { + $httpDownloader = Factory::createHttpDownloader($io, $config); + } + if ($process === null) { + $process = new ProcessExecutor($io); + $process->enableAsync(); + } + $rm = new RepositoryManager($io, $config, $httpDownloader, $eventDispatcher, $process); $rm->setRepositoryClass('composer', 'Composer\Repository\ComposerRepository'); $rm->setRepositoryClass('vcs', 'Composer\Repository\VcsRepository'); @@ -140,6 +153,18 @@ class RepositoryFactory return $rm; } + /** + * @return RepositoryInterface[] + */ + public static function defaultReposWithDefaultManager(IOInterface $io): array + { + $manager = RepositoryFactory::manager($io, $config = Factory::createConfig($io)); + $io->loadConfiguration($config); + + return RepositoryFactory::defaultRepos($io, $config, $manager); + } + + /** * @param array $repoConfigs * diff --git a/src/Composer/Repository/Vcs/GitHubDriver.php b/src/Composer/Repository/Vcs/GitHubDriver.php index 6e81bc584..0e60070bb 100644 --- a/src/Composer/Repository/Vcs/GitHubDriver.php +++ b/src/Composer/Repository/Vcs/GitHubDriver.php @@ -295,6 +295,13 @@ class GitHubDriver extends VcsDriver $resource = $this->getApiUrl() . '/repos/'.$this->owner.'/'.$this->repository.'/contents/' . $file . '?ref='.urlencode($identifier); $resource = $this->getContents($resource)->decodeJson(); + + // The GitHub contents API only returns files up to 1MB as base64 encoded files + // larger files either need be fetched with a raw accept header or by using the git blob endpoint + if ((!isset($resource['content']) || $resource['content'] === '') && $resource['encoding'] === 'none' && isset($resource['git_url'])) { + $resource = $this->getContents($resource['git_url'])->decodeJson(); + } + if (empty($resource['content']) || $resource['encoding'] !== 'base64' || !($content = base64_decode($resource['content']))) { throw new \RuntimeException('Could not retrieve ' . $file . ' for '.$identifier); } diff --git a/src/Composer/Util/ErrorHandler.php b/src/Composer/Util/ErrorHandler.php index 80a0f9eed..5fa1a3dd3 100644 --- a/src/Composer/Util/ErrorHandler.php +++ b/src/Composer/Util/ErrorHandler.php @@ -38,8 +38,10 @@ class ErrorHandler */ public static function handle(int $level, string $message, string $file, int $line): bool { + $isDeprecationNotice = $level === E_DEPRECATED || $level === E_USER_DEPRECATED; + // error code is not included in error_reporting - if (!(error_reporting() & $level)) { + if (!$isDeprecationNotice && !(error_reporting() & $level)) { return true; } @@ -48,7 +50,7 @@ class ErrorHandler "\na legitimately suppressed error that you were not supposed to see."; } - if ($level !== E_DEPRECATED && $level !== E_USER_DEPRECATED) { + if (!$isDeprecationNotice) { throw new \ErrorException($message, 0, $level, $file, $line); } diff --git a/src/Composer/Util/Http/CurlDownloader.php b/src/Composer/Util/Http/CurlDownloader.php index 051fca277..3799074f1 100644 --- a/src/Composer/Util/Http/CurlDownloader.php +++ b/src/Composer/Util/Http/CurlDownloader.php @@ -17,6 +17,7 @@ use Composer\Downloader\MaxFileSizeExceededException; use Composer\IO\IOInterface; use Composer\Downloader\TransportException; use Composer\Pcre\Preg; +use Composer\Util\Platform; use Composer\Util\StreamContextFactory; use Composer\Util\AuthHelper; use Composer\Util\Url; @@ -394,8 +395,19 @@ class CurlDownloader $response = new CurlResponse(array('url' => $progress['url']), $statusCode, $headers, $contents, $progress); $this->io->writeError('['.$statusCode.'] '.Url::sanitize($progress['url']), true, IOInterface::DEBUG); } else { + $maxFileSize = $job['options']['max_file_size'] ?? null; rewind($job['bodyHandle']); - $contents = stream_get_contents($job['bodyHandle']); + if ($maxFileSize !== null) { + $contents = stream_get_contents($job['bodyHandle'], $maxFileSize); + // Gzipped responses with missing Content-Length header cannot be detected during the file download + // because $progress['size_download'] refers to the gzipped size downloaded, not the actual file size + if ($contents !== false && Platform::strlen($contents) >= $maxFileSize) { + throw new MaxFileSizeExceededException('Maximum allowed download size reached. Downloaded ' . Platform::strlen($contents) . ' of allowed ' . $maxFileSize . ' bytes'); + } + } else { + $contents = stream_get_contents($job['bodyHandle']); + } + $response = new CurlResponse(array('url' => $progress['url']), $statusCode, $headers, $contents, $progress); $this->io->writeError('['.$statusCode.'] '.Url::sanitize($progress['url']), true, IOInterface::DEBUG); } diff --git a/src/Composer/Util/Platform.php b/src/Composer/Util/Platform.php index be37ba621..d27bda08c 100644 --- a/src/Composer/Util/Platform.php +++ b/src/Composer/Util/Platform.php @@ -201,6 +201,9 @@ class Platform { if ($fd === null) { $fd = defined('STDOUT') ? STDOUT : fopen('php://stdout', 'w'); + if ($fd === false) { + return false; + } } // detect msysgit/mingw and assume this is a tty because detection diff --git a/src/Composer/Util/ProcessExecutor.php b/src/Composer/Util/ProcessExecutor.php index a4c707cf9..2bcd0b0a4 100644 --- a/src/Composer/Util/ProcessExecutor.php +++ b/src/Composer/Util/ProcessExecutor.php @@ -455,7 +455,8 @@ class ProcessExecutor // New lines break cmd.exe command parsing $argument = strtr($argument, "\n", ' '); - $quote = strpbrk($argument, " \t") !== false; + // In addition to whitespace, commas need quoting to preserve paths + $quote = strpbrk($argument, " \t,") !== false; $argument = Preg::replace('/(\\\\*)"/', '$1$1\\"', $argument, -1, $dquotes); $meta = $dquotes || Preg::isMatch('/%[^%]+%|![^!]+!/', $argument); diff --git a/tests/Composer/Test/DependencyResolver/PoolBuilderTest.php b/tests/Composer/Test/DependencyResolver/PoolBuilderTest.php index 7e6249113..316fd82b5 100644 --- a/tests/Composer/Test/DependencyResolver/PoolBuilderTest.php +++ b/tests/Composer/Test/DependencyResolver/PoolBuilderTest.php @@ -87,9 +87,11 @@ class PoolBuilderTest extends TestCase chdir(__DIR__.'/Fixtures/poolbuilder/'); $repositorySet = new RepositorySet($minimumStability, $stabilityFlags, $rootAliases, $rootReferences); + $config = new Config(false); + $rm = RepositoryFactory::manager($io = new NullIO(), $config); foreach ($packageRepos as $packages) { if (isset($packages['type'])) { - $repo = RepositoryFactory::createRepo(new NullIO, new Config(false), $packages); + $repo = RepositoryFactory::createRepo($io, $config, $packages, $rm); $repositorySet->addRepository($repo); continue; } diff --git a/tests/Composer/Test/Json/ComposerSchemaTest.php b/tests/Composer/Test/Json/ComposerSchemaTest.php index a458ef1a0..4f836cb9f 100644 --- a/tests/Composer/Test/Json/ComposerSchemaTest.php +++ b/tests/Composer/Test/Json/ComposerSchemaTest.php @@ -25,9 +25,9 @@ class ComposerSchemaTest extends TestCase $expectedError = array( array( 'property' => 'name', - 'message' => 'Does not match the regex pattern ^[a-z0-9]([_.-]?[a-z0-9]+)*/[a-z0-9](([_.]?|-{0,2})[a-z0-9]+)*$', + 'message' => 'Does not match the regex pattern ^[a-z0-9]([_.-]?[a-z0-9]++)*+/[a-z0-9](([_.]|-{1,2})?[a-z0-9]++)*+$', 'constraint' => 'pattern', - 'pattern' => '^[a-z0-9]([_.-]?[a-z0-9]+)*/[a-z0-9](([_.]?|-{0,2})[a-z0-9]+)*$', + 'pattern' => '^[a-z0-9]([_.-]?[a-z0-9]++)*+/[a-z0-9](([_.]|-{1,2})?[a-z0-9]++)*+$', ), ); diff --git a/tests/Composer/Test/Package/Archiver/ArchivableFilesFinderTest.php b/tests/Composer/Test/Package/Archiver/ArchivableFilesFinderTest.php index 4aa222fd5..3f5a488c8 100644 --- a/tests/Composer/Test/Package/Archiver/ArchivableFilesFinderTest.php +++ b/tests/Composer/Test/Package/Archiver/ArchivableFilesFinderTest.php @@ -43,6 +43,7 @@ class ArchivableFilesFinderTest extends TestCase ); $fileTree = array( + '.foo', 'A/prefixA.foo', 'A/prefixB.foo', 'A/prefixC.foo', @@ -110,6 +111,7 @@ class ArchivableFilesFinderTest extends TestCase '/prefixA.foo', 'prefixC.*', '!*/*/*/prefixC.foo', + '.*', ); $this->finder = new ArchivableFilesFinder($this->sources, $excludes); @@ -214,6 +216,7 @@ class ArchivableFilesFinderTest extends TestCase '/!important!.txt', '/!important_too!.txt', '/#weirdfile', + '/.foo', '/A/prefixA.foo', '/A/prefixB.foo', '/A/prefixC.foo', diff --git a/tests/Composer/Test/Util/ProcessExecutorTest.php b/tests/Composer/Test/Util/ProcessExecutorTest.php index 67769da5b..8af418d4d 100644 --- a/tests/Composer/Test/Util/ProcessExecutorTest.php +++ b/tests/Composer/Test/Util/ProcessExecutorTest.php @@ -179,6 +179,9 @@ class ProcessExecutorTest extends TestCase // no whitespace must not be quoted 'no-ws' => array('abc', 'abc', "'abc'"), + // commas must be quoted + 'comma' => array('a,bc', '"a,bc"', "'a,bc'"), + // double-quotes must be backslash-escaped 'dq' => array('a"bc', 'a\^"bc', "'a\"bc'"),