Skip to content

Commit

Permalink
Adding Uri::fromData named constructor
Browse files Browse the repository at this point in the history
  • Loading branch information
nyamsprod committed Sep 29, 2023
1 parent bae16ca commit 3e30cea
Show file tree
Hide file tree
Showing 4 changed files with 135 additions and 7 deletions.
7 changes: 7 additions & 0 deletions docs/uri/7.0/rfc3986.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,9 @@ echo Uri::fromTemplate($template, $variables)->toString();
$uri = Uri::fromFileContents('path/to/my/png/image.png');
echo $uri; //returns 'data:image/png;charset=binary;base64,...'
//where '...' represent the base64 representation of the file

$uri = Uri::fromData('Héllo World!', 'text/plain', 'charset=utf8');
echo $uri; // returns data:text/plain;charset=utf8,H%C3%A9llo%20World%21
~~~

The `fromBaseUri` method resolves URI using the same logic behind URL construction
Expand All @@ -86,6 +89,10 @@ named constructor generates a [Data URI](https://datatracker.ietf.org/doc/html/r
following its RFC specification. with the provided file location, the method will
base64 encode the content of the file and return the generated URI.

The `fromData`
named constructor generates a [Data URI](https://datatracker.ietf.org/doc/html/rfc2397)
following its RFC specification. with the provided data and an optional mimetype and parameters.

Last but not least, you can easily translate Windows and Unix paths to URI using the two
following methods.

Expand Down
2 changes: 1 addition & 1 deletion uri/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ All Notable changes to `League\Uri` will be documented in this file

### Added

- None
- `Uri::fromData`

### Fixed

Expand Down
81 changes: 81 additions & 0 deletions uri/FactoryTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,87 @@ public static function validFilePath(): array
];
}

/** @dataProvider provideValidData */
public function testFromData(string $data, ?string $mimetype, ?string $parameters, string $expected): void
{
$arguments = [$data];
if (null !== $mimetype) {
$arguments[] = $mimetype;
}

if (null !== $parameters) {
$arguments[] = $parameters;
}

self::assertSame($expected, Uri::fromData(...$arguments)->toString());
}

public static function provideValidData(): iterable
{
yield 'empty data' => [
'data' => '',
'mimetype' => null,
'parameters' => null,
'expected' => 'data:text/plain;charset=us-ascii,',
];

yield 'simple string' => [
'data' => 'Hello World!',
'mimetype' => null,
'parameters' => null,
'expected' => 'data:text/plain;charset=us-ascii,Hello%20World%21',
];

yield 'changing the mimetype' => [
'data' => 'Hello World!',
'mimetype' => 'text/no-plain',
'parameters' => null,
'expected' => 'data:text/no-plain;charset=us-ascii,Hello%20World%21',
];

yield 'empty parameters' => [
'data' => 'Hello World!',
'mimetype' => null,
'parameters' => '',
'expected' => 'data:text/plain;charset=us-ascii,Hello%20World%21',
];

yield 'changing the parameters' => [
'data' => 'Hello World!',
'mimetype' => 'text/no-plain',
'parameters' => 'foo=bar',
'expected' => 'data:text/no-plain;foo=bar,Hello%20World%21',
];

yield 'the parameters can start with the ; character' => [
'data' => 'Hello World!',
'mimetype' => 'text/no-plain',
'parameters' => ';foo=bar',
'expected' => 'data:text/no-plain;foo=bar,Hello%20World%21',
];
}

public function testFromDataFailsWithInvalidMimeType(): void
{
$this->expectException(SyntaxError::class);

Uri::fromData('Hello World!', 'text\plain');
}

public function testFromDataFailsWithReservedParameterName(): void
{
$this->expectException(SyntaxError::class);

Uri::fromData('Hello World!', 'text/plain', 'base64=foobar');
}

public function testFromDataFailsWithMalformedParameters(): void
{
$this->expectException(SyntaxError::class);

Uri::fromData('Hello World!', 'text/plain', 'foobar');
}

