You are here

UriResolver.php in Lockr 7.3

Namespace

GuzzleHttp\Psr7

File

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

namespace GuzzleHttp\Psr7;

use Psr\Http\Message\UriInterface;

/**
 * Resolves a URI reference in the context of a base URI and the opposite way.
 *
 * @author Tobias Schultze
 *
 * @link https://tools.ietf.org/html/rfc3986#section-5
 */
final class UriResolver {

  /**
   * Removes dot segments from a path and returns the new path.
   *
   * @param string $path
   *
   * @return string
   * @link http://tools.ietf.org/html/rfc3986#section-5.2.4
   */
  public static function removeDotSegments($path) {
    if ($path === '' || $path === '/') {
      return $path;
    }
    $results = [];
    $segments = explode('/', $path);
    foreach ($segments as $segment) {
      if ($segment === '..') {
        array_pop($results);
      }
      elseif ($segment !== '.') {
        $results[] = $segment;
      }
    }
    $newPath = implode('/', $results);
    if ($path[0] === '/' && (!isset($newPath[0]) || $newPath[0] !== '/')) {

      // Re-add the leading slash if necessary for cases like "/.."
      $newPath = '/' . $newPath;
    }
    elseif ($newPath !== '' && ($segment === '.' || $segment === '..')) {

      // Add the trailing slash if necessary
      // If newPath is not empty, then $segment must be set and is the last segment from the foreach
      $newPath .= '/';
    }
    return $newPath;
  }

  /**
   * Converts the relative URI into a new URI that is resolved against the base URI.
   *
   * @param UriInterface $base Base URI
   * @param UriInterface $rel  Relative URI
   *
   * @return UriInterface
   * @link http://tools.ietf.org/html/rfc3986#section-5.2
   */
  public static function resolve(UriInterface $base, UriInterface $rel) {
    if ((string) $rel === '') {

      // we can simply return the same base URI instance for this same-document reference
      return $base;
    }
    if ($rel
      ->getScheme() != '') {
      return $rel
        ->withPath(self::removeDotSegments($rel
        ->getPath()));
    }
    if ($rel
      ->getAuthority() != '') {
      $targetAuthority = $rel
        ->getAuthority();
      $targetPath = self::removeDotSegments($rel
        ->getPath());
      $targetQuery = $rel
        ->getQuery();
    }
    else {
      $targetAuthority = $base
        ->getAuthority();
      if ($rel
        ->getPath() === '') {
        $targetPath = $base
          ->getPath();
        $targetQuery = $rel
          ->getQuery() != '' ? $rel
          ->getQuery() : $base
          ->getQuery();
      }
      else {
        if ($rel
          ->getPath()[0] === '/') {
          $targetPath = $rel
            ->getPath();
        }
        else {
          if ($targetAuthority != '' && $base
            ->getPath() === '') {
            $targetPath = '/' . $rel
              ->getPath();
          }
          else {
            $lastSlashPos = strrpos($base
              ->getPath(), '/');
            if ($lastSlashPos === false) {
              $targetPath = $rel
                ->getPath();
            }
            else {
              $targetPath = substr($base
                ->getPath(), 0, $lastSlashPos + 1) . $rel
                ->getPath();
            }
          }
        }
        $targetPath = self::removeDotSegments($targetPath);
        $targetQuery = $rel
          ->getQuery();
      }
    }
    return new Uri(Uri::composeComponents($base
      ->getScheme(), $targetAuthority, $targetPath, $targetQuery, $rel
      ->getFragment()));
  }

