From ebcaeeafc48b69f497f82b9700ddf54bfe975f71 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Fri, 25 Oct 2024 10:13:01 +0200 Subject: [PATCH] [HttpClient] Filter private IPs before connecting when Host == IP --- NoPrivateNetworkHttpClient.php | 13 +++++++++++- Tests/NoPrivateNetworkHttpClientTest.php | 27 ++++++++++++++++++++++-- 2 files changed, 37 insertions(+), 3 deletions(-) diff --git a/NoPrivateNetworkHttpClient.php b/NoPrivateNetworkHttpClient.php index 757a9e8..c252fce 100644 --- a/NoPrivateNetworkHttpClient.php +++ b/NoPrivateNetworkHttpClient.php @@ -77,9 +77,20 @@ public function request(string $method, string $url, array $options = []): Respo } $subnets = $this->subnets; + $lastUrl = ''; $lastPrimaryIp = ''; - $options['on_progress'] = function (int $dlNow, int $dlSize, array $info) use ($onProgress, $subnets, &$lastPrimaryIp): void { + $options['on_progress'] = function (int $dlNow, int $dlSize, array $info) use ($onProgress, $subnets, &$lastUrl, &$lastPrimaryIp): void { + if ($info['url'] !== $lastUrl) { + $host = trim(parse_url($info['url'], PHP_URL_HOST) ?: '', '[]'); + + if ($host && IpUtils::checkIp($host, $subnets ?? self::PRIVATE_SUBNETS)) { + throw new TransportException(sprintf('Host "%s" is blocked for "%s".', $host, $info['url'])); + } + + $lastUrl = $info['url']; + } + if ($info['primary_ip'] !== $lastPrimaryIp) { if ($info['primary_ip'] && IpUtils::checkIp($info['primary_ip'], $subnets ?? self::PRIVATE_SUBNETS)) { throw new TransportException(sprintf('IP "%s" is blocked for "%s".', $info['primary_ip'], $info['url'])); diff --git a/Tests/NoPrivateNetworkHttpClientTest.php b/Tests/NoPrivateNetworkHttpClientTest.php index 8c51e9e..7130c09 100644 --- a/Tests/NoPrivateNetworkHttpClientTest.php +++ b/Tests/NoPrivateNetworkHttpClientTest.php @@ -65,10 +65,10 @@ public static function getExcludeData(): array /** * @dataProvider getExcludeData */ - public function testExclude(string $ipAddr, $subnets, bool $mustThrow) + public function testExcludeByIp(string $ipAddr, $subnets, bool $mustThrow) { $content = 'foo'; - $url = sprintf('http://%s/', 0 < substr_count($ipAddr, ':') ? sprintf('[%s]', $ipAddr) : $ipAddr); + $url = sprintf('http://%s/', strtr($ipAddr, '.:', '--')); if ($mustThrow) { $this->expectException(TransportException::class); @@ -85,6 +85,29 @@ public function testExclude(string $ipAddr, $subnets, bool $mustThrow) } } + /** + * @dataProvider getExcludeData + */ + public function testExcludeByHost(string $ipAddr, $subnets, bool $mustThrow) + { + $content = 'foo'; + $url = sprintf('http://%s/', str_contains($ipAddr, ':') ? sprintf('[%s]', $ipAddr) : $ipAddr); + + if ($mustThrow) { + $this->expectException(TransportException::class); + $this->expectExceptionMessage(sprintf('Host "%s" is blocked for "%s".', $ipAddr, $url)); + } + + $previousHttpClient = $this->getHttpClientMock($url, $ipAddr, $content); + $client = new NoPrivateNetworkHttpClient($previousHttpClient, $subnets); + $response = $client->request('GET', $url); + + if (!$mustThrow) { + $this->assertEquals($content, $response->getContent()); + $this->assertEquals(200, $response->getStatusCode()); + } + } + public function testCustomOnProgressCallback() { $ipAddr = '104.26.14.6';