• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

voku / httpful / 5593279780

pending completion
5593279780

push

github

voku
Merge branch 'master' of ssh://github.com/voku/httpful

* 'master' of ssh://github.com/voku/httpful:
  Fix CS again
  Fix CS
  Fix typos
  Fix curly braces in strings

12 of 12 new or added lines in 3 files covered. (100.0%)

1653 of 2504 relevant lines covered (66.01%)

80.06 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

43.81
/src/Httpful/UriResolver.php
1
<?php
2

3
declare(strict_types=1);
4

5
namespace Httpful;
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
 * @see https://tools.ietf.org/html/rfc3986#section-5
13
 *
14
 * @internal
15
 */
16
final class UriResolver
17
{
18
    private function __construct()
19
    {
20
        // cannot be instantiated
21
    }
×
22

23
    /**
24
     * Combine url components into a url.
25
     *
26
     * @param mixed $parsed_url
27
     *
28
     * @return string
29
     */
30
    public static function unparseUrl($parsed_url)
31
    {
32
        $scheme = isset($parsed_url['scheme']) ? $parsed_url['scheme'] . '://' : '';
×
33
        $user = $parsed_url['user'] ?? '';
×
34
        $pass = isset($parsed_url['pass']) ? ':' . $parsed_url['pass'] : '';
×
35
        $pass = ($user || $pass) ? $pass . '@' : '';
×
36
        $host = $parsed_url['host'] ?? '';
×
37
        $port = isset($parsed_url['port']) ? ':' . $parsed_url['port'] : '';
×
38
        $path = $parsed_url['path'] ?? '';
×
39
        $query = isset($parsed_url['query']) ? '?' . $parsed_url['query'] : '';
×
40
        $fragment = isset($parsed_url['fragment']) ? '#' . $parsed_url['fragment'] : '';
×
41

42
        return $scheme . $user . $pass . $host . $port . $path . $query . $fragment;
×
43
    }
44

45
    /**
46
     * Returns the target URI as a relative reference from the base URI.
47
     *
48
     * This method is the counterpart to resolve():
49
     *
50
     *    (string) $target === (string) UriResolver::resolve($base, UriResolver::relativize($base, $target))
51
     *
52
     * One use-case is to use the current request URI as base URI and then generate relative links in your documents
53
     * to reduce the document size or offer self-contained downloadable document archives.
54
     *
55
     *    $base = new Uri('http://example.com/a/b/');
56
     *    echo UriResolver::relativize($base, new Uri('http://example.com/a/b/c'));  // prints 'c'.
57
     *    echo UriResolver::relativize($base, new Uri('http://example.com/a/x/y'));  // prints '../x/y'.
58
     *    echo UriResolver::relativize($base, new Uri('http://example.com/a/b/?q')); // prints '?q'.
59
     *    echo UriResolver::relativize($base, new Uri('http://example.org/a/b/'));   // prints '//example.org/a/b/'.
60
     *
61
     * This method also accepts a target that is already relative and will try to relativize it further. Only a
62
     * relative-path reference will be returned as-is.
63
     *
64
     *    echo UriResolver::relativize($base, new Uri('/a/b/c'));  // prints 'c' as well
65
     *
66
     * @param UriInterface $base   Base URI
67
     * @param UriInterface $target Target URI
68
     *
69
     * @return UriInterface The relative URI reference
70
     */
71
    public static function relativize(UriInterface $base, UriInterface $target): UriInterface
72
    {
73
        if (
74
            $target->getScheme() !== ''
×
75
            &&
76
            (
77
                $base->getScheme() !== $target->getScheme()
×
78
                ||
79
                ($target->getAuthority() === '' && $base->getAuthority() !== '')
×
80
            )
81
        ) {
82
            return $target;
×
83
        }
84

85
        if (Uri::isRelativePathReference($target)) {
×
86
            // As the target is already highly relative we return it as-is. It would be possible to resolve
87
            // the target with `$target = self::resolve($base, $target);` and then try make it more relative
88
            // by removing a duplicate query. But let's not do that automatically.
89
            return $target;
×
90
        }
91

92
        $authority = $target->getAuthority();
×
93
        if (
94
            $authority !== ''
×
95
            &&
96
            $base->getAuthority() !== $authority
×
97
        ) {
98
            return $target->withScheme('');
×
99
        }
100

101
        // We must remove the path before removing the authority because if the path starts with two slashes, the URI
102
        // would turn invalid. And we also cannot set a relative path before removing the authority, as that is also
103
        // invalid.
104
        $emptyPathUri = $target->withScheme('')->withPath('')->withUserInfo('')->withPort(null)->withHost('');
×
105

106
        if ($base->getPath() !== $target->getPath()) {
×
107
            return $emptyPathUri->withPath(self::getRelativePath($base, $target));
×
108
        }
109

110
        if ($base->getQuery() === $target->getQuery()) {
×
111
            // Only the target fragment is left. And it must be returned even if base and target fragment are the same.
112
            return $emptyPathUri->withQuery('');
×
113
        }
114

115
        // If the base URI has a query but the target has none, we cannot return an empty path reference as it would
116
        // inherit the base query component when resolving.
117
        if ($target->getQuery() === '') {
×
118
            $segments = \explode('/', $target->getPath());
×
119
            $lastSegment = \end($segments);
×
120

121
            return $emptyPathUri->withPath($lastSegment === '' ? './' : $lastSegment);
×
122
        }
123

124
        return $emptyPathUri;
×
125
    }
126

127
    /**
128
     * Removes dot segments from a path and returns the new path.
129
     *
130
     * @param string $path
131
     *
132
     * @return string
133
     *
134
     * @see http://tools.ietf.org/html/rfc3986#section-5.2.4
135
     */
136
    public static function removeDotSegments($path): string
137
    {
138
        if ($path === '' || $path === '/') {
184✔
139
            return $path;
84✔
140
        }
141

142
        $results = [];
100✔
143
        $segments = \explode('/', $path);
100✔
144
        $segment = '';
100✔
145
        foreach ($segments as $segment) {
100✔
146
            if ($segment === '..') {
100✔
147
                \array_pop($results);
×
148
            } elseif ($segment !== '.') {
100✔
149
                $results[] = $segment;
100✔
150
            }
151
        }
152

153
        $newPath = \implode('/', $results);
100✔
154

155
        if (
156
            $path[0] === '/'
100✔
157
            &&
158
            (
159
                !isset($newPath[0])
98✔
160
                ||
75✔
161
                $newPath[0] !== '/'
100✔
162
            )
163
        ) {
164
            // Re-add the leading slash if necessary for cases like "/.."
165
            $newPath = '/' . $newPath;
×
166
        } elseif (
167
            $newPath !== ''
100✔
168
            &&
169
            (
170
                $segment
100✔
171
                &&
75✔
172
                (
75✔
173
                    $segment === '.'
100✔
174
                    ||
75✔
175
                    $segment === '..'
100✔
176
                )
75✔
177
            )
178
        ) {
179
            // Add the trailing slash if necessary
180
            // If newPath is not empty, then $segment must be set and is the last segment from the foreach
181
            $newPath .= '/';
×
182
        }
183

184
        return $newPath;
100✔
185
    }
186

187
    /**
188
     * Converts the relative URI into a new URI that is resolved against the base URI.
189
     *
190
     * @param UriInterface $base Base URI
191
     * @param UriInterface $rel  Relative URI
192
     *
193
     * @return UriInterface
194
     *
195
     * @see http://tools.ietf.org/html/rfc3986#section-5.2
196
     */
197
    public static function resolve(UriInterface $base, UriInterface $rel): UriInterface
198
    {
199
        if ((string) $rel === '') {
460✔
200
            // we can simply return the same base URI instance for this same-document reference
201
            return $base;
460✔
202
        }
203

204
        if ($rel->getScheme() !== '') {
184✔
205
            return $rel->withPath(self::removeDotSegments($rel->getPath()));
180✔
206
        }
207

208
        if ($rel->getAuthority() !== '') {
4✔
209
            $targetAuthority = $rel->getAuthority();
×
210
            $targetPath = self::removeDotSegments($rel->getPath());
×
211
            $targetQuery = $rel->getQuery();
×
212
        } else {
213
            $targetAuthority = $base->getAuthority();
4✔
214
            if ($rel->getPath() === '') {
4✔
215
                $targetPath = $base->getPath();
×
216
                $targetQuery = $rel->getQuery() !== '' ? $rel->getQuery() : $base->getQuery();
×
217
            } else {
218
                if ($rel->getPath()[0] === '/') {
4✔
219
                    $targetPath = $rel->getPath();
×
220
                } elseif ($targetAuthority !== '' && $base->getPath() === '') {
4✔
221
                    $targetPath = '/' . $rel->getPath();
×
222
                } else {
223
                    $lastSlashPos = \strrpos($base->getPath(), '/');
4✔
224
                    if ($lastSlashPos === false) {
4✔
225
                        $targetPath = $rel->getPath();
4✔
226
                    } else {
227
                        $targetPath = \substr($base->getPath(), 0, $lastSlashPos + 1) . $rel->getPath();
×
228
                    }
229
                }
230
                $targetPath = self::removeDotSegments($targetPath);
4✔
231
                $targetQuery = $rel->getQuery();
4✔
232
            }
233
        }
234

235
        return new Uri(
4✔
236
            Uri::composeComponents(
4✔
237
                $base->getScheme(),
4✔
238
                $targetAuthority,
4✔
239
                $targetPath,
4✔
240
                $targetQuery,
4✔
241
                $rel->getFragment()
4✔
242
            )
3✔
243
        );
3✔
244
    }
245

246
    private static function getRelativePath(UriInterface $base, UriInterface $target): string
247
    {
248
        $sourceSegments = \explode('/', $base->getPath());
×
249
        $targetSegments = \explode('/', $target->getPath());
×
250

251
        \array_pop($sourceSegments);
×
252

253
        $targetLastSegment = \array_pop($targetSegments);
×
254
        foreach ($sourceSegments as $i => $segment) {
×
255
            if (isset($targetSegments[$i]) && $segment === $targetSegments[$i]) {
×
256
                unset($sourceSegments[$i], $targetSegments[$i]);
×
257
            } else {
258
                break;
×
259
            }
260
        }
261

262
        $targetSegments[] = $targetLastSegment;
×
263

264
        $relativePath = \str_repeat('../', \count($sourceSegments)) . \implode('/', $targetSegments);
×
265

266
        // A reference to am empty last segment or an empty first sub-segment must be prefixed with "./".
267
        // This also applies to a segment with a colon character (e.g., "file:colon") that cannot be used
268
        // as the first segment of a relative-path reference, as it would be mistaken for a scheme name.
269
        /* @phpstan-ignore-next-line | FP? */
270
        if ($relativePath === '' || \strpos(\explode('/', $relativePath, 2)[0], ':') !== false) {
×
271
            $relativePath = "./{$relativePath}";
×
272
        } elseif ($relativePath[0] === '/') {
×
273
            if ($base->getAuthority() !== '' && $base->getPath() === '') {
×
274
                // In this case an extra slash is added by resolve() automatically. So we must not add one here.
275
                $relativePath = ".{$relativePath}";
×
276
            } else {
277
                $relativePath = "./{$relativePath}";
×
278
            }
279
        }
280

281
        return $relativePath;
×
282
    }
283
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc