PluginProbe
Yoast SEO – Advanced SEO with real-time guidance and built-in AI / 28.5
Yoast SEO – Advanced SEO with real-time guidance and built-in AI v28.5
28.5 28.4 28.3 28.2 28.1 28.0 27.9 27.8 27.7 27.6 27.5 trunk 18.0 18.1 18.2 18.3 18.4 18.4.1 18.5 18.5.1 18.6 18.7 18.8 18.9 19.0 All 129 releases
wordpress-seo / vendor_prefixed / guzzlehttp / psr7 / src / Uri.php

Uri.php in Yoast SEO – Advanced SEO with real-time guidance and built-in AI 28.5, at vendor_prefixed/guzzlehttp/psr7/src/Uri.php

671 lines 26.6 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 YoastSEO_Vendor\GuzzleHttp\Psr7;
5
6 use YoastSEO_Vendor\GuzzleHttp\Psr7\Exception\MalformedUriException;
7 use YoastSEO_Vendor\Psr\Http\Message\UriInterface;
8 /**
9 * PSR-7 URI implementation.
10 *
11 * @author Michael Dowling
12 * @author Tobias Schultze
13 * @author Matthew Weier O'Phinney
14 */
15 class Uri implements \YoastSEO_Vendor\Psr\Http\Message\UriInterface, \JsonSerializable
16 {
17 /**
18 * Absolute http and https URIs require a host per RFC 7230 Section 2.7
19 * but in generic URIs the host can be empty. So for http(s) URIs
20 * we apply this default host when no host is given yet to form a
21 * valid URI.
22 */
23 private const HTTP_DEFAULT_HOST = 'localhost';
24 private const DEFAULT_PORTS = ['http' => 80, 'https' => 443, 'ftp' => 21, 'gopher' => 70, 'nntp' => 119, 'news' => 119, 'telnet' => 23, 'tn3270' => 23, 'imap' => 143, 'pop' => 110, 'ldap' => 389];
25 private const QUERY_SEPARATORS_REPLACEMENT = ['=' => '%3D', '&' => '%26', '+' => '%2B'];
26 /** @var string Uri scheme. */
27 private $scheme = '';
28 /** @var string Uri user info. */
29 private $userInfo = '';
30 /** @var string Uri host. */
31 private $host = '';
32 /** @var int|null Uri port. */
33 private $port;
34 /** @var string Uri path. */
35 private $path = '';
36 /** @var string Uri query string. */
37 private $query = '';
38 /** @var string Uri fragment. */
39 private $fragment = '';
40 public function __construct(string $uri = '')
41 {
42 if ($uri !== '') {
43 $parts = self::parse($uri);
44 if ($parts === \false) {
45 throw new \YoastSEO_Vendor\GuzzleHttp\Psr7\Exception\MalformedUriException("Unable to parse URI: {$uri}");
46 }
47 try {
48 $this->applyParts($parts);
49 } catch (\YoastSEO_Vendor\GuzzleHttp\Psr7\Exception\MalformedUriException $e) {
50 throw $e;
51 } catch (\InvalidArgumentException $e) {
52 throw new \YoastSEO_Vendor\GuzzleHttp\Psr7\Exception\MalformedUriException($e->getMessage(), 0, $e);
53 }
54 }
55 }
56 /**
57 * UTF-8 aware \parse_url() replacement.
58 *
59 * The internal function produces broken output for non ASCII domain names
60 * (IDN) when used with locales other than "C".
61 *
62 * On the other hand, cURL understands IDN correctly only when UTF-8 locale
63 * is configured ("C.UTF-8", "en_US.UTF-8", etc.).
64 *
65 * @see https://bugs.php.net/bug.php?id=52923
66 * @see https://www.php.net/manual/en/function.parse-url.php#114817
67 * @see https://curl.haxx.se/libcurl/c/CURLOPT_URL.html#ENCODING
68 *
69 * @return array|false
70 */
71 private static function parse(string $url)
72 {
73 if (self::isPathNoSchemeReference($url)) {
74 return self::parsePathNoSchemeReference($url);
75 }
76 // Preserve bracketed IPv6 literals before encoding, including dotted IPv4
77 // tails. DEL (\x7F) is excluded so a raw-DEL host falls through to the
78 // general path and is rejected rather than silently mutated by parse_url().
79 $prefix = '';
80 $ipv6Prefix = \preg_match('%\\A([0-9A-Za-z+.-]+://\\[[^\\]\\x00-\\x20\\x7F/?#@]+\\])(.*)\\z%s', $url, $matches);
81 if ($ipv6Prefix === \false) {
82 return \false;
83 }
84 if ($ipv6Prefix === 1) {
85 /** @var array{0:string, 1:string, 2:string} $matches */
86 $suffix = $matches[2];
87 // After the bracketed host only an optional numeric port and/or a
88 // path, query, or fragment may follow. Anything else (for example
89 // `:80@evil` or `:80x`) would let parse_url() reinterpret a
90 // different host.
91 if (\preg_match('%\\A(?::[0-9]*)?(?:[/?#].*)?\\z%s', $suffix) !== 1) {
92 return \false;
93 }
94 $prefix = $matches[1];
95 $url = $suffix;
96 }
97 /** @var string|null */
98 $encodedUrl = \preg_replace_callback('%[^:/@?&=#]+%usD', static function ($matches) {
99 return \urlencode($matches[0]);
100 }, $url);
101 if ($encodedUrl === null) {
102 return \false;
103 }
104 $result = \parse_url($prefix . $encodedUrl);
105 if ($result === \false) {
106 return \false;
107 }
108 return \array_map('urldecode', $result);
109 }
110 private static function isPathNoSchemeReference(string $url) : bool
111 {
112 if ($url === '' || $url[0] === '/' || $url[0] === '?' || $url[0] === '#') {
113 return \false;
114 }
115 $firstSegment = \substr($url, 0, \strcspn($url, '/?#'));
116 return \strpos($firstSegment, ':') === \false;
117 }
118 /**
119 * @return array{path: string, query?: string, fragment?: string}
120 */
121 private static function parsePathNoSchemeReference(string $url) : array
122 {
123 $parts = [];
124 if (\false !== ($fragmentPosition = \strpos($url, '#'))) {
125 $parts['fragment'] = \substr($url, $fragmentPosition + 1);
126 $url = \substr($url, 0, $fragmentPosition);
127 }
128 if (\false !== ($queryPosition = \strpos($url, '?'))) {
129 $parts['query'] = \substr($url, $queryPosition + 1);
130 $url = \substr($url, 0, $queryPosition);
131 }
132 $parts['path'] = $url;
133 return $parts;
134 }
135 public function __toString() : string
136 {
137 return self::composeComponents($this->scheme, $this->getAuthority(), $this->path, $this->query, $this->fragment);
138 }
139 /**
140 * Composes a URI reference string from its various components.
141 *
142 * Usually this method does not need to be called manually but instead is used indirectly via
143 * `Psr\Http\Message\UriInterface::__toString`.
144 *
145 * PSR-7 UriInterface treats an empty component the same as a missing component as
146 * getQuery(), getFragment() etc. always return a string. This explains the slight
147 * difference to RFC 3986 Section 5.3.
148 *
149 * Another adjustment is that the authority separator is added even when the authority is missing/empty
150 * for the "file" scheme. This is because PHP stream functions like `file_get_contents` only work with
151 * `file:///myfile` but not with `file:/myfile` although they are equivalent according to RFC 3986. But
152 * `file:///` is the more common syntax for the file scheme anyway (Chrome for example redirects to
153 * that format).
154 *
155 * @see https://datatracker.ietf.org/doc/html/rfc3986#section-5.3
156 */
157 public static function composeComponents(?string $scheme, ?string $authority, string $path, ?string $query, ?string $fragment) : string
158 {
159 $uri = '';
160 // weak type checks to also accept null until we can add scalar type hints
161 if ($scheme != '') {
162 $uri .= $scheme . ':';
163 }
164 if ($authority != '' || $scheme === 'file') {
165 $uri .= '//' . $authority;
166 }
167 if ($authority != '' && $path != '' && $path[0] != '/') {
168 $path = '/' . $path;
169 }
170 $uri .= $path;
171 if ($query != '') {
172 $uri .= '?' . $query;
173 }
174 if ($fragment != '') {
175 $uri .= '#' . $fragment;
176 }
177 return $uri;
178 }
179 /**
180 * Whether the URI has the default port of the current scheme.
181 *
182 * `Psr\Http\Message\UriInterface::getPort` may return null or the standard port. This method can be used
183 * independently of the implementation.
184 */
185 public static function isDefaultPort(\YoastSEO_Vendor\Psr\Http\Message\UriInterface $uri) : bool
186 {
187 return $uri->getPort() === null || isset(self::DEFAULT_PORTS[$uri->getScheme()]) && $uri->getPort() === self::DEFAULT_PORTS[$uri->getScheme()];
188 }
189 /**
190 * Whether the URI is absolute, i.e. it has a scheme.
191 *
192 * An instance of UriInterface can either be an absolute URI or a relative reference. This method returns true
193 * if it is the former. An absolute URI has a scheme. A relative reference is used to express a URI relative
194 * to another URI, the base URI. Relative references can be divided into several forms:
195 * - network-path references, e.g. '//example.com/path'
196 * - absolute-path references, e.g. '/path'
197 * - relative-path references, e.g. 'subpath'
198 *
199 * @see Uri::isNetworkPathReference
200 * @see Uri::isAbsolutePathReference
201 * @see Uri::isRelativePathReference
202 * @see https://datatracker.ietf.org/doc/html/rfc3986#section-4
203 */
204 public static function isAbsolute(\YoastSEO_Vendor\Psr\Http\Message\UriInterface $uri) : bool
205 {
206 return $uri->getScheme() !== '';
207 }
208 /**
209 * Whether the URI is a network-path reference.
210 *
211 * A relative reference that begins with two slash characters is termed an network-path reference.
212 *
213 * @see https://datatracker.ietf.org/doc/html/rfc3986#section-4.2
214 */
215 public static function isNetworkPathReference(\YoastSEO_Vendor\Psr\Http\Message\UriInterface $uri) : bool
216 {
217 return $uri->getScheme() === '' && $uri->getAuthority() !== '';
218 }
219 /**
220 * Whether the URI is a absolute-path reference.
221 *
222 * A relative reference that begins with a single slash character is termed an absolute-path reference.
223 *
224 * @see https://datatracker.ietf.org/doc/html/rfc3986#section-4.2
225 */
226 public static function isAbsolutePathReference(\YoastSEO_Vendor\Psr\Http\Message\UriInterface $uri) : bool
227 {
228 return $uri->getScheme() === '' && $uri->getAuthority() === '' && isset($uri->getPath()[0]) && $uri->getPath()[0] === '/';
229 }
230 /**
231 * Whether the URI is a relative-path reference.
232 *
233 * A relative reference that does not begin with a slash character is termed a relative-path reference.
234 *
235 * @see https://datatracker.ietf.org/doc/html/rfc3986#section-4.2
236 */
237 public static function isRelativePathReference(\YoastSEO_Vendor\Psr\Http\Message\UriInterface $uri) : bool
238 {
239 return $uri->getScheme() === '' && $uri->getAuthority() === '' && (!isset($uri->getPath()[0]) || $uri->getPath()[0] !== '/');
240 }
241 /**
242 * Whether the URI is a same-document reference.
243 *
244 * A same-document reference refers to a URI that is, aside from its fragment
245 * component, identical to the base URI. When no base URI is given, only an empty
246 * URI reference (apart from its fragment) is considered a same-document reference.
247 *
248 * @param UriInterface $uri The URI to check
249 * @param UriInterface|null $base An optional base URI to compare against
250 *
251 * @see https://datatracker.ietf.org/doc/html/rfc3986#section-4.4
252 */
253 public static function isSameDocumentReference(\YoastSEO_Vendor\Psr\Http\Message\UriInterface $uri, ?\YoastSEO_Vendor\Psr\Http\Message\UriInterface $base = null) : bool
254 {
255 if ($base !== null) {
256 $uri = \YoastSEO_Vendor\GuzzleHttp\Psr7\UriResolver::resolve($base, $uri);
257 return $uri->getScheme() === $base->getScheme() && $uri->getAuthority() === $base->getAuthority() && $uri->getPath() === $base->getPath() && $uri->getQuery() === $base->getQuery();
258 }
259 return $uri->getScheme() === '' && $uri->getAuthority() === '' && $uri->getPath() === '' && $uri->getQuery() === '';
260 }
261 /**
262 * Creates a new URI with a specific query string value removed.
263 *
264 * Any existing query string values that exactly match the provided key are
265 * removed.
266 *
267 * @param UriInterface $uri URI to use as a base.
268 * @param string $key Query string key to remove.
269 */
270 public static function withoutQueryValue(\YoastSEO_Vendor\Psr\Http\Message\UriInterface $uri, string $key) : \YoastSEO_Vendor\Psr\Http\Message\UriInterface
271 {
272 $result = self::getFilteredQueryString($uri, [$key]);
273 return $uri->withQuery(\implode('&', $result));
274 }
275 /**
276 * Creates a new URI with a specific query string value.
277 *
278 * Any existing query string values that exactly match the provided key are
279 * removed and replaced with the given key value pair.
280 *
281 * A value of null will set the query string key without a value, e.g. "key"
282 * instead of "key=value".
283 *
284 * @param UriInterface $uri URI to use as a base.
285 * @param string $key Key to set.
286 * @param string|null $value Value to set
287 */
288 public static function withQueryValue(\YoastSEO_Vendor\Psr\Http\Message\UriInterface $uri, string $key, ?string $value) : \YoastSEO_Vendor\Psr\Http\Message\UriInterface
289 {
290 $result = self::getFilteredQueryString($uri, [$key]);
291 $result[] = self::generateQueryString($key, $value);
292 return $uri->withQuery(\implode('&', $result));
293 }
294 /**
295 * Creates a new URI with multiple specific query string values.
296 *
297 * It has the same behavior as withQueryValue() but for an associative array of key => value.
298 *
299 * @param UriInterface $uri URI to use as a base.
300 * @param (string|null)[] $keyValueArray Associative array of key and values
301 */
302 public static function withQueryValues(\YoastSEO_Vendor\Psr\Http\Message\UriInterface $uri, array $keyValueArray) : \YoastSEO_Vendor\Psr\Http\Message\UriInterface
303 {
304 $result = self::getFilteredQueryString($uri, \array_keys($keyValueArray));
305 foreach ($keyValueArray as $key => $value) {
306 $result[] = self::generateQueryString((string) $key, $value !== null ? self::stringifyQueryValue($value) : null);
307 }
308 return $uri->withQuery(\implode('&', $result));
309 }
310 /**
311 * Stringifies a non-null query value, deprecating non-string values that
312 * guzzlehttp/psr7 3.0 will reject. Non-finite floats are normalized to the
313 * strings PHP coerces them to, as implicit coercion of NAN emits a warning
314 * on PHP 8.5.
315 *
316 * @param mixed $value
317 */
318 private static function stringifyQueryValue($value) : string
319 {
320 if (!\is_string($value)) {
321 \YoastSEO_Vendor\trigger_deprecation('guzzlehttp/psr7', '2.12', 'Passing %s to Uri::withQueryValues() is deprecated; cast it to a string. guzzlehttp/psr7 3.0 will only accept string or null query values.', \gettype($value));
322 if (\is_float($value) && !\is_finite($value)) {
323 return \is_nan($value) ? 'NAN' : ($value > 0 ? 'INF' : '-INF');
324 }
325 }
326 return (string) $value;
327 }
328 /**
329 * Creates a URI from a hash of `parse_url` components.
330 *
331 * @see https://www.php.net/manual/en/function.parse-url.php
332 *
333 * @throws MalformedUriException If the components do not form a valid URI.
334 */
335 public static function fromParts(array $parts) : \YoastSEO_Vendor\Psr\Http\Message\UriInterface
336 {
337 $uri = new self();
338 try {
339 $uri->applyParts($parts);
340 $uri->validateState();
341 } catch (\YoastSEO_Vendor\GuzzleHttp\Psr7\Exception\MalformedUriException $e) {
342 throw $e;
343 } catch (\InvalidArgumentException $e) {
344 throw new \YoastSEO_Vendor\GuzzleHttp\Psr7\Exception\MalformedUriException($e->getMessage(), 0, $e);
345 }
346 return $uri;
347 }
348 /**
349 * @throws \InvalidArgumentException If the host is invalid.
350 *
351 * @internal
352 */
353 public static function assertValidHost(string $host) : void
354 {
355 if ($host === '') {
356 return;
357 }
358 // Reject control characters and URI authority delimiters so getHost()
359 // cannot disagree with the on-wire authority.
360 $invalidHost = \preg_match('/[\\x00-\\x20\\x7F\\/\\?#@\\\\]/', $host);
361 if ($invalidHost === \false) {
362 throw new \RuntimeException('Unable to validate URI host: ' . \preg_last_error_msg());
363 }
364 if ($invalidHost === 1) {
365 throw new \InvalidArgumentException(\sprintf('Invalid host: "%s"', $host));
366 }
367 if (\strpos($host, '[') !== \false || \strpos($host, ']') !== \false) {
368 if ($host[0] !== '[' || \substr($host, -1) !== ']') {
369 throw new \InvalidArgumentException(\sprintf('Invalid host: "%s"', $host));
370 }
371 return;
372 }
373 if (\strpos($host, ':') !== \false) {
374 throw new \InvalidArgumentException(\sprintf('Invalid host: "%s"', $host));
375 }
376 }
377 public function getScheme() : string
378 {
379 return $this->scheme;
380 }
381 public function getAuthority() : string
382 {
383 $authority = $this->host;
384 if ($this->userInfo !== '') {
385 $authority = $this->userInfo . '@' . $authority;
386 }
387 if ($this->port !== null) {
388 $authority .= ':' . $this->port;
389 }
390 return $authority;
391 }
392 public function getUserInfo() : string
393 {
394 return $this->userInfo;
395 }
396 public function getHost() : string
397 {
398 return $this->host;
399 }
400 public function getPort() : ?int
401 {
402 return $this->port;
403 }
404 public function getPath() : string
405 {
406 return $this->path;
407 }
408 public function getQuery() : string
409 {
410 return $this->query;
411 }
412 public function getFragment() : string
413 {
414 return $this->fragment;
415 }
416 public function withScheme($scheme) : \YoastSEO_Vendor\Psr\Http\Message\UriInterface
417 {
418 $scheme = $this->filterScheme($scheme);
419 if ($this->scheme === $scheme) {
420 return $this;
421 }
422 $new = clone $this;
423 $new->scheme = $scheme;
424 $new->removeDefaultPort();
425 $new->validateState();
426 return $new;
427 }
428 public function withUserInfo($user, $password = null) : \YoastSEO_Vendor\Psr\Http\Message\UriInterface
429 {
430 $info = $this->filterUserInfoComponent($user);
431 if ($password !== null) {
432 $info .= ':' . $this->filterUserInfoComponent($password);
433 }
434 if ($this->userInfo === $info) {
435 return $this;
436 }
437 $new = clone $this;
438 $new->userInfo = $info;
439 $new->validateState();
440 return $new;
441 }
442 public function withHost($host) : \YoastSEO_Vendor\Psr\Http\Message\UriInterface
443 {
444 $host = $this->filterHost($host);
445 if ($this->host === $host) {
446 return $this;
447 }
448 $new = clone $this;
449 $new->host = $host;
450 $new->validateState();
451 return $new;
452 }
453 public function withPort($port) : \YoastSEO_Vendor\Psr\Http\Message\UriInterface
454 {
455 if ($port !== null && !\is_int($port)) {
456 \YoastSEO_Vendor\trigger_deprecation('guzzlehttp/psr7', '2.11', 'Passing %s to UriInterface::withPort() is deprecated; guzzlehttp/psr7 3.0 requires int|null.', \get_debug_type($port));
457 }
458 $port = $this->filterPort($port);
459 if ($this->port === $port) {
460 return $this;
461 }
462 $new = clone $this;
463 $new->port = $port;
464 $new->removeDefaultPort();
465 $new->validateState();
466 return $new;
467 }
468 public function withPath($path) : \YoastSEO_Vendor\Psr\Http\Message\UriInterface
469 {
470 $path = $this->filterPath($path);
471 if ($this->path === $path) {
472 return $this;
473 }
474 $new = clone $this;
475 $new->path = $path;
476 $new->validateState();
477 return $new;
478 }
479 public function withQuery($query) : \YoastSEO_Vendor\Psr\Http\Message\UriInterface
480 {
481 $query = $this->filterQueryAndFragment($query);
482 if ($this->query === $query) {
483 return $this;
484 }
485 $new = clone $this;
486 $new->query = $query;
487 return $new;
488 }
489 public function withFragment($fragment) : \YoastSEO_Vendor\Psr\Http\Message\UriInterface
490 {
491 $fragment = $this->filterQueryAndFragment($fragment);
492 if ($this->fragment === $fragment) {
493 return $this;
494 }
495 $new = clone $this;
496 $new->fragment = $fragment;
497 return $new;
498 }
499 public function jsonSerialize() : string
500 {
501 return $this->__toString();
502 }
503 /**
504 * Apply parse_url parts to a URI.
505 *
506 * @param array $parts Array of parse_url parts to apply.
507 */
508 private function applyParts(array $parts) : void
509 {
510 $this->scheme = isset($parts['scheme']) ? $this->filterScheme($parts['scheme']) : '';
511 $this->userInfo = isset($parts['user']) ? $this->filterUserInfoComponent($parts['user']) : '';
512 $this->host = isset($parts['host']) ? $this->filterHost($parts['host']) : '';
513 $this->port = isset($parts['port']) ? $this->filterPort($parts['port']) : null;
514 $this->path = isset($parts['path']) ? $this->filterPath($parts['path']) : '';
515 $this->query = isset($parts['query']) ? $this->filterQueryAndFragment($parts['query']) : '';
516 $this->fragment = isset($parts['fragment']) ? $this->filterQueryAndFragment($parts['fragment']) : '';
517 if (isset($parts['pass'])) {
518 $this->userInfo .= ':' . $this->filterUserInfoComponent($parts['pass']);
519 }
520 $this->removeDefaultPort();
521 }
522 /**
523 * @param mixed $scheme
524 *
525 * @throws \InvalidArgumentException If the scheme is invalid.
526 */
527 private function filterScheme($scheme) : string
528 {
529 if (!\is_string($scheme)) {
530 throw new \InvalidArgumentException('Scheme must be a string');
531 }
532 $scheme = \YoastSEO_Vendor\GuzzleHttp\Psr7\Utils::asciiToLower($scheme);
533 if ($scheme !== '' && !\preg_match('/^[a-z][a-z0-9.+-]*$/D', $scheme)) {
534 \YoastSEO_Vendor\trigger_deprecation('guzzlehttp/psr7', '2.11', 'Passing "%s" as a URI scheme is deprecated; guzzlehttp/psr7 3.0 requires URI schemes to match RFC 3986 syntax and begin with a letter.', $scheme);
535 }
536 return $scheme;
537 }
538 /**
539 * @param mixed $component
540 *
541 * @throws \InvalidArgumentException If the user info is invalid.
542 */
543 private function filterUserInfoComponent($component) : string
544 {
545 if (!\is_string($component)) {
546 throw new \InvalidArgumentException('User info must be a string');
547 }
548 return $this->filterComponent('/(?:[^%' . \YoastSEO_Vendor\GuzzleHttp\Psr7\Rfc3986::CHAR_UNRESERVED . \YoastSEO_Vendor\GuzzleHttp\Psr7\Rfc3986::CHAR_SUB_DELIMS . ']+|%(?![A-Fa-f0-9]{2}))/', $component, 'Unable to filter URI user info');
549 }
550 /**
551 * @param mixed $host
552 *
553 * @throws \InvalidArgumentException If the host is invalid.
554 */
555 private function filterHost($host) : string
556 {
557 if (!\is_string($host)) {
558 throw new \InvalidArgumentException('Host must be a string');
559 }
560 $host = \YoastSEO_Vendor\GuzzleHttp\Psr7\Utils::asciiToLower($host);
561 self::assertValidHost($host);
562 return $host;
563 }
564 /**
565 * @param mixed $port
566 *
567 * @throws \InvalidArgumentException If the port is invalid.
568 */
569 private function filterPort($port) : ?int
570 {
571 if ($port === null) {
572 return null;
573 }
574 $port = (int) $port;
575 if (0 > $port || 0xffff < $port) {
576 throw new \InvalidArgumentException(\sprintf('Invalid port: %d. Must be between 0 and 65535', $port));
577 }
578 return $port;
579 }
580 /**
581 * @param (string|int)[] $keys
582 *
583 * @return string[]
584 */
585 private static function getFilteredQueryString(\YoastSEO_Vendor\Psr\Http\Message\UriInterface $uri, array $keys) : array
586 {
587 $current = $uri->getQuery();
588 if ($current === '') {
589 return [];
590 }
591 $decodedKeys = \array_map(function ($k) : string {
592 return \rawurldecode((string) $k);
593 }, $keys);
594 return \array_filter(\explode('&', $current), function ($part) use($decodedKeys) {
595 return !\in_array(\rawurldecode(\explode('=', $part)[0]), $decodedKeys, \true);
596 });
597 }
598 private static function generateQueryString(string $key, ?string $value) : string
599 {
600 // Query string separators ("=", "&") and literal plus signs ("+") within the
601 // key or value need to be encoded
602 // (while preventing double-encoding) before setting the query string. All other
603 // chars that need percent-encoding will be encoded by withQuery().
604 $queryString = \strtr($key, self::QUERY_SEPARATORS_REPLACEMENT);
605 if ($value !== null) {
606 $queryString .= '=' . \strtr($value, self::QUERY_SEPARATORS_REPLACEMENT);
607 }
608 return $queryString;
609 }
610 private function removeDefaultPort() : void
611 {
612 if ($this->port !== null && self::isDefaultPort($this)) {
613 $this->port = null;
614 }
615 }
616 /**
617 * Filters the path of a URI
618 *
619 * @param mixed $path
620 *
621 * @throws \InvalidArgumentException If the path is invalid.
622 */
623 private function filterPath($path) : string
624 {
625 if (!\is_string($path)) {
626 throw new \InvalidArgumentException('Path must be a string');
627 }
628 return $this->filterComponent('/(?:[^' . \YoastSEO_Vendor\GuzzleHttp\Psr7\Rfc3986::CHAR_UNRESERVED . \YoastSEO_Vendor\GuzzleHttp\Psr7\Rfc3986::CHAR_SUB_DELIMS . '%:@\\/]++|%(?![A-Fa-f0-9]{2}))/', $path, 'Unable to filter URI path');
629 }
630 /**
631 * Filters the query string or fragment of a URI.
632 *
633 * @param mixed $str
634 *
635 * @throws \InvalidArgumentException If the query or fragment is invalid.
636 */
637 private function filterQueryAndFragment($str) : string
638 {
639 if (!\is_string($str)) {
640 throw new \InvalidArgumentException('Query and fragment must be a string');
641 }
642 return $this->filterComponent('/(?:[^' . \YoastSEO_Vendor\GuzzleHttp\Psr7\Rfc3986::CHAR_UNRESERVED . \YoastSEO_Vendor\GuzzleHttp\Psr7\Rfc3986::CHAR_SUB_DELIMS . '%:@\\/\\?]++|%(?![A-Fa-f0-9]{2}))/', $str, 'Unable to filter URI query or fragment');
643 }
644 private function filterComponent(string $pattern, string $component, string $context) : string
645 {
646 $filtered = \preg_replace_callback($pattern, [$this, 'rawurlencodeMatchZero'], $component);
647 if ($filtered === null) {
648 throw new \RuntimeException($context . ': ' . \preg_last_error_msg());
649 }
650 return $filtered;
651 }
652 private function rawurlencodeMatchZero(array $match) : string
653 {
654 return \rawurlencode($match[0]);
655 }
656 private function validateState() : void
657 {
658 if ($this->host === '' && ($this->scheme === 'http' || $this->scheme === 'https')) {
659 $this->host = self::HTTP_DEFAULT_HOST;
660 }
661 if ($this->getAuthority() === '') {
662 if (0 === \strpos($this->path, '//')) {
663 throw new \YoastSEO_Vendor\GuzzleHttp\Psr7\Exception\MalformedUriException('The path of a URI without an authority must not start with two slashes "//"');
664 }
665 if ($this->scheme === '' && \false !== \strpos(\explode('/', $this->path, 2)[0], ':')) {
666 throw new \YoastSEO_Vendor\GuzzleHttp\Psr7\Exception\MalformedUriException('A relative URI must not have a path beginning with a segment containing a colon');
667 }
668 }
669 }
670 }
671