diff --git a/components/CHANGELOG.md b/components/CHANGELOG.md index 9f9d0923..33e8bdb9 100644 --- a/components/CHANGELOG.md +++ b/components/CHANGELOG.md @@ -10,6 +10,7 @@ All Notable changes to `League\Uri\Components` will be documented in this file - `URLSearchParams::when` conditional method to ease component building logic. - `Modifier::prependQueryParameters` returns a modifier with prepend query paramters - `Modifier::when` conditional method to ease component building logic. +- `Modifier::whatWgHost` returns the host as normalized by the WHATWG algorithm - `Modifier::with*` method from the underlying `Uri` object are proxy to improve DX. - `Query::decoded` the string representation of the component decoded. - `URLSearchParams::decoded` the string representation of the component decoded. diff --git a/components/Modifier.php b/components/Modifier.php index 3578fd8c..4e85eb12 100644 --- a/components/Modifier.php +++ b/components/Modifier.php @@ -26,6 +26,7 @@ use League\Uri\Contracts\PathInterface; use League\Uri\Contracts\UriAccess; use League\Uri\Contracts\UriInterface; +use League\Uri\Exceptions\MissingFeature; use League\Uri\Exceptions\SyntaxError; use League\Uri\Idna\Converter as IdnaConverter; use League\Uri\IPv4\Converter as IPv4Converter; @@ -36,6 +37,7 @@ use Stringable; use function array_map; +use function filter_var; use function get_object_vars; use function is_bool; use function ltrim; @@ -44,6 +46,9 @@ use function str_ends_with; use function str_starts_with; +use const FILTER_FLAG_IPV4; +use const FILTER_VALIDATE_IP; + /** * @method static withScheme(Stringable|string|null $scheme) returns a new instance with the specified scheme. * @method static withUserInfo(Stringable|string|null $user, Stringable|string|null $password = null) returns a new instance with the specified user info. @@ -597,6 +602,27 @@ public function replaceLabel(int $offset, Stringable|string|null $label): static )); } + public function whatWgHost(): static + { + $host = $this->uri->getHost(); + try { + $converted = IPv4Converter::fromEnvironment()->toDecimal($host); + } catch (MissingFeature) { + $converted = null; + } + + if (false === filter_var($converted, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) { + $converted = IPv6Converter::compress($host); + } + + return match (true) { + null !== $converted => new static($this->uri->withHost($converted)), + '' === $host, + $this->uri instanceof UriInterface => $this, + default => new static($this->uri->withHost((string) Uri::fromComponents(['host' => $host])->getHost())), + }; + } + /********************************* * Path modifier methods *********************************/ diff --git a/components/ModifierTest.php b/components/ModifierTest.php index b2eb581e..fb8e8f03 100644 --- a/components/ModifierTest.php +++ b/components/ModifierTest.php @@ -883,4 +883,16 @@ public function it_will_remove_empty_pairs_fix_issue_133(): void self::assertNull($removeEmptyPairs('https://a.b/c?=d')); self::assertNull($removeEmptyPairs('https://a.b/c?=')); } + + #[Test] + public function it_will_convert_uri_host_following_whatwg_rules(): void + { + self::assertSame( + '192.168.2.13', + Modifier::from(Http::new('https://0:0@0xc0a8020d/0?0#0')) + ->whatWgHost() + ->getUri() + ->getHost() + ); + } } diff --git a/docs/components/7.0/modifiers.md b/docs/components/7.0/modifiers.md index 01854e79..4a89b8b0 100644 --- a/docs/components/7.0/modifiers.md +++ b/docs/components/7.0/modifiers.md @@ -145,6 +145,7 @@ echo Modifier::from('http://bébé.be')