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