Skip to content

Commit 5ea4b77

Browse files
Merge branch '6.4' into 7.1
* 6.4: Work around parse_url() bug (bis) Bump Symfony version to 6.4.16 fix PHP 7.2 compatibility silence PHP warnings issued by Redis::connect() Update VERSION for 6.4.15 Update CHANGELOG for 6.4.15 Bump Symfony version to 5.4.48 Update VERSION for 5.4.47 Update CHANGELOG for 5.4.47 [Routing] Fix: lost priority when defining hosts in configuration fix dumping tests to skip with data providers
2 parents c30d91a + 3208197 commit 5ea4b77

File tree

7 files changed

+46
-23
lines changed

7 files changed

+46
-23
lines changed

CurlHttpClient.php

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -417,8 +417,9 @@ private static function createRedirectResolver(array $options, string $host, int
417417
}
418418
}
419419

420-
return static function ($ch, string $location, bool $noContent) use (&$redirectHeaders, $options) {
420+
return static function ($ch, string $location, bool $noContent, bool &$locationHasHost) use (&$redirectHeaders, $options) {
421421
try {
422+
$locationHasHost = false;
422423
$location = self::parseUrl($location);
423424
} catch (InvalidArgumentException) {
424425
return null;
@@ -430,9 +431,11 @@ private static function createRedirectResolver(array $options, string $host, int
430431
$redirectHeaders['with_auth'] = array_filter($redirectHeaders['with_auth'], $filterContentHeaders);
431432
}
432433

433-
if ($redirectHeaders && $host = parse_url('http:'.$location['authority'], \PHP_URL_HOST)) {
434-
$port = parse_url('http:'.$location['authority'], \PHP_URL_PORT) ?: ('http:' === $location['scheme'] ? 80 : 443);
435-
$requestHeaders = $redirectHeaders['host'] === $host && $redirectHeaders['port'] === $port ? $redirectHeaders['with_auth'] : $redirectHeaders['no_auth'];
434+
$locationHasHost = isset($location['authority']);
435+
436+
if ($redirectHeaders && $locationHasHost) {
437+
$port = parse_url($location['authority'], \PHP_URL_PORT) ?: ('http:' === $location['scheme'] ? 80 : 443);
438+
$requestHeaders = parse_url($location['authority'], \PHP_URL_HOST) === $redirectHeaders['host'] && $redirectHeaders['port'] === $port ? $redirectHeaders['with_auth'] : $redirectHeaders['no_auth'];
436439
curl_setopt($ch, \CURLOPT_HTTPHEADER, $requestHeaders);
437440
} elseif ($noContent && $redirectHeaders) {
438441
curl_setopt($ch, \CURLOPT_HTTPHEADER, $redirectHeaders['with_auth']);

HttpClientTrait.php

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -626,29 +626,37 @@ private static function resolveUrl(array $url, ?array $base, array $queryDefault
626626
*/
627627
private static function parseUrl(string $url, array $query = [], array $allowedSchemes = ['http' => 80, 'https' => 443]): array
628628
{
629-
if (false === $parts = parse_url($url)) {
630-
if ('/' !== ($url[0] ?? '') || false === $parts = parse_url($url.'#')) {
631-
throw new InvalidArgumentException(sprintf('Malformed URL "%s".', $url));
632-
}
633-
unset($parts['fragment']);
629+
$tail = '';
630+
631+
if (false === $parts = parse_url(\strlen($url) !== strcspn($url, '?#') ? $url : $url.$tail = '#')) {
632+
throw new InvalidArgumentException(sprintf('Malformed URL "%s".', $url));
634633
}
635634

636635
if ($query) {
637636
$parts['query'] = self::mergeQueryString($parts['query'] ?? null, $query, true);
638637
}
639638

639+
$scheme = $parts['scheme'] ?? null;
640+
$host = $parts['host'] ?? null;
641+
642+
if (!$scheme && $host && !str_starts_with($url, '//')) {
643+
$parts = parse_url(':/'.$url.$tail);
644+
$parts['path'] = substr($parts['path'], 2);
645+
$scheme = $host = null;
646+
}
647+
640648
$port = $parts['port'] ?? 0;
641649

642-
if (null !== $scheme = $parts['scheme'] ?? null) {
650+
if (null !== $scheme) {
643651
if (!isset($allowedSchemes[$scheme = strtolower($scheme)])) {
644-
throw new InvalidArgumentException(sprintf('Unsupported scheme in "%s".', $url));
652+
throw new InvalidArgumentException(sprintf('Unsupported scheme in "%s": "%s" expected.', $url, implode('" or "', array_keys($allowedSchemes))));
645653
}
646654

647655
$port = $allowedSchemes[$scheme] === $port ? 0 : $port;
648656
$scheme .= ':';
649657
}
650658

651-
if (null !== $host = $parts['host'] ?? null) {
659+
if (null !== $host) {
652660
if (!\defined('INTL_IDNA_VARIANT_UTS46') && preg_match('/[\x80-\xFF]/', $host)) {
653661
throw new InvalidArgumentException(sprintf('Unsupported IDN "%s", try enabling the "intl" PHP extension or running "composer require symfony/polyfill-intl-idn".', $host));
654662
}
@@ -676,7 +684,7 @@ private static function parseUrl(string $url, array $query = [], array $allowedS
676684
'authority' => null !== $host ? '//'.(isset($parts['user']) ? $parts['user'].(isset($parts['pass']) ? ':'.$parts['pass'] : '').'@' : '').$host : null,
677685
'path' => isset($parts['path'][0]) ? $parts['path'] : null,
678686
'query' => isset($parts['query']) ? '?'.$parts['query'] : null,
679-
'fragment' => isset($parts['fragment']) ? '#'.$parts['fragment'] : null,
687+
'fragment' => isset($parts['fragment']) && !$tail ? '#'.$parts['fragment'] : null,
680688
];
681689
}
682690

NativeHttpClient.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -389,6 +389,7 @@ private static function createRedirectResolver(array $options, string $host, str
389389
return null;
390390
}
391391

392+
$locationHasHost = isset($url['authority']);
392393
$url = self::resolveUrl($url, $info['url']);
393394
$info['redirect_url'] = implode('', $url);
394395

@@ -422,7 +423,7 @@ private static function createRedirectResolver(array $options, string $host, str
422423

423424
[$host, $port] = self::parseHostPort($url, $info);
424425

425-
if (false !== (parse_url($location.'#', \PHP_URL_HOST) ?? false)) {
426+
if ($locationHasHost) {
426427
// Authorization and Cookie headers MUST NOT follow except for the initial host name
427428
$requestHeaders = $redirectHeaders['host'] === $host && $redirectHeaders['port'] === $port ? $redirectHeaders['with_auth'] : $redirectHeaders['no_auth'];
428429
$requestHeaders[] = 'Host: '.$host.$port;

Response/CurlResponse.php

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -425,17 +425,18 @@ private static function parseHeaderLine($ch, string $data, array &$info, array &
425425
$info['http_method'] = 'HEAD' === $info['http_method'] ? 'HEAD' : 'GET';
426426
curl_setopt($ch, \CURLOPT_CUSTOMREQUEST, $info['http_method']);
427427
}
428+
$locationHasHost = false;
428429

429-
if (null === $info['redirect_url'] = $resolveRedirect($ch, $location, $noContent)) {
430+
if (null === $info['redirect_url'] = $resolveRedirect($ch, $location, $noContent, $locationHasHost)) {
430431
$options['max_redirects'] = curl_getinfo($ch, \CURLINFO_REDIRECT_COUNT);
431432
curl_setopt($ch, \CURLOPT_FOLLOWLOCATION, false);
432433
curl_setopt($ch, \CURLOPT_MAXREDIRS, $options['max_redirects']);
433-
} else {
434-
$url = parse_url($location ?? ':');
434+
} elseif ($locationHasHost) {
435+
$url = parse_url($info['redirect_url']);
435436

436-
if (isset($url['host']) && null !== $ip = $multi->dnsCache->hostnames[$url['host'] = strtolower($url['host'])] ?? null) {
437+
if (null !== $ip = $multi->dnsCache->hostnames[$url['host'] = strtolower($url['host'])] ?? null) {
437438
// Populate DNS cache for redirects if needed
438-
$port = $url['port'] ?? ('http' === ($url['scheme'] ?? parse_url(curl_getinfo($ch, \CURLINFO_EFFECTIVE_URL), \PHP_URL_SCHEME)) ? 80 : 443);
439+
$port = $url['port'] ?? ('http' === $url['scheme'] ? 80 : 443);
439440
curl_setopt($ch, \CURLOPT_RESOLVE, ["{$url['host']}:$port:$ip"]);
440441
$multi->dnsCache->removals["-{$url['host']}:$port"] = "-{$url['host']}:$port";
441442
}

Tests/HttpClientTestCase.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -490,6 +490,15 @@ public function testNoPrivateNetworkWithResolve()
490490
$client->request('GET', 'http://symfony.com', ['resolve' => ['symfony.com' => '127.0.0.1']]);
491491
}
492492

493+
public function testNoRedirectWithInvalidLocation()
494+
{
495+
$client = $this->getHttpClient(__FUNCTION__);
496+
497+
$response = $client->request('GET', 'http://localhost:8057/302-no-scheme');
498+
499+
$this->assertSame(302, $response->getStatusCode());
500+
}
501+
493502
/**
494503
* @dataProvider getRedirectWithAuthTests
495504
*/

Tests/HttpClientTraitTest.php

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,7 @@ public static function provideResolveUrl(): array
214214
[self::RFC3986_BASE, 'g/../h', 'http://a/b/c/h'],
215215
[self::RFC3986_BASE, 'g;x=1/./y', 'http://a/b/c/g;x=1/y'],
216216
[self::RFC3986_BASE, 'g;x=1/../y', 'http://a/b/c/y'],
217+
[self::RFC3986_BASE, 'g/h:123/i', 'http://a/b/c/g/h:123/i'],
217218
// dot-segments in the query or fragment
218219
[self::RFC3986_BASE, 'g?y/./x', 'http://a/b/c/g?y/./x'],
219220
[self::RFC3986_BASE, 'g?y/../x', 'http://a/b/c/g?y/../x'],
@@ -239,14 +240,14 @@ public static function provideResolveUrl(): array
239240
public function testResolveUrlWithoutScheme()
240241
{
241242
$this->expectException(InvalidArgumentException::class);
242-
$this->expectExceptionMessage('Invalid URL: scheme is missing in "//localhost:8080". Did you forget to add "http(s)://"?');
243+
$this->expectExceptionMessage('Unsupported scheme in "localhost:8080": "http" or "https" expected.');
243244
self::resolveUrl(self::parseUrl('localhost:8080'), null);
244245
}
245246

246-
public function testResolveBaseUrlWitoutScheme()
247+
public function testResolveBaseUrlWithoutScheme()
247248
{
248249
$this->expectException(InvalidArgumentException::class);
249-
$this->expectExceptionMessage('Invalid URL: scheme is missing in "//localhost:8081". Did you forget to add "http(s)://"?');
250+
$this->expectExceptionMessage('Unsupported scheme in "localhost:8081": "http" or "https" expected.');
250251
self::resolveUrl(self::parseUrl('/foo'), self::parseUrl('localhost:8081'));
251252
}
252253

composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
"php": ">=8.2",
2626
"psr/log": "^1|^2|^3",
2727
"symfony/deprecation-contracts": "^2.5|^3",
28-
"symfony/http-client-contracts": "^3.4.1",
28+
"symfony/http-client-contracts": "^3.4.3",
2929
"symfony/service-contracts": "^2.5|^3"
3030
},
3131
"require-dev": {

0 commit comments

Comments
 (0)