M src/Url.php => src/Url.php +8 -8
@@ 163,18 163,18 @@ final class Url implements \JsonSerializable
{
$hasTrailingSlash = $this->path[-1] === '/';
- $explode = explode('/', $this->path);
- foreach ($explode as $i => $part) {
+ $parts = explode('/', $this->path);
+ $result = ['/'];
+ foreach ($parts as $part) {
if (in_array($part, ['', '.'])) {
- unset($explode[$i]);
continue;
- }
- if ($part === '..') {
- unset($explode[$i - 1]);
- unset($explode[$i]);
+ } else if ($part === '..') {
+ array_pop($result);
+ } else {
+ $result[] = $part;
}
}
- $path = implode('/', $explode);
+ $path = implode('/', $result);
if ($hasTrailingSlash) {
$path .= '/';
}
M test/url.php => test/url.php +2 -1
@@ 12,7 12,7 @@ assertEquals('https://example.com', (string)url('https://example.com//'));
assertEquals('https://example.com', (string)url('https://example.com/./'));
assertEquals('https://example.com', (string)url('https://example.com//./'));
assertEquals('https://example.com', (string)url('https://example.com/.///'));
-assertEquals('https://example.com', (string)url('https://example.com/../'));
+assertEquals('https://example.com/a', (string)url('https://example.com/../a'));
assertEquals('https://example.com', (string)url('https://example.com/foo/..'));
assertEquals('https://example.com/foo', (string)url('https://example.com/foo/bar/..'));
assertEquals('https://example.com/foo?bar=', (string)url('https://example.com/foo?bar'));
@@ 23,3 23,4 @@ assertEquals('https://example.com/foo', (string)url('https://example.com/foo/bar
assertEquals('https://example.com', (string)url('https://example.com/../..'));
assertEquals('https://example.com/foo?foo=bar', (string) url('https://example.com/foo?foo=bar'));
assertEquals('https://blog.zulip.com/2022/05/05/public-access-option', (string) url('https://blog.zulip.com/2022/05/05/public-access-option'));
+assertEquals('https://kevincox.ca/feed.atom', (string) url('https://kevincox.ca/2022/05/../../feed.atom'));