-
-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Improve internal codebase * Deprecate usage of PSR-7 in API * Adding IPv6 Converter and usage in uri-interfaces and uri-components
- Loading branch information
Showing
6 changed files
with
253 additions
and
16 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace League\Uri\IPv6; | ||
|
||
use Stringable; | ||
use ValueError; | ||
use const FILTER_FLAG_IPV6; | ||
use const FILTER_VALIDATE_IP; | ||
|
||
use function filter_var; | ||
use function inet_pton; | ||
use function implode; | ||
use function str_split; | ||
use function strtolower; | ||
use function unpack; | ||
|
||
final class Converter | ||
{ | ||
public static function compressIp(string $ipv6): string | ||
{ | ||
if (false === filter_var($ipv6, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) { | ||
throw new ValueError('The submitted IP is not a valid IPv6 address.'); | ||
} | ||
|
||
return (string) inet_ntop((string) inet_pton($ipv6)); | ||
} | ||
|
||
public static function expandIp(string $ipv6): string | ||
{ | ||
if (false === filter_var($ipv6, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) { | ||
throw new ValueError('The submitted IP is not a valid IPv6 address.'); | ||
} | ||
|
||
$hex = (array) unpack("H*hex", (string) inet_pton($ipv6)); | ||
|
||
return implode(':', str_split(strtolower($hex['hex'] ?? ''), 4)); | ||
} | ||
|
||
public static function compress(Stringable|string|null $host): ?string | ||
{ | ||
$components = self::parse($host); | ||
if (null === $components['ipv6']) { | ||
return match ($host) { | ||
null => $host, | ||
default => (string) $host, | ||
}; | ||
} | ||
|
||
$components['ipv6'] = self::compressIp($components['ipv6']); | ||
|
||
return self::build($components); | ||
} | ||
|
||
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, | ||
}; | ||
} | ||
|
||
$components['ipv6'] = self::expandIp($components['ipv6']); | ||
|
||
return self::build($components); | ||
} | ||
|
||
private static function build(array $components): string | ||
{ | ||
$components['ipv6'] ??= null; | ||
$components['zoneIdentifier'] ??= null; | ||
|
||
return '['.$components['ipv6'].match ($components['zoneIdentifier']) { | ||
null => '', | ||
default => '%'.$components['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]; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace League\Uri\IPv6; | ||
|
||
use PHPUnit\Framework\Attributes\DataProvider; | ||
use PHPUnit\Framework\TestCase; | ||
use ValueError; | ||
|
||
final class ConverterTest extends TestCase | ||
{ | ||
#[DataProvider('ipv6NormalizationUriProvider')] | ||
public function testItCanExpandOrCompressTheHost( | ||
string $ipv6, | ||
string $ipv6Compressed, | ||
string $ipv6Expanded, | ||
): void { | ||
|
||
self::assertSame($ipv6Compressed, Converter::compress($ipv6)); | ||
self::assertSame($ipv6Expanded, Converter::expand($ipv6)); | ||
} | ||
|
||
public static function ipv6NormalizationUriProvider(): iterable | ||
{ | ||
yield 'no change happen with a non IP ipv6' => [ | ||
'ipv6' => 'example.com', | ||
'ipv6Compressed' => 'example.com', | ||
'ipv6Expanded' => 'example.com', | ||
]; | ||
|
||
yield 'no change happen with a IPv4 ipv6' => [ | ||
'ipv6' => '127.0.0.1', | ||
'ipv6Compressed' => '127.0.0.1', | ||
'ipv6Expanded' => '127.0.0.1', | ||
]; | ||
|
||
yield 'IPv6 gets expanded if needed' => [ | ||
'ipv6' => '[fe80::a%25en1]', | ||
'ipv6Compressed' => '[fe80::a%25en1]', | ||
'ipv6Expanded' => '[fe80:0000:0000:0000:0000:0000:0000:000a%25en1]', | ||
]; | ||
|
||
yield 'IPv6 gets compressed if needed' => [ | ||
'ipv6' => '[0000:0000:0000:0000:0000:0000:0000:0001]', | ||
'ipv6Compressed' => '[::1]', | ||
'ipv6Expanded' => '[0000:0000:0000:0000:0000:0000:0000:0001]', | ||
]; | ||
} | ||
|
||
#[DataProvider('invalidIpv6')] | ||
public function testItFailsToCompressANonIpv6(string $invalidIp): void | ||
{ | ||
$this->expectException(ValueError::class); | ||
|
||
Converter::compressIp($invalidIp); | ||
} | ||
|
||
#[DataProvider('invalidIpv6')] | ||
public function testItFailsToExpandANonIpv6(string $invalidIp): void | ||
{ | ||
$this->expectException(ValueError::class); | ||
|
||
Converter::expandIp($invalidIp); | ||
} | ||
|
||
public static function invalidIpv6(): iterable | ||
{ | ||
yield 'hostname' => ['invalidIp' => 'example.com']; | ||
|
||
yield 'IPv4' => ['invalidIp' => '127.0.0.2']; | ||
|
||
yield 'ip future' => ['invalidIp' => '[v42.fdfsffd]']; | ||
|
||
yield 'IPv6 with zoneIdentifier' => ['invalidIp' => 'fe80::a%25en1']; | ||
} | ||
} |
Oops, something went wrong.