diff --git a/BaseUri.php b/BaseUri.php index b98687cc..4b7ab80a 100644 --- a/BaseUri.php +++ b/BaseUri.php @@ -149,6 +149,38 @@ public function windowsPath(): ?string }; } + /** + * Returns a string representation of a File URI according to RFC8089. + * + * The method will return null if the URI scheme is not the `file` scheme + */ + public function toRfc8089(): ?string + { + $path = $this->uri->getPath(); + + return match (true) { + 'file' !== $this->uri->getScheme() => null, + in_array($this->uri->getAuthority(), ['', null, 'localhost'], true) => 'file:'.match (true) { + '' === $path, + '/' === $path[0] => $path, + default => '/'.$path, + }, + default => (string) $this->uri, + }; + } + + /** + * Tells whether the `file` scheme base URI represents a local file. + */ + public function isLocalFile(): bool + { + return match (true) { + 'file' !== $this->uri->getScheme() => false, + in_array($this->uri->getAuthority(), ['', null, 'localhost'], true) => true, + default => false, + }; + } + /** * Tells whether two URI do not share the same origin. */ diff --git a/BaseUriTest.php b/BaseUriTest.php index 22aaee1c..259826a8 100644 --- a/BaseUriTest.php +++ b/BaseUriTest.php @@ -531,7 +531,6 @@ public function testReturnsWindowsPath(?string $expected, string $input): void { self::assertSame($expected, BaseUri::from($input)->windowsPath()); self::assertSame($expected, BaseUri::from(Utils::uriFor($input))->windowsPath()); - } public static function windowLocalPathProvider(): array @@ -575,4 +574,33 @@ public static function windowLocalPathProvider(): array ], ]; } + + /** @dataProvider rfc8089UriProvider */ + public function testReturnsRFC8089UriString(?string $expected, string $input): void + { + self::assertSame($expected, BaseUri::from($input)->toRfc8089()); + self::assertSame($expected, BaseUri::from(Utils::uriFor($input))->toRfc8089()); + } + + public static function rfc8089UriProvider(): iterable + { + return [ + 'localhost' => [ + 'expected' => 'file:/etc/fstab', + 'input' => 'file://localhost/etc/fstab', + ], + 'empty authority' => [ + 'expected' => 'file:/etc/fstab', + 'input' => 'file:///etc/fstab', + ], + 'file with authority' => [ + 'expected' => 'file://yesman/etc/fstab', + 'input' => 'file://yesman/etc/fstab', + ], + 'invalid scheme' => [ + 'expected' => null, + 'input' => 'foobar://yesman/etc/fstab', + ], + ]; + } } diff --git a/CHANGELOG.md b/CHANGELOG.md index 895d525e..0defd223 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,8 +7,10 @@ All Notable changes to `League\Uri` will be documented in this file ### Added - `Uri::fromData` +- `Uri::fromRfc8089` - `BaseUri::unixPath` - `BaseUri::windowsPath` +- `BaseUri::toRfc8089` ### Fixed diff --git a/FactoryTest.php b/FactoryTest.php index 28532c90..cc2c9062 100644 --- a/FactoryTest.php +++ b/FactoryTest.php @@ -221,6 +221,50 @@ public static function windowLocalPathProvider(): array ]; } + /** @dataProvider rfc8089UriProvider */ + public function testCreateFromRfc8089(string $expected, string $uri): void + { + self::assertSame($expected, Uri::fromRfc8089($uri)->toString()); + } + + public static function rfc8089UriProvider(): iterable + { + return [ + 'empty authority' => [ + 'expected' => 'file:///etc/fstab', + 'uri' => 'file:/etc/fstab', + ], + 'localhost' => [ + 'expected' => 'file:///etc/fstab', + 'uri' => 'file://localhost/etc/fstab', + ], + 'file with authority' => [ + 'expected' => 'file://yesman/etc/fstab', + 'uri' => 'file://yesman/etc/fstab', + ], + ]; + } + + /** @dataProvider invalidRfc8089UriProvider */ + public function testIfFailsToGenerateAnUriFromRfc8089(string $invalidUri): void + { + $this->expectException(SyntaxError::class); + + Uri::fromRfc8089($invalidUri); + } + + public static function invalidRfc8089UriProvider(): iterable + { + return [ + 'unsupported scheme' => [ + 'invalidUri' => 'http://www.example.com', + ], + 'missing scheme' => [ + 'invalidUri' => '//localhost/etc/fstab', + ], + ]; + } + public function testCreateFromUri(): void { $expected = 'https://login:pass@secure.example.com:443/test/query.php?kingkong=toto#doc3'; diff --git a/Uri.php b/Uri.php index 67ba95e3..69fe0dce 100644 --- a/Uri.php +++ b/Uri.php @@ -588,6 +588,23 @@ public static function fromWindowsPath(Stringable|string $path): self return Uri::fromComponents(['host' => $host, 'path' => '/'.$path, 'scheme' => 'file']); } + /** + * Creates a new instance from a RFC8089 compatible URI. + * + * @see https://datatracker.ietf.org/doc/html/rfc8089 + */ + public static function fromRfc8089(Stringable|string $uri): UriInterface + { + $fileUri = self::new((string) preg_replace(',^(file:/)([^/].*)$,i', 'file:///$2', (string) $uri)); + $scheme = $fileUri->getScheme(); + + return match (true) { + 'file' !== $scheme => throw new SyntaxError('As per RFC8089, the URI scheme must be `file`.'), + 'localhost' === $fileUri->getAuthority() => $fileUri->withHost(''), + default => $fileUri, + }; + } + /** * Create a new instance from the environment. */