Skip to content

Commit a9abe16

Browse files
committed
Adding Modifier::normalizeIp and Modifier::normalizeHost
1 parent 7e13a01 commit a9abe16

File tree

4 files changed

+129
-25
lines changed

4 files changed

+129
-25
lines changed

components/CHANGELOG.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ All Notable changes to `League\Uri\Components` will be documented in this file
1010
- `URLSearchParams::when` conditional method to ease component building logic.
1111
- `Modifier::prependQueryParameters` returns a modifier with prepend query paramters
1212
- `Modifier::when` conditional method to ease component building logic.
13-
- `Modifier::normalizeHostIp` returns the host as normalized by the WHATWG algorithm
13+
- `Modifier::normalizeIp` returns the host normalized for IPv6 and IPv4 addresses
14+
- `Modifier::normalizeHost` returns the host as normalized by the WHATWG algorithm
1415
- `Modifier::with*` method from the underlying `Uri` object are proxy to improve DX.
1516
- `Query::decoded` the string representation of the component decoded.
1617
- `URLSearchParams::decoded` the string representation of the component decoded.

components/Modifier.php

+40-9
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838

3939
use function filter_var;
4040
use function get_object_vars;
41+
use function in_array;
4142
use function is_bool;
4243
use function ltrim;
4344
use function rtrim;
@@ -88,12 +89,24 @@ public function __toString(): string
8889

8990
public function toString(): string
9091
{
91-
return ($this->uri instanceof UriRenderer) ? $this->uri->toString() : Uri::new($this->uri)->toString();
92+
if ($this->uri instanceof Psr7UriInterface) {
93+
return $this->uri->__toString();
94+
}
95+
96+
return $this->uri->toString();
9297
}
9398

9499
public function toDisplayString(): string
95100
{
96-
return ($this->uri instanceof UriRenderer) ? $this->uri->toDisplayString() : Uri::new($this->uri)->toDisplayString();
101+
if ($this->uri instanceof Psr7UriInterface) {
102+
return Uri::new($this->uri)->toDisplayString();
103+
}
104+
105+
if ($this->uri instanceof UriRenderer) {
106+
return $this->uri->toDisplayString();
107+
}
108+
109+
return Uri::new($this->uri)->toDisplayString();
97110
}
98111

99112
public function withScheme(Stringable|string|null $scheme): static
@@ -657,9 +670,13 @@ public function replaceLabel(int $offset, Stringable|string|null $label): static
657670
return new static($this->uri->withHost(static::normalizeComponent($newHost, $this->uri)));
658671
}
659672

660-
public function normalizeHostIp(): static
673+
public function normalizeIp(): static
661674
{
662675
$host = $this->uri->getHost();
676+
if (in_array($host, [null, ''], true)) {
677+
return $this;
678+
}
679+
663680
try {
664681
$converted = IPv4Converter::fromEnvironment()->toDecimal($host);
665682
} catch (MissingFeature) {
@@ -670,12 +687,26 @@ public function normalizeHostIp(): static
670687
$converted = IPv6Converter::compress($host);
671688
}
672689

673-
return match (true) {
674-
null !== $converted => new static($this->uri->withHost($converted)),
675-
'' === $host,
676-
$this->uri instanceof UriInterface => $this,
677-
default => new static($this->uri->withHost((string) Uri::fromComponents(['host' => $host])->getHost())),
678-
};
690+
if ($converted !== $host) {
691+
return new static($this->uri->withHost($converted));
692+
}
693+
694+
return $this;
695+
}
696+
697+
public function normalizeHost(): static
698+
{
699+
$host = $this->uri->getHost();
700+
if (in_array($host, [null, ''], true)) {
701+
return $this;
702+
}
703+
704+
$new = $this->normalizeIp();
705+
if ($new->uri->getHost() !== $host) {
706+
return $new;
707+
}
708+
709+
return new static($this->uri->withHost(Host::new($host)->toAscii()));
679710
}
680711

