Verzeichnisstruktur phpBB-3.3.15
- Veröffentlicht
- 28.08.2024
So funktioniert es
|
Auf das letzte Element klicken. Dies geht jeweils ein Schritt zurück |
Auf das Icon klicken, dies öffnet das Verzeichnis. Nochmal klicken schließt das Verzeichnis. |
|
(Beispiel Datei-Icons)
|
Auf das Icon klicken um den Quellcode anzuzeigen |
Uri.php
001 <?php
002
003 namespace GuzzleHttp\Psr7;
004
005 use Psr\Http\Message\UriInterface;
006
007 /**
008 * PSR-7 URI implementation.
009 *
010 * @author Michael Dowling
011 * @author Tobias Schultze
012 * @author Matthew Weier O'Phinney
013 */
014 class Uri implements UriInterface
015 {
016 /**
017 * Absolute http and https URIs require a host per RFC 7230 Section 2.7
018 * but in generic URIs the host can be empty. So for http(s) URIs
019 * we apply this default host when no host is given yet to form a
020 * valid URI.
021 */
022 const HTTP_DEFAULT_HOST = 'localhost';
023
024 private static $defaultPorts = [
025 'http' => 80,
026 'https' => 443,
027 'ftp' => 21,
028 'gopher' => 70,
029 'nntp' => 119,
030 'news' => 119,
031 'telnet' => 23,
032 'tn3270' => 23,
033 'imap' => 143,
034 'pop' => 110,
035 'ldap' => 389,
036 ];
037
038 private static $charUnreserved = 'a-zA-Z0-9_\-\.~';
039 private static $charSubDelims = '!\$&\'\(\)\*\+,;=';
040 private static $replaceQuery = ['=' => '%3D', '&' => '%26'];
041
042 /** @var string Uri scheme. */
043 private $scheme = '';
044
045 /** @var string Uri user info. */
046 private $userInfo = '';
047
048 /** @var string Uri host. */
049 private $host = '';
050
051 /** @var int|null Uri port. */
052 private $port;
053
054 /** @var string Uri path. */
055 private $path = '';
056
057 /** @var string Uri query string. */
058 private $query = '';
059
060 /** @var string Uri fragment. */
061 private $fragment = '';
062
063 /**
064 * @param string $uri URI to parse
065 */
066 public function __construct($uri = '')
067 {
068 // weak type check to also accept null until we can add scalar type hints
069 if ($uri != '') {
070 $parts = self::parse($uri);
071 if ($parts === false) {
072 throw new \InvalidArgumentException("Unable to parse URI: $uri");
073 }
074 $this->applyParts($parts);
075 }
076 }
077
078 /**
079 * UTF-8 aware \parse_url() replacement.
080 *
081 * The internal function produces broken output for non ASCII domain names
082 * (IDN) when used with locales other than "C".
083 *
084 * On the other hand, cURL understands IDN correctly only when UTF-8 locale
085 * is configured ("C.UTF-8", "en_US.UTF-8", etc.).
086 *
087 * @see https://bugs.php.net/bug.php?id=52923
088 * @see https://www.php.net/manual/en/function.parse-url.php#114817
089 * @see https://curl.haxx.se/libcurl/c/CURLOPT_URL.html#ENCODING
090 *
091 * @param string $url
092 *
093 * @return array|false
094 */
095 private static function parse($url)
096 {
097 // If IPv6
098 $prefix = '';
099 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 * `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 * `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