diff --git a/src/Symfony/Component/HttpClient/NoPrivateNetworkHttpClient.php b/src/Symfony/Component/HttpClient/NoPrivateNetworkHttpClient.php index f334572726a35..ef80d4f0dd9bf 100644 --- a/src/Symfony/Component/HttpClient/NoPrivateNetworkHttpClient.php +++ b/src/Symfony/Component/HttpClient/NoPrivateNetworkHttpClient.php @@ -30,21 +30,6 @@ final class NoPrivateNetworkHttpClient implements HttpClientInterface, LoggerAwa { use HttpClientTrait; - private const PRIVATE_SUBNETS = [ - '127.0.0.0/8', - '10.0.0.0/8', - '192.168.0.0/16', - '172.16.0.0/12', - '169.254.0.0/16', - '0.0.0.0/8', - '240.0.0.0/4', - '::1/128', - 'fc00::/7', - 'fe80::/10', - '::ffff:0:0/96', - '::/128', - ]; - private HttpClientInterface $client; private string|array|null $subnets; @@ -74,7 +59,7 @@ public function request(string $method, string $url, array $options = []): Respo $options['on_progress'] = function (int $dlNow, int $dlSize, array $info) use ($onProgress, $subnets, &$lastPrimaryIp): void { if ($info['primary_ip'] !== $lastPrimaryIp) { - if ($info['primary_ip'] && IpUtils::checkIp($info['primary_ip'], $subnets ?? self::PRIVATE_SUBNETS)) { + if ($info['primary_ip'] && IpUtils::checkIp($info['primary_ip'], $subnets ?? IpUtils::PRIVATE_SUBNETS)) { throw new TransportException(sprintf('IP "%s" is blocked for "%s".', $info['primary_ip'], $info['url'])); } diff --git a/src/Symfony/Component/HttpClient/composer.json b/src/Symfony/Component/HttpClient/composer.json index 4fc2ea35d8776..79740066dd7ab 100644 --- a/src/Symfony/Component/HttpClient/composer.json +++ b/src/Symfony/Component/HttpClient/composer.json @@ -42,6 +42,9 @@ "symfony/process": "^5.4|^6.0", "symfony/stopwatch": "^5.4|^6.0" }, + "conflict": { + "symfony/http-foundation": "<6.3" + }, "autoload": { "psr-4": { "Symfony\\Component\\HttpClient\\": "" }, "exclude-from-classmap": [ diff --git a/src/Symfony/Component/HttpFoundation/CHANGELOG.md b/src/Symfony/Component/HttpFoundation/CHANGELOG.md index 7b6a048882642..b40e8c7e79a0c 100644 --- a/src/Symfony/Component/HttpFoundation/CHANGELOG.md +++ b/src/Symfony/Component/HttpFoundation/CHANGELOG.md @@ -10,6 +10,7 @@ CHANGELOG * Create migration for session table when pdo handler is used * Add support for Relay PHP extension for Redis * The `Response::sendHeaders()` method now takes an optional HTTP status code as parameter, allowing to send informational responses such as Early Hints responses (103 status code) + * Add `IpUtils::isPrivateIp` * Deprecate conversion of invalid values in `ParameterBag::getInt()` and `ParameterBag::getBoolean()`, * Deprecate ignoring invalid values when using `ParameterBag::filter()`, unless flag `FILTER_NULL_ON_FAILURE` is set diff --git a/src/Symfony/Component/HttpFoundation/IpUtils.php b/src/Symfony/Component/HttpFoundation/IpUtils.php index 8f78d1b1d629a..4f17be64a122e 100644 --- a/src/Symfony/Component/HttpFoundation/IpUtils.php +++ b/src/Symfony/Component/HttpFoundation/IpUtils.php @@ -18,6 +18,21 @@ */ class IpUtils { + public const PRIVATE_SUBNETS = [ + '127.0.0.0/8', // RFC1700 (Loopback) + '10.0.0.0/8', // RFC1918 + '192.168.0.0/16', // RFC1918 + '172.16.0.0/12', // RFC1918 + '169.254.0.0/16', // RFC3927 + '0.0.0.0/8', // RFC5735 + '240.0.0.0/4', // RFC1112 + '::1/128', // Loopback + 'fc00::/7', // Unique Local Address + 'fe80::/10', // Link Local Address + '::ffff:0:0/96', // IPv4 translations + '::/128', // Unspecified address + ]; + private static array $checkedIps = []; /** @@ -191,4 +206,12 @@ public static function anonymize(string $ip): string return $ip; } + + /** + * Checks if an IPv4 or IPv6 address is contained in the list of private IP subnets. + */ + public static function isPrivateIp(string $requestIp): bool + { + return self::checkIp($requestIp, self::PRIVATE_SUBNETS); + } } diff --git a/src/Symfony/Component/HttpFoundation/Tests/IpUtilsTest.php b/src/Symfony/Component/HttpFoundation/Tests/IpUtilsTest.php index a8416a513d4b5..7146f32ddabe0 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/IpUtilsTest.php +++ b/src/Symfony/Component/HttpFoundation/Tests/IpUtilsTest.php @@ -154,4 +154,35 @@ public static function getIp4SubnetMaskZeroData() [false, '1.2.3.4', '256.256.256/0'], // invalid CIDR notation ]; } + + /** + * @dataProvider getIsPrivateIpData + */ + public function testIsPrivateIp(string $ip, bool $matches) + { + $this->assertSame($matches, IpUtils::isPrivateIp($ip)); + } + + public static function getIsPrivateIpData(): array + { + return [ + // private + ['127.0.0.1', true], + ['10.0.0.1', true], + ['192.168.0.1', true], + ['172.16.0.1', true], + ['169.254.0.1', true], + ['0.0.0.1', true], + ['240.0.0.1', true], + ['::1', true], + ['fc00::1', true], + ['fe80::1', true], + ['::ffff:0:1', true], + ['fd00::1', true], + + // public + ['104.26.14.6', false], + ['2606:4700:20::681a:e06', false], + ]; + } }