From d36cd30d118205f6badab24f2b2698bf91990af7 Mon Sep 17 00:00:00 2001 From: Stephan Date: Tue, 19 Mar 2024 15:18:59 +0000 Subject: [PATCH] HttpDownloader: add option to prevent access to private network (#11895) --- src/Composer/Util/Http/CurlDownloader.php | 16 +++++++++++++++- src/Composer/Util/RemoteFilesystem.php | 4 ++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/src/Composer/Util/Http/CurlDownloader.php b/src/Composer/Util/Http/CurlDownloader.php index f5bbe24aa..e512f0824 100644 --- a/src/Composer/Util/Http/CurlDownloader.php +++ b/src/Composer/Util/Http/CurlDownloader.php @@ -23,13 +23,14 @@ use Composer\Util\AuthHelper; use Composer\Util\Url; use Composer\Util\HttpDownloader; use React\Promise\Promise; +use Symfony\Component\HttpFoundation\IpUtils; /** * @internal * @author Jordi Boggiano * @author Nicolas Grekas * @phpstan-type Attributes array{retryAuthFailure: bool, redirects: int<0, max>, retries: int<0, max>, storeAuth: 'prompt'|bool, ipResolve: 4|6|null} - * @phpstan-type Job array{url: non-empty-string, origin: string, attributes: Attributes, options: mixed[], progress: mixed[], curlHandle: \CurlHandle, filename: string|null, headerHandle: resource, bodyHandle: resource, resolve: callable, reject: callable} + * @phpstan-type Job array{url: non-empty-string, origin: string, attributes: Attributes, options: mixed[], progress: mixed[], curlHandle: \CurlHandle, filename: string|null, headerHandle: resource, bodyHandle: resource, resolve: callable, reject: callable, primaryIp: string} */ class CurlDownloader { @@ -279,6 +280,7 @@ class CurlDownloader 'bodyHandle' => $bodyHandle, 'resolve' => $resolve, 'reject' => $reject, + 'primaryIp' => '', ]; $usingProxy = $proxy->getFormattedUrl(' using proxy (%s)'); @@ -505,6 +507,18 @@ class CurlDownloader } } + if (isset($progress['primary_ip']) && $progress['primary_ip'] !== $this->jobs[$i]['primaryIp']) { + if ( + isset($this->jobs[$i]['options']['prevent_ip_access_callable']) && + is_callable($this->jobs[$i]['options']['prevent_ip_access_callable']) && + $this->jobs[$i]['options']['prevent_ip_access_callable']($progress['primary_ip']) + ) { + throw new TransportException(sprintf('IP "%s" is blocked for "%s".', $progress['primary_ip'], $progress['url'])); + } + + $this->jobs[$i]['primaryIp'] = (string) $progress['primary_ip']; + } + // TODO progress } } diff --git a/src/Composer/Util/RemoteFilesystem.php b/src/Composer/Util/RemoteFilesystem.php index 0cc9df772..6fa3437a3 100644 --- a/src/Composer/Util/RemoteFilesystem.php +++ b/src/Composer/Util/RemoteFilesystem.php @@ -249,6 +249,10 @@ class RemoteFilesystem $origFileUrl = $fileUrl; + if (isset($options['prevent_ip_access_callable'])) { + throw new \RuntimeException("RemoteFilesystem doesn't support the 'prevent_ip_access_callable' config."); + } + if (isset($options['gitlab-token'])) { $fileUrl .= (false === strpos($fileUrl, '?') ? '?' : '&') . 'access_token='.$options['gitlab-token']; unset($options['gitlab-token']);