Skip to content

Commit

Permalink
Improve internal codebase (#138)
Browse files Browse the repository at this point in the history
* Improve internal codebase
* Deprecate usage of PSR-7 in API
* Adding IPv6 Converter and usage in uri-interfaces and uri-components
  • Loading branch information
nyamsprod committed Jun 26, 2024
1 parent dd36cad commit c0ec580
Show file tree
Hide file tree
Showing 22 changed files with 482 additions and 137 deletions.
7 changes: 5 additions & 2 deletions components/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
19 changes: 10 additions & 9 deletions components/Components/Authority.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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;
Expand All @@ -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));

Expand All @@ -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();
Expand All @@ -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];

Expand All @@ -104,7 +105,7 @@ public function value(): ?string
}

private static function getAuthorityValue(
UserInfoInterface $userInfo,
#[SensitiveParameter] UserInfoInterface $userInfo,
HostInterface $host,
PortInterface $port
): ?string {
Expand Down Expand Up @@ -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);

Expand All @@ -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);
}
Expand All @@ -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);
}
Expand Down Expand Up @@ -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);
}
Expand Down
1 change: 1 addition & 0 deletions components/Components/Host.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
2 changes: 0 additions & 2 deletions components/Components/URLSearchParams.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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(),
};
Expand Down
35 changes: 25 additions & 10 deletions components/Components/UserInfo.php
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -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()),
Expand All @@ -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];

Expand All @@ -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();
Expand All @@ -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(),
};
}

Expand All @@ -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}
*/
Expand Down Expand Up @@ -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);
}
Expand All @@ -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);
}
Expand All @@ -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);
}
Expand Down
21 changes: 21 additions & 0 deletions components/Modifier.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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) {
Expand Down Expand Up @@ -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.
*
Expand Down
39 changes: 39 additions & 0 deletions components/ModifierTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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',
];
}
}
1 change: 1 addition & 0 deletions docs/_data/menu.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
30 changes: 30 additions & 0 deletions docs/components/7.0/modifiers.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
<?php

use League\Uri\Modifier;

$uri = 'http://[1050:0000:0000:0000:0005:0000:300c:326b]/path/to/the/sky.php';
echo Modifier::from($uri)->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
<?php

use League\Uri\Modifier;

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

### Modifier::removeZoneIdentifier

Removes the host zone identifier if present
Expand Down
10 changes: 6 additions & 4 deletions docs/interfaces/7.0/contracts.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -51,7 +53,7 @@ Delimiter characters are not part of the URI component and **must not** be added
<?php

public UriInterface::withScheme(Stringable|string|null $scheme): self
public UriInterface::withUserInfo(Stringable|string|null $user [, string $password = null]): self
public UriInterface::withUserInfo(Stringable|string|null $user [, Stringable|string|null $password = null]): self
public UriInterface::withHost(Stringable|string|null $host): self
public UriInterface::withPort(?int $port): self
public UriInterface::withPath(Stringable|string $path): self
Expand All @@ -64,8 +66,8 @@ public UriInterface::withFragment(Stringable|string|null $fragment): self
This interface exposes the same methods as `Psr\Http\Message\UriInterface`. But, differs on the following keys:

- This interface does not require the `http` and `https` schemes to be supported.
- Setter and Getter component methods, with the exception of the path component, accept and can return the `null` value.
- If no scheme is present, you are not required to fallback to `http` and `https` schemes specific validation rules.
- Setter and Getter component methods, except the path component, accept and can return the `null` value.
- If no scheme is present, the requirement to fall back to `http` and `https` schemes specific validation rules is not enforced.

### League\Uri\Contract\UriComponentInterface

Expand All @@ -78,7 +80,7 @@ The `UriComponentInterface` interface defines the following methods to access th
~~~php
<?php
public UriComponentInterface::value(): ?string
public UriComponentInterface::toString(): ?string
public UriComponentInterface::toString(): string
public UriComponentInterface::getUriComponent(): ?string
public UriComponentInterface::jsonSerialize(): ?string
public UriComponentInterface::__toString(): string
Expand Down
Loading

0 comments on commit c0ec580

Please sign in to comment.