PluginProbe
Depicter — Popup & Slider Builder / 1.3.2
Depicter — Popup & Slider Builder v1.3.2
4.8.1 trunk 1.0.0 1.1.0 1.1.2 1.1.4 1.1.6 1.1.7 1.1.8 1.1.9 1.2.0 1.3.0 1.3.1 1.3.2 1.3.3 1.3.5 1.3.8 1.5.0 1.5.1 1.5.2 1.5.5 1.6.0 1.6.1 1.6.2 1.7.0 All 76 releases
depicter / modules / GuzzleHttp / Psr7 / Uri.php

Uri.php in Depicter — Popup & Slider Builder 1.3.2, at modules/GuzzleHttp/Psr7/Uri.php

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