From 70be1e22bd41733ba72346d42d6b5af554c64efe Mon Sep 17 00:00:00 2001 From: ignace nyamagana butera Date: Mon, 24 Jun 2024 21:14:16 +0200 Subject: [PATCH] Adding IPv6 Converter and usage in uri-interfaces and uri-components --- components/CHANGELOG.md | 4 ++ components/Components/Host.php | 11 ++++ components/Components/UserInfo.php | 21 ++++++- components/Modifier.php | 10 ++++ docs/_data/menu.yml | 1 + docs/components/7.0/host.md | 6 ++ docs/components/7.0/modifiers.md | 30 ++++++++++ docs/interfaces/7.0/contracts.md | 10 ++-- docs/interfaces/7.0/ipv6.md | 41 +++++++++++++ interfaces/CHANGELOG.md | 2 +- interfaces/IPv6/Converter.php | 94 ++++++++++++++++++++++++++++++ 11 files changed, 222 insertions(+), 8 deletions(-) create mode 100644 docs/interfaces/7.0/ipv6.md create mode 100644 interfaces/IPv6/Converter.php diff --git a/components/CHANGELOG.md b/components/CHANGELOG.md index 8b6d1d5f..ed8b4e84 100644 --- a/components/CHANGELOG.md +++ b/components/CHANGELOG.md @@ -8,6 +8,10 @@ All Notable changes to `League\Uri\Components` will be documented in this file - `UrlSearchParams::uniqueKeyCount` - `Modifier::getIdnUriString` +- `Modifier::hostToIpv6Compressed` +- `Modifier::hostToIpv6Expanded` +- `Host::expand` +- `Host::compress`; ### Fixed diff --git a/components/Components/Host.php b/components/Components/Host.php index ee2c95dc..a1cf04b9 100644 --- a/components/Components/Host.php +++ b/components/Components/Host.php @@ -22,6 +22,7 @@ use League\Uri\Idna\Converter as IdnConverter; use League\Uri\IPv4\Converter as IPv4Converter; use League\Uri\IPv4Normalizer; +use League\Uri\IPv6\Converter; use Psr\Http\Message\UriInterface as Psr7UriInterface; use Stringable; @@ -333,6 +334,16 @@ public function toUnicode(): ?string }; } + public function compress(): ?string + { + return Converter::compress($this->value()); + } + + public function expand(): ?string + { + return Converter::expand($this->value()); + } + public function getIpVersion(): ?string { return $this->ipVersion; diff --git a/components/Components/UserInfo.php b/components/Components/UserInfo.php index 4f13cf29..312b5bfd 100644 --- a/components/Components/UserInfo.php +++ b/components/Components/UserInfo.php @@ -113,9 +113,8 @@ public static function new(#[SensitiveParameter] Stringable|string|null $value = public function value(): ?string { return match (true) { - null === $this->username => null, - null === $this->password => Encoder::encodeUser($this->username), - default => Encoder::encodeUser($this->username).':'.Encoder::encodePassword($this->password), + null === $this->password => $this->getUsername(), + default => $this->getUsername().':'.$this->getPassword(), }; } @@ -134,6 +133,22 @@ public function getPass(): ?string return $this->password; } + public function getUsername(): ?string + { + return match ($this->username) { + null => null, + default => Encoder::encodeUser($this->username) + }; + } + + public function getPassword(): ?string + { + return match ($this->password) { + null => null, + default => Encoder::encodePassword($this->password) + }; + } + /** * @return array{user: ?string, pass: ?string} */ diff --git a/components/Modifier.php b/components/Modifier.php index a2297985..5f936d8b 100644 --- a/components/Modifier.php +++ b/components/Modifier.php @@ -451,6 +451,16 @@ public function hostToHexadecimal(): static }; } + public function hostToIpv6Compressed(): static + { + return new static($this->uri->withHost(Host::fromUri($this->uri)->compress())); + } + + public function hostToIpv6Expanded(): static + { + return new static($this->uri->withHost(Host::fromUri($this->uri)->expand())); + } + /** * Prepend a label or a host to the current URI host. * diff --git a/docs/_data/menu.yml b/docs/_data/menu.yml index a30d3f9f..1b091dd6 100644 --- a/docs/_data/menu.yml +++ b/docs/_data/menu.yml @@ -29,6 +29,7 @@ packages: URI Parser: '/interfaces/7.0/uri-parser-builder/' Query Parser: '/interfaces/7.0/query-parser-builder/' IPv4 Converter: '/interfaces/7.0/ipv4/' + IPv6 Converter: '/interfaces/7.0/ipv6/' IDN Converter: '/interfaces/7.0/idn/' '2.0': Documentation: diff --git a/docs/components/7.0/host.md b/docs/components/7.0/host.md index 2393b31e..f01810d7 100644 --- a/docs/components/7.0/host.md +++ b/docs/components/7.0/host.md @@ -73,8 +73,14 @@ public Host::isIpv6(): bool public Host::isIpFuture(): bool public Host::hasZoneIdentifier(): bool public Host::withoutZoneIdentifier(): self +public Host::compress(): string +public Host::expand(): string ~~~ +

