f46c1db9e07812e8d0069a4d9beb145ea3bfbf31
[yaffs-website] / vendor / guzzlehttp / psr7 / src / Uri.php
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         $current = $uri->getQuery();
305         if ($current === '') {
306             return $uri;
307         }
308
309         $decodedKey = rawurldecode($key);
310         $result = array_filter(explode('&', $current), function ($part) use ($decodedKey) {
311             return rawurldecode(explode('=', $part)[0]) !== $decodedKey;
312         });
313
314         return $uri->withQuery(implode('&', $result));
315     }
316
317     /**
318      * Creates a new URI with a specific query string value.
319      *
320      * Any existing query string values that exactly match the provided key are
321      * removed and replaced with the given key value pair.
322      *
323      * A value of null will set the query string key without a value, e.g. "key"
324      * instead of "key=value".
325      *
326      * @param UriInterface $uri   URI to use as a base.
327      * @param string       $key   Key to set.
328      * @param string|null  $value Value to set
329      *
330      * @return UriInterface
331      */
332     public static function withQueryValue(UriInterface $uri, $key, $value)
333     {
334         $current = $uri->getQuery();
335
336         if ($current === '') {
337             $result = [];
338         } else {
339             $decodedKey = rawurldecode($key);
340             $result = array_filter(explode('&', $current), function ($part) use ($decodedKey) {
341                 return rawurldecode(explode('=', $part)[0]) !== $decodedKey;
342             });
343         }
344
345         // Query string separators ("=", "&") within the key or value need to be encoded
346         // (while preventing double-encoding) before setting the query string. All other
347         // chars that need percent-encoding will be encoded by withQuery().
348         $key = strtr($key, self::$replaceQuery);
349
350         if ($value !== null) {
351             $result[] = $key . '=' . strtr($value, self::$replaceQuery);
352         } else {
353             $result[] = $key;
354         }
355
356         return $uri->withQuery(implode('&', $result));
357     }
358
359     /**
360      * Creates a URI from a hash of `parse_url` components.
361      *
362      * @param array $parts
363      *
364      * @return UriInterface
365      * @link http://php.net/manual/en/function.parse-url.php
366      *
367      * @throws \InvalidArgumentException If the components do not form a valid URI.
368      */
369     public static function fromParts(array $parts)
370     {
371         $uri = new self();
372         $uri->applyParts($parts);
373         $uri->validateState();
374
375         return $uri;
376     }
377
378     public function getScheme()
379     {
380         return $this->scheme;
381     }
382
383     public function getAuthority()
384     {
385         $authority = $this->host;
386         if ($this->userInfo !== '') {
387             $authority = $this->userInfo . '@' . $authority;
388         }
389
390         if ($this->port !== null) {
391             $authority .= ':' . $this->port;
392         }
393
394         return $authority;
395     }
396
397     public function getUserInfo()
398     {
399         return $this->userInfo;
400     }
401
402     public function getHost()
403     {
404         return $this->host;
405     }
406
407     public function getPort()
408     {
409         return $this->port;
410     }
411
412     public function getPath()
413     {
414         return $this->path;
415     }
416
417     public function getQuery()
418     {
419         return $this->query;
420     }
421
422     public function getFragment()
423     {
424         return $this->fragment;
425     }
426
427     public function withScheme($scheme)
428     {
429         $scheme = $this->filterScheme($scheme);
430
431         if ($this->scheme === $scheme) {
432             return $this;
433         }
434
435         $new = clone $this;
436         $new->scheme = $scheme;
437         $new->removeDefaultPort();
438         $new->validateState();
439
440         return $new;
441     }
442
443     public function withUserInfo($user, $password = null)
444     {
445         $info = $user;
446         if ($password != '') {
447             $info .= ':' . $password;
448         }
449
450         if ($this->userInfo === $info) {
451             return $this;
452         }
453
454         $new = clone $this;
455         $new->userInfo = $info;
456         $new->validateState();
457
458         return $new;
459     }
460
461     public function withHost($host)
462     {
463         $host = $this->filterHost($host);
464
465         if ($this->host === $host) {
466             return $this;
467         }
468
469         $new = clone $this;
470         $new->host = $host;
471         $new->validateState();
472
473         return $new;
474     }
475
476     public function withPort($port)
477     {
478         $port = $this->filterPort($port);
479
480         if ($this->port === $port) {
481             return $this;
482         }
483
484         $new = clone $this;
485         $new->port = $port;
486         $new->removeDefaultPort();
487         $new->validateState();
488
489         return $new;
490     }
491
492     public function withPath($path)
493     {
494         $path = $this->filterPath($path);
495
496         if ($this->path === $path) {
497             return $this;
498         }
499
500         $new = clone $this;
501         $new->path = $path;
502         $new->validateState();
503
504         return $new;
505     }
506
507     public function withQuery($query)
508     {
509         $query = $this->filterQueryAndFragment($query);
510
511         if ($this->query === $query) {
512             return $this;
513         }
514
515         $new = clone $this;
516         $new->query = $query;
517
518         return $new;
519     }
520
521     public function withFragment($fragment)
522     {
523         $fragment = $this->filterQueryAndFragment($fragment);
524
525         if ($this->fragment === $fragment) {
526             return $this;
527         }
528
529         $new = clone $this;
530         $new->fragment = $fragment;
531
532         return $new;
533     }
534
535     /**
536      * Apply parse_url parts to a URI.
537      *
538      * @param array $parts Array of parse_url parts to apply.
539      */
540     private function applyParts(array $parts)
541     {
542         $this->scheme = isset($parts['scheme'])
543             ? $this->filterScheme($parts['scheme'])
544             : '';
545         $this->userInfo = isset($parts['user']) ? $parts['user'] : '';
546         $this->host = isset($parts['host'])
547             ? $this->filterHost($parts['host'])
548             : '';
549         $this->port = isset($parts['port'])
550             ? $this->filterPort($parts['port'])
551             : null;
552         $this->path = isset($parts['path'])
553             ? $this->filterPath($parts['path'])
554             : '';
555         $this->query = isset($parts['query'])
556             ? $this->filterQueryAndFragment($parts['query'])
557             : '';
558         $this->fragment = isset($parts['fragment'])
559             ? $this->filterQueryAndFragment($parts['fragment'])
560             : '';
561         if (isset($parts['pass'])) {
562             $this->userInfo .= ':' . $parts['pass'];
563         }
564
565         $this->removeDefaultPort();
566     }
567
568     /**
569      * @param string $scheme
570      *
571      * @return string
572      *
573      * @throws \InvalidArgumentException If the scheme is invalid.
574      */
575     private function filterScheme($scheme)
576     {
577         if (!is_string($scheme)) {
578             throw new \InvalidArgumentException('Scheme must be a string');
579         }
580
581         return strtolower($scheme);
582     }
583
584     /**
585      * @param string $host
586      *
587      * @return string
588      *
589      * @throws \InvalidArgumentException If the host is invalid.
590      */
591     private function filterHost($host)
592     {
593         if (!is_string($host)) {
594             throw new \InvalidArgumentException('Host must be a string');
595         }
596
597         return strtolower($host);
598     }
599
600     /**
601      * @param int|null $port
602      *
603      * @return int|null
604      *
605      * @throws \InvalidArgumentException If the port is invalid.
606      */
607     private function filterPort($port)
608     {
609         if ($port === null) {
610             return null;
611         }
612
613         $port = (int) $port;
614         if (1 > $port || 0xffff < $port) {
615             throw new \InvalidArgumentException(
616                 sprintf('Invalid port: %d. Must be between 1 and 65535', $port)
617             );
618         }
619
620         return $port;
621     }
622
623     private function removeDefaultPort()
624     {
625         if ($this->port !== null && self::isDefaultPort($this)) {
626             $this->port = null;
627         }
628     }
629
630     /**
631      * Filters the path of a URI
632      *
633      * @param string $path
634      *
635      * @return string
636      *
637      * @throws \InvalidArgumentException If the path is invalid.
638      */
639     private function filterPath($path)
640     {
641         if (!is_string($path)) {
642             throw new \InvalidArgumentException('Path must be a string');
643         }
644
645         return preg_replace_callback(
646             '/(?:[^' . self::$charUnreserved . self::$charSubDelims . '%:@\/]++|%(?![A-Fa-f0-9]{2}))/',
647             [$this, 'rawurlencodeMatchZero'],
648             $path
649         );
650     }
651
652     /**
653      * Filters the query string or fragment of a URI.
654      *
655      * @param string $str
656      *
657      * @return string
658      *
659      * @throws \InvalidArgumentException If the query or fragment is invalid.
660      */
661     private function filterQueryAndFragment($str)
662     {
663         if (!is_string($str)) {
664             throw new \InvalidArgumentException('Query and fragment must be a string');
665         }
666
667         return preg_replace_callback(
668             '/(?:[^' . self::$charUnreserved . self::$charSubDelims . '%:@\/\?]++|%(?![A-Fa-f0-9]{2}))/',
669             [$this, 'rawurlencodeMatchZero'],
670             $str
671         );
672     }
673
674     private function rawurlencodeMatchZero(array $match)
675     {
676         return rawurlencode($match[0]);
677     }
678
679     private function validateState()
680     {
681         if ($this->host === '' && ($this->scheme === 'http' || $this->scheme === 'https')) {
682             $this->host = self::HTTP_DEFAULT_HOST;
683         }
684
685         if ($this->getAuthority() === '') {
686             if (0 === strpos($this->path, '//')) {
687                 throw new \InvalidArgumentException('The path of a URI without an authority must not start with two slashes "//"');
688             }
689             if ($this->scheme === '' && false !== strpos(explode('/', $this->path, 2)[0], ':')) {
690                 throw new \InvalidArgumentException('A relative URI must not have a path beginning with a segment containing a colon');
691             }
692         } elseif (isset($this->path[0]) && $this->path[0] !== '/') {
693             @trigger_error(
694                 'The path of a URI with an authority must start with a slash "/" or be empty. Automagically fixing the URI ' .
695                 'by adding a leading slash to the path is deprecated since version 1.4 and will throw an exception instead.',
696                 E_USER_DEPRECATED
697             );
698             $this->path = '/'. $this->path;
699             //throw new \InvalidArgumentException('The path of a URI with an authority must start with a slash "/" or be empty');
700         }
701     }
702 }