681712
/*********************************

components/ModifierTest.php

+63-8
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,14 @@
1313

1414
use GuzzleHttp\Psr7\Utils;
1515
use League\Uri\Components\DataPath;
16+
use League\Uri\Contracts\UriInterface;
1617
use League\Uri\Exceptions\SyntaxError;
1718
use PHPUnit\Framework\Attributes\CoversClass;
1819
use PHPUnit\Framework\Attributes\DataProvider;
1920
use PHPUnit\Framework\Attributes\Group;
2021
use PHPUnit\Framework\Attributes\Test;
2122
use PHPUnit\Framework\TestCase;
23+
use Psr\Http\Message\UriInterface as Psr7UriInterface;
2224

2325
use const PHP_QUERY_RFC3986;
2426

@@ -885,15 +887,68 @@ public function it_will_remove_empty_pairs_fix_issue_133(): void
885887
}
886888

887889
#[Test]
888-
public function it_will_convert_uri_host_following_whatwg_rules(): void
890+
#[DataProvider('normalizedHostProvider')]
891+
public function it_will_convert_uri_host_following_whatwg_rules(?string $expectedHost, UriInterface|Psr7UriInterface|string $uri): void
889892
{
890-
self::assertSame(
891-
'192.168.2.13',
892-
Modifier::from(Http::new('https://0:0@0xc0a8020d/0?0#0'))
893-
->normalizeHostIp()
894-
->getUri()
895-
->getHost()
896-
);
893+
self::assertSame($expectedHost, Modifier::from($uri)->normalizeHost()->getUri()->getHost());
894+
}
895+
896+
public static function normalizedHostProvider(): iterable
897+
{
898+
yield 'null host with league uri interface' => [
899+
'expectedHost' => null,
900+
'uri' => Uri::new('mailto:[email protected]'),
901+
];
902+
903+
yield 'empty host with psr7 uri interface' => [
904+
'expectedHost' => '',
905+
'uri' => Http::new('/uri/without/host'),
906+
];
907+
908+
yield 'non decimal IPv4 with psr7 uri interface' => [
909+
'expectedHost' => '192.168.2.13',
910+
'uri' => Http::new('https://0:0@0xc0a8020d/0?0#0'),
911+
];
912+
913+
yield 'non decimal IPv4 with league uri interface' => [
914+
'expectedHost' => '192.168.2.13',
915+
'uri' => Uri::new('https://0:0@0xc0a8020d/0?0#0'),
916+
];
917+
918+
yield 'non decimal IPv4 with string' => [
919+
'expectedHost' => '192.168.2.13',
920+
'uri' => 'https://0:0@0xc0a8020d/0?0#0',
921+
];
922+
923+
yield 'unicode host with string' => [
924+
'expectedHost' => 'xn--bb-bjab.be',
925+
'uri' => 'https://bébé.be/0?0#0',
926+
];
927+
928+
yield 'unicode host with league uri interface' => [
929+
'expectedHost' => 'xn--bb-bjab.be',
930+
'uri' => Uri::new('https://bébé.be/0?0#0'),
931+
];
932+
933+
yield 'unicode host with uri interface' => [
934+
'expectedHost' => 'xn--bb-bjab.be',
935+
'uri' => Http::new('https://bébé.be/0?0#0'),
936+
];
937+
938+
yield 'IPv6 host with PSR7 uri interface' => [
939+
'expectedHost' => '[::1]',
940+
'uri' => Http::new('https://[0000:0000:0000:0000:0000:0000:0000:0001]/0?0#0'),
941+
];
942+
943+
yield 'IPv6 host with league uri interface' => [
944+
'expectedHost' => '[::1]',
945+
'uri' => Uri::new('https://[0000:0000:0000:0000:0000:0000:0000:0001]/0?0#0'),
946+
];
947+
948+
yield 'IPv6 host with string uri' => [
949+
'expectedHost' => '[::1]',
950+
'uri' => 'https://[0000:0000:0000:0000:0000:0000:0000:0001]/0?0#0',
951+
];
897952
}
898953

899954
#[Test]

docs/components/7.0/modifiers.md

+24-7
Original file line numberDiff line numberDiff line change
@@ -476,9 +476,9 @@ See the [IPv6 Converter documentation](/components/7.0/ipv6/) page for more info
476476

477477
use League\Uri\Modifier;
478478

479-
$uri = 'http://[0000:0000:0000:0000:0000:0000:0000:0001]/path/to/the/sky.php';
480-
echo Modifier::from($uri)->hostToIpv6Compressed()->getUriString();
481-
//display 'http://[::1]/path/to/the/sky.php'
479+
$uri = 'http://[::1]/path/to/the/sky.php';
480+
echo Modifier::from($uri)->hostToIpv6Expanded()->getUriString();
481+
//display 'http://[0000:0000:0000:0000:0000:0000:0000:0001]/path/to/the/sky.php'
482482
~~~
483483

484484
### Modifier::removeZoneIdentifier
@@ -498,19 +498,36 @@ echo get_class($newUri); //display \Zend\Diactoros\Uri
498498
echo $newUri; //display 'http://[fe80::1234]/path/to/the/sky.php'
499499
~~~
500500

501-
### Modifier::normalizeHostIp
501+
### Modifier::normalizeIp
502+
503+
Format the IP host:
504+
505+
- it will compress the IP representation if the host is an IPv6 address
506+
- it will convert the host to its IPv4 decimal format if possible
507+
508+
<p class="message-notice">available since version <code>7.6.0</code></p>
509+
510+
~~~php
511+
$uri = "https://0:0@0:0";
512+
echo Modifier::from($uri)->normalizeIp()->getUriString();
513+
//display "https://0:[email protected]:0"
514+
~~~
515+
516+
### Modifier::normalizeHost
502517

503-
Returns the host as formatted following WHATWG host formatting
518+
If the host is an IP address or a registrable domain that can be assimilated to
519+
an IPv4 address it will use the `Modifier::normalizeIp` method. Otherwise, it
520+
will try to convert the host into its ASCII format.
504521

505522
<p class="message-notice">available since version <code>7.6.0</code></p>
506523

507524
~~~php
508525
$uri = "https://0:0@0:0";
509-
echo Modifier::from($uri)->normalizeHostIp()->getUriString();
526+
echo Modifier::from($uri)->normalizeHost()->getUriString();
510527
//display "https://0:[email protected]:0"
511528
~~~
512529

513-
In case of IPv4 and/or IPv6 some extra normalization are applied.
530+
This is the algorithm used by the WHATWG URL specification.
514531

515532
### Modifier::addRootLabel
516533

0 commit comments

Comments
 (0)