  /**
   * Returns the target URI as a relative reference from the base URI.
   *
   * This method is the counterpart to resolve():
   *
   *    (string) $target === (string) UriResolver::resolve($base, UriResolver::relativize($base, $target))
   *
   * One use-case is to use the current request URI as base URI and then generate relative links in your documents
   * to reduce the document size or offer self-contained downloadable document archives.
   *
   *    $base = new Uri('http://example.com/a/b/');
   *    echo UriResolver::relativize($base, new Uri('http://example.com/a/b/c'));  // prints 'c'.
   *    echo UriResolver::relativize($base, new Uri('http://example.com/a/x/y'));  // prints '../x/y'.
   *    echo UriResolver::relativize($base, new Uri('http://example.com/a/b/?q')); // prints '?q'.
   *    echo UriResolver::relativize($base, new Uri('http://example.org/a/b/'));   // prints '//example.org/a/b/'.
   *
   * This method also accepts a target that is already relative and will try to relativize it further. Only a
   * relative-path reference will be returned as-is.
   *
   *    echo UriResolver::relativize($base, new Uri('/a/b/c'));  // prints 'c' as well
   *
   * @param UriInterface $base   Base URI
   * @param UriInterface $target Target URI
   *
   * @return UriInterface The relative URI reference
   */
  public static function relativize(UriInterface $base, UriInterface $target) {
    if ($target
      ->getScheme() !== '' && ($base
      ->getScheme() !== $target
      ->getScheme() || $target
      ->getAuthority() === '' && $base
      ->getAuthority() !== '')) {
      return $target;
    }
    if (Uri::isRelativePathReference($target)) {

      // As the target is already highly relative we return it as-is. It would be possible to resolve
      // the target with `$target = self::resolve($base, $target);` and then try make it more relative
      // by removing a duplicate query. But let's not do that automatically.
      return $target;
    }
    if ($target
      ->getAuthority() !== '' && $base
      ->getAuthority() !== $target
      ->getAuthority()) {
      return $target
        ->withScheme('');
    }

    // We must remove the path before removing the authority because if the path starts with two slashes, the URI
    // would turn invalid. And we also cannot set a relative path before removing the authority, as that is also
    // invalid.
    $emptyPathUri = $target
      ->withScheme('')
      ->withPath('')
      ->withUserInfo('')
      ->withPort(null)
      ->withHost('');
    if ($base
      ->getPath() !== $target
      ->getPath()) {
      return $emptyPathUri
        ->withPath(self::getRelativePath($base, $target));
    }
    if ($base
      ->getQuery() === $target
      ->getQuery()) {

      // Only the target fragment is left. And it must be returned even if base and target fragment are the same.
      return $emptyPathUri
        ->withQuery('');
    }

    // If the base URI has a query but the target has none, we cannot return an empty path reference as it would
    // inherit the base query component when resolving.
    if ($target
      ->getQuery() === '') {
      $segments = explode('/', $target
        ->getPath());
      $lastSegment = end($segments);
      return $emptyPathUri
        ->withPath($lastSegment === '' ? './' : $lastSegment);
    }
    return $emptyPathUri;
  }
  private static function getRelativePath(UriInterface $base, UriInterface $target) {
    $sourceSegments = explode('/', $base
      ->getPath());
    $targetSegments = explode('/', $target
      ->getPath());
    array_pop($sourceSegments);
    $targetLastSegment = array_pop($targetSegments);
    foreach ($sourceSegments as $i => $segment) {
      if (isset($targetSegments[$i]) && $segment === $targetSegments[$i]) {
        unset($sourceSegments[$i], $targetSegments[$i]);
      }
      else {
        break;
      }
    }
    $targetSegments[] = $targetLastSegment;
    $relativePath = str_repeat('../', count($sourceSegments)) . implode('/', $targetSegments);

    // A reference to am empty last segment or an empty first sub-segment must be prefixed with "./".
    // This also applies to a segment with a colon character (e.g., "file:colon") that cannot be used
    // as the first segment of a relative-path reference, as it would be mistaken for a scheme name.
    if ('' === $relativePath || false !== strpos(explode('/', $relativePath, 2)[0], ':')) {
      $relativePath = "./{$relativePath}";
    }
    elseif ('/' === $relativePath[0]) {
      if ($base
        ->getAuthority() != '' && $base
        ->getPath() === '') {

        // In this case an extra slash is added by resolve() automatically. So we must not add one here.
        $relativePath = ".{$relativePath}";
      }
      else {
        $relativePath = "./{$relativePath}";
      }
    }
    return $relativePath;
  }
  private function __construct() {

    // cannot be instantiated
  }

}

Classes

Namesort descending Description
UriResolver Resolves a URI reference in the context of a base URI and the opposite way.