Skip to content

Commit 1475f44

Browse files
Merge branch '5.4' into 6.4
* 5.4: Work around parse_url() bug (bis) fix PHP 7.2 compatibility silence PHP warnings issued by Redis::connect() 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 23eb4d6 + 4b8695c commit 1475f44

File tree

18 files changed

+167
-76
lines changed

18 files changed

+167
-76
lines changed

src/Symfony/Bridge/PhpUnit/Legacy/SymfonyTestsListenerTrait.php

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
use Doctrine\Common\Annotations\AnnotationRegistry;
1515
use PHPUnit\Framework\AssertionFailedError;
16+
use PHPUnit\Framework\DataProviderTestSuite;
1617
use PHPUnit\Framework\RiskyTestError;
1718
use PHPUnit\Framework\TestCase;
1819
use PHPUnit\Framework\TestSuite;
@@ -193,7 +194,13 @@ public function startTestSuite($suite): void
193194
public function addSkippedTest($test, \Exception $e, $time): void
194195
{
195196
if (0 < $this->state) {
196-
$this->isSkipped[\get_class($test)][$test->getName()] = 1;
197+
if ($test instanceof DataProviderTestSuite) {
198+
foreach ($test->tests() as $testWithDataProvider) {
199+
$this->isSkipped[\get_class($testWithDataProvider)][$testWithDataProvider->getName()] = 1;
200+
}
201+
} else {
202+
$this->isSkipped[\get_class($test)][$test->getName()] = 1;
203+
}
197204
}
198205
}
199206

src/Symfony/Component/DomCrawler/Tests/UriResolverTest.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,8 @@ public static function provideResolverTests()
8787

8888
['http://', 'http://localhost', 'http://'],
8989
['/foo:123', 'http://localhost', 'http://localhost/foo:123'],
90+
['foo:123', 'http://localhost/', 'foo:123'],
91+
['foo/bar:1/baz', 'http://localhost/', 'http://localhost/foo/bar:1/baz'],
9092
];
9193
}
9294
}

src/Symfony/Component/DomCrawler/UriResolver.php

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,12 +32,8 @@ public static function resolve(string $uri, ?string $baseUri): string
3232
{
3333
$uri = trim($uri);
3434

35-
if (false === ($scheme = parse_url($uri, \PHP_URL_SCHEME)) && '/' === ($uri[0] ?? '')) {
36-
$scheme = parse_url($uri.'#', \PHP_URL_SCHEME);
37-
}
38-
3935
// absolute URL?
40-
if (null !== $scheme) {
36+
if (null !== parse_url(\strlen($uri) !== strcspn($uri, '?#') ? $uri : $uri.'#', \PHP_URL_SCHEME)) {
4137
return $uri;
4238
}
4339

src/Symfony/Component/HttpClient/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']);

src/Symfony/Component/HttpClient/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

src/Symfony/Component/HttpClient/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;

src/Symfony/Component/HttpClient/Response/CurlResponse.php

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -421,17 +421,18 @@ private static function parseHeaderLine($ch, string $data, array &$info, array &
421421
$info['http_method'] = 'HEAD' === $info['http_method'] ? 'HEAD' : 'GET';
422422
curl_setopt($ch, \CURLOPT_CUSTOMREQUEST, $info['http_method']);
423423
}
424+
$locationHasHost = false;
424425

425-
if (null === $info['redirect_url'] = $resolveRedirect($ch, $location, $noContent)) {
426+
if (null === $info['redirect_url'] = $resolveRedirect($ch, $location, $noContent, $locationHasHost)) {
426427
$options['max_redirects'] = curl_getinfo($ch, \CURLINFO_REDIRECT_COUNT);
427428
curl_setopt($ch, \CURLOPT_FOLLOWLOCATION, false);
428429
curl_setopt($ch, \CURLOPT_MAXREDIRS, $options['max_redirects']);
429-
} else {
430-
$url = parse_url($location ?? ':');
430+
} elseif ($locationHasHost) {
431+
$url = parse_url($info['redirect_url']);
431432

432-
if (isset($url['host']) && null !== $ip = $multi->dnsCache->hostnames[$url['host'] = strtolower($url['host'])] ?? null) {
433+
if (null !== $ip = $multi->dnsCache->hostnames[$url['host'] = strtolower($url['host'])] ?? null) {
433434
// Populate DNS cache for redirects if needed
434-
$port = $url['port'] ?? ('http' === ($url['scheme'] ?? parse_url(curl_getinfo($ch, \CURLINFO_EFFECTIVE_URL), \PHP_URL_SCHEME)) ? 80 : 443);
435+
$port = $url['port'] ?? ('http' === $url['scheme'] ? 80 : 443);
435436
curl_setopt($ch, \CURLOPT_RESOLVE, ["{$url['host']}:$port:$ip"]);
436437
$multi->dnsCache->removals["-{$url['host']}:$port"] = "-{$url['host']}:$port";
437438
}

src/Symfony/Component/HttpClient/Tests/HttpClientTestCase.php

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

488+
public function testNoRedirectWithInvalidLocation()
489+
{
490+
$client = $this->getHttpClient(__FUNCTION__);
491+
492+
$response = $client->request('GET', 'http://localhost:8057/302-no-scheme');
493+
494+
$this->assertSame(302, $response->getStatusCode());
495+
}
496+
488497
/**
489498
* @dataProvider getRedirectWithAuthTests
490499
*/

src/Symfony/Component/HttpClient/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

src/Symfony/Component/HttpClient/composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
"php": ">=8.1",
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)