From 550cc6768b091fd448de94e6f83cead3a69e150c Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Mon, 29 Jul 2024 14:56:13 +0200 Subject: [PATCH 1/3] [HttpClient] Disable HTTP/2 PUSH by default when using curl --- CurlHttpClient.php | 2 +- Tests/CurlHttpClientTest.php | 20 +++++++++++--------- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/CurlHttpClient.php b/CurlHttpClient.php index 3a2fba0..19bd456 100644 --- a/CurlHttpClient.php +++ b/CurlHttpClient.php @@ -67,7 +67,7 @@ final class CurlHttpClient implements HttpClientInterface, LoggerAwareInterface, * * @see HttpClientInterface::OPTIONS_DEFAULTS for available options */ - public function __construct(array $defaultOptions = [], int $maxHostConnections = 6, int $maxPendingPushes = 50) + public function __construct(array $defaultOptions = [], int $maxHostConnections = 6, int $maxPendingPushes = 0) { if (!\extension_loaded('curl')) { throw new \LogicException('You cannot use the "Symfony\Component\HttpClient\CurlHttpClient" as the "curl" extension is not installed.'); diff --git a/Tests/CurlHttpClientTest.php b/Tests/CurlHttpClientTest.php index 3ff0c9a..8e50d13 100644 --- a/Tests/CurlHttpClientTest.php +++ b/Tests/CurlHttpClientTest.php @@ -22,17 +22,19 @@ class CurlHttpClientTest extends HttpClientTestCase { protected function getHttpClient(string $testCase): HttpClientInterface { - if (false !== strpos($testCase, 'Push')) { - if (\PHP_VERSION_ID >= 70300 && \PHP_VERSION_ID < 70304) { - $this->markTestSkipped('PHP 7.3.0 to 7.3.3 don\'t support HTTP/2 PUSH'); - } - - if (!\defined('CURLMOPT_PUSHFUNCTION') || 0x073D00 > ($v = curl_version())['version_number'] || !(\CURL_VERSION_HTTP2 & $v['features'])) { - $this->markTestSkipped('curl <7.61 is used or it is not compiled with support for HTTP/2 PUSH'); - } + if (!str_contains($testCase, 'Push')) { + return new CurlHttpClient(['verify_peer' => false, 'verify_host' => false]); } - return new CurlHttpClient(['verify_peer' => false, 'verify_host' => false]); + if (\PHP_VERSION_ID >= 70300 && \PHP_VERSION_ID < 70304) { + $this->markTestSkipped('PHP 7.3.0 to 7.3.3 don\'t support HTTP/2 PUSH'); + } + + if (!\defined('CURLMOPT_PUSHFUNCTION') || 0x073D00 > ($v = curl_version())['version_number'] || !(\CURL_VERSION_HTTP2 & $v['features'])) { + $this->markTestSkipped('curl <7.61 is used or it is not compiled with support for HTTP/2 PUSH'); + } + + return new CurlHttpClient(['verify_peer' => false, 'verify_host' => false], 6, 50); } public function testBindToPort() From 6ad5e27962a4cdc4e9c6cd895786122758777ca9 Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Mon, 12 Aug 2024 16:13:30 +0200 Subject: [PATCH 2/3] reject malformed URLs with a meaningful exception --- HttpClientTrait.php | 6 ++++++ Tests/HttpClientTestCase.php | 11 +++++++++++ Tests/HttpClientTraitTest.php | 2 -- 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/HttpClientTrait.php b/HttpClientTrait.php index 3f44f36..d436a4c 100644 --- a/HttpClientTrait.php +++ b/HttpClientTrait.php @@ -445,6 +445,8 @@ private static function jsonEncode($value, ?int $flags = null, int $maxDepth = 5 */ private static function resolveUrl(array $url, ?array $base, array $queryDefaults = []): array { + $givenUrl = $url; + if (null !== $base && '' === ($base['scheme'] ?? '').($base['authority'] ?? '')) { throw new InvalidArgumentException(sprintf('Invalid "base_uri" option: host or scheme is missing in "%s".', implode('', $base))); } @@ -498,6 +500,10 @@ private static function resolveUrl(array $url, ?array $base, array $queryDefault $url['query'] = null; } + if (null !== $url['scheme'] && null === $url['authority']) { + throw new InvalidArgumentException(\sprintf('Invalid URL: host is missing in "%s".', implode('', $givenUrl))); + } + return $url; } diff --git a/Tests/HttpClientTestCase.php b/Tests/HttpClientTestCase.php index 9a1c177..d1213f0 100644 --- a/Tests/HttpClientTestCase.php +++ b/Tests/HttpClientTestCase.php @@ -13,6 +13,7 @@ use PHPUnit\Framework\SkippedTestSuiteError; use Symfony\Component\HttpClient\Exception\ClientException; +use Symfony\Component\HttpClient\Exception\InvalidArgumentException; use Symfony\Component\HttpClient\Exception\TransportException; use Symfony\Component\HttpClient\Internal\ClientState; use Symfony\Component\HttpClient\Response\StreamWrapper; @@ -455,4 +456,14 @@ public function testNullBody() $this->expectNotToPerformAssertions(); } + + public function testMisspelledScheme() + { + $httpClient = $this->getHttpClient(__FUNCTION__); + + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('Invalid URL: host is missing in "http:/localhost:8057/".'); + + $httpClient->request('GET', 'http:/localhost:8057/'); + } } diff --git a/Tests/HttpClientTraitTest.php b/Tests/HttpClientTraitTest.php index 2f42eb8..aa03378 100644 --- a/Tests/HttpClientTraitTest.php +++ b/Tests/HttpClientTraitTest.php @@ -63,7 +63,6 @@ public function testResolveUrl(string $base, string $url, string $expected) public static function provideResolveUrl(): array { return [ - [self::RFC3986_BASE, 'http:h', 'http:h'], [self::RFC3986_BASE, 'g', 'http://a/b/c/g'], [self::RFC3986_BASE, './g', 'http://a/b/c/g'], [self::RFC3986_BASE, 'g/', 'http://a/b/c/g/'], @@ -117,7 +116,6 @@ public static function provideResolveUrl(): array ['http://u:p@a/b/c/d;p?q', '.', 'http://u:p@a/b/c/'], // path ending with slash or no slash at all ['http://a/b/c/d/', 'e', 'http://a/b/c/d/e'], - ['http:no-slash', 'e', 'http:e'], // falsey relative parts [self::RFC3986_BASE, '//0', 'http://0/'], [self::RFC3986_BASE, '0', 'http://a/b/c/0'], From 4d547e5259221bd37685f4ddc8e8947acc2cb755 Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Fri, 16 Aug 2024 12:20:10 +0200 Subject: [PATCH 3/3] do not overwrite the host to request --- CurlHttpClient.php | 8 ++++---- Tests/CurlHttpClientTest.php | 16 ++++++++++++++++ 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/CurlHttpClient.php b/CurlHttpClient.php index 19bd456..478f9c0 100644 --- a/CurlHttpClient.php +++ b/CurlHttpClient.php @@ -185,10 +185,10 @@ public function request(string $method, string $url, array $options = []): Respo $multi->reset(); } - foreach ($options['resolve'] as $host => $ip) { - $resolve[] = null === $ip ? "-$host:$port" : "$host:$port:$ip"; - $multi->dnsCache->hostnames[$host] = $ip; - $multi->dnsCache->removals["-$host:$port"] = "-$host:$port"; + foreach ($options['resolve'] as $resolveHost => $ip) { + $resolve[] = null === $ip ? "-$resolveHost:$port" : "$resolveHost:$port:$ip"; + $multi->dnsCache->hostnames[$resolveHost] = $ip; + $multi->dnsCache->removals["-$resolveHost:$port"] = "-$resolveHost:$port"; } $curlopts[\CURLOPT_RESOLVE] = $resolve; diff --git a/Tests/CurlHttpClientTest.php b/Tests/CurlHttpClientTest.php index 8e50d13..9ea9762 100644 --- a/Tests/CurlHttpClientTest.php +++ b/Tests/CurlHttpClientTest.php @@ -128,4 +128,20 @@ public function testOverridingInternalAttributesUsingCurlOptions() ], ]); } + + public function testKeepAuthorizationHeaderOnRedirectToSameHostWithConfiguredHostToIpAddressMapping() + { + $httpClient = $this->getHttpClient(__FUNCTION__); + $response = $httpClient->request('POST', 'http://127.0.0.1:8057/301', [ + 'headers' => [ + 'Authorization' => 'Basic Zm9vOmJhcg==', + ], + 'resolve' => [ + 'symfony.com' => '10.10.10.10', + ], + ]); + + $this->assertSame(200, $response->getStatusCode()); + $this->assertSame('/302', $response->toArray()['REQUEST_URI'] ?? null); + } }