You are here

Uri.php in Lockr 7.3

Namespace

GuzzleHttp\Psr7

File

vendor/guzzlehttp/psr7/src/Uri.php
View source
<?php

namespace GuzzleHttp\Psr7;

use Psr\Http\Message\UriInterface;

/**
 * PSR-7 URI implementation.
 *
 * @author Michael Dowling
 * @author Tobias Schultze
 * @author Matthew Weier O'Phinney
 */
class Uri implements UriInterface {

  /**
   * Absolute http and https URIs require a host per RFC 7230 Section 2.7
   * but in generic URIs the host can be empty. So for http(s) URIs
   * we apply this default host when no host is given yet to form a
   * valid URI.
   */
  const HTTP_DEFAULT_HOST = 'localhost';
  private static $defaultPorts = [
    'http' => 80,
    'https' => 443,
    'ftp' => 21,
    'gopher' => 70,
    'nntp' => 119,
    'news' => 119,
    'telnet' => 23,
    'tn3270' => 23,
    'imap' => 143,
    'pop' => 110,
    'ldap' => 389,
  ];
  private static $charUnreserved = 'a-zA-Z0-9_\\-\\.~';
  private static $charSubDelims = '!\\$&\'\\(\\)\\*\\+,;=';
  private static $replaceQuery = [
    '=' => '%3D',
    '&' => '%26',
  ];

  /** @var string Uri scheme. */
  private $scheme = '';

  /** @var string Uri user info. */
  private $userInfo = '';

  /** @var string Uri host. */
  private $host = '';

  /** @var int|null Uri port. */
  private $port;

  /** @var string Uri path. */
  private $path = '';

  /** @var string Uri query string. */
  private $query = '';

  /** @var string Uri fragment. */
  private $fragment = '';

  /**
   * @param string $uri URI to parse
   */
  public function __construct($uri = '') {

    // weak type check to also accept null until we can add scalar type hints
    if ($uri != '') {
      $parts = parse_url($uri);
      if ($parts === false) {
        throw new \InvalidArgumentException("Unable to parse URI: {$uri}");
      }
      $this
        ->applyParts($parts);
    }
  }
  public function __toString() {
    return self::composeComponents($this->scheme, $this
      ->getAuthority(), $this->path, $this->query, $this->fragment);
  }

  /**
   * Composes a URI reference string from its various components.
   *
   * Usually this method does not need to be called manually but instead is used indirectly via
   * `Psr\Http\Message\UriInterface::__toString`.
   *
   * PSR-7 UriInterface treats an empty component the same as a missing component as
   * getQuery(), getFragment() etc. always return a string. This explains the slight
   * difference to RFC 3986 Section 5.3.
   *
   * Another adjustment is that the authority separator is added even when the authority is missing/empty
   * for the "file" scheme. This is because PHP stream functions like `file_get_contents` only work with
   * `file:///myfile` but not with `file:/myfile` although they are equivalent according to RFC 3986. But
   * `file:///` is the more common syntax for the file scheme anyway (Chrome for example redirects to
   * that format).
   *
   * @param string $scheme
   * @param string $authority
   * @param string $path
   * @param string $query
   * @param string $fragment
   *
   * @return string
   *
   * @link https://tools.ietf.org/html/rfc3986#section-5.3
   */
  public static function composeComponents($scheme, $authority, $path, $query, $fragment) {
    $uri = '';

    // weak type checks to also accept null until we can add scalar type hints
    if ($scheme != '') {
      $uri .= $scheme . ':';
    }
    if ($authority != '' || $scheme === 'file') {
      $uri .= '//' . $authority;
    }
    $uri .= $path;
    if ($query != '') {
      $uri .= '?' . $query;
    }
    if ($fragment != '') {
      $uri .= '#' . $fragment;
    }
    return $uri;
  }

  /**
   * Whether the URI has the default port of the current scheme.
   *
   * `Psr\Http\Message\UriInterface::getPort` may return null or the standard port. This method can be used
   * independently of the implementation.
   *
   * @param UriInterface $uri
   *
   * @return bool
   */
  public static function isDefaultPort(UriInterface $uri) {
    return $uri
      ->getPort() === null || isset(self::$defaultPorts[$uri
      ->getScheme()]) && $uri
      ->getPort() === self::$defaultPorts[$uri
      ->getScheme()];
  }

  /**
   * Whether the URI is absolute, i.e. it has a scheme.
   *
   * An instance of UriInterface can either be an absolute URI or a relative reference. This method returns true
   * if it is the former. An absolute URI has a scheme. A relative reference is used to express a URI relative
   * to another URI, the base URI. Relative references can be divided into several forms:
   * - network-path references, e.g. '//example.com/path'
   * - absolute-path references, e.g. '/path'
   * - relative-path references, e.g. 'subpath'
   *
   * @param UriInterface $uri
   *
   * @return bool
   * @see Uri::isNetworkPathReference
   * @see Uri::isAbsolutePathReference
   * @see Uri::isRelativePathReference
   * @link https://tools.ietf.org/html/rfc3986#section-4
   */
  public static function isAbsolute(UriInterface $uri) {
    return $uri
      ->getScheme() !== '';
  }

