PluginProbe
Yoast SEO – Advanced SEO with real-time guidance and built-in AI / 18.2
Yoast SEO – Advanced SEO with real-time guidance and built-in AI v18.2
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 18.2, at vendor_prefixed/guzzlehttp/psr7/src/Uri.php

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