diff options
Diffstat (limited to 'vendor/league/uri-interfaces/Encoder.php')
-rw-r--r-- | vendor/league/uri-interfaces/Encoder.php | 176 |
1 files changed, 176 insertions, 0 deletions
diff --git a/vendor/league/uri-interfaces/Encoder.php b/vendor/league/uri-interfaces/Encoder.php new file mode 100644 index 000000000..4324e03c8 --- /dev/null +++ b/vendor/league/uri-interfaces/Encoder.php @@ -0,0 +1,176 @@ +<?php + +/** + * League.Uri (https://uri.thephpleague.com) + * + * (c) Ignace Nyamagana Butera <nyamsprod@gmail.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace League\Uri; + +use Closure; +use League\Uri\Contracts\UriComponentInterface; +use League\Uri\Exceptions\SyntaxError; +use SensitiveParameter; +use Stringable; + +use function preg_match; +use function preg_replace_callback; +use function rawurldecode; +use function rawurlencode; +use function strtoupper; + +final class Encoder +{ + private const REGEXP_CHARS_INVALID = '/[\x00-\x1f\x7f]/'; + private const REGEXP_CHARS_ENCODED = ',%[A-Fa-f0-9]{2},'; + private const REGEXP_CHARS_PREVENTS_DECODING = ',% + 2[A-F|1-2|4-9]| + 3[0-9|B|D]| + 4[1-9|A-F]| + 5[0-9|A|F]| + 6[1-9|A-F]| + 7[0-9|E] + ,ix'; + private const REGEXP_PART_SUBDELIM = "\!\$&'\(\)\*\+,;\=%"; + private const REGEXP_PART_UNRESERVED = 'A-Za-z\d_\-.~'; + private const REGEXP_PART_ENCODED = '%(?![A-Fa-f\d]{2})'; + + /** + * Encode User. + * + * All generic delimiters MUST be encoded + */ + public static function encodeUser(Stringable|string|null $component): ?string + { + static $pattern = '/[^'.self::REGEXP_PART_UNRESERVED.self::REGEXP_PART_SUBDELIM.']+|'.self::REGEXP_PART_ENCODED.'/'; + + return self::encode($component, $pattern); + } + + /** + * Encode Password. + * + * Generic delimiters ":" MUST NOT be encoded + */ + public static function encodePassword(#[SensitiveParameter] Stringable|string|null $component): ?string + { + static $pattern = '/[^'.self::REGEXP_PART_UNRESERVED.self::REGEXP_PART_SUBDELIM.':]+|'.self::REGEXP_PART_ENCODED.'/'; + + return self::encode($component, $pattern); + } + + /** + * Encode Path. + * + * Generic delimiters ":", "@", and "/" MUST NOT be encoded + */ + public static function encodePath(Stringable|string|null $component): string + { + static $pattern = '/[^'.self::REGEXP_PART_UNRESERVED.self::REGEXP_PART_SUBDELIM.':@\/]+|'.self::REGEXP_PART_ENCODED.'/'; + + return (string) self::encode($component, $pattern); + } + + /** + * Encode Query or Fragment. + * + * Generic delimiters ":", "@", "?", and "/" MUST NOT be encoded + */ + public static function encodeQueryOrFragment(Stringable|string|null $component): ?string + { + static $pattern = '/[^'.self::REGEXP_PART_UNRESERVED.self::REGEXP_PART_SUBDELIM.':@\/?]+|'.self::REGEXP_PART_ENCODED.'/'; + + return self::encode($component, $pattern); + } + + public static function encodeQueryKeyValue(mixed $component): ?string + { + static $pattern = '/[^'.self::REGEXP_PART_UNRESERVED.']+|'.self::REGEXP_PART_ENCODED.'/'; + + $encodeMatches = static fn (array $matches): string => match (1) { + preg_match('/[^'.self::REGEXP_PART_UNRESERVED.']/', rawurldecode($matches[0])) => rawurlencode($matches[0]), + default => $matches[0], + }; + + $component = self::filterComponent($component); + + return match (true) { + !is_scalar($component) => throw new SyntaxError(sprintf('A pair key/value must be a scalar value `%s` given.', gettype($component))), + 1 === preg_match(self::REGEXP_CHARS_INVALID, $component) => rawurlencode($component), + 1 === preg_match($pattern, $component) => (string) preg_replace_callback($pattern, $encodeMatches(...), $component), + default => $component, + }; + } + + /** + * Decodes the URI component without decoding the unreserved characters which are already encoded. + */ + public static function decodePartial(Stringable|string|int|null $component): ?string + { + $decodeMatches = static fn (array $matches): string => match (1) { + preg_match(self::REGEXP_CHARS_PREVENTS_DECODING, $matches[0]) => strtoupper($matches[0]), + default => rawurldecode($matches[0]), + }; + + return self::decode($component, $decodeMatches); + } + + /** + * Decodes all the URI component characters. + */ + public static function decodeAll(Stringable|string|int|null $component): ?string + { + $decodeMatches = static fn (array $matches): string => rawurldecode($matches[0]); + + return self::decode($component, $decodeMatches); + } + + private static function filterComponent(mixed $component): ?string + { + return match (true) { + true === $component => '1', + false === $component => '0', + $component instanceof UriComponentInterface => $component->value(), + $component instanceof Stringable, + is_scalar($component) => (string) $component, + null === $component => null, + default => throw new SyntaxError(sprintf('The component must be a scalar value `%s` given.', gettype($component))), + }; + } + + private static function encode(Stringable|string|int|bool|null $component, string $pattern): ?string + { + $component = self::filterComponent($component); + $encodeMatches = static fn (array $matches): string => match (1) { + preg_match('/[^'.self::REGEXP_PART_UNRESERVED.']/', rawurldecode($matches[0])) => rawurlencode($matches[0]), + default => $matches[0], + }; + + return match (true) { + null === $component, + '' === $component => $component, + default => (string) preg_replace_callback($pattern, $encodeMatches(...), $component), + }; + } + + /** + * Decodes all the URI component characters. + */ + private static function decode(Stringable|string|int|null $component, Closure $decodeMatches): ?string + { + $component = self::filterComponent($component); + + return match (true) { + null === $component => null, + 1 === preg_match(self::REGEXP_CHARS_INVALID, $component) => throw new SyntaxError('Invalid component string: '.$component.'.'), + 1 === preg_match(self::REGEXP_CHARS_ENCODED, $component) => preg_replace_callback(self::REGEXP_CHARS_ENCODED, $decodeMatches(...), $component), + default => $component, + }; + } +} |