PluginProbe
WCPOS – Point of Sale (POS) plugin for WooCommerce / 1.10.20
WCPOS – Point of Sale (POS) plugin for WooCommerce v1.10.20
1.10.20 1.10.19 1.10.18 1.10.17 1.10.16 1.10.15 1.10.13 1.10.14 1.10.12 1.10.11 1.10.10 1.10.9 1.10.8 untagged-3d9b7ccddc54df87c672 1.10.7 1.10.6 1.10.5 1.10.3 1.10.4 1.10.2 1.10.1 1.10.0 1.9.17 1.9.15 1.9.16 All 164 releases
woocommerce-pos / vendor_prefixed / guzzlehttp / psr7 / src / UriNormalizer.php

UriNormalizer.php in WCPOS – Point of Sale (POS) plugin for WooCommerce 1.10.20, at vendor_prefixed/guzzlehttp/psr7/src/UriNormalizer.php

379 lines 18.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 declare (strict_types=1);
4 namespace WCPOS\Vendor\GuzzleHttp\Psr7;
5
6 use WCPOS\Vendor\Psr\Http\Message\UriInterface;
7 /**
8 * Provides methods to normalize and compare URIs.
9 *
10 * @author Tobias Schultze
11 *
12 * @see https://datatracker.ietf.org/doc/html/rfc3986#section-6
13 */
14 final class UriNormalizer
15 {
16 /**
17 * Default normalizations which only include the ones that preserve
18 * semantics.
19 */
20 public const PRESERVING_NORMALIZATIONS = self::CAPITALIZE_PERCENT_ENCODING | self::DECODE_UNRESERVED_CHARACTERS | self::CONVERT_EMPTY_PATH | self::REMOVE_DEFAULT_HOST | self::REMOVE_DEFAULT_PORT | self::REMOVE_DOT_SEGMENTS | self::CANONICALIZE_IPV6_HOST;
21 /**
22 * All letters within a percent-encoding triplet (e.g., "%3A") are
23 * case-insensitive, and should be capitalized. This applies to the
24 * userinfo, host, path, query, and fragment components. Bracketed
25 * IP-literal hosts are skipped as a legacy tolerance for nonstandard values
26 * other implementations may carry; zone-identifier text was briefly valid
27 * URI syntax under RFC 6874, which RFC 9844 obsoleted and reverted. The
28 * userinfo and host are only rewritten when the value returned by the
29 * implementation matches the normalized form, and a userinfo with an empty
30 * user segment is never rewritten. No percent-encoding normalization is
31 * applied to a component that contains malformed percent syntax, such as a
32 * `%` not followed by two hexadecimal digits.
33 *
34 * Example: http://example.org/a%c2%b1b → http://example.org/a%C2%B1b
35 */
36 public const CAPITALIZE_PERCENT_ENCODING = 1;
37 /**
38 * Decodes percent-encoded octets of unreserved characters.
39 *
40 * For consistency, percent-encoded octets in the ranges of ALPHA (%41–%5A
41 * and %61–%7A), DIGIT (%30–%39), hyphen (%2D), period (%2E), underscore
42 * (%5F), or tilde (%7E) should not be created by URI producers and, when
43 * found in a URI, should be decoded to their corresponding unreserved
44 * characters by URI normalizers. This applies to the userinfo, host, path,
45 * query, and fragment components. Since the host is case-insensitive and
46 * PSR-7 requires it to be lowercase, octets decoded in the host are
47 * lowercased (e.g., "%41" becomes "a"). Bracketed IP-literal hosts are
48 * skipped as a legacy tolerance for nonstandard values other
49 * implementations may carry; zone-identifier text was briefly valid URI
50 * syntax under RFC 6874, which RFC 9844 obsoleted and reverted. The
51 * userinfo and host are only rewritten when the value returned by the
52 * implementation matches the normalized form, and a userinfo with an empty
53 * user segment is never rewritten. No percent-encoding normalization is
54 * applied to a component that contains malformed percent syntax, such as a
55 * `%` not followed by two hexadecimal digits.
56 *
57 * Example: http://example.org/%7Eusern%61me/ → http://example.org/~username/
58 */
59 public const DECODE_UNRESERVED_CHARACTERS = 2;
60 /**
61 * Converts the empty path to "/" for http and https URIs.
62 *
63 * Example: http://example.org → http://example.org/
64 */
65 public const CONVERT_EMPTY_PATH = 4;
66 /**
67 * Removes the default host of the given URI scheme from the URI.
68 *
69 * Only the "file" scheme defines the default host "localhost". All of
70 * `file:/myfile`, `file:///myfile`, and `file://localhost/myfile` are
71 * equivalent according to RFC 3986. The first format is not accepted by
72 * PHPs stream functions and thus already normalized implicitly to the
73 * second format in the Uri class. See
74 * `GuzzleHttp\Psr7\Uri::composeComponents`.
75 *
76 * When removing the host leaves a URI without an authority whose path
77 * begins with `//`, the path is serialized with a `/.` prefix.
78 *
79 * Example: file://localhost/myfile → file:///myfile
80 * Example: file://localhost//x → file:///.//x
81 */
82 public const REMOVE_DEFAULT_HOST = 8;
83 /**
84 * Removes the default port of the given URI scheme from the URI.
85 *
86 * Example: http://example.org:80/ → http://example.org/
87 */
88 public const REMOVE_DEFAULT_PORT = 16;
89 /**
90 * Removes unnecessary dot-segments.
91 *
92 * Dot-segments in relative-path references are not removed as it would
93 * change the semantics of the URI reference.
94 *
95 * Example: http://example.org/../a/b/../c/./d.html → http://example.org/a/c/d.html
96 */
97 public const REMOVE_DOT_SEGMENTS = 32;
98 /**
99 * Paths which include two or more adjacent slashes are converted to one.
100 *
101 * Webservers usually ignore duplicate slashes and treat those URIs
102 * equivalent. But in theory those URIs do not need to be equivalent. So
103 * this normalization may change the semantics. Encoded slashes (%2F) are
104 * not removed.
105 *
106 * Example: http://example.org//foo///bar.html → http://example.org/foo/bar.html
107 */
108 public const REMOVE_DUPLICATE_SLASHES = 64;
109 /**
110 * Sort query parameters with their values in alphabetical order.
111 *
112 * However, the order of parameters in a URI may be significant (this is not
113 * defined by the standard). So this normalization is not safe and may
114 * change the semantics of the URI.
115 *
116 * Example: ?lang=en&article=fred → ?article=fred&lang=en
117 *
118 * Note: The sorting is neither locale nor Unicode aware (the URI query does
119 * not get decoded at all) as the purpose is to be able to compare URIs in a
120 * reproducible way, not to have the params sorted perfectly.
121 */
122 public const SORT_QUERY_PARAMETERS = 128;
123 /**
124 * Canonicalizes IPv6 hosts to their RFC 5952 form.
125 *
126 * IPv6 addresses allow leading zeros and multiple placements of the `::`
127 * elision, so the same address has many textual spellings. The canonical
128 * form is required for IPv6 literals in URIs by RFC 5952 Section 6 and
129 * never changes what the URI refers to. Native `Uri` instances already
130 * guarantee canonical output; for other implementations, the canonical
131 * host is requested through `withHost()` and the result is kept only when
132 * the returned `getHost()` exactly matches the requested spelling,
133 * otherwise this step leaves the URI unchanged while other selected
134 * normalizations still apply, and setter exceptions propagate.
135 *
136 * Example: http://[::0:0a]/ → http://[::a]/
137 */
138 public const CANONICALIZE_IPV6_HOST = 256;
139 /**
140 * Returns a normalized URI.
141 *
142 * The scheme and host component are already normalized to lowercase per
143 * PSR-7 UriInterface. This method adds additional normalizations that can
144 * be configured with the `$flags` parameter, which is a bitmask of
145 * normalizations to apply.
146 *
147 * PSR-7 UriInterface cannot distinguish between an empty component and a
148 * missing component as `getQuery()`, `getFragment()` etc. always return a
149 * string. This means the URIs `/?#` and `/` are treated equivalent which is
150 * not necessarily true according to RFC 3986. But that difference is highly
151 * uncommon in reality. So this potential normalization is implied in PSR-7
152 * as well.
153 *
154 * A path the URI cannot hold, such as a `//`-leading path without an
155 * authority or a relative-path reference whose first segment contains a
156 * colon, is prefixed with `/.` or `./` respectively instead of throwing, as
157 * `UriResolver::resolve()` does. The percent-encoding normalizations only
158 * do so where they rewrote the path. For example, decoding `a%41:` yields
159 * `./aA:`, since `aA:` would be an absolute URI with the scheme `aa`.
160 *
161 * @param UriInterface $uri The URI to normalize
162 * @param int $flags A bitmask of normalizations to apply, see constants
163 *
164 * @see https://datatracker.ietf.org/doc/html/rfc3986#section-6.2
165 */
166 public static function normalize(UriInterface $uri, int $flags = self::PRESERVING_NORMALIZATIONS) : UriInterface
167 {
168 if ($flags & self::CAPITALIZE_PERCENT_ENCODING) {
169 $uri = self::capitalizePercentEncoding($uri);
170 }
171 if ($flags & self::DECODE_UNRESERVED_CHARACTERS) {
172 $uri = self::decodeUnreservedCharacters($uri);
173 }
174 if ($flags & self::CONVERT_EMPTY_PATH && $uri->getPath() === '' && ($uri->getScheme() === 'http' || $uri->getScheme() === 'https')) {
175 $uri = $uri->withPath('/');
176 }
177 if ($flags & self::REMOVE_DEFAULT_HOST && $uri->getScheme() === 'file' && $uri->getHost() === 'localhost') {
178 if ($uri->getUserInfo() === '' && $uri->getPort() === null) {
179 $path = Uri::rawPath($uri);
180 if (\str_starts_with($path, '//')) {
181 // "/." keeps a "//" path unambiguous once the authority is gone
182 $uri = $uri->withPath('/.' . $path);
183 }
184 }
185 $uri = $uri->withHost('');
186 }
187 if ($flags & self::REMOVE_DEFAULT_PORT && $uri->getPort() !== null && Uri::isDefaultPort($uri)) {
188 $uri = $uri->withPort(null);
189 }
190 $removeDotSegments = $flags & self::REMOVE_DOT_SEGMENTS && !Uri::isRelativePathReference($uri);
191 if ($removeDotSegments || $flags & self::REMOVE_DUPLICATE_SLASHES) {
192 $path = Uri::rawPath($uri);
193 if ($removeDotSegments) {
194 $path = UriResolver::removeDotSegments($path);
195 }
196 if ($flags & self::REMOVE_DUPLICATE_SLASHES) {
197 $path = \preg_replace('#//++#', '/', $path);
198 if ($path === null) {
199 throw new \RuntimeException('Unable to remove duplicate slashes from URI path: ' . \preg_last_error_msg());
200 }
201 }
202 $uri = $uri->withPath(UriResolver::guardedPath($uri, $path));
203 }
204 if ($flags & self::SORT_QUERY_PARAMETERS && $uri->getQuery() !== '') {
205 $queryKeyValues = \explode('&', $uri->getQuery());
206 \sort($queryKeyValues);
207 $uri = $uri->withQuery(\implode('&', $queryKeyValues));
208 }
209 if ($flags & self::CANONICALIZE_IPV6_HOST) {
210 $uri = self::canonicalizeIpv6Host($uri);
211 }
212 return $uri;
213 }
214 /**
215 * Whether two URIs can be considered equivalent.
216 *
217 * Both URIs are normalized automatically before comparison with the given
218 * `$normalizations` bitmask. The method also accepts relative URI
219 * references and returns true when they are equivalent. This of course
220 * assumes they will be resolved against the same base URI. If this is not
221 * the case, determination of equivalence or difference of relative
222 * references does not mean anything.
223 *
224 * @param UriInterface $uri1 An URI to compare
225 * @param UriInterface $uri2 An URI to compare
226 * @param int $normalizations A bitmask of normalizations to apply, see constants
227 *
228 * @see https://datatracker.ietf.org/doc/html/rfc3986#section-6.1
229 */
230 public static function isEquivalent(UriInterface $uri1, UriInterface $uri2, int $normalizations = self::PRESERVING_NORMALIZATIONS) : bool
231 {
232 return (string) self::normalize($uri1, $normalizations) === (string) self::normalize($uri2, $normalizations);
233 }
234 private static function capitalizePercentEncoding(UriInterface $uri) : UriInterface
235 {
236 $regex = '/(?:%' . Rfc3986::HEX_OCTET . ')++/';
237 $callback = function (array $match) : string {
238 return Utils::asciiToUpper($match[0]);
239 };
240 $uri = self::withNormalizedUserInfo($uri, $regex, $callback);
241 $uri = self::withNormalizedHost($uri, $regex, $callback);
242 return self::withGuardedPath($uri, self::normalizePercentEncodingInComponent(Uri::rawPath($uri), $regex, $callback))->withQuery(self::normalizePercentEncodingInComponent($uri->getQuery(), $regex, $callback))->withFragment(self::normalizePercentEncodingInComponent($uri->getFragment(), $regex, $callback));
243 }
244 private static function decodeUnreservedCharacters(UriInterface $uri) : UriInterface
245 {
246 $regex = '/%(?:2D|2E|5F|7E|3[0-9]|[46][1-9A-F]|[57][0-9A])/i';
247 $callback = function (array $match) : string {
248 return \rawurldecode($match[0]);
249 };
250 // The host is case-insensitive and PSR-7 requires it to be lowercase,
251 // so decoded ALPHA octets (e.g. "%41") must land lowercase even for
252 // implementations whose withHost() does not normalize the case.
253 $hostCallback = function (array $match) : string {
254 return Utils::asciiToLower(\rawurldecode($match[0]));
255 };
256 $uri = self::withNormalizedUserInfo($uri, $regex, $callback);
257 $uri = self::withNormalizedHost($uri, $regex, $hostCallback);
258 return self::withGuardedPath($uri, self::normalizePercentEncodingInComponent(Uri::rawPath($uri), $regex, $callback))->withQuery(self::normalizePercentEncodingInComponent($uri->getQuery(), $regex, $callback))->withFragment(self::normalizePercentEncodingInComponent($uri->getFragment(), $regex, $callback));
259 }
260 /**
261 * Writes the given path only when it differs from the current one, guarded
262 * so the write cannot throw.
263 */
264 private static function withGuardedPath(UriInterface $uri, string $path) : UriInterface
265 {
266 if ($path === Uri::rawPath($uri)) {
267 return $uri;
268 }
269 return $uri->withPath(UriResolver::guardedPath($uri, $path));
270 }
271 /**
272 * @param callable(array): string $callback
273 */
274 private static function withNormalizedUserInfo(UriInterface $uri, string $regex, callable $callback) : UriInterface
275 {
276 $userInfo = $uri->getUserInfo();
277 if (!\str_contains($userInfo, '%')) {
278 return $uri;
279 }
280 $normalized = self::normalizePercentEncodingInComponent($userInfo, $regex, $callback);
281 if ($normalized === $userInfo) {
282 return $uri;
283 }
284 // Normalization cannot create a colon: decoding is confined to
285 // unreserved characters and capitalization keeps octets encoded. So
286 // splitting on the first colon preserves the user/password boundary.
287 $parts = \explode(':', $normalized, 2);
288 // PSR-7 defines withUserInfo('') as removing the userinfo, so a
289 // userinfo with an empty user segment (e.g. ":pass") cannot be
290 // expressed through the setter and is preserved as-is instead.
291 if ($parts[0] === '') {
292 return $uri;
293 }
294 $candidate = $uri->withUserInfo($parts[0], $parts[1] ?? null);
295 // Normalization must never lose or corrupt information, so verify the
296 // representation the setter returned and leave the component untouched
297 // when the implementation cannot represent the normalized form.
298 if ($candidate->getUserInfo() !== $normalized) {
299 return $uri;
300 }
301 return $candidate;
302 }
303 /**
304 * @param callable(array): string $callback
305 */
306 private static function withNormalizedHost(UriInterface $uri, string $regex, callable $callback) : UriInterface
307 {
308 $host = $uri->getHost();
309 // Bracketed IP-literal hosts are skipped as a legacy tolerance for
310 // nonstandard values other implementations may carry, such as a zone
311 // identifier in "[fe80::1%25eth0]"; that text was briefly valid URI
312 // syntax under RFC 6874, which RFC 9844 obsoleted and reverted.
313 if (\str_starts_with($host, '[') || !\str_contains($host, '%')) {
314 return $uri;
315 }
316 $normalized = self::normalizePercentEncodingInComponent($host, $regex, $callback);
317 if ($normalized === $host) {
318 return $uri;
319 }
320 $candidate = $uri->withHost($normalized);
321 // Normalization must never lose or corrupt information, so verify the
322 // representation the setter returned and leave the component untouched
323 // when the implementation cannot represent the normalized form.
324 if ($candidate->getHost() !== $normalized) {
325 return $uri;
326 }
327 return $candidate;
328 }
329 /**
330 * @param callable(array): string $callback
331 */
332 private static function normalizePercentEncodingInComponent(string $component, string $regex, callable $callback) : string
333 {
334 // Decoding a valid triplet that follows a dangling "%" would complete
335 // the malformed sequence into a new valid triplet ("example%6%31com"
336 // becomes "example%61com"), turning malformed text valid and breaking
337 // idempotence, so a component containing malformed percent syntax is
338 // returned unchanged.
339 $malformed = \preg_match('/%(?!' . Rfc3986::HEX_OCTET . ')/', $component);
340 if ($malformed === \false) {
341 throw new \RuntimeException('Unable to scan URI component percent-encoding: ' . \preg_last_error_msg());
342 }
343 if ($malformed === 1) {
344 return $component;
345 }
346 $normalized = \preg_replace_callback($regex, $callback, $component);
347 if ($normalized === null) {
348 throw new \RuntimeException('Unable to normalize URI component percent-encoding: ' . \preg_last_error_msg());
349 }
350 return $normalized;
351 }
352 private static function canonicalizeIpv6Host(UriInterface $uri) : UriInterface
353 {
354 $host = $uri->getHost();
355 if (!\str_starts_with($host, '[') || !\str_ends_with($host, ']')) {
356 return $uri;
357 }
358 // Foreign UriInterface implementations may carry IPvFuture literals,
359 // IPv6 zone identifiers, uppercase text, or invalid spellings;
360 // tryCanonicalizeIpv6() canonicalizes only what is unambiguously an
361 // IPv6 address and leaves everything else untouched.
362 $canonical = Rfc3986::tryCanonicalizeIpv6(\substr($host, 1, -1));
363 if ($canonical === null || '[' . $canonical . ']' === $host) {
364 return $uri;
365 }
366 $candidate = $uri->withHost('[' . $canonical . ']');
367 // Normalization must never corrupt a component, so keep the original
368 // host when the implementation does not retain the canonical form.
369 if ($candidate->getHost() !== '[' . $canonical . ']') {
370 return $uri;
371 }
372 return $candidate;
373 }
374 private function __construct()
375 {
376 // cannot be instantiated
377 }
378 }
379