| 1 | 
           efrain | 
           1 | 
           <?php
  | 
        
        
            | 
            | 
           2 | 
              | 
        
        
            | 
            | 
           3 | 
           declare(strict_types=1);
  | 
        
        
            | 
            | 
           4 | 
              | 
        
        
            | 
            | 
           5 | 
           namespace GuzzleHttp\Psr7;
  | 
        
        
            | 
            | 
           6 | 
              | 
        
        
            | 
            | 
           7 | 
           use Psr\Http\Message\UriInterface;
  | 
        
        
            | 
            | 
           8 | 
              | 
        
        
            | 
            | 
           9 | 
           /**
  | 
        
        
            | 
            | 
           10 | 
            * Resolves a URI reference in the context of a base URI and the opposite way.
  | 
        
        
            | 
            | 
           11 | 
            *
  | 
        
        
            | 
            | 
           12 | 
            * @author Tobias Schultze
  | 
        
        
            | 
            | 
           13 | 
            *
  | 
        
        
           | 1441 | 
           ariadna | 
           14 | 
            * @see https://datatracker.ietf.org/doc/html/rfc3986#section-5
  | 
        
        
           | 1 | 
           efrain | 
           15 | 
            */
  | 
        
        
            | 
            | 
           16 | 
           final class UriResolver
  | 
        
        
            | 
            | 
           17 | 
           {
  | 
        
        
            | 
            | 
           18 | 
               /**
  | 
        
        
            | 
            | 
           19 | 
                * Removes dot segments from a path and returns the new path.
  | 
        
        
            | 
            | 
           20 | 
                *
  | 
        
        
           | 1441 | 
           ariadna | 
           21 | 
                * @see https://datatracker.ietf.org/doc/html/rfc3986#section-5.2.4
  | 
        
        
           | 1 | 
           efrain | 
           22 | 
                */
  | 
        
        
            | 
            | 
           23 | 
               public static function removeDotSegments(string $path): string
  | 
        
        
            | 
            | 
           24 | 
               {
  | 
        
        
            | 
            | 
           25 | 
                   if ($path === '' || $path === '/') {
  | 
        
        
            | 
            | 
           26 | 
                       return $path;
  | 
        
        
            | 
            | 
           27 | 
                   }
  | 
        
        
            | 
            | 
           28 | 
              | 
        
        
            | 
            | 
           29 | 
                   $results = [];
  | 
        
        
            | 
            | 
           30 | 
                   $segments = explode('/', $path);
  | 
        
        
            | 
            | 
           31 | 
                   foreach ($segments as $segment) {
  | 
        
        
            | 
            | 
           32 | 
                       if ($segment === '..') {
  | 
        
        
            | 
            | 
           33 | 
                           array_pop($results);
  | 
        
        
            | 
            | 
           34 | 
                       } elseif ($segment !== '.') {
  | 
        
        
            | 
            | 
           35 | 
                           $results[] = $segment;
  | 
        
        
            | 
            | 
           36 | 
                       }
  | 
        
        
            | 
            | 
           37 | 
                   }
  | 
        
        
            | 
            | 
           38 | 
              | 
        
        
            | 
            | 
           39 | 
                   $newPath = implode('/', $results);
  | 
        
        
            | 
            | 
           40 | 
              | 
        
        
            | 
            | 
           41 | 
                   if ($path[0] === '/' && (!isset($newPath[0]) || $newPath[0] !== '/')) {
  | 
        
        
            | 
            | 
           42 | 
                       // Re-add the leading slash if necessary for cases like "/.."
  | 
        
        
           | 1441 | 
           ariadna | 
           43 | 
                       $newPath = '/'.$newPath;
  | 
        
        
           | 1 | 
           efrain | 
           44 | 
                   } elseif ($newPath !== '' && ($segment === '.' || $segment === '..')) {
  | 
        
        
            | 
            | 
           45 | 
                       // Add the trailing slash if necessary
  | 
        
        
            | 
            | 
           46 | 
                       // If newPath is not empty, then $segment must be set and is the last segment from the foreach
  | 
        
        
            | 
            | 
           47 | 
                       $newPath .= '/';
  | 
        
        
            | 
            | 
           48 | 
                   }
  | 
        
        
            | 
            | 
           49 | 
              | 
        
        
            | 
            | 
           50 | 
                   return $newPath;
  | 
        
        
            | 
            | 
           51 | 
               }
  | 
        
        
            | 
            | 
           52 | 
              | 
        
        
            | 
            | 
           53 | 
               /**
  | 
        
        
            | 
            | 
           54 | 
                * Converts the relative URI into a new URI that is resolved against the base URI.
  | 
        
        
            | 
            | 
           55 | 
                *
  | 
        
        
           | 1441 | 
           ariadna | 
           56 | 
                * @see https://datatracker.ietf.org/doc/html/rfc3986#section-5.2
  | 
        
        
           | 1 | 
           efrain | 
           57 | 
                */
  | 
        
        
            | 
            | 
           58 | 
               public static function resolve(UriInterface $base, UriInterface $rel): UriInterface
  | 
        
        
            | 
            | 
           59 | 
               {
  | 
        
        
            | 
            | 
           60 | 
                   if ((string) $rel === '') {
  | 
        
        
            | 
            | 
           61 | 
                       // we can simply return the same base URI instance for this same-document reference
  | 
        
        
            | 
            | 
           62 | 
                       return $base;
  | 
        
        
            | 
            | 
           63 | 
                   }
  | 
        
        
            | 
            | 
           64 | 
              | 
        
        
            | 
            | 
           65 | 
                   if ($rel->getScheme() != '') {
  | 
        
        
            | 
            | 
           66 | 
                       return $rel->withPath(self::removeDotSegments($rel->getPath()));
  | 
        
        
            | 
            | 
           67 | 
                   }
  | 
        
        
            | 
            | 
           68 | 
              | 
        
        
            | 
            | 
           69 | 
                   if ($rel->getAuthority() != '') {
  | 
        
        
            | 
            | 
           70 | 
                       $targetAuthority = $rel->getAuthority();
  | 
        
        
            | 
            | 
           71 | 
                       $targetPath = self::removeDotSegments($rel->getPath());
  | 
        
        
            | 
            | 
           72 | 
                       $targetQuery = $rel->getQuery();
  | 
        
        
            | 
            | 
           73 | 
                   } else {
  | 
        
        
            | 
            | 
           74 | 
                       $targetAuthority = $base->getAuthority();
  | 
        
        
            | 
            | 
           75 | 
                       if ($rel->getPath() === '') {
  | 
        
        
            | 
            | 
           76 | 
                           $targetPath = $base->getPath();
  | 
        
        
            | 
            | 
           77 | 
                           $targetQuery = $rel->getQuery() != '' ? $rel->getQuery() : $base->getQuery();
  | 
        
        
            | 
            | 
           78 | 
                       } else {
  | 
        
        
            | 
            | 
           79 | 
                           if ($rel->getPath()[0] === '/') {
  | 
        
        
            | 
            | 
           80 | 
                               $targetPath = $rel->getPath();
  | 
        
        
            | 
            | 
           81 | 
                           } else {
  | 
        
        
            | 
            | 
           82 | 
                               if ($targetAuthority != '' && $base->getPath() === '') {
  | 
        
        
           | 1441 | 
           ariadna | 
           83 | 
                                   $targetPath = '/'.$rel->getPath();
  | 
        
        
           | 1 | 
           efrain | 
           84 | 
                               } else {
  | 
        
        
            | 
            | 
           85 | 
                                   $lastSlashPos = strrpos($base->getPath(), '/');
  | 
        
        
            | 
            | 
           86 | 
                                   if ($lastSlashPos === false) {
  | 
        
        
            | 
            | 
           87 | 
                                       $targetPath = $rel->getPath();
  | 
        
        
            | 
            | 
           88 | 
                                   } else {
  | 
        
        
           | 1441 | 
           ariadna | 
           89 | 
                                       $targetPath = substr($base->getPath(), 0, $lastSlashPos + 1).$rel->getPath();
  | 
        
        
           | 1 | 
           efrain | 
           90 | 
                                   }
  | 
        
        
            | 
            | 
           91 | 
                               }
  | 
        
        
            | 
            | 
           92 | 
                           }
  | 
        
        
            | 
            | 
           93 | 
                           $targetPath = self::removeDotSegments($targetPath);
  | 
        
        
            | 
            | 
           94 | 
                           $targetQuery = $rel->getQuery();
  | 
        
        
            | 
            | 
           95 | 
                       }
  | 
        
        
            | 
            | 
           96 | 
                   }
  | 
        
        
            | 
            | 
           97 | 
              | 
        
        
            | 
            | 
           98 | 
                   return new Uri(Uri::composeComponents(
  | 
        
        
            | 
            | 
           99 | 
                       $base->getScheme(),
  | 
        
        
            | 
            | 
           100 | 
                       $targetAuthority,
  | 
        
        
            | 
            | 
           101 | 
                       $targetPath,
  | 
        
        
            | 
            | 
           102 | 
                       $targetQuery,
  | 
        
        
            | 
            | 
           103 | 
                       $rel->getFragment()
  | 
        
        
            | 
            | 
           104 | 
                   ));
  | 
        
        
            | 
            | 
           105 | 
               }
  | 
        
        
            | 
            | 
           106 | 
              | 
        
        
            | 
            | 
           107 | 
               /**
  | 
        
        
            | 
            | 
           108 | 
                * Returns the target URI as a relative reference from the base URI.
  | 
        
        
            | 
            | 
           109 | 
                *
  | 
        
        
            | 
            | 
           110 | 
                * This method is the counterpart to resolve():
  | 
        
        
            | 
            | 
           111 | 
                *
  | 
        
        
            | 
            | 
           112 | 
                *    (string) $target === (string) UriResolver::resolve($base, UriResolver::relativize($base, $target))
  | 
        
        
            | 
            | 
           113 | 
                *
  | 
        
        
            | 
            | 
           114 | 
                * One use-case is to use the current request URI as base URI and then generate relative links in your documents
  | 
        
        
            | 
            | 
           115 | 
                * to reduce the document size or offer self-contained downloadable document archives.
  | 
        
        
            | 
            | 
           116 | 
                *
  | 
        
        
            | 
            | 
           117 | 
                *    $base = new Uri('http://example.com/a/b/');
  | 
        
        
            | 
            | 
           118 | 
                *    echo UriResolver::relativize($base, new Uri('http://example.com/a/b/c'));  // prints 'c'.
  | 
        
        
            | 
            | 
           119 | 
                *    echo UriResolver::relativize($base, new Uri('http://example.com/a/x/y'));  // prints '../x/y'.
  | 
        
        
            | 
            | 
           120 | 
                *    echo UriResolver::relativize($base, new Uri('http://example.com/a/b/?q')); // prints '?q'.
  | 
        
        
            | 
            | 
           121 | 
                *    echo UriResolver::relativize($base, new Uri('http://example.org/a/b/'));   // prints '//example.org/a/b/'.
  | 
        
        
            | 
            | 
           122 | 
                *
  | 
        
        
            | 
            | 
           123 | 
                * This method also accepts a target that is already relative and will try to relativize it further. Only a
  | 
        
        
            | 
            | 
           124 | 
                * relative-path reference will be returned as-is.
  | 
        
        
            | 
            | 
           125 | 
                *
  | 
        
        
            | 
            | 
           126 | 
                *    echo UriResolver::relativize($base, new Uri('/a/b/c'));  // prints 'c' as well
  | 
        
        
            | 
            | 
           127 | 
                */
  | 
        
        
            | 
            | 
           128 | 
               public static function relativize(UriInterface $base, UriInterface $target): UriInterface
  | 
        
        
            | 
            | 
           129 | 
               {
  | 
        
        
           | 1441 | 
           ariadna | 
           130 | 
                   if ($target->getScheme() !== ''
  | 
        
        
            | 
            | 
           131 | 
                       && ($base->getScheme() !== $target->getScheme() || $target->getAuthority() === '' && $base->getAuthority() !== '')
  | 
        
        
           | 1 | 
           efrain | 
           132 | 
                   ) {
  | 
        
        
            | 
            | 
           133 | 
                       return $target;
  | 
        
        
            | 
            | 
           134 | 
                   }
  | 
        
        
            | 
            | 
           135 | 
              | 
        
        
            | 
            | 
           136 | 
                   if (Uri::isRelativePathReference($target)) {
  | 
        
        
            | 
            | 
           137 | 
                       // As the target is already highly relative we return it as-is. It would be possible to resolve
  | 
        
        
            | 
            | 
           138 | 
                       // the target with `$target = self::resolve($base, $target);` and then try make it more relative
  | 
        
        
            | 
            | 
           139 | 
                       // by removing a duplicate query. But let's not do that automatically.
  | 
        
        
            | 
            | 
           140 | 
                       return $target;
  | 
        
        
            | 
            | 
           141 | 
                   }
  | 
        
        
            | 
            | 
           142 | 
              | 
        
        
            | 
            | 
           143 | 
                   if ($target->getAuthority() !== '' && $base->getAuthority() !== $target->getAuthority()) {
  | 
        
        
            | 
            | 
           144 | 
                       return $target->withScheme('');
  | 
        
        
            | 
            | 
           145 | 
                   }
  | 
        
        
            | 
            | 
           146 | 
              | 
        
        
            | 
            | 
           147 | 
                   // We must remove the path before removing the authority because if the path starts with two slashes, the URI
  | 
        
        
            | 
            | 
           148 | 
                   // would turn invalid. And we also cannot set a relative path before removing the authority, as that is also
  | 
        
        
            | 
            | 
           149 | 
                   // invalid.
  | 
        
        
            | 
            | 
           150 | 
                   $emptyPathUri = $target->withScheme('')->withPath('')->withUserInfo('')->withPort(null)->withHost('');
  | 
        
        
            | 
            | 
           151 | 
              | 
        
        
            | 
            | 
           152 | 
                   if ($base->getPath() !== $target->getPath()) {
  | 
        
        
            | 
            | 
           153 | 
                       return $emptyPathUri->withPath(self::getRelativePath($base, $target));
  | 
        
        
            | 
            | 
           154 | 
                   }
  | 
        
        
            | 
            | 
           155 | 
              | 
        
        
            | 
            | 
           156 | 
                   if ($base->getQuery() === $target->getQuery()) {
  | 
        
        
            | 
            | 
           157 | 
                       // Only the target fragment is left. And it must be returned even if base and target fragment are the same.
  | 
        
        
            | 
            | 
           158 | 
                       return $emptyPathUri->withQuery('');
  | 
        
        
            | 
            | 
           159 | 
                   }
  | 
        
        
            | 
            | 
           160 | 
              | 
        
        
            | 
            | 
           161 | 
                   // If the base URI has a query but the target has none, we cannot return an empty path reference as it would
  | 
        
        
            | 
            | 
           162 | 
                   // inherit the base query component when resolving.
  | 
        
        
            | 
            | 
           163 | 
                   if ($target->getQuery() === '') {
  | 
        
        
            | 
            | 
           164 | 
                       $segments = explode('/', $target->getPath());
  | 
        
        
            | 
            | 
           165 | 
                       /** @var string $lastSegment */
  | 
        
        
            | 
            | 
           166 | 
                       $lastSegment = end($segments);
  | 
        
        
            | 
            | 
           167 | 
              | 
        
        
            | 
            | 
           168 | 
                       return $emptyPathUri->withPath($lastSegment === '' ? './' : $lastSegment);
  | 
        
        
            | 
            | 
           169 | 
                   }
  | 
        
        
            | 
            | 
           170 | 
              | 
        
        
            | 
            | 
           171 | 
                   return $emptyPathUri;
  | 
        
        
            | 
            | 
           172 | 
               }
  | 
        
        
            | 
            | 
           173 | 
              | 
        
        
            | 
            | 
           174 | 
               private static function getRelativePath(UriInterface $base, UriInterface $target): string
  | 
        
        
            | 
            | 
           175 | 
               {
  | 
        
        
            | 
            | 
           176 | 
                   $sourceSegments = explode('/', $base->getPath());
  | 
        
        
            | 
            | 
           177 | 
                   $targetSegments = explode('/', $target->getPath());
  | 
        
        
            | 
            | 
           178 | 
                   array_pop($sourceSegments);
  | 
        
        
            | 
            | 
           179 | 
                   $targetLastSegment = array_pop($targetSegments);
  | 
        
        
            | 
            | 
           180 | 
                   foreach ($sourceSegments as $i => $segment) {
  | 
        
        
            | 
            | 
           181 | 
                       if (isset($targetSegments[$i]) && $segment === $targetSegments[$i]) {
  | 
        
        
            | 
            | 
           182 | 
                           unset($sourceSegments[$i], $targetSegments[$i]);
  | 
        
        
            | 
            | 
           183 | 
                       } else {
  | 
        
        
            | 
            | 
           184 | 
                           break;
  | 
        
        
            | 
            | 
           185 | 
                       }
  | 
        
        
            | 
            | 
           186 | 
                   }
  | 
        
        
            | 
            | 
           187 | 
                   $targetSegments[] = $targetLastSegment;
  | 
        
        
           | 1441 | 
           ariadna | 
           188 | 
                   $relativePath = str_repeat('../', count($sourceSegments)).implode('/', $targetSegments);
  | 
        
        
           | 1 | 
           efrain | 
           189 | 
              | 
        
        
            | 
            | 
           190 | 
                   // A reference to am empty last segment or an empty first sub-segment must be prefixed with "./".
  | 
        
        
            | 
            | 
           191 | 
                   // This also applies to a segment with a colon character (e.g., "file:colon") that cannot be used
  | 
        
        
            | 
            | 
           192 | 
                   // as the first segment of a relative-path reference, as it would be mistaken for a scheme name.
  | 
        
        
            | 
            | 
           193 | 
                   if ('' === $relativePath || false !== strpos(explode('/', $relativePath, 2)[0], ':')) {
  | 
        
        
            | 
            | 
           194 | 
                       $relativePath = "./$relativePath";
  | 
        
        
            | 
            | 
           195 | 
                   } elseif ('/' === $relativePath[0]) {
  | 
        
        
            | 
            | 
           196 | 
                       if ($base->getAuthority() != '' && $base->getPath() === '') {
  | 
        
        
            | 
            | 
           197 | 
                           // In this case an extra slash is added by resolve() automatically. So we must not add one here.
  | 
        
        
            | 
            | 
           198 | 
                           $relativePath = ".$relativePath";
  | 
        
        
            | 
            | 
           199 | 
                       } else {
  | 
        
        
            | 
            | 
           200 | 
                           $relativePath = "./$relativePath";
  | 
        
        
            | 
            | 
           201 | 
                       }
  | 
        
        
            | 
            | 
           202 | 
                   }
  | 
        
        
            | 
            | 
           203 | 
              | 
        
        
            | 
            | 
           204 | 
                   return $relativePath;
  | 
        
        
            | 
            | 
           205 | 
               }
  | 
        
        
            | 
            | 
           206 | 
              | 
        
        
            | 
            | 
           207 | 
               private function __construct()
  | 
        
        
            | 
            | 
           208 | 
               {
  | 
        
        
            | 
            | 
           209 | 
                   // cannot be instantiated
  | 
        
        
            | 
            | 
           210 | 
               }
  | 
        
        
            | 
            | 
           211 | 
           }
  |