| 1 |
<?php |
| 2 |
|
| 3 |
declare (strict_types=1); |
| 4 |
namespace WCPOS\Vendor\GuzzleHttp\Psr7; |
| 5 |
|
| 6 |
/** |
| 7 |
* @internal |
| 8 |
*/ |
| 9 |
final class UriParser |
| 10 |
{ |
| 11 |
private function __construct() |
| 12 |
{ |
| 13 |
} |
| 14 |
/** |
| 15 |
* UTF-8 aware \parse_url() replacement. |
| 16 |
* |
| 17 |
* The internal function produces broken output for non ASCII domain names |
| 18 |
* (IDN) when used with locales other than "C". |
| 19 |
* |
| 20 |
* On the other hand, cURL understands IDN correctly only when UTF-8 locale |
| 21 |
* is configured ("C.UTF-8", "en_US.UTF-8", etc.). |
| 22 |
* |
| 23 |
* @see https://bugs.php.net/bug.php?id=52923 |
| 24 |
* @see https://www.php.net/manual/en/function.parse-url.php#114817 |
| 25 |
* @see https://curl.se/libcurl/c/CURLOPT_URL.html#ENCODING |
| 26 |
* |
| 27 |
* @return array|false |
| 28 |
*/ |
| 29 |
public static function parse(string $url) |
| 30 |
{ |
| 31 |
if (self::isPathNoSchemeReference($url)) { |
| 32 |
return self::parsePathNoSchemeReference($url); |
| 33 |
} |
| 34 |
// Preserve bracketed IP-literals (IPv6 or IPvFuture) in scheme, userinfo, |
| 35 |
// and network-path authorities before encoding. Userinfo is encoded |
| 36 |
// separately so raw bytes cannot reach parse_url(), which mutates |
| 37 |
// control characters instead of failing. |
| 38 |
$prefix = ''; |
| 39 |
$ipv6Prefix = \preg_match('%\\A((?:[0-9A-Za-z+.-]+:)?//)(?:([^/?#@]*)(@))?(\\[[^\\]\\x00-\\x20\\x7F/?#@]+\\])(.*)\\z%s', $url, $matches); |
| 40 |
if ($ipv6Prefix === \false) { |
| 41 |
return \false; |
| 42 |
} |
| 43 |
if ($ipv6Prefix === 1) { |
| 44 |
/** @var array{0:string, 1:string, 2:string, 3:string, 4:string, 5:string} $matches */ |
| 45 |
$suffix = $matches[5]; |
| 46 |
// After the bracketed host only an optional numeric port and/or a |
| 47 |
// path, query, or fragment may follow. Anything else (for example |
| 48 |
// `:80@evil` or `:80x`) would let parse_url() reinterpret a |
| 49 |
// different host. |
| 50 |
if (\preg_match('%\\A(?::[0-9]*)?(?:[/?#].*)?\\z%s', $suffix) !== 1) { |
| 51 |
return \false; |
| 52 |
} |
| 53 |
// RFC 3986 IP-literals contain no percent-encoding, so reject any |
| 54 |
// "%" in the bracketed host rather than letting the urldecode() |
| 55 |
// below turn an encoded octet into a different literal. This keeps |
| 56 |
// parsing aligned with withHost()/Rfc3986::isValidHost(). |
| 57 |
if (\str_contains($matches[4], '%')) { |
| 58 |
return \false; |
| 59 |
} |
| 60 |
$prefix = $matches[1]; |
| 61 |
if ($matches[3] === '@') { |
| 62 |
/** @var string|null */ |
| 63 |
$encodedUserInfo = \preg_replace_callback('%[^:/@?&=#]+%usD', static function (array $matches) : string { |
| 64 |
return \urlencode($matches[0]); |
| 65 |
}, $matches[2]); |
| 66 |
if ($encodedUserInfo === null) { |
| 67 |
return \false; |
| 68 |
} |
| 69 |
$prefix .= $encodedUserInfo . '@'; |
| 70 |
} |
| 71 |
$prefix .= $matches[4]; |
| 72 |
$url = $suffix; |
| 73 |
} |
| 74 |
/** @var string|null */ |
| 75 |
$encodedUrl = \preg_replace_callback('%[^:/@?&=#]+%usD', static function (array $matches) : string { |
| 76 |
return \urlencode($matches[0]); |
| 77 |
}, $url); |
| 78 |
if ($encodedUrl === null) { |
| 79 |
return \false; |
| 80 |
} |
| 81 |
$result = \parse_url($prefix . $encodedUrl); |
| 82 |
if ($result === \false) { |
| 83 |
return \false; |
| 84 |
} |
| 85 |
return \array_map('urldecode', $result); |
| 86 |
} |
| 87 |
private static function isPathNoSchemeReference(string $url) : bool |
| 88 |
{ |
| 89 |
if ($url === '' || \str_starts_with($url, '/') || \str_starts_with($url, '?') || \str_starts_with($url, '#')) { |
| 90 |
return \false; |
| 91 |
} |
| 92 |
$firstSegment = \substr($url, 0, \strcspn($url, '/?#')); |
| 93 |
return !\str_contains($firstSegment, ':'); |
| 94 |
} |
| 95 |
/** |
| 96 |
* @return array{path: string, query?: string, fragment?: string} |
| 97 |
*/ |
| 98 |
private static function parsePathNoSchemeReference(string $url) : array |
| 99 |
{ |
| 100 |
$parts = []; |
| 101 |
if (\false !== ($fragmentPosition = \strpos($url, '#'))) { |
| 102 |
$parts['fragment'] = \substr($url, $fragmentPosition + 1); |
| 103 |
$url = \substr($url, 0, $fragmentPosition); |
| 104 |
} |
| 105 |
if (\false !== ($queryPosition = \strpos($url, '?'))) { |
| 106 |
$parts['query'] = \substr($url, $queryPosition + 1); |
| 107 |
$url = \substr($url, 0, $queryPosition); |
| 108 |
} |
| 109 |
$parts['path'] = $url; |
| 110 |
return $parts; |
| 111 |
} |
| 112 |
} |
| 113 |
|