Skip to content

Commit

Permalink
Introduce the Encoder class
Browse files Browse the repository at this point in the history
* Introduce the Encoder class to normalize encoding/decoding in all packages
* Introduce the KeyValuePairConverter class to normalize key/value parsing and building
* Rewrite QueryString parser/builder class
* Add new methods to QueryString
  • Loading branch information
nyamsprod committed Aug 19, 2023
1 parent c7a7e9c commit c44615f
Show file tree
Hide file tree
Showing 3 changed files with 33 additions and 113 deletions.
18 changes: 18 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,24 @@

All Notable changes to `League\Uri` will be documented in this file

## Next - TBD

### Added

- None

### Fixed

- Using the `Encoder` class to normalize encoding and decoding in all packages

### Deprecated

- None

### Removed

- None

## [7.0.0](https://github.com/thephpleague/uri/compare/6.8.0...7.0.0) - 2023-08-10

### Added
Expand Down
92 changes: 13 additions & 79 deletions Uri.php
Original file line number Diff line number Diff line change
Expand Up @@ -70,24 +70,6 @@ final class Uri implements UriInterface
*/
private const REGEXP_INVALID_CHARS = '/[\x00-\x1f\x7f]/';

/**
* RFC3986 Sub delimiter characters regular expression pattern.
*
* @link https://tools.ietf.org/html/rfc3986#section-2.2
*
* @var string
*/
private const REGEXP_CHARS_SUBDELIM = "\!\$&'\(\)\*\+,;\=%";

/**
* RFC3986 unreserved characters regular expression pattern.
*
* @link https://tools.ietf.org/html/rfc3986#section-2.3
*
* @var string
*/
private const REGEXP_CHARS_UNRESERVED = 'A-Za-z\d_\-\.~';

/**
* RFC3986 schema regular expression pattern.
*
Expand Down Expand Up @@ -248,8 +230,8 @@ private function __construct(
$this->port = $this->formatPort($port);
$this->authority = $this->setAuthority();
$this->path = $this->formatPath($path);
$this->query = $this->formatQueryAndFragment($query);
$this->fragment = $this->formatQueryAndFragment($fragment);
$this->query = Encoder::encodeQueryOrFragment($query);
$this->fragment = Encoder::encodeQueryOrFragment($fragment);

$this->assertValidState();
}
Expand Down Expand Up @@ -280,27 +262,10 @@ private function formatUserInfo(
?string $user,
#[SensitiveParameter] ?string $password
): ?string {
if (null === $user) {
return null;
}

static $userPattern = '/[^%'.self::REGEXP_CHARS_UNRESERVED.self::REGEXP_CHARS_SUBDELIM.']++|%(?![A-Fa-f\d]{2})/';
$user = preg_replace_callback($userPattern, Uri::urlEncodeMatch(...), $user);
if (null === $password) {
return $user;
}

static $passwordPattern = '/[^%:'.self::REGEXP_CHARS_UNRESERVED.self::REGEXP_CHARS_SUBDELIM.']++|%(?![A-Fa-f\d]{2})/';

return $user.':'.preg_replace_callback($passwordPattern, Uri::urlEncodeMatch(...), $password);
}

/**
* Returns the RFC3986 encoded string matched.
*/
private static function urlEncodeMatch(array $matches): string
{
return rawurlencode($matches[0]);
return match (true) {
null === $password => Encoder::encodeUser($user),
default => Encoder::encodeUser($user).':'.Encoder::encodePassword($password),
};
}

/**
Expand Down Expand Up @@ -720,21 +685,11 @@ private function setAuthority(): ?string
*/
private function formatPath(string $path): string
{
if ('data' === $this->scheme) {
$path = $this->formatDataPath($path);
}

if ('/' !== $path) {
static $pattern = '/[^'.self::REGEXP_CHARS_UNRESERVED.self::REGEXP_CHARS_SUBDELIM.':@\/}{]++|%(?![A-Fa-f\d]{2})/';

$path = (string) preg_replace_callback($pattern, Uri::urlEncodeMatch(...), $path);
}

if ('file' === $this->scheme) {
return $this->formatFilePath($path);
}

return $path;
return match (true) {
'data' === $this->scheme => Encoder::encodePath($this->formatDataPath($path)),
'file' === $this->scheme => $this->formatFilePath(Encoder::encodePath($path)),
default => Encoder::encodePath($path),
};
}

