Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 80257ea

Browse filesBrowse files
Work around parse_url() bug (bis)
1 parent fde6ae9 commit 80257ea
Copy full SHA for 80257ea

File tree

Expand file treeCollapse file tree

12 files changed

+60
-35
lines changed
Filter options
Expand file treeCollapse file tree

12 files changed

+60
-35
lines changed

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

Copy file name to clipboardExpand all lines: src/Symfony/Component/DomCrawler/Tests/UriResolverTest.php
+2Lines 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

Copy file name to clipboardExpand all lines: src/Symfony/Component/DomCrawler/UriResolver.php
+1-5Lines 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

Copy file name to clipboardExpand all lines: src/Symfony/Component/HttpClient/CurlHttpClient.php
+6-3Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -421,8 +421,9 @@ private static function createRedirectResolver(array $options, string $host): \C
421421
}
422422
}
423423

424-
return static function ($ch, string $location, bool $noContent) use (&$redirectHeaders, $options) {
424+
return static function ($ch, string $location, bool $noContent, bool &$locationHasHost) use (&$redirectHeaders, $options) {
425425
try {
426+
$locationHasHost = false;
426427
$location = self::parseUrl($location);
427428
} catch (InvalidArgumentException $e) {
428429
return null;
@@ -436,8 +437,10 @@ private static function createRedirectResolver(array $options, string $host): \C
436437
$redirectHeaders['with_auth'] = array_filter($redirectHeaders['with_auth'], $filterContentHeaders);
437438
}
438439

439-
if ($redirectHeaders && $host = parse_url('http:'.$location['authority'], \PHP_URL_HOST)) {
440-
$requestHeaders = $redirectHeaders['host'] === $host ? $redirectHeaders['with_auth'] : $redirectHeaders['no_auth'];
440+
$locationHasHost = isset($location['authority']);
441+
442+
if ($redirectHeaders && $locationHasHost) {
443+
$requestHeaders = parse_url($location['authority'], \PHP_URL_HOST) === $redirectHeaders['host'] ? $redirectHeaders['with_auth'] : $redirectHeaders['no_auth'];
441444
curl_setopt($ch, \CURLOPT_HTTPHEADER, $requestHeaders);
442445
} elseif ($noContent && $redirectHeaders) {
443446
curl_setopt($ch, \CURLOPT_HTTPHEADER, $redirectHeaders['with_auth']);

‎src/Symfony/Component/HttpClient/HttpClientTrait.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/HttpClient/HttpClientTrait.php
+17-9Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -514,29 +514,37 @@ private static function resolveUrl(array $url, ?array $base, array $queryDefault
514514
*/
515515
private static function parseUrl(string $url, array $query = [], array $allowedSchemes = ['http' => 80, 'https' => 443]): array
516516
{
517-
if (false === $parts = parse_url($url)) {
518-
if ('/' !== ($url[0] ?? '') || false === $parts = parse_url($url.'#')) {
519-
throw new InvalidArgumentException(sprintf('Malformed URL "%s".', $url));
520-
}
521-
unset($parts['fragment']);
517+
$tail = '';
518+
519+
if (false === $parts = parse_url(\strlen($url) !== strcspn($url, '?#') ? $url : $url.$tail = '#')) {
520+
throw new InvalidArgumentException(sprintf('Malformed URL "%s".', $url));
522521
}
523522

524523
if ($query) {
525524
$parts['query'] = self::mergeQueryString($parts['query'] ?? null, $query, true);
526525
}
527526

527+
$scheme = $parts['scheme'] ?? null;
528+
$host = $parts['host'] ?? null;
529+
530+
if (!$scheme && $host && !str_starts_with($url, '//')) {
531+
$parts = parse_url(':/'.$url.$tail);
532+
$parts['path'] = substr($parts['path'], 2);
533+
$scheme = $host = null;
534+
}
535+
528536
$port = $parts['port'] ?? 0;
529537

530-
if (null !== $scheme = $parts['scheme'] ?? null) {
538+
if (null !== $scheme) {
531539
if (!isset($allowedSchemes[$scheme = strtolower($scheme)])) {
532-
throw new InvalidArgumentException(sprintf('Unsupported scheme in "%s".', $url));
540+
throw new InvalidArgumentException(sprintf('Unsupported scheme in "%s": "%s" expected.', $url, implode('" or "', array_keys($allowedSchemes))));
533541
}
534542

535543
$port = $allowedSchemes[$scheme] === $port ? 0 : $port;
536544
$scheme .= ':';
537545
}
538546

539-
if (null !== $host = $parts['host'] ?? null) {
547+
if (null !== $host) {
540548
if (!\defined('INTL_IDNA_VARIANT_UTS46') && preg_match('/[\x80-\xFF]/', $host)) {
541549
throw new InvalidArgumentException(sprintf('Unsupported IDN "%s", try enabling the "intl" PHP extension or running "composer require symfony/polyfill-intl-idn".', $host));
542550
}
@@ -564,7 +572,7 @@ private static function parseUrl(string $url, array $query = [], array $allowedS
564572
'authority' => null !== $host ? '//'.(isset($parts['user']) ? $parts['user'].(isset($parts['pass']) ? ':'.$parts['pass'] : '').'@' : '').$host : null,
565573
'path' => isset($parts['path'][0]) ? $parts['path'] : null,
566574
'query' => isset($parts['query']) ? '?'.$parts['query'] : null,
567-
'fragment' => isset($parts['fragment']) ? '#'.$parts['fragment'] : null,
575+
'fragment' => isset($parts['fragment']) && !$tail ? '#'.$parts['fragment'] : null,
568576
];
569577
}
570578

‎src/Symfony/Component/HttpClient/NativeHttpClient.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/HttpClient/NativeHttpClient.php
+2-1Lines 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, ?ar
389389
return null;
390390
}
391391

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

@@ -424,7 +425,7 @@ private static function createRedirectResolver(array $options, string $host, ?ar
424425

425426
[$host, $port] = self::parseHostPort($url, $info);
426427

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

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

Copy file name to clipboardExpand all lines: src/Symfony/Component/HttpClient/Response/CurlResponse.php
+6-5Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -436,17 +436,18 @@ private static function parseHeaderLine($ch, string $data, array &$info, array &
436436
$info['http_method'] = 'HEAD' === $info['http_method'] ? 'HEAD' : 'GET';
437437
curl_setopt($ch, \CURLOPT_CUSTOMREQUEST, $info['http_method']);
438438
}
439+
$locationHasHost = false;
439440

440-
if (null === $info['redirect_url'] = $resolveRedirect($ch, $location, $noContent)) {
441+
if (null === $info['redirect_url'] = $resolveRedirect($ch, $location, $noContent, $locationHasHost)) {
441442
$options['max_redirects'] = curl_getinfo($ch, \CURLINFO_REDIRECT_COUNT);
442443
curl_setopt($ch, \CURLOPT_FOLLOWLOCATION, false);
443444
curl_setopt($ch, \CURLOPT_MAXREDIRS, $options['max_redirects']);
444-
} else {
445-
$url = parse_url($location ?? ':');
445+
} elseif ($locationHasHost) {
446+
$url = parse_url($info['redirect_url']);
446447

447-
if (isset($url['host']) && null !== $ip = $multi->dnsCache->hostnames[$url['host'] = strtolower($url['host'])] ?? null) {
448+
if (null !== $ip = $multi->dnsCache->hostnames[$url['host'] = strtolower($url['host'])] ?? null) {
448449
// Populate DNS cache for redirects if needed
449-
$port = $url['port'] ?? ('http' === ($url['scheme'] ?? parse_url(curl_getinfo($ch, \CURLINFO_EFFECTIVE_URL), \PHP_URL_SCHEME)) ? 80 : 443);
450+
$port = $url['port'] ?? ('http' === $url['scheme'] ? 80 : 443);
450451
curl_setopt($ch, \CURLOPT_RESOLVE, ["{$url['host']}:$port:$ip"]);
451452
$multi->dnsCache->removals["-{$url['host']}:$port"] = "-{$url['host']}:$port";
452453
}

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

Copy file name to clipboardExpand all lines: src/Symfony/Component/HttpClient/Tests/HttpClientTestCase.php
+9Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -489,4 +489,13 @@ public function testNoPrivateNetworkWithResolve()
489489

490490
$client->request('GET', 'http://symfony.com', ['resolve' => ['symfony.com' => '127.0.0.1']]);
491491
}
492+
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+
}
492501
}

‎src/Symfony/Component/HttpClient/Tests/HttpClientTraitTest.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/HttpClient/Tests/HttpClientTraitTest.php
+4-3Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ public static function provideResolveUrl(): array
102102
[self::RFC3986_BASE, 'g/../h', 'http://a/b/c/h'],
103103
[self::RFC3986_BASE, 'g;x=1/./y', 'http://a/b/c/g;x=1/y'],
104104
[self::RFC3986_BASE, 'g;x=1/../y', 'http://a/b/c/y'],
105+
[self::RFC3986_BASE, 'g/h:123/i', 'http://a/b/c/g/h:123/i'],
105106
// dot-segments in the query or fragment
106107
[self::RFC3986_BASE, 'g?y/./x', 'http://a/b/c/g?y/./x'],
107108
[self::RFC3986_BASE, 'g?y/../x', 'http://a/b/c/g?y/../x'],
@@ -127,14 +128,14 @@ public static function provideResolveUrl(): array
127128
public function testResolveUrlWithoutScheme()
128129
{
129130
$this->expectException(InvalidArgumentException::class);
130-
$this->expectExceptionMessage('Invalid URL: scheme is missing in "//localhost:8080". Did you forget to add "http(s)://"?');
131+
$this->expectExceptionMessage('Unsupported scheme in "localhost:8080": "http" or "https" expected.');
131132
self::resolveUrl(self::parseUrl('localhost:8080'), null);
132133
}
133134

134-
public function testResolveBaseUrlWitoutScheme()
135+
public function testResolveBaseUrlWithoutScheme()
135136
{
136137
$this->expectException(InvalidArgumentException::class);
137-
$this->expectExceptionMessage('Invalid URL: scheme is missing in "//localhost:8081". Did you forget to add "http(s)://"?');
138+
$this->expectExceptionMessage('Unsupported scheme in "localhost:8081": "http" or "https" expected.');
138139
self::resolveUrl(self::parseUrl('/foo'), self::parseUrl('localhost:8081'));
139140
}
140141

‎src/Symfony/Component/HttpClient/composer.json

Copy file name to clipboardExpand all lines: src/Symfony/Component/HttpClient/composer.json
+1-1Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
"php": ">=7.2.5",
2626
"psr/log": "^1|^2|^3",
2727
"symfony/deprecation-contracts": "^2.1|^3",
28-
"symfony/http-client-contracts": "^2.5.3",
28+
"symfony/http-client-contracts": "^2.5.4",
2929
"symfony/polyfill-php73": "^1.11",
3030
"symfony/polyfill-php80": "^1.16",
3131
"symfony/service-contracts": "^1.0|^2|^3"

‎src/Symfony/Component/HttpFoundation/Request.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/HttpFoundation/Request.php
+4-7Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -358,12 +358,7 @@ public static function create(string $uri, string $method = 'GET', array $parame
358358
$server['PATH_INFO'] = '';
359359
$server['REQUEST_METHOD'] = strtoupper($method);
360360

361-
if (false === ($components = parse_url($uri)) && '/' === ($uri[0] ?? '')) {
362-
$components = parse_url($uri.'#');
363-
unset($components['fragment']);
364-
}
365-
366-
if (false === $components) {
361+
if (false === $components = parse_url(\strlen($uri) !== strcspn($uri, '?#') ? $uri : $uri.'#')) {
367362
throw new BadRequestException('Invalid URI.');
368363
}
369364

@@ -386,9 +381,11 @@ public static function create(string $uri, string $method = 'GET', array $parame
386381
if ('https' === $components['scheme']) {
387382
$server['HTTPS'] = 'on';
388383
$server['SERVER_PORT'] = 443;
389-
} else {
384+
} elseif ('http' === $components['scheme']) {
390385
unset($server['HTTPS']);
391386
$server['SERVER_PORT'] = 80;
387+
} else {
388+
throw new BadRequestException('Invalid URI: http(s) scheme expected.');
392389
}
393390
}
394391

‎src/Symfony/Component/HttpFoundation/Tests/RequestTest.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/HttpFoundation/Tests/RequestTest.php
+2-1Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -310,7 +310,8 @@ public function testCreateWithRequestUri()
310310
* ["foo\u0000"]
311311
* [" foo"]
312312
* ["foo "]
313-
* [":"]
313+
* ["//"]
314+
* ["foo:bar"]
314315
*/
315316
public function testCreateWithBadRequestUri(string $uri)
316317
{

‎src/Symfony/Contracts/HttpClient/Test/Fixtures/web/index.php

Copy file name to clipboardExpand all lines: src/Symfony/Contracts/HttpClient/Test/Fixtures/web/index.php
+6Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,12 @@
9898
}
9999
break;
100100

101+
case '/302-no-scheme':
102+
if (!isset($vars['HTTP_AUTHORIZATION'])) {
103+
header('Location: localhost:8067', true, 302);
104+
}
105+
break;
106+
101107
case '/302/relative':
102108
header('Location: ..', true, 302);
103109
break;

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.