  /**
   * Whether the URI is a network-path reference.
   *
   * A relative reference that begins with two slash characters is termed an network-path reference.
   *
   * @param UriInterface $uri
   *
   * @return bool
   * @link https://tools.ietf.org/html/rfc3986#section-4.2
   */
  public static function isNetworkPathReference(UriInterface $uri) {
    return $uri
      ->getScheme() === '' && $uri
      ->getAuthority() !== '';
  }

  /**
   * Whether the URI is a absolute-path reference.
   *
   * A relative reference that begins with a single slash character is termed an absolute-path reference.
   *
   * @param UriInterface $uri
   *
   * @return bool
   * @link https://tools.ietf.org/html/rfc3986#section-4.2
   */
  public static function isAbsolutePathReference(UriInterface $uri) {
    return $uri
      ->getScheme() === '' && $uri
      ->getAuthority() === '' && isset($uri
      ->getPath()[0]) && $uri
      ->getPath()[0] === '/';
  }

  /**
   * Whether the URI is a relative-path reference.
   *
   * A relative reference that does not begin with a slash character is termed a relative-path reference.
   *
   * @param UriInterface $uri
   *
   * @return bool
   * @link https://tools.ietf.org/html/rfc3986#section-4.2
   */
  public static function isRelativePathReference(UriInterface $uri) {
    return $uri
      ->getScheme() === '' && $uri
      ->getAuthority() === '' && (!isset($uri
      ->getPath()[0]) || $uri
      ->getPath()[0] !== '/');
  }

  /**
   * Whether the URI is a same-document reference.
   *
   * A same-document reference refers to a URI that is, aside from its fragment
   * component, identical to the base URI. When no base URI is given, only an empty
   * URI reference (apart from its fragment) is considered a same-document reference.
   *
   * @param UriInterface      $uri  The URI to check
   * @param UriInterface|null $base An optional base URI to compare against
   *
   * @return bool
   * @link https://tools.ietf.org/html/rfc3986#section-4.4
   */
  public static function isSameDocumentReference(UriInterface $uri, UriInterface $base = null) {
    if ($base !== null) {
      $uri = UriResolver::resolve($base, $uri);
      return $uri
        ->getScheme() === $base
        ->getScheme() && $uri
        ->getAuthority() === $base
        ->getAuthority() && $uri
        ->getPath() === $base
        ->getPath() && $uri
        ->getQuery() === $base
        ->getQuery();
    }
    return $uri
      ->getScheme() === '' && $uri
      ->getAuthority() === '' && $uri
      ->getPath() === '' && $uri
      ->getQuery() === '';
  }

  /**
   * Removes dot segments from a path and returns the new path.
   *
   * @param string $path
   *
   * @return string
   *
   * @deprecated since version 1.4. Use UriResolver::removeDotSegments instead.
   * @see UriResolver::removeDotSegments
   */
  public static function removeDotSegments($path) {
    return UriResolver::removeDotSegments($path);
  }

  /**
   * Converts the relative URI into a new URI that is resolved against the base URI.
   *
   * @param UriInterface        $base Base URI
   * @param string|UriInterface $rel  Relative URI
   *
   * @return UriInterface
   *
   * @deprecated since version 1.4. Use UriResolver::resolve instead.
   * @see UriResolver::resolve
   */
  public static function resolve(UriInterface $base, $rel) {
    if (!$rel instanceof UriInterface) {
      $rel = new self($rel);
    }
    return UriResolver::resolve($base, $rel);
  }

  /**
   * Creates a new URI with a specific query string value removed.
   *
   * Any existing query string values that exactly match the provided key are
   * removed.
   *
   * @param UriInterface $uri URI to use as a base.
   * @param string       $key Query string key to remove.
   *
   * @return UriInterface
   */
  public static function withoutQueryValue(UriInterface $uri, $key) {
    $result = self::getFilteredQueryString($uri, [
      $key,
    ]);
    return $uri
      ->withQuery(implode('&', $result));
  }

  /**
   * Creates a new URI with a specific query string value.
   *
   * Any existing query string values that exactly match the provided key are
   * removed and replaced with the given key value pair.
   *
   * A value of null will set the query string key without a value, e.g. "key"
   * instead of "key=value".
   *
   * @param UriInterface $uri   URI to use as a base.
   * @param string       $key   Key to set.
   * @param string|null  $value Value to set
   *
   * @return UriInterface
   */
  public static function withQueryValue(UriInterface $uri, $key, $value) {
    $result = self::getFilteredQueryString($uri, [
      $key,
    ]);
    $result[] = self::generateQueryString($key, $value);
    return $uri
      ->withQuery(implode('&', $result));
  }