The Host::compress and Host::expand, +are added in version 7.6.0 and normalized an IPv6 host, See the IPv6 Converter documentation page for more information. +

+ ### Host::fromIp This method allows creating a Host object from an IP. diff --git a/docs/components/7.0/modifiers.md b/docs/components/7.0/modifiers.md index 6718dca1..824ac745 100644 --- a/docs/components/7.0/modifiers.md +++ b/docs/components/7.0/modifiers.md @@ -368,6 +368,36 @@ echo Modifier::from($uri)->hostToOctal()->getUri(); //display 'http://0xc0a811/path/to/the/sky.php' ~~~ +### Modifier::hostToIpv6Compressed + +Normalizes the URI host content to a compressed IPv6 notation if possible. +See the [IPv6 Converter documentation](/components/7.0/ipv6/) page for more information. + +~~~php +hostToIpv6Compressed()->getUriString(); +//display 'http://[1050::5:0:300c:326b]/path/to/the/sky.php' +~~~ + +### Modifier::hostToIpv6Expanded + +Normalizes the URI host content to a expanded IPv6 notation if possible. +See the [IPv6 Converter documentation](/components/7.0/ipv6/) page for more information. + +~~~php +hostToIpv6Compressed()->getUriString(); +//display 'http://[::1]/path/to/the/sky.php' +~~~ + ### Modifier::removeZoneIdentifier Removes the host zone identifier if present diff --git a/docs/interfaces/7.0/contracts.md b/docs/interfaces/7.0/contracts.md index 3fd0c240..a55bf1d4 100644 --- a/docs/interfaces/7.0/contracts.md +++ b/docs/interfaces/7.0/contracts.md @@ -27,6 +27,8 @@ The `UriInterface` interface defines the following methods to access the URI str public UriInterface::getScheme(): ?string public UriInterface::getUserInfo(): ?string +public UriInterface::getUsername(): ?string +public UriInterface::getPassword(): ?string public UriInterface::getHost(): ?string public UriInterface::getPort(): ?int public UriInterface::getAuthority(): ?string @@ -51,7 +53,7 @@ Delimiter characters are not part of the URI component and **must not** be added $host, + default => (string) $host, + }; + } + + [$ipv6, $zoneIdentifier] = $components; + $ipv6 = (string) inet_ntop((string) inet_pton($ipv6)); + + return self::build($ipv6, $zoneIdentifier); + } + + public static function expand(Stringable|string|null $host): ?string + { + $components = self::parse($host); + if (null === $components['ipv6']) { + return match ($host) { + null => $host, + default => (string) $host, + }; + } + + [$ipv6, $zoneIdentifier] = $components; + $hex = (array) unpack("H*hex", (string) inet_pton($ipv6)); + $ipv6 = implode(':', str_split(strtolower($hex['hex'] ?? ''), 4)); + + return self::build($ipv6, $zoneIdentifier); + } + + private static function build(string $ip, ?string $zoneIdentifier): string + { + $zoneIdentifier = match ($zoneIdentifier) { + null => '', + default => '%'.$zoneIdentifier, + }; + + return '['.$ip.$zoneIdentifier.']'; + } + + /**] + * @param Stringable|string|null $host + * + * @return array{ipv6:?string, zoneIdentifier:?string} + */ + private static function parse(Stringable|string|null $host): array + { + if ($host === null) { + return ['ipv6' => null, 'zoneIdentifier' => null]; + } + + $host = (string) $host; + if ($host === '') { + return ['ipv6' => null, 'zoneIdentifier' => null]; + } + + if (!str_starts_with($host, '[')) { + return ['ipv6' => null, 'zoneIdentifier' => null]; + } + + if (!str_ends_with($host, ']')) { + return ['ipv6' => null, 'zoneIdentifier' => null]; + } + + [$ipv6, $zoneIdentifier] = explode('%', substr($host, 1, -1), 2) + [1 => null]; + if (false === filter_var($ipv6, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) { + return ['ipv6' => null, 'zoneIdentifier' => null]; + } + + return ['ipv6' => $ipv6, 'zoneIdentifier' => $zoneIdentifier]; + } +}