Skip to content

Commit 852e2c6

Browse files
committed
Improve Host normalization
1 parent d4f348e commit 852e2c6

File tree

4 files changed

+16
-37
lines changed

4 files changed

+16
-37
lines changed

interfaces/Encoder.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -309,7 +309,7 @@ public static function normalizeHost(Stringable|string|null $host): ?string
309309

310310
return (!str_contains($host, '%')) ? $host : preg_replace_callback(
311311
'/%[a-f0-9]{2}/',
312-
fn (array $matches): string => strtoupper($matches[0]),
312+
fn (array $matches) => 1 === preg_match('/%([0-7][0-9a-f])/', $matches[0]) ? rawurldecode($matches[0]) : strtoupper($matches[0]),
313313
$host
314314
);
315315
}

interfaces/UriString.php

Lines changed: 5 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -32,12 +32,10 @@
3232
use function in_array;
3333
use function inet_pton;
3434
use function preg_match;
35-
use function preg_replace_callback;
3635
use function rawurldecode;
3736
use function sprintf;
3837
use function strpos;
3938
use function strtolower;
40-
use function strtoupper;
4139
use function substr;
4240

4341
use const FILTER_FLAG_IPV4;
@@ -305,18 +303,11 @@ public static function parseNormalized(Stringable|string $uri): array
305303

306304
$host = $components['host'];
307305
if (null !== $host && false === filter_var($host, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
308-
$host = (string) IPv6Converter::normalize($host);
309-
if ($host === $components['host']) {
310-
$host = (string) preg_replace_callback(
311-
'/%[0-9A-F]{2}/i',
312-
fn (array $matches): string => strtoupper($matches[0]),
313-
strtolower($host)
314-
);
315-
if ($isSupported) {
316-
$idnaHost = IdnaConverter::toAscii(rawurldecode($components['host']));
317-
if (!$idnaHost->hasErrors()) {
318-
$host = $idnaHost->domain();
319-
}
306+
$host = (string) Encoder::normalizeHost($host);
307+
if ($isSupported) {
308+
$idnaHost = IdnaConverter::toAscii($host);
309+
if (!$idnaHost->hasErrors()) {
310+
$host = $idnaHost->domain();
320311
}
321312
}
322313

@@ -782,10 +773,6 @@ public static function isHost(Stringable|string|null $host): bool
782773
*/
783774
private static function filterRegisteredName(string $host): void
784775
{
785-
if ($host !== Encoder::decodeUnreservedCharacters($host)) {
786-
throw new SyntaxError('Host `'.$host.'` is invalid: only UTF-8 characters sequence can be percent encoded in host.');
787-
}
788-
789776
$formattedHost = rawurldecode($host);
790777
if ($formattedHost !== $host) {
791778
if (IdnaConverter::toAscii($formattedHost)->hasErrors()) {

interfaces/UriStringTest.php

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -745,7 +745,6 @@ public static function invalidUriProvider(): array
745745
'invalid port (1)' => ['//host:port/path?query#fragment'],
746746
'invalid port (2)' => ['//host:-892358/path?query#fragment'],
747747
'invalid host' => ['http://exam ple.com'],
748-
'invalid host with invalid encoded characters' => ['http://ex%61mple.com'],
749748
'invalid ipv6 host (1)' => ['scheme://[127.0.0.1]/path?query#fragment'],
750749
'invalid ipv6 host (2)' => ['scheme://]::1[/path?query#fragment'],
751750
'invalid ipv6 host (3)' => ['scheme://[::1|/path?query#fragment'],

uri/Uri.php

Lines changed: 10 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,6 @@
7676
use function strpos;
7777
use function strspn;
7878
use function strtolower;
79-
use function strtoupper;
8079
use function substr;
8180
use function trim;
8281

@@ -382,24 +381,19 @@ private function formatHost(?string $host): ?string
382381
private function formatRegisteredName(string $host): string
383382
{
384383
$formattedHost = rawurldecode($host);
385-
if ($formattedHost !== $host) {
386-
387-
if (IdnaConverter::toAscii($formattedHost)->hasErrors()) {
388-
throw new SyntaxError('The host `'.$host.'` is invalid : the registered name contains invalid characters.');
389-
}
384+
if ($formattedHost === $host) {
385+
return match (1) {
386+
preg_match(self::REGEXP_HOST_REGNAME, $formattedHost) => $formattedHost,
387+
preg_match(self::REGEXP_HOST_GEN_DELIMS, $formattedHost) => throw new SyntaxError('The host `'.$host.'` is invalid : a registered name cannot contain URI delimiters or spaces.'),
388+
default => IdnaConverter::toAsciiOrFail($host),
389+
};
390+
}
390391

391-
return (string) preg_replace_callback(
392-
'/%[0-9A-F]{2}/i',
393-
fn (array $matches) => strtoupper($matches[0]),
394-
strtolower($host)
395-
);
392+
if (IdnaConverter::toAscii($formattedHost)->hasErrors()) {
393+
throw new SyntaxError('The host `'.$host.'` is invalid : the registered name contains invalid characters.');
396394
}
397395

398-
return match (1) {
399-
preg_match(self::REGEXP_HOST_REGNAME, $formattedHost) => $formattedHost,
400-
preg_match(self::REGEXP_HOST_GEN_DELIMS, $formattedHost) => throw new SyntaxError('The host `'.$host.'` is invalid : a registered name cannot contain URI delimiters or spaces.'),
401-
default => IdnaConverter::toAsciiOrFail($host),
402-
};
396+
return (string) Encoder::normalizeHost($host);
403397
}
404398

405399
/**
@@ -1185,7 +1179,6 @@ public function toDisplayString(): string
11851179
{
11861180
$components = $this->toComponents();
11871181

1188-
11891182
unset($components['port']);
11901183
if (null !== $components['host']) {
11911184
$components['host'] = IdnaConverter::toUnicode($components['host'])->domain();

0 commit comments

Comments
 (0)