From 2b3649c3e5bd53114be7aac643f1f85f8c506215 Mon Sep 17 00:00:00 2001 From: ignace nyamagana butera Date: Wed, 26 Jun 2024 07:15:51 +0200 Subject: [PATCH] Improve internal codebase (#138) * Improve internal codebase * Deprecate usage of PSR-7 in API * Adding IPv6 Converter and usage in uri-interfaces and uri-components --- CHANGELOG.md | 7 ++++-- Components/Authority.php | 19 +++++++++-------- Components/Host.php | 1 + Components/URLSearchParams.php | 2 -- Components/UserInfo.php | 35 +++++++++++++++++++++--------- Modifier.php | 21 ++++++++++++++++++ ModifierTest.php | 39 ++++++++++++++++++++++++++++++++++ 7 files changed, 101 insertions(+), 23 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b9d95bb95..859bc9ddb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,14 +8,17 @@ All Notable changes to `League\Uri\Components` will be documented in this file - `UrlSearchParams::uniqueKeyCount` - `Modifier::getIdnUriString` +- `Modifier::hostToIpv6Compressed` +- `Modifier::hostToIpv6Expanded` ### Fixed -- None +- Adding `SensitiveParameter` attribute in the `UserInfo` and the `Authority` class. +- Remove Usage of PSR-7 `UriInterface` in `UrlSearchParams` class ### Deprecated -- None +- Usage of PSR-7 `UriFactoryInterface` is deprecated in `Modifier` class ### Removed diff --git a/Components/Authority.php b/Components/Authority.php index d7c1cc380..b06cb970e 100644 --- a/Components/Authority.php +++ b/Components/Authority.php @@ -21,6 +21,7 @@ use League\Uri\Exceptions\SyntaxError; use League\Uri\UriString; use Psr\Http\Message\UriInterface as Psr7UriInterface; +use SensitiveParameter; use Stringable; final class Authority extends Component implements AuthorityInterface @@ -32,7 +33,7 @@ final class Authority extends Component implements AuthorityInterface public function __construct( Stringable|string|null $host, Stringable|string|int|null $port = null, - Stringable|string|null $userInfo = null + #[SensitiveParameter] Stringable|string|null $userInfo = null ) { $this->host = !$host instanceof HostInterface ? Host::new($host) : $host; $this->port = !$port instanceof PortInterface ? Port::new($port) : $port; @@ -45,7 +46,7 @@ public function __construct( /** * @throws SyntaxError If the component contains invalid HostInterface part. */ - public static function new(Stringable|string|null $value = null): self + public static function new(#[SensitiveParameter] Stringable|string|null $value = null): self { $components = UriString::parseAuthority(self::filterComponent($value)); @@ -62,7 +63,7 @@ public static function new(Stringable|string|null $value = null): self /** * Create a new instance from a URI object. */ - public static function fromUri(Stringable|string $uri): self + public static function fromUri(#[SensitiveParameter] Stringable|string $uri): self { $uri = self::filterUri($uri); $authority = $uri->getAuthority(); @@ -87,7 +88,7 @@ public static function fromUri(Stringable|string $uri): self * port? : ?int * } $components */ - public static function fromComponents(array $components): self + public static function fromComponents(#[SensitiveParameter] array $components): self { $components += ['user' => null, 'pass' => null, 'host' => null, 'port' => null]; @@ -104,7 +105,7 @@ public function value(): ?string } private static function getAuthorityValue( - UserInfoInterface $userInfo, + #[SensitiveParameter] UserInfoInterface $userInfo, HostInterface $host, PortInterface $port ): ?string { @@ -180,7 +181,7 @@ public function withPort(Stringable|string|int|null $port): AuthorityInterface }; } - public function withUserInfo(Stringable|string|null $user, Stringable|string|null $password = null): AuthorityInterface + public function withUserInfo(Stringable|string|null $user, #[SensitiveParameter] Stringable|string|null $password = null): AuthorityInterface { $userInfo = new UserInfo($user, $password); @@ -200,7 +201,7 @@ public function withUserInfo(Stringable|string|null $user, Stringable|string|nul * * Create a new instance from a URI object. */ - public static function createFromUri(UriInterface|Psr7UriInterface $uri): self + public static function createFromUri(#[SensitiveParameter] UriInterface|Psr7UriInterface $uri): self { return self::fromUri($uri); } @@ -215,7 +216,7 @@ public static function createFromUri(UriInterface|Psr7UriInterface $uri): self * * Returns a new instance from a string or a stringable object. */ - public static function createFromString(Stringable|string $authority): self + public static function createFromString(#[SensitiveParameter] Stringable|string $authority): self { return self::new($authority); } @@ -255,7 +256,7 @@ public static function createFromNull(): self * port? : ?int * } $components */ - public static function createFromComponents(array $components): self + public static function createFromComponents(#[SensitiveParameter] array $components): self { return self::fromComponents($components); } diff --git a/Components/Host.php b/Components/Host.php index ee2c95dc5..04d241e9d 100644 --- a/Components/Host.php +++ b/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; diff --git a/Components/URLSearchParams.php b/Components/URLSearchParams.php index 706ad2796..32e921399 100644 --- a/Components/URLSearchParams.php +++ b/Components/URLSearchParams.php @@ -25,7 +25,6 @@ use League\Uri\KeyValuePair\Converter; use League\Uri\QueryString; use League\Uri\Uri; -use Psr\Http\Message\UriInterface as Psr7UriInterface; use Stringable; use function array_is_list; @@ -202,7 +201,6 @@ public static function fromAssociative(object|array $associative): self public static function fromUri(Stringable|string $uri): self { $query = match (true) { - $uri instanceof Psr7UriInterface, $uri instanceof UriInterface => $uri->getQuery(), default => Uri::new($uri)->getQuery(), }; diff --git a/Components/UserInfo.php b/Components/UserInfo.php index 0c88eb306..312b5bfd6 100644 --- a/Components/UserInfo.php +++ b/Components/UserInfo.php @@ -50,7 +50,7 @@ public function __construct( /** * Create a new instance from a URI object. */ - public static function fromUri(Stringable|string $uri): self + public static function fromUri(#[SensitiveParameter] Stringable|string $uri): self { $uri = self::filterUri($uri); $component = $uri->getUserInfo(); @@ -64,7 +64,7 @@ public static function fromUri(Stringable|string $uri): self /** * Create a new instance from an Authority object. */ - public static function fromAuthority(Stringable|string|null $authority): self + public static function fromAuthority(#[SensitiveParameter] Stringable|string|null $authority): self { return match (true) { $authority instanceof AuthorityInterface => self::new($authority->getUserInfo()), @@ -80,7 +80,7 @@ public static function fromAuthority(Stringable|string|null $authority): self * * @param array{user? : ?string, pass? : ?string} $components */ - public static function fromComponents(array $components): self + public static function fromComponents(#[SensitiveParameter] array $components): self { $components += ['user' => null, 'pass' => null]; @@ -93,7 +93,7 @@ public static function fromComponents(array $components): self /** * Creates a new instance from an encoded string. */ - public static function new(Stringable|string|null $value = null): self + public static function new(#[SensitiveParameter] Stringable|string|null $value = null): self { if ($value instanceof UriComponentInterface) { $value = $value->value(); @@ -113,9 +113,8 @@ public static function new(Stringable|string|null $value = null): self 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} */ @@ -176,7 +191,7 @@ public function withPass(#[SensitiveParameter] Stringable|string|null $password) * * Create a new instance from a URI object. */ - public static function createFromUri(Psr7UriInterface|UriInterface $uri): self + public static function createFromUri(#[SensitiveParameter] Psr7UriInterface|UriInterface $uri): self { return self::fromUri($uri); } @@ -191,7 +206,7 @@ public static function createFromUri(Psr7UriInterface|UriInterface $uri): self * * Create a new instance from an Authority object. */ - public static function createFromAuthority(AuthorityInterface|Stringable|string $authority): self + public static function createFromAuthority(#[SensitiveParameter] AuthorityInterface|Stringable|string $authority): self { return self::fromAuthority($authority); } @@ -206,7 +221,7 @@ public static function createFromAuthority(AuthorityInterface|Stringable|string * * Creates a new instance from an encoded string. */ - public static function createFromString(Stringable|string $userInfo): self + public static function createFromString(#[SensitiveParameter] Stringable|string $userInfo): self { return self::new($userInfo); } diff --git a/Modifier.php b/Modifier.php index dc3209676..1d04a62a9 100644 --- a/Modifier.php +++ b/Modifier.php @@ -26,6 +26,7 @@ use League\Uri\Exceptions\SyntaxError; use League\Uri\Idna\Converter as IdnConverter; use League\Uri\IPv4\Converter as IPv4Converter; +use League\Uri\IPv6\Converter; use League\Uri\KeyValuePair\Converter as KeyValuePairConverter; use Psr\Http\Message\UriFactoryInterface; use Psr\Http\Message\UriInterface as Psr7UriInterface; @@ -43,6 +44,12 @@ final public function __construct(protected readonly Psr7UriInterface|UriInterfa { } + /** + * @param Stringable|string $uri + * @param UriFactoryInterface|null $uriFactory Deprecated, will be removed in the next major release + * + * @return static + */ public static function from(Stringable|string $uri, ?UriFactoryInterface $uriFactory = null): static { return new static(match (true) { @@ -445,6 +452,20 @@ public function hostToHexadecimal(): static }; } + public function hostToIpv6Compressed(): static + { + return new static($this->uri->withHost( + Converter::compress($this->uri->getHost()) + )); + } + + public function hostToIpv6Expanded(): static + { + return new static($this->uri->withHost( + Converter::expand($this->uri->getHost()) + )); + } + /** * Prepend a label or a host to the current URI host. * diff --git a/ModifierTest.php b/ModifierTest.php index ce6346a48..3996ae903 100644 --- a/ModifierTest.php +++ b/ModifierTest.php @@ -859,4 +859,43 @@ public static function idnUriProvider(): iterable 'input' => "http://bébé.be:80?q=toto le héros", ]; } + + #[DataProvider('ipv6NormalizationUriProvider')] + public function testItCanExpandOrCompressTheHost( + string $inputUri, + string $compressedUri, + string $expandedUri, + ): void { + $uri = Modifier::from(Http::new($inputUri)); + + self::assertSame($compressedUri, $uri->hostToIpv6Compressed()->getUriString()); + self::assertSame($expandedUri, $uri->hostToIpv6Expanded()->getUriString()); + } + + public static function ipv6NormalizationUriProvider(): iterable + { + yield 'no change happen with a non IP host' => [ + 'inputUri' => 'https://example.com/foo/bar', + 'compressedUri' => 'https://example.com/foo/bar', + 'expandedUri' => 'https://example.com/foo/bar', + ]; + + yield 'no change happen with a IPv4 host' => [ + 'inputUri' => 'https://127.0.0.1/foo/bar', + 'compressedUri' => 'https://127.0.0.1/foo/bar', + 'expandedUri' => 'https://127.0.0.1/foo/bar', + ]; + + yield 'IPv6 gets expanded if needed' => [ + 'inputUri' => 'https://[fe80::a%25en1]/foo/bar', + 'compressedUri' => 'https://[fe80::a%25en1]/foo/bar', + 'expandedUri' => 'https://[fe80:0000:0000:0000:0000:0000:0000:000a%25en1]/foo/bar', + ]; + + yield 'IPv6 gets compressed if needed' => [ + 'inputUri' => 'https://[0000:0000:0000:0000:0000:0000:0000:0001]/foo/bar', + 'compressedUri' => 'https://[::1]/foo/bar', + 'expandedUri' => 'https://[0000:0000:0000:0000:0000:0000:0000:0001]/foo/bar', + ]; + } }