  /**
   * Creates a new URI with multiple specific query string values.
   *
   * It has the same behavior as withQueryValue() but for an associative array of key => value.
   *
   * @param UriInterface $uri           URI to use as a base.
   * @param array        $keyValueArray Associative array of key and values
   *
   * @return UriInterface
   */
  public static function withQueryValues(UriInterface $uri, array $keyValueArray) {
    $result = self::getFilteredQueryString($uri, array_keys($keyValueArray));
    foreach ($keyValueArray as $key => $value) {
      $result[] = self::generateQueryString($key, $value);
    }
    return $uri
      ->withQuery(implode('&', $result));
  }

  /**
   * Creates a URI from a hash of `parse_url` components.
   *
   * @param array $parts
   *
   * @return UriInterface
   * @link http://php.net/manual/en/function.parse-url.php
   *
   * @throws \InvalidArgumentException If the components do not form a valid URI.
   */
  public static function fromParts(array $parts) {
    $uri = new self();
    $uri
      ->applyParts($parts);
    $uri
      ->validateState();
    return $uri;
  }
  public function getScheme() {
    return $this->scheme;
  }
  public function getAuthority() {
    $authority = $this->host;
    if ($this->userInfo !== '') {
      $authority = $this->userInfo . '@' . $authority;
    }
    if ($this->port !== null) {
      $authority .= ':' . $this->port;
    }
    return $authority;
  }
  public function getUserInfo() {
    return $this->userInfo;
  }
  public function getHost() {
    return $this->host;
  }
  public function getPort() {
    return $this->port;
  }
  public function getPath() {
    return $this->path;
  }
  public function getQuery() {
    return $this->query;
  }
  public function getFragment() {
    return $this->fragment;
  }
  public function withScheme($scheme) {
    $scheme = $this
      ->filterScheme($scheme);
    if ($this->scheme === $scheme) {
      return $this;
    }
    $new = clone $this;
    $new->scheme = $scheme;
    $new
      ->removeDefaultPort();
    $new
      ->validateState();
    return $new;
  }
  public function withUserInfo($user, $password = null) {
    $info = $user;
    if ($password != '') {
      $info .= ':' . $password;
    }
    if ($this->userInfo === $info) {
      return $this;
    }
    $new = clone $this;
    $new->userInfo = $info;
    $new
      ->validateState();
    return $new;
  }
  public function withHost($host) {
    $host = $this
      ->filterHost($host);
    if ($this->host === $host) {
      return $this;
    }
    $new = clone $this;
    $new->host = $host;
    $new
      ->validateState();
    return $new;
  }
  public function withPort($port) {
    $port = $this
      ->filterPort($port);
    if ($this->port === $port) {
      return $this;
    }
    $new = clone $this;
    $new->port = $port;
    $new
      ->removeDefaultPort();
    $new
      ->validateState();
    return $new;
  }
  public function withPath($path) {
    $path = $this
      ->filterPath($path);
    if ($this->path === $path) {
      return $this;
    }
    $new = clone $this;
    $new->path = $path;
    $new
      ->validateState();
    return $new;
  }
  public function withQuery($query) {
    $query = $this
      ->filterQueryAndFragment($query);
    if ($this->query === $query) {
      return $this;
    }
    $new = clone $this;
    $new->query = $query;
    return $new;
  }
  public function withFragment($fragment) {
    $fragment = $this
      ->filterQueryAndFragment($fragment);
    if ($this->fragment === $fragment) {
      return $this;
    }
    $new = clone $this;
    $new->fragment = $fragment;
    return $new;
  }

  /**
   * Apply parse_url parts to a URI.
   *
   * @param array $parts Array of parse_url parts to apply.
   */
  private function applyParts(array $parts) {
    $this->scheme = isset($parts['scheme']) ? $this
      ->filterScheme($parts['scheme']) : '';
    $this->userInfo = isset($parts['user']) ? $parts['user'] : '';
    $this->host = isset($parts['host']) ? $this
      ->filterHost($parts['host']) : '';
    $this->port = isset($parts['port']) ? $this
      ->filterPort($parts['port']) : null;
    $this->path = isset($parts['path']) ? $this
      ->filterPath($parts['path']) : '';
    $this->query = isset($parts['query']) ? $this
      ->filterQueryAndFragment($parts['query']) : '';
    $this->fragment = isset($parts['fragment']) ? $this
      ->filterQueryAndFragment($parts['fragment']) : '';
    if (isset($parts['pass'])) {
      $this->userInfo .= ':' . $parts['pass'];
    }
    $this
      ->removeDefaultPort();
  }

