Subversion Repositories php-qbpwcf

Rev

Details | Last modification | View Log | RSS feed

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