PluginProbe
StockPack – Stock photos and AI images from Unsplash, Adobe Stock, Freepik and more / 2.5.6
StockPack – Stock photos and AI images from Unsplash, Adobe Stock, Freepik and more v2.5.6
3.7.2 3.7.1 3.7.0 3.6.0 3.6.1 3.5.2 trunk 2.0 2.1 2.2 2.3 2.4 2.4.1 2.4.2 2.4.3 2.5.0 2.5.1 2.5.2 2.5.3 2.5.4 2.5.5 2.5.6 2.5.7 2.6.0 2.6.1 All 73 releases
stockpack / vendor / guzzlehttp / psr7 / src / Uri.php

Uri.php in StockPack – Stock photos and AI images from Unsplash, Adobe Stock, Freepik and more 2.5.6, at vendor/guzzlehttp/psr7/src/Uri.php

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