  /**
   * @param string $scheme
   *
   * @return string
   *
   * @throws \InvalidArgumentException If the scheme is invalid.
   */
  private function filterScheme($scheme) {
    if (!is_string($scheme)) {
      throw new \InvalidArgumentException('Scheme must be a string');
    }
    return strtolower($scheme);
  }

  /**
   * @param string $host
   *
   * @return string
   *
   * @throws \InvalidArgumentException If the host is invalid.
   */
  private function filterHost($host) {
    if (!is_string($host)) {
      throw new \InvalidArgumentException('Host must be a string');
    }
    return strtolower($host);
  }

  /**
   * @param int|null $port
   *
   * @return int|null
   *
   * @throws \InvalidArgumentException If the port is invalid.
   */
  private function filterPort($port) {
    if ($port === null) {
      return null;
    }
    $port = (int) $port;
    if (1 > $port || 0xffff < $port) {
      throw new \InvalidArgumentException(sprintf('Invalid port: %d. Must be between 1 and 65535', $port));
    }
    return $port;
  }

  /**
   * @param UriInterface $uri
   * @param array        $keys
   *
   * @return array
   */
  private static function getFilteredQueryString(UriInterface $uri, array $keys) {
    $current = $uri
      ->getQuery();
    if ($current === '') {
      return [];
    }
    $decodedKeys = array_map('rawurldecode', $keys);
    return array_filter(explode('&', $current), function ($part) use ($decodedKeys) {
      return !in_array(rawurldecode(explode('=', $part)[0]), $decodedKeys, true);
    });
  }

  /**
   * @param string      $key
   * @param string|null $value
   *
   * @return string
   */
  private static function generateQueryString($key, $value) {

    // Query string separators ("=", "&") within the key or value need to be encoded
    // (while preventing double-encoding) before setting the query string. All other
    // chars that need percent-encoding will be encoded by withQuery().
    $queryString = strtr($key, self::$replaceQuery);
    if ($value !== null) {
      $queryString .= '=' . strtr($value, self::$replaceQuery);
    }
    return $queryString;
  }
  private function removeDefaultPort() {
    if ($this->port !== null && self::isDefaultPort($this)) {
      $this->port = null;
    }
  }

  /**
   * Filters the path of a URI
   *
   * @param string $path
   *
   * @return string
   *
   * @throws \InvalidArgumentException If the path is invalid.
   */
  private function filterPath($path) {
    if (!is_string($path)) {
      throw new \InvalidArgumentException('Path must be a string');
    }
    return preg_replace_callback('/(?:[^' . self::$charUnreserved . self::$charSubDelims . '%:@\\/]++|%(?![A-Fa-f0-9]{2}))/', [
      $this,
      'rawurlencodeMatchZero',
    ], $path);
  }

  /**
   * Filters the query string or fragment of a URI.
   *
   * @param string $str
   *
   * @return string
   *
   * @throws \InvalidArgumentException If the query or fragment is invalid.
   */
  private function filterQueryAndFragment($str) {
    if (!is_string($str)) {
      throw new \InvalidArgumentException('Query and fragment must be a string');
    }
    return preg_replace_callback('/(?:[^' . self::$charUnreserved . self::$charSubDelims . '%:@\\/\\?]++|%(?![A-Fa-f0-9]{2}))/', [
      $this,
      'rawurlencodeMatchZero',
    ], $str);
  }
  private function rawurlencodeMatchZero(array $match) {
    return rawurlencode($match[0]);
  }
  private function validateState() {
    if ($this->host === '' && ($this->scheme === 'http' || $this->scheme === 'https')) {
      $this->host = self::HTTP_DEFAULT_HOST;
    }
    if ($this
      ->getAuthority() === '') {
      if (0 === strpos($this->path, '//')) {
        throw new \InvalidArgumentException('The path of a URI without an authority must not start with two slashes "//"');
      }
      if ($this->scheme === '' && false !== strpos(explode('/', $this->path, 2)[0], ':')) {
        throw new \InvalidArgumentException('A relative URI must not have a path beginning with a segment containing a colon');
      }
    }
    elseif (isset($this->path[0]) && $this->path[0] !== '/') {
      @trigger_error('The path of a URI with an authority must start with a slash "/" or be empty. Automagically fixing the URI ' . 'by adding a leading slash to the path is deprecated since version 1.4 and will throw an exception instead.', E_USER_DEPRECATED);
      $this->path = '/' . $this->path;

      //throw new \InvalidArgumentException('The path of a URI with an authority must start with a slash "/" or be empty');
    }
  }

}

Classes

Namesort descending Description
Uri PSR-7 URI implementation.