/**
* @dataProvider unixpathProvider
*/
Expand Down
52 changes: 46 additions & 6 deletions uri/Uri.php
Original file line number Diff line number Diff line change
Expand Up @@ -506,6 +506,46 @@ public static function fromFileContents(Stringable|string $path, $context = null
]);
}

/**
* Create a new instance from a data string.
*
* @throws SyntaxError If the parameter syntax is invalid
*/
public static function fromData(string $data, string $mimetype = '', string $parameters = ''): self
{
static $regexpMimetype = ',^\w+/[-.\w]+(?:\+[-.\w]+)?$,';

$mimetype = match (true) {
'' == $mimetype => 'text/plain',
1 === preg_match($regexpMimetype, $mimetype) => $mimetype,
default => throw new SyntaxError('Invalid mimeType, `'.$mimetype.'`.'),
};

if ('' != $parameters) {
if (str_starts_with($parameters, ';')) {
$parameters = substr($parameters, 1);
}

$validateParameter = function (string $parameter): bool {
$properties = explode('=', $parameter);

return 2 != count($properties) || 'base64' === strtolower($properties[0]);
};

$params = array_filter(explode(';', $parameters));
if ([] !== array_filter($params, $validateParameter(...))) {
throw new SyntaxError(sprintf('Invalid mediatype parameters, `%s`.', $parameters));
}

$parameters = ';'.$parameters;
}

return self::fromComponents([
'scheme' => 'data',
'path' => self::formatDataPath($mimetype.$parameters.','.rawurlencode($data)),
]);
}

/**
* Create a new instance from a Unix path string.
*/
Expand Down Expand Up @@ -690,7 +730,7 @@ private function setAuthority(): ?string
private function formatPath(string $path): string
{
return match (true) {
'data' === $this->scheme => Encoder::encodePath($this->formatDataPath($path)),
'data' === $this->scheme => Encoder::encodePath(self::formatDataPath($path)),
'file' === $this->scheme => $this->formatFilePath(Encoder::encodePath($path)),
default => Encoder::encodePath($path),
};
Expand All @@ -703,7 +743,7 @@ private function formatPath(string $path): string
*
* @throws SyntaxError If the path is not compliant with RFC2397
*/
private function formatDataPath(string $path): string
private static function formatDataPath(string $path): string
{
if ('' == $path) {
return 'text/plain;charset=us-ascii,';
Expand All @@ -726,7 +766,7 @@ private function formatDataPath(string $path): string
$parameters = 'charset=us-ascii';
}

$this->assertValidPath($mimetype, $parameters, $data);
self::assertValidPath($mimetype, $parameters, $data);

return $mimetype.';'.$parameters.','.$data;
}
Expand All @@ -738,7 +778,7 @@ private function formatDataPath(string $path): string
*
* @throws SyntaxError If the mediatype or the data are not compliant with the RFC2397
*/
private function assertValidPath(string $mimetype, string $parameters, string $data): void
private static function assertValidPath(string $mimetype, string $parameters, string $data): void
{
if (1 !== preg_match(self::REGEXP_MIMETYPE, $mimetype)) {
throw new SyntaxError('The path mimetype `'.$mimetype.'` is invalid.');
Expand All @@ -749,7 +789,7 @@ private function assertValidPath(string $mimetype, string $parameters, string $d
$parameters = substr($parameters, 0, - strlen($matches[0]));
}

$res = array_filter(array_filter(explode(';', $parameters), $this->validateParameter(...)));
$res = array_filter(array_filter(explode(';', $parameters), self::validateParameter(...)));
if ([] !== $res) {
throw new SyntaxError('The path paremeters `'.$parameters.'` is invalid.');
}
Expand All @@ -767,7 +807,7 @@ private function assertValidPath(string $mimetype, string $parameters, string $d
/**
* Validate mediatype parameter.
*/
private function validateParameter(string $parameter): bool
private static function validateParameter(string $parameter): bool
{
$properties = explode('=', $parameter);

Expand Down

0 comments on commit 3e30cea

Please sign in to comment.