From 8a83c7de8dd1b9c26a77b7b697c9aef3fbfea640 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 | 34 +++++++++++++++++++++ components/Components/UserInfo.php | 21 +++++++++++-- components/Modifier.php | 10 ++++++ interfaces/CHANGELOG.md | 2 +- interfaces/IPv6/Converter.php | 49 ++++++++++++++++++++++++++++++ 6 files changed, 116 insertions(+), 4 deletions(-) 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..e13a8a2e 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,39 @@ public function toUnicode(): ?string }; } + public function compress(): ?string + { + if ('6' !== $this->ipVersion) { + return $this->host; + } + + /** @var string $ip */ + $ip = $this->getIp(); + if (! $this->hasZoneIdentifier) { + return '['. Converter::compress($ip) .']'; + } + + [$ipv6, $zoneId] = explode('%', $ip, 2); + + return '['. Converter::compress($ipv6) . '%' . $zoneId . ']'; + } + + public function expand(): ?string + { + if ('6' !== $this->ipVersion) { + return $this->host; + } + + /** @var string $ip */ + $ip = $this->getIp(); + [$ipv6, $zoneId] = explode('%', $ip, 2); + + return match ($this->hasZoneIdentifier) { + false => '['.Converter::expand($ipv6).']', + true => '['.Converter::expand($ipv6). '%' . $zoneId.']', + }; + } + 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/interfaces/CHANGELOG.md b/interfaces/CHANGELOG.md index ffb8afda..c78ca0b8 100644 --- a/interfaces/CHANGELOG.md +++ b/interfaces/CHANGELOG.md @@ -8,7 +8,7 @@ All Notable changes to `League\Uri\Interfaces` will be documented in this file - `UriInterface::getUsername` returns the encoded user component of the URI. - `UriInterface::getPassword` returns the encoded scheme-specific information about how to gain authorization to access the resource. - +- `Uri\IPv6\Converter` allow expanding and compressing IPv6. ### Fixed - Adding Host resolution caching to speed up URI parsing in `UriString` diff --git a/interfaces/IPv6/Converter.php b/interfaces/IPv6/Converter.php new file mode 100644 index 00000000..d483ae49 --- /dev/null +++ b/interfaces/IPv6/Converter.php @@ -0,0 +1,49 @@ + null, + self::isValidIpv6($ipv6) => (string) inet_ntop((string) inet_pton((string) $ipv6)), + default => (string) $ipv6, + }; + } + + public static function expand(Stringable|string|null $ipv6): ?string + { + if (null === $ipv6) { + return null; + } + + $ipv6 = (string) $ipv6; + if (!self::isValidIpv6($ipv6)) { + return $ipv6; + } + + $hex = (array) unpack("H*hex", (string) inet_pton($ipv6)); + + return implode(':', str_split(strtolower($hex['hex'] ?? ''), 4)); + } + + private static function isValidIpv6(Stringable|string|null $ipv6): bool + { + return (bool) filter_var((string) $ipv6, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6); + } +}