"Fossies" - the Fresh Open Source Software Archive

Member "grav/vendor/guzzlehttp/psr7/src/Uri.php" (1 Sep 2020, 21508 Bytes) of package /linux/www/grav-v1.6.27.zip:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) PHP source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "Uri.php" see the Fossies "Dox" file reference documentation.

    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 }