Skip to content

Commit

Permalink
UrlImmutable, UrlScript: added resolve()
Browse files Browse the repository at this point in the history
  • Loading branch information
dg committed Nov 5, 2024
1 parent ad29269 commit 5be5e6e
Show file tree
Hide file tree
Showing 4 changed files with 185 additions and 0 deletions.
42 changes: 42 additions & 0 deletions src/Http/UrlImmutable.php
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,48 @@ public function isEqual(string|Url|self $url): bool
}


/**
* Resolves relative URLs in the same way as browser. If path is relative, it is resolved against
* base URL, if begins with /, it is resolved against the host root.
*/
public function resolve(string $reference): self
{
$ref = new self($reference);
if ($ref->scheme !== '') {
$ref->path = Url::removeDotSegments($ref->path);
return $ref;
}

$ref->scheme = $this->scheme;

if ($ref->host !== '') {
$ref->path = Url::removeDotSegments($ref->path);
return $ref;
}

$ref->host = $this->host;
$ref->port = $this->port;

if ($ref->path === '') {
$ref->path = $this->path;
$ref->query = $ref->query ?: $this->query;
} elseif (str_starts_with($ref->path, '/')) {
$ref->path = Url::removeDotSegments($ref->path);
} else {
$ref->path = Url::removeDotSegments($this->mergePath($ref->path));
}
return $ref;
}


/** @internal */
protected function mergePath(string $path): string
{
$pos = strrpos($this->path, '/');
return $pos === false ? $path : substr($this->path, 0, $pos + 1) . $path;
}


public function jsonSerialize(): string
{
return $this->getAbsoluteUrl();
Expand Down
5 changes: 5 additions & 0 deletions src/Http/UrlScript.php
Original file line number Diff line number Diff line change
Expand Up @@ -106,4 +106,9 @@ public function getPathInfo(): string
}


/** @internal */
protected function mergePath(string $path): string
{
return $this->basePath . $path;
}
}
69 changes: 69 additions & 0 deletions tests/Http/UrlImmutable.resolve.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
<?php

declare(strict_types=1);

use Nette\Http\UrlImmutable;
use Tester\Assert;

require __DIR__ . '/../bootstrap.php';


$tests = [
'https://example.com/path/' => [
// absolute URLs with various schemes
'a:' => 'a:',
'a:b' => 'a:b',
'http://other.com/test' => 'http://other.com/test',
'https://other.com/test' => 'https://other.com/test',
'ftp://other.com/test' => 'ftp://other.com/test',

// protocol-relative URLs - keep current scheme
'//other.com/test' => 'https://other.com/test',

// root-relative paths
'/test' => 'https://example.com/test',
'/test/' => 'https://example.com/test/',

// relative paths
'sibling' => 'https://example.com/path/sibling',
'../parent' => 'https://example.com/parent',
'child/' => 'https://example.com/path/child/',
],

// base dir with query string and fragment
'https://example.com/path/?q=123#frag' => [
'' => 'https://example.com/path/?q=123',
'file' => 'https://example.com/path/file',
'./file' => 'https://example.com/path/file',
'/root' => 'https://example.com/root',
'subdir/?q=456' => 'https://example.com/path/subdir/?q=456',
'subdir/#frag' => 'https://example.com/path/subdir/#frag',
'../file' => 'https://example.com/file',
'/../file' => 'https://example.com/file',
'file?newq=/..#newfrag/..' => 'https://example.com/path/file?newq=%2F..#newfrag/..',
'?newq=/..' => 'https://example.com/path/?newq=%2F..',
'#newfrag/..' => 'https://example.com/path/?q=123#newfrag/..',
],

// base file with query string and fragment
'https://example.com/path/file?q=123#frag' => [
'' => 'https://example.com/path/file?q=123',
'file' => 'https://example.com/path/file',
'./file' => 'https://example.com/path/file',
'/root' => 'https://example.com/root',
'subdir/file?q=123' => 'https://example.com/path/subdir/file?q=123',
'subdir/file#frag' => 'https://example.com/path/subdir/file#frag',
'../file' => 'https://example.com/file',
'/../file' => 'https://example.com/file',
'?newq=/..' => 'https://example.com/path/file?newq=%2F..',
'#newfrag/..' => 'https://example.com/path/file?q=123#newfrag/..',
],
];


foreach ($tests as $base => $paths) {
$url = new UrlImmutable($base);
foreach ($paths as $path => $expected) {
Assert::same($expected, (string) $url->resolve($path), "Base: $base, Reference: $path");
}
}
69 changes: 69 additions & 0 deletions tests/Http/UrlScript.resolve.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
<?php

declare(strict_types=1);

use Nette\Http\UrlScript;
use Tester\Assert;

require __DIR__ . '/../bootstrap.php';


$tests = [
'https://example.com/path/' => [
// absolute URLs with various schemes
'a:' => 'a:',
'a:b' => 'a:b',
'http://other.com/test' => 'http://other.com/test',
'https://other.com/test' => 'https://other.com/test',
'ftp://other.com/test' => 'ftp://other.com/test',

// protocol-relative URLs - keep current scheme
'//other.com/test' => 'https://other.com/test',

// root-relative paths
'/test' => 'https://example.com/test',
'/test/' => 'https://example.com/test/',

// relative paths
'sibling' => 'https://example.com/sibling',
'../parent' => 'https://example.com/parent',
'child/' => 'https://example.com/child/',
],

// base dir with query string and fragment
'https://example.com/path/?q=123#frag' => [
'' => 'https://example.com/path/?q=123',
'file' => 'https://example.com/file',
'./file' => 'https://example.com/file',
'/root' => 'https://example.com/root',
'subdir/?q=456' => 'https://example.com/subdir/?q=456',
'subdir/#frag' => 'https://example.com/subdir/#frag',
'../file' => 'https://example.com/file',
'/../file' => 'https://example.com/file',
'file?newq=/..#newfrag/..' => 'https://example.com/file?newq=%2F..#newfrag/..',
'?newq=/..' => 'https://example.com/path/?newq=%2F..',
'#newfrag/..' => 'https://example.com/path/?q=123#newfrag/..',
],

// base file with query string and fragment
'https://example.com/path/file?q=123#frag' => [
'' => 'https://example.com/path/file?q=123',
'file' => 'https://example.com/file',
'./file' => 'https://example.com/file',
'/root' => 'https://example.com/root',
'subdir/file?q=123' => 'https://example.com/subdir/file?q=123',
'subdir/file#frag' => 'https://example.com/subdir/file#frag',
'../file' => 'https://example.com/file',
'/../file' => 'https://example.com/file',
'?newq=/..' => 'https://example.com/path/file?newq=%2F..',
'#newfrag/..' => 'https://example.com/path/file?q=123#newfrag/..',
],
];


foreach ($tests as $base => $paths) {
$url = new UrlScript($base, '/');
foreach ($paths as $path => $expected) {
Assert::same($expected, (string) $url->resolve($path), "Base: $base, Reference: $path");
}
}

0 comments on commit 5be5e6e

Please sign in to comment.