/**
Expand Down Expand Up @@ -827,27 +782,6 @@ private function formatFilePath(string $path): string
);
}

/**
* Format the Query or the Fragment component.
*
* Returns a array containing:
* <ul>
* <li> the formatted component (a string or null)</li>
* <li> a boolean flag telling wether the delimiter is to be added to the component
* when building the URI string representation</li>
* </ul>
*/
private function formatQueryAndFragment(?string $component): ?string
{
if (null === $component || '' === $component) {
return $component;
}

static $pattern = '/[^'.self::REGEXP_CHARS_UNRESERVED.self::REGEXP_CHARS_SUBDELIM.':@\/?]++|%(?![A-Fa-f\d]{2})/';

return preg_replace_callback($pattern, self::urlEncodeMatch(...), $component);
}

/**
* assert the URI internal state is valid.
*
Expand Down Expand Up @@ -1198,7 +1132,7 @@ public function withPath(Stringable|string $path): UriInterface

public function withQuery(Stringable|string|null $query): UriInterface
{
$query = $this->formatQueryAndFragment($this->filterString($query));
$query = Encoder::encodeQueryOrFragment($this->filterString($query));
if ($query === $this->query) {
return $this;
}
Expand All @@ -1212,7 +1146,7 @@ public function withQuery(Stringable|string|null $query): UriInterface

public function withFragment(Stringable|string|null $fragment): UriInterface
{
$fragment = $this->formatQueryAndFragment($this->filterString($fragment));
$fragment = Encoder::encodeQueryOrFragment($this->filterString($fragment));
if ($fragment === $this->fragment) {
return $this;
}
Expand Down
36 changes: 2 additions & 34 deletions UriTemplate/Operator.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

namespace League\Uri\UriTemplate;

use League\Uri\Encoder;
use League\Uri\Exceptions\SyntaxError;
use Stringable;
use function implode;
Expand All @@ -32,24 +33,6 @@
*/
enum Operator: string
{
/**
* RFC3986 Sub delimiter characters regular expression pattern.
*
* @link https://tools.ietf.org/html/rfc3986#section-2.2
*
* @var string
*/
private const REGEXP_CHARS_SUBDELIM = "\!\$&'\(\)\*\+,;\=%";

/**
* RFC3986 unreserved characters regular expression pattern.
*
* @link https://tools.ietf.org/html/rfc3986#section-2.3
*
* @var string
*/
private const REGEXP_CHARS_UNRESERVED = 'A-Za-z\d_\-\.~';

/**
* Expression regular expression pattern.
*
Expand Down Expand Up @@ -104,7 +87,7 @@ public function isNamed(): bool
public function decode(string $var): string
{
return match ($this) {
Operator::ReservedChars, Operator::Fragment => self::encodeQueryOrFragment($var),
Operator::ReservedChars, Operator::Fragment => (string) Encoder::encodeQueryOrFragment($var),
default => rawurlencode($var),
};
}
Expand Down Expand Up @@ -238,19 +221,4 @@ private function replaceList(array $value, VarSpecifier $varSpec): array

return [implode(',', $pairs), $useQuery];
}

/**
* Returns the RFC3986 encoded string matched.
*/
private static function urlEncodeMatch(array $matches): string
{
return rawurlencode($matches[0]);
}

private static function encodeQueryOrFragment(string $uriPart): string
{
static $pattern = '/[^'.self::REGEXP_CHARS_UNRESERVED.self::REGEXP_CHARS_SUBDELIM.':@\/?]++|%(?![A-Fa-f\d]{2})/';

return (string) preg_replace_callback($pattern, self::urlEncodeMatch(...), $uriPart);
}
}

0 comments on commit c44615f

Please sign in to comment.