diff --git a/phpstan/baseline.neon b/phpstan/baseline.neon index f2e9f63da0e2..7eced10576a5 100644 --- a/phpstan/baseline.neon +++ b/phpstan/baseline.neon @@ -1575,11 +1575,6 @@ parameters: count: 1 path: ../src/Composer/Console/Application.php - - - message: "#^Only booleans are allowed in a negated boolean, string given\\.$#" - count: 1 - path: ../src/Composer/Console/Application.php - - message: "#^Only booleans are allowed in a negated boolean, string\\|false given\\.$#" count: 2 @@ -1595,11 +1590,6 @@ parameters: count: 1 path: ../src/Composer/Console/Application.php - - - message: "#^Only booleans are allowed in an if condition, string given\\.$#" - count: 1 - path: ../src/Composer/Console/Application.php - - message: "#^Only booleans are allowed in an if condition, string\\|null given\\.$#" count: 2 @@ -2295,11 +2285,6 @@ parameters: count: 1 path: ../src/Composer/Downloader/FileDownloader.php - - - message: "#^Parameter \\#1 \\$url of function parse_url expects string, string\\|null given\\.$#" - count: 2 - path: ../src/Composer/Downloader/FileDownloader.php - - message: "#^Short ternary operator is not allowed\\. Use null coalesce operator if applicable or consider using long ternary\\.$#" count: 4 @@ -2395,11 +2380,6 @@ parameters: count: 1 path: ../src/Composer/Downloader/GzipDownloader.php - - - message: "#^Parameter \\#1 \\$url of function parse_url expects string, string\\|null given\\.$#" - count: 1 - path: ../src/Composer/Downloader/GzipDownloader.php - - message: "#^Parameter \\#1 \\$zp of function gzclose expects resource, resource\\|false given\\.$#" count: 1 @@ -3515,11 +3495,6 @@ parameters: count: 10 path: ../src/Composer/Package/Loader/ArrayLoader.php - - - message: "#^Instanceof between Composer\\\\Package\\\\CompletePackage and Composer\\\\Package\\\\CompletePackage will always evaluate to true\\.$#" - count: 1 - path: ../src/Composer/Package/Loader/ArrayLoader.php - - message: "#^Instanceof between Composer\\\\Package\\\\CompletePackage and Composer\\\\Package\\\\CompletePackageInterface will always evaluate to true\\.$#" count: 1 @@ -3555,11 +3530,6 @@ parameters: count: 1 path: ../src/Composer/Package/Loader/ArrayLoader.php - - - message: "#^Call to function is_string\\(\\) with string will always evaluate to true\\.$#" - count: 1 - path: ../src/Composer/Package/Loader/JsonLoader.php - - message: "#^Parameter \\#1 \\$json of static method Composer\\\\Json\\\\JsonFile\\:\\:parseJson\\(\\) expects string\\|null, string\\|false given\\.$#" count: 1 @@ -4305,16 +4275,6 @@ parameters: count: 1 path: ../src/Composer/Repository/FilesystemRepository.php - - - message: "#^Call to function is_array\\(\\) with array\\ will always evaluate to true\\.$#" - count: 2 - path: ../src/Composer/Repository/FilterRepository.php - - - - message: "#^Call to function is_bool\\(\\) with bool will always evaluate to true\\.$#" - count: 1 - path: ../src/Composer/Repository/FilterRepository.php - - message: "#^Only booleans are allowed in &&, string\\|null given on the left side\\.$#" count: 1 @@ -4503,11 +4463,6 @@ parameters: count: 1 path: ../src/Composer/Repository/RepositoryFactory.php - - - message: "#^Parameter \\#3 \\$name of method Composer\\\\Repository\\\\RepositoryManager\\:\\:createRepository\\(\\) expects string\\|null, int\\|string given\\.$#" - count: 1 - path: ../src/Composer/Repository/RepositoryFactory.php - - message: "#^Only booleans are allowed in an if condition, Composer\\\\Package\\\\BasePackage\\|null given\\.$#" count: 1 @@ -4793,21 +4748,11 @@ parameters: count: 3 path: ../src/Composer/Repository/Vcs/GitLabDriver.php - - - message: "#^Parameter \\#1 \\$haystack of function strpos expects string, string\\|false given\\.$#" - count: 2 - path: ../src/Composer/Repository/Vcs/GitLabDriver.php - - message: "#^Parameter \\#2 \\$str of function explode expects string, string\\|null given\\.$#" count: 1 path: ../src/Composer/Repository/Vcs/GitLabDriver.php - - - message: "#^Property Composer\\\\Repository\\\\Vcs\\\\VcsDriver\\:\\:\\$originUrl \\(string\\) does not accept string\\|false\\.$#" - count: 1 - path: ../src/Composer/Repository/Vcs/GitLabDriver.php - - message: "#^Short ternary operator is not allowed\\. Use null coalesce operator if applicable or consider using long ternary\\.$#" count: 2 @@ -5183,96 +5128,11 @@ parameters: count: 1 path: ../src/Composer/Util/ErrorHandler.php - - - message: "#^Cannot call method getPathname\\(\\) on SplFileInfo\\|string\\.$#" - count: 1 - path: ../src/Composer/Util/Filesystem.php - - - - message: "#^Cannot call method isDir\\(\\) on SplFileInfo\\|string\\.$#" - count: 1 - path: ../src/Composer/Util/Filesystem.php - - message: "#^Casting to string something that's already string\\.$#" - count: 2 - path: ../src/Composer/Util/Filesystem.php - - - - message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#" - count: 1 - path: ../src/Composer/Util/Filesystem.php - - - - message: "#^Method Composer\\\\Util\\\\Filesystem\\:\\:size\\(\\) should return int but returns int\\<0, max\\>\\|false\\.$#" - count: 1 - path: ../src/Composer/Util/Filesystem.php - - - - message: "#^Offset 'message' does not exist on array\\{type\\: int, message\\: string, file\\: string, line\\: int\\}\\|null\\.$#" - count: 2 - path: ../src/Composer/Util/Filesystem.php - - - - message: "#^Only booleans are allowed in a negated boolean, Composer\\\\Util\\\\ProcessExecutor\\|null given\\.$#" - count: 1 - path: ../src/Composer/Util/Filesystem.php - - - - message: "#^Only booleans are allowed in a negated boolean, int\\<0, max\\> given\\.$#" - count: 1 - path: ../src/Composer/Util/Filesystem.php - - - - message: "#^Only booleans are allowed in a negated boolean, string\\|false given\\.$#" - count: 1 - path: ../src/Composer/Util/Filesystem.php - - - - message: "#^Only booleans are allowed in a ternary operator condition, array\\\\|false given\\.$#" - count: 1 - path: ../src/Composer/Util/Filesystem.php - - - - message: "#^Only booleans are allowed in a ternary operator condition, int\\<0, max\\> given\\.$#" - count: 1 - path: ../src/Composer/Util/Filesystem.php - - - - message: "#^Only numeric types are allowed in \\+, bool given on the right side\\.$#" - count: 1 - path: ../src/Composer/Util/Filesystem.php - - - - message: "#^Parameter \\#1 \\$fp of function fclose expects resource, resource\\|false given\\.$#" count: 4 path: ../src/Composer/Util/Filesystem.php - - - message: "#^Parameter \\#1 \\$fp of function feof expects resource, resource\\|false given\\.$#" - count: 1 - path: ../src/Composer/Util/Filesystem.php - - - - message: "#^Parameter \\#1 \\$fp of function fread expects resource, resource\\|false given\\.$#" - count: 2 - path: ../src/Composer/Util/Filesystem.php - - - - message: "#^Parameter \\#1 \\$source of function stream_copy_to_stream expects resource, resource\\|false given\\.$#" - count: 1 - path: ../src/Composer/Util/Filesystem.php - - - - message: "#^Parameter \\#2 \\$dest of function stream_copy_to_stream expects resource, resource\\|false given\\.$#" - count: 1 - path: ../src/Composer/Util/Filesystem.php - - - - message: "#^Short ternary operator is not allowed\\. Use null coalesce operator if applicable or consider using long ternary\\.$#" - count: 1 - path: ../src/Composer/Util/Filesystem.php - - message: "#^Call to function in_array\\(\\) requires parameter \\#3 to be set\\.$#" count: 1 @@ -5385,7 +5245,7 @@ parameters: - message: "#^Call to function is_resource\\(\\) with resource will always evaluate to true\\.$#" - count: 4 + count: 3 path: ../src/Composer/Util/Http/CurlDownloader.php - @@ -6283,11 +6143,6 @@ parameters: count: 1 path: ../tests/Composer/Test/AllFunctionalTest.php - - - message: "#^Method Composer\\\\Test\\\\AllFunctionalTest\\:\\:getTestFiles\\(\\) should return array\\\\> but returns array\\\\>\\.$#" - count: 1 - path: ../tests/Composer/Test/AllFunctionalTest.php - - message: "#^Only booleans are allowed in an if condition, string\\|false given\\.$#" count: 1 @@ -6438,11 +6293,6 @@ parameters: count: 1 path: ../tests/Composer/Test/DependencyResolver/PoolBuilderTest.php - - - message: "#^Parameter \\#1 \\$filename of function file_get_contents expects string, string\\|false given\\.$#" - count: 1 - path: ../tests/Composer/Test/DependencyResolver/PoolBuilderTest.php - - message: "#^Parameter \\#2 \\$fixturesDir of method Composer\\\\Test\\\\DependencyResolver\\\\PoolBuilderTest\\:\\:readTestFile\\(\\) expects string, string\\|false given\\.$#" count: 1 @@ -6458,11 +6308,6 @@ parameters: count: 1 path: ../tests/Composer/Test/DependencyResolver/PoolBuilderTest.php - - - message: "#^Parameter \\#2 \\.\\.\\.\\$values of function sprintf expects bool\\|float\\|int\\|string\\|null, array\\|string given\\.$#" - count: 2 - path: ../tests/Composer/Test/DependencyResolver/PoolBuilderTest.php - - message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#" count: 1 @@ -6478,11 +6323,6 @@ parameters: count: 1 path: ../tests/Composer/Test/DependencyResolver/PoolOptimizerTest.php - - - message: "#^Parameter \\#1 \\$filename of function file_get_contents expects string, string\\|false given\\.$#" - count: 1 - path: ../tests/Composer/Test/DependencyResolver/PoolOptimizerTest.php - - message: "#^Parameter \\#2 \\$fixturesDir of method Composer\\\\Test\\\\DependencyResolver\\\\PoolOptimizerTest\\:\\:readTestFile\\(\\) expects string, string\\|false given\\.$#" count: 1 @@ -6493,11 +6333,6 @@ parameters: count: 1 path: ../tests/Composer/Test/DependencyResolver/PoolOptimizerTest.php - - - message: "#^Parameter \\#2 \\.\\.\\.\\$values of function sprintf expects bool\\|float\\|int\\|string\\|null, array\\|string given\\.$#" - count: 2 - path: ../tests/Composer/Test/DependencyResolver/PoolOptimizerTest.php - - message: "#^Parameter \\#1 \\$packages of class Composer\\\\DependencyResolver\\\\Pool constructor expects array\\, array\\\\|null given\\.$#" count: 1 @@ -6696,11 +6531,6 @@ parameters: count: 1 path: ../tests/Composer/Test/InstallerTest.php - - - message: "#^Parameter \\#1 \\$filename of function file_get_contents expects string, string\\|false given\\.$#" - count: 1 - path: ../tests/Composer/Test/InstallerTest.php - - message: "#^Parameter \\#1 \\$json of function json_decode expects string, string\\|false\\|null given\\.$#" count: 1 @@ -6726,11 +6556,6 @@ parameters: count: 1 path: ../tests/Composer/Test/InstallerTest.php - - - message: "#^Parameter \\#2 \\.\\.\\.\\$values of function sprintf expects bool\\|float\\|int\\|string\\|null, array\\|string given\\.$#" - count: 2 - path: ../tests/Composer/Test/InstallerTest.php - - message: "#^Parameter \\#4 \\$composerFileContents of class Composer\\\\Package\\\\Locker constructor expects string, string\\|false given\\.$#" count: 1 @@ -6791,11 +6616,6 @@ parameters: count: 1 path: ../tests/Composer/Test/Mock/ProcessExecutorMock.php - - - message: "#^Offset 'return' on array\\{return\\: int, stdout\\?\\: string, stderr\\?\\: string\\} on left side of \\?\\? always exists and is not nullable\\.$#" - count: 1 - path: ../tests/Composer/Test/Mock/ProcessExecutorMock.php - - message: "#^Offset 'stderr' does not exist on array\\{cmd\\: array\\\\|string, return\\?\\: int, stdout\\?\\: string, stderr\\?\\: string, callback\\?\\: callable\\(\\)\\: mixed\\}\\.$#" count: 1 diff --git a/phpstan/config.neon b/phpstan/config.neon index e5469b6a7364..5ac2cd37b1ca 100644 --- a/phpstan/config.neon +++ b/phpstan/config.neon @@ -17,6 +17,7 @@ parameters: - '../tests/Composer/Test/PolyfillTestCase.php' reportUnmatchedIgnoredErrors: false + treatPhpDocTypesAsCertain: false ignoreErrors: # unused parameters diff --git a/src/Composer/Autoload/AutoloadGenerator.php b/src/Composer/Autoload/AutoloadGenerator.php index 0ddcc9df1fd4..ffb0cd83cfa5 100644 --- a/src/Composer/Autoload/AutoloadGenerator.php +++ b/src/Composer/Autoload/AutoloadGenerator.php @@ -744,11 +744,11 @@ protected function getPathCode(Filesystem $filesystem, string $basePath, string /** * @param array $packageMap - * @param bool $checkPlatform + * @param bool|'php-only' $checkPlatform * @param string[] $devPackageNames * @return ?string */ - protected function getPlatformCheck(array $packageMap, bool $checkPlatform, array $devPackageNames) + protected function getPlatformCheck(array $packageMap, $checkPlatform, array $devPackageNames) { $lowestPhpVersion = Bound::zero(); $requiredExtensions = array(); diff --git a/src/Composer/Downloader/FileDownloader.php b/src/Composer/Downloader/FileDownloader.php index 738a386ae231..39eaf6528b40 100644 --- a/src/Composer/Downloader/FileDownloader.php +++ b/src/Composer/Downloader/FileDownloader.php @@ -343,7 +343,7 @@ public function install(PackageInterface $package, string $path, bool $output = $this->filesystem->emptyDirectory($path); $this->filesystem->ensureDirectoryExists($path); - $this->filesystem->rename($this->getFileName($package, $path), $path . '/' . pathinfo(parse_url($package->getDistUrl(), PHP_URL_PATH), PATHINFO_BASENAME)); + $this->filesystem->rename($this->getFileName($package, $path), $path . '/' . pathinfo(parse_url(strtr((string) $package->getDistUrl(), '\\', '/'), PHP_URL_PATH), PATHINFO_BASENAME)); if ($package->getBinaries()) { // Single files can not have a mode set like files in archives @@ -436,7 +436,7 @@ public function remove(PackageInterface $package, string $path, bool $output = t */ protected function getFileName(PackageInterface $package, string $path): string { - return rtrim($this->config->get('vendor-dir').'/composer/tmp-'.md5($package.spl_object_hash($package)).'.'.pathinfo(parse_url($package->getDistUrl(), PHP_URL_PATH), PATHINFO_EXTENSION), '.'); + return rtrim($this->config->get('vendor-dir').'/composer/tmp-'.md5($package.spl_object_hash($package)).'.'.pathinfo(parse_url(strtr((string) $package->getDistUrl(), '\\', '/'), PHP_URL_PATH), PATHINFO_EXTENSION), '.'); } /** diff --git a/src/Composer/Downloader/GzipDownloader.php b/src/Composer/Downloader/GzipDownloader.php index 60ab50549201..4e91b8023b4e 100644 --- a/src/Composer/Downloader/GzipDownloader.php +++ b/src/Composer/Downloader/GzipDownloader.php @@ -26,7 +26,7 @@ class GzipDownloader extends ArchiveDownloader { protected function extract(PackageInterface $package, string $file, string $path): PromiseInterface { - $filename = pathinfo(parse_url($package->getDistUrl(), PHP_URL_PATH), PATHINFO_FILENAME); + $filename = pathinfo(parse_url(strtr((string) $package->getDistUrl(), '\\', '/'), PHP_URL_PATH), PATHINFO_FILENAME); $targetFilepath = $path . DIRECTORY_SEPARATOR . $filename; // Try to use gunzip on *nix diff --git a/src/Composer/IO/BaseIO.php b/src/Composer/IO/BaseIO.php index d7247e411766..4f9bc1da926a 100644 --- a/src/Composer/IO/BaseIO.php +++ b/src/Composer/IO/BaseIO.php @@ -201,6 +201,8 @@ public function debug($message, array $context = array()): void public function log($level, $message, array $context = array()): void { + $message = (string) $message; + if (in_array($level, array(LogLevel::EMERGENCY, LogLevel::ALERT, LogLevel::CRITICAL, LogLevel::ERROR))) { $this->writeError(''.$message.''); } elseif ($level === LogLevel::WARNING) { diff --git a/src/Composer/Package/Loader/ValidatingArrayLoader.php b/src/Composer/Package/Loader/ValidatingArrayLoader.php index cce97be794ce..ac55d89e4959 100644 --- a/src/Composer/Package/Loader/ValidatingArrayLoader.php +++ b/src/Composer/Package/Loader/ValidatingArrayLoader.php @@ -286,7 +286,7 @@ public function load(array $config, string $class = 'Composer\Package\CompletePa // check requires for exact constraints ($this->flags & self::CHECK_STRICT_CONSTRAINTS) && 'require' === $linkType - && $linkConstraint instanceof Constraint && $linkConstraint->getOperator() === Constraint::STR_OP_EQ + && $linkConstraint instanceof Constraint && in_array($linkConstraint->getOperator(), ['==', '='], true) && (new Constraint('>=', '1.0.0.0-dev'))->matches($linkConstraint) ) { $this->warnings[] = $linkType.'.'.$package.' : exact version constraints ('.$constraint.') should be avoided if the package follows semantic versioning'; diff --git a/src/Composer/Repository/ComposerRepository.php b/src/Composer/Repository/ComposerRepository.php index 72005682b2aa..3e0146b2ebcb 100644 --- a/src/Composer/Repository/ComposerRepository.php +++ b/src/Composer/Repository/ComposerRepository.php @@ -142,7 +142,7 @@ public function __construct(array $repoConfig, IOInterface $io, Config $config, $repoConfig['url'] = (extension_loaded('openssl') ? 'https' : 'http') . substr($repoConfig['url'], 6); } - $urlBits = parse_url($repoConfig['url']); + $urlBits = parse_url(strtr($repoConfig['url'], '\\', '/')); if ($urlBits === false || empty($urlBits['scheme'])) { throw new \UnexpectedValueException('Invalid url given for Composer repository: '.$repoConfig['url']); } @@ -998,7 +998,7 @@ private function isVersionAcceptable(?ConstraintInterface $constraint, string $n */ private function getPackagesJsonUrl(): string { - $jsonUrlParts = parse_url($this->url); + $jsonUrlParts = parse_url(strtr($this->url, '\\', '/')); if (isset($jsonUrlParts['path']) && false !== strpos($jsonUrlParts['path'], '.json')) { return $this->url; diff --git a/src/Composer/Util/Filesystem.php b/src/Composer/Util/Filesystem.php index 6d089017e39a..21ea5711590b 100644 --- a/src/Composer/Util/Filesystem.php +++ b/src/Composer/Util/Filesystem.php @@ -291,7 +291,7 @@ public function unlink(string $path) if (!$unlinked) { $error = error_get_last(); - $message = 'Could not delete '.$path.': ' . @$error['message']; + $message = 'Could not delete '.$path.': ' . ($error['message'] ?? ''); if (Platform::isWindows()) { $message .= "\nThis can be due to an antivirus or the Windows Search Indexer locking the file while they are analyzed"; } @@ -322,7 +322,7 @@ public function rmdir(string $path) if (!$deleted) { $error = error_get_last(); - $message = 'Could not delete '.$path.': ' . @$error['message']; + $message = 'Could not delete '.$path.': ' . ($error['message'] ?? ''); if (Platform::isWindows()) { $message .= "\nThis can be due to an antivirus or the Windows Search Indexer locking the file while they are analyzed"; } @@ -375,7 +375,6 @@ public function copy(string $source, string $target) $this->ensureDirectoryExists($target); $result = true; - /** @var RecursiveDirectoryIterator $ri */ foreach ($ri as $file) { $targetPath = $target . DIRECTORY_SEPARATOR . $ri->getSubPathname(); if ($file->isDir()) { @@ -451,8 +450,8 @@ public function findShortestPath(string $from, string $to, bool $directories = f throw new \InvalidArgumentException(sprintf('$from (%s) and $to (%s) must be absolute paths.', $from, $to)); } - $from = lcfirst($this->normalizePath($from)); - $to = lcfirst($this->normalizePath($to)); + $from = $this->normalizePath($from); + $to = $this->normalizePath($to); if ($directories) { $from = rtrim($from, '/') . '/dummy_file'; @@ -463,7 +462,7 @@ public function findShortestPath(string $from, string $to, bool $directories = f } $commonPath = $to; - while (strpos($from.'/', $commonPath.'/') !== 0 && '/' !== $commonPath && !Preg::isMatch('{^[a-z]:/?$}i', $commonPath)) { + while (strpos($from.'/', $commonPath.'/') !== 0 && '/' !== $commonPath && !Preg::isMatch('{^[A-Z]:/?$}i', $commonPath)) { $commonPath = strtr(\dirname($commonPath), '\\', '/'); } @@ -472,10 +471,15 @@ public function findShortestPath(string $from, string $to, bool $directories = f } $commonPath = rtrim($commonPath, '/') . '/'; - $sourcePathDepth = substr_count(substr($from, \strlen($commonPath)), '/'); + $sourcePathDepth = substr_count((string) substr($from, \strlen($commonPath)), '/'); $commonPathCode = str_repeat('../', $sourcePathDepth); - return ($commonPathCode . substr($to, \strlen($commonPath))) ?: './'; + $result = $commonPathCode . substr($to, \strlen($commonPath)); + if (\strlen($result) === 0) { + return './'; + } + + return $result; } /** @@ -494,15 +498,15 @@ public function findShortestPathCode(string $from, string $to, bool $directories throw new \InvalidArgumentException(sprintf('$from (%s) and $to (%s) must be absolute paths.', $from, $to)); } - $from = lcfirst($this->normalizePath($from)); - $to = lcfirst($this->normalizePath($to)); + $from = $this->normalizePath($from); + $to = $this->normalizePath($to); if ($from === $to) { return $directories ? '__DIR__' : '__FILE__'; } $commonPath = $to; - while (strpos($from.'/', $commonPath.'/') !== 0 && '/' !== $commonPath && !Preg::isMatch('{^[a-z]:/?$}i', $commonPath) && '.' !== $commonPath) { + while (strpos($from.'/', $commonPath.'/') !== 0 && '/' !== $commonPath && !Preg::isMatch('{^[A-Z]:/?$}i', $commonPath) && '.' !== $commonPath) { $commonPath = strtr(\dirname($commonPath), '\\', '/'); } @@ -512,17 +516,17 @@ public function findShortestPathCode(string $from, string $to, bool $directories $commonPath = rtrim($commonPath, '/') . '/'; if (strpos($to, $from.'/') === 0) { - return '__DIR__ . '.var_export(substr($to, \strlen($from)), true); + return '__DIR__ . '.var_export((string) substr($to, \strlen($from)), true); } - $sourcePathDepth = substr_count(substr($from, \strlen($commonPath)), '/') + $directories; + $sourcePathDepth = substr_count((string) substr($from, \strlen($commonPath)), '/') + (int) $directories; if ($staticCode) { $commonPathCode = "__DIR__ . '".str_repeat('/..', $sourcePathDepth)."'"; } else { $commonPathCode = str_repeat('dirname(', $sourcePathDepth).'__DIR__'.str_repeat(')', $sourcePathDepth); } - $relTarget = substr($to, \strlen($commonPath)); + $relTarget = (string) substr($to, \strlen($commonPath)); - return $commonPathCode . (\strlen($relTarget) ? '.' . var_export('/' . $relTarget, true) : ''); + return $commonPathCode . (\strlen($relTarget) > 0 ? '.' . var_export('/' . $relTarget, true) : ''); } /** @@ -553,7 +557,7 @@ public function size(string $path) return $this->directorySize($path); } - return filesize($path); + return (int) filesize($path); } /** @@ -589,16 +593,19 @@ public function normalizePath(string $path) $up = false; foreach (explode('/', $path) as $chunk) { - if ('..' === $chunk && ($absolute !== '' || $up)) { + if ('..' === $chunk && (\strlen($absolute) > 0 || $up)) { array_pop($parts); - $up = !(empty($parts) || '..' === end($parts)); + $up = !(\count($parts) === 0 || '..' === end($parts)); } elseif ('.' !== $chunk && '' !== $chunk) { $parts[] = $chunk; $up = '..' !== $chunk; } } - return $prefix.((string) $absolute).implode('/', $parts); + // ensure c: is normalized to C: + $prefix = Preg::replaceCallback('{(^|://)[a-z]:$}i', function (array $m) { return strtoupper($m[0]); }, $prefix); + + return $prefix.$absolute.implode('/', $parts); } /** @@ -640,7 +647,7 @@ public static function getPlatformPath(string $path) $path = Preg::replace('{^(?:file:///([a-z]):?/)}i', 'file://$1:/', $path); } - return (string) Preg::replace('{^file://}i', '', $path); + return Preg::replace('{^file://}i', '', $path); } /** @@ -695,7 +702,7 @@ protected function directorySize(string $directory) */ protected function getProcess() { - if (!$this->processExecutor) { + if (null === $this->processExecutor) { $this->processExecutor = new ProcessExecutor(); } @@ -789,7 +796,7 @@ private function resolveSymlinkedDirectorySymlink(string $pathname): string $resolved = rtrim($pathname, '/'); - if (!\strlen($resolved)) { + if (0 === \strlen($resolved)) { return $pathname; } @@ -859,7 +866,7 @@ public function isJunction(string $junction) $stat = lstat($junction); // S_ISDIR test (S_IFDIR is 0x4000, S_IFMT is 0xF000 bitmask) - return $stat ? 0x4000 !== ($stat['mode'] & 0xF000) : false; + return is_array($stat) ? 0x4000 !== ($stat['mode'] & 0xF000) : false; } /** @@ -889,8 +896,8 @@ public function removeJunction(string $junction) */ public function filePutContentsIfModified(string $path, string $content) { - $currentContent = @file_get_contents($path); - if (!$currentContent || ($currentContent != $content)) { + $currentContent = Silencer::call('file_get_contents', $path); + if (false === $currentContent || $currentContent !== $content) { return file_put_contents($path, $content); } @@ -908,23 +915,20 @@ public function filePutContentsIfModified(string $path, string $content) public function safeCopy(string $source, string $target) { if (!file_exists($target) || !file_exists($source) || !$this->filesAreEqual($source, $target)) { - $source = fopen($source, 'r'); - $target = fopen($target, 'w+'); + $sourceHandle = fopen($source, 'r'); + assert($sourceHandle !== false, 'Could not open "'.$source.'" for reading.'); + $targetHandle = fopen($target, 'w+'); + assert($targetHandle !== false, 'Could not open "'.$target.'" for writing.'); - stream_copy_to_stream($source, $target); - fclose($source); - fclose($target); + stream_copy_to_stream($sourceHandle, $targetHandle); + fclose($sourceHandle); + fclose($targetHandle); } } /** * compare 2 files * https://stackoverflow.com/questions/3060125/can-i-use-file-get-contents-to-compare-two-files - * - * @param string $a - * @param string $b - * - * @return bool */ private function filesAreEqual(string $a, string $b): bool { @@ -934,19 +938,21 @@ private function filesAreEqual(string $a, string $b): bool } // Check if content is different - $ah = fopen($a, 'rb'); - $bh = fopen($b, 'rb'); + $aHandle = fopen($a, 'rb'); + assert($aHandle !== false, 'Could not open "'.$a.'" for reading.'); + $bHandle = fopen($b, 'rb'); + assert($bHandle !== false, 'Could not open "'.$b.'" for reading.'); $result = true; - while (!feof($ah)) { - if (fread($ah, 8192) != fread($bh, 8192)) { + while (!feof($aHandle)) { + if (fread($aHandle, 8192) !== fread($bHandle, 8192)) { $result = false; break; } } - fclose($ah); - fclose($bh); + fclose($aHandle); + fclose($bHandle); return $result; } diff --git a/src/Composer/Util/RemoteFilesystem.php b/src/Composer/Util/RemoteFilesystem.php index 5aa7d668522c..a22998e55aca 100644 --- a/src/Composer/Util/RemoteFilesystem.php +++ b/src/Composer/Util/RemoteFilesystem.php @@ -219,7 +219,7 @@ public function findStatusMessage(array $headers) */ protected function get(string $originUrl, string $fileUrl, array $additionalOptions = array(), string $fileName = null, bool $progress = true) { - $this->scheme = parse_url($fileUrl, PHP_URL_SCHEME); + $this->scheme = parse_url(strtr($fileUrl, '\\', '/'), PHP_URL_SCHEME); $this->bytesMax = 0; $this->originUrl = $originUrl; $this->fileUrl = $fileUrl; diff --git a/tests/Composer/Test/Util/FilesystemTest.php b/tests/Composer/Test/Util/FilesystemTest.php index e40e532674eb..e035f24fc6b1 100644 --- a/tests/Composer/Test/Util/FilesystemTest.php +++ b/tests/Composer/Test/Util/FilesystemTest.php @@ -74,19 +74,19 @@ public function providePathCouplesAsCode(): array array('/foo/bin/run', '/bar/bin/run', false, "'/bar/bin/run'"), array('c:/bin/run', 'c:/vendor/acme/bin/run', false, "dirname(__DIR__).'/vendor/acme/bin/run'"), array('c:\\bin\\run', 'c:/vendor/acme/bin/run', false, "dirname(__DIR__).'/vendor/acme/bin/run'"), - array('c:/bin/run', 'd:/vendor/acme/bin/run', false, "'d:/vendor/acme/bin/run'"), - array('c:\\bin\\run', 'd:/vendor/acme/bin/run', false, "'d:/vendor/acme/bin/run'"), + array('c:/bin/run', 'D:/vendor/acme/bin/run', false, "'D:/vendor/acme/bin/run'"), + array('c:\\bin\\run', 'd:/vendor/acme/bin/run', false, "'D:/vendor/acme/bin/run'"), array('/foo/bar', '/foo/bar', true, "__DIR__"), array('/foo/bar/', '/foo/bar', true, "__DIR__"), array('/foo/bar', '/foo/baz', true, "dirname(__DIR__).'/baz'"), array('/foo/bin/run', '/foo/vendor/acme/bin/run', true, "dirname(dirname(__DIR__)).'/vendor/acme/bin/run'"), array('/foo/bin/run', '/bar/bin/run', true, "'/bar/bin/run'"), array('/bin/run', '/bin/run', true, "__DIR__"), - array('c:/bin/run', 'c:\\bin/run', true, "__DIR__"), + array('c:/bin/run', 'C:\\bin/run', true, "__DIR__"), array('c:/bin/run', 'c:/vendor/acme/bin/run', true, "dirname(dirname(__DIR__)).'/vendor/acme/bin/run'"), array('c:\\bin\\run', 'c:/vendor/acme/bin/run', true, "dirname(dirname(__DIR__)).'/vendor/acme/bin/run'"), - array('c:/bin/run', 'd:/vendor/acme/bin/run', true, "'d:/vendor/acme/bin/run'"), - array('c:\\bin\\run', 'd:/vendor/acme/bin/run', true, "'d:/vendor/acme/bin/run'"), + array('c:/bin/run', 'd:/vendor/acme/bin/run', true, "'D:/vendor/acme/bin/run'"), + array('c:\\bin\\run', 'd:/vendor/acme/bin/run', true, "'D:/vendor/acme/bin/run'"), array('C:/Temp/test', 'C:\Temp', true, "dirname(__DIR__)"), array('C:/Temp', 'C:\Temp\test', true, "__DIR__ . '/test'"), array('/tmp/test', '/tmp', true, "dirname(__DIR__)"), @@ -96,7 +96,7 @@ public function providePathCouplesAsCode(): array array('/tmp/test/../vendor', '/tmp/test', true, "dirname(__DIR__).'/test'"), array('/tmp/test/.././vendor', '/tmp/test', true, "dirname(__DIR__).'/test'"), array('C:/Temp', 'c:\Temp\..\..\test', true, "dirname(__DIR__).'/test'"), - array('C:/Temp/../..', 'd:\Temp\..\..\test', true, "'d:/test'"), + array('C:/Temp/../..', 'd:\Temp\..\..\test', true, "'D:/test'"), array('/foo/bar', '/foo/bar_vendor', true, "dirname(__DIR__).'/bar_vendor'"), array('/foo/bar_vendor', '/foo/bar', true, "dirname(__DIR__).'/bar'"), array('/foo/bar_vendor', '/foo/bar/src', true, "dirname(__DIR__).'/bar/src'"), @@ -106,7 +106,7 @@ public function providePathCouplesAsCode(): array array('/tmp/test/../vendor', '/tmp/test', true, "__DIR__ . '/..'.'/test'", true), array('/tmp/test/.././vendor', '/tmp/test', true, "__DIR__ . '/..'.'/test'", true), array('C:/Temp', 'c:\Temp\..\..\test', true, "__DIR__ . '/..'.'/test'", true), - array('C:/Temp/../..', 'd:\Temp\..\..\test', true, "'d:/test'", true), + array('C:/Temp/../..', 'd:\Temp\..\..\test', true, "'D:/test'", true), array('/foo/bar', '/foo/bar_vendor', true, "__DIR__ . '/..'.'/bar_vendor'", true), array('/foo/bar_vendor', '/foo/bar', true, "__DIR__ . '/..'.'/bar'", true), array('/foo/bar_vendor', '/foo/bar/src', true, "__DIR__ . '/..'.'/bar/src'", true), @@ -141,11 +141,11 @@ public function providePathCouples(): array array('/foo/bin/run', '/foo/vendor/acme/bin/run', "../vendor/acme/bin/run"), array('/foo/bin/run', '/bar/bin/run', "/bar/bin/run"), array('/foo/bin/run', '/bar/bin/run', "/bar/bin/run", true), - array('c:/foo/bin/run', 'd:/bar/bin/run', "d:/bar/bin/run", true), + array('c:/foo/bin/run', 'd:/bar/bin/run', "D:/bar/bin/run", true), array('c:/bin/run', 'c:/vendor/acme/bin/run', "../vendor/acme/bin/run"), array('c:\\bin\\run', 'c:/vendor/acme/bin/run', "../vendor/acme/bin/run"), - array('c:/bin/run', 'd:/vendor/acme/bin/run', "d:/vendor/acme/bin/run"), - array('c:\\bin\\run', 'd:/vendor/acme/bin/run', "d:/vendor/acme/bin/run"), + array('c:/bin/run', 'd:/vendor/acme/bin/run', "D:/vendor/acme/bin/run"), + array('c:\\bin\\run', 'd:/vendor/acme/bin/run', "D:/vendor/acme/bin/run"), array('C:/Temp/test', 'C:\Temp', "./"), array('/tmp/test', '/tmp', "./"), array('C:/Temp/test/sub', 'C:\Temp', "../"), @@ -160,7 +160,7 @@ public function providePathCouples(): array array('/tmp/test/.././vendor', '/tmp/test', '../test', true), array('C:/Temp', 'c:\Temp\..\..\test', "../test", true), array('C:/Temp/../..', 'c:\Temp\..\..\test', "./test", true), - array('C:/Temp/../..', 'D:\Temp\..\..\test', "d:/test", true), + array('C:/Temp/../..', 'D:\Temp\..\..\test', "D:/test", true), array('/tmp', '/tmp/../../test', '/test', true), array('/foo/bar', '/foo/bar_vendor', '../bar_vendor', true), array('/foo/bar_vendor', '/foo/bar', '../bar', true), @@ -217,24 +217,24 @@ public function provideNormalizedPaths(): array { return array( array('../foo', '../foo'), - array('c:/foo/bar', 'c:/foo//bar'), + array('C:/foo/bar', 'c:/foo//bar'), array('C:/foo/bar', 'C:/foo/./bar'), array('C:/foo/bar', 'C://foo//bar'), array('C:/foo/bar', 'C:///foo//bar'), array('C:/bar', 'C:/foo/../bar'), array('/bar', '/foo/../bar/'), - array('phar://c:/Foo', 'phar://c:/Foo/Bar/..'), - array('phar://c:/Foo', 'phar://c:///Foo/Bar/..'), - array('phar://c:/', 'phar://c:/Foo/Bar/../../../..'), + array('phar://C:/Foo', 'phar://c:/Foo/Bar/..'), + array('phar://C:/Foo', 'phar://c:///Foo/Bar/..'), + array('phar://C:/', 'phar://c:/Foo/Bar/../../../..'), array('/', '/Foo/Bar/../../../..'), array('/', '/'), array('/', '//'), array('/', '///'), array('/Foo', '///Foo'), - array('c:/', 'c:\\'), + array('C:/', 'c:\\'), array('../src', 'Foo/Bar/../../../src'), - array('c:../b', 'c:.\\..\\a\\..\\b'), - array('phar://c:../Foo', 'phar://c:../Foo'), + array('C:../b', 'c:.\\..\\a\\..\\b'), + array('phar://C:../Foo', 'phar://c:../Foo'), array('//foo/bar', '\\\\foo\\bar'), ); }