Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 11 additions & 21 deletions packages/http/src/IsRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

use Tempest\Http\Cookie\Cookie;
use Tempest\Http\Session\Session;
use Tempest\Support\Uri\Uri;
use Tempest\Validation\SkipValidation;

use function Tempest\Container\get;
Expand Down Expand Up @@ -64,8 +65,16 @@ public function __construct(
$this->files = $files;
$this->raw = $raw;

$this->path ??= $this->resolvePath();
$this->query ??= $this->resolveQuery();
if ($this->method === Method::CONNECT) {
$this->path ??= '';
$this->query ??= [];

return;
}

$uri = Uri::from($this->uri);
Comment thread
xHeaven marked this conversation as resolved.
$this->path ??= rawurldecode($uri->path ?? '');
$this->query ??= $uri->query;
}

public function get(string $key, mixed $default = null): mixed
Expand Down Expand Up @@ -94,25 +103,6 @@ public function getCookie(string $name): ?Cookie
return $this->cookies[$name] ?? null;
}

private function resolvePath(): string
{
$decodedUri = rawurldecode($this->uri);
$parsedUrl = parse_url($decodedUri);

return $parsedUrl['path'] ?? '';
}

private function resolveQuery(): array
{
$decodedUri = rawurldecode($this->uri);
$parsedUrl = parse_url($decodedUri);
$queryString = $parsedUrl['query'] ?? '';

parse_str($queryString, $query);

return $query;
}

public function has(string $key): bool
{
if ($this->hasBody($key)) {
Expand Down
16 changes: 16 additions & 0 deletions packages/http/tests/GenericRequestTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

use LogicException;
use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\Attributes\TestWith;
use PHPUnit\Framework\TestCase;
use Tempest\Http\ContentType;
use Tempest\Http\GenericRequest;
Expand Down Expand Up @@ -176,6 +177,21 @@ public function accepts_can_handle_priorities(): void
$this->assertTrue($request->accepts(ContentType::XML));
}

#[Test]
#[TestWith(['example.com:443'])]
#[TestWith(['[::1]:443'])]
public function connect_requests_skip_uri_parsing(string $uri): void
{
$request = new GenericRequest(
method: Method::CONNECT,
uri: $uri,
);

$this->assertSame($uri, $request->uri);
$this->assertSame('', $request->path);
$this->assertSame([], $request->query);
}

#[Test]
public function accepts_returns_true_on_first_match(): void
{
Expand Down
3 changes: 2 additions & 1 deletion packages/router/src/Static/StaticGenerateCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
use Tempest\Support\Filesystem;
use Tempest\Support\Regex;
use Tempest\Support\Str;
use Tempest\Support\Uri;
use Tempest\View\Exceptions\ViewCompilationFailed;
use Tempest\View\View;
use Tempest\View\ViewRenderer;
Expand Down Expand Up @@ -124,7 +125,7 @@ public function __invoke(
$params = [$params];
}

$uri = parse_url(uri($staticPage->handler, ...$params), PHP_URL_PATH);
$uri = Uri\get_path(uri($staticPage->handler, ...$params));

$fileName = $uri === '/'
? 'index.html'
Expand Down
55 changes: 43 additions & 12 deletions packages/support/src/Uri/Uri.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,7 @@
use Tempest\Support\Str;
use Tempest\Support\Str\ImmutableString;
use Tempest\Support\Str\MutableString;

use function parse_url;
use Uri\WhatWg\Url;

final class Uri implements Stringable
{
Expand Down Expand Up @@ -67,21 +66,53 @@ public function __construct(
*/
public static function from(string $uri): self
{
$parts = parse_url($uri);
$canBeAbsolute = $uri !== '' && $uri !== '*' && ! str_contains('/?#', $uri[0]);

if ($canBeAbsolute && ($url = Url::parse($uri)) instanceof Url) {
return new self(
scheme: $url->getScheme(),
user: $url->getUsername(),
password: $url->getPassword() === '' ? null : $url->getPassword(),
host: $url->getAsciiHost(),
port: $url->getPort(),
path: $url->getPath() ?: null,
queryString: $url->getQuery(),
fragment: $url->getFragment(),
);
}

static $baseUrl = null;

if ($parts === false) {
$url = Url::parse($uri, $baseUrl ??= new Url('http://localhost'));

if (! $url instanceof Url) {
return new self(path: $uri);
}

if (str_starts_with($uri, '//')) {
static $secureBaseUrl = null;

$httpsUrl = Url::parse($uri, $secureBaseUrl ??= new Url('https://localhost'));

return new self(
user: $url->getUsername(),
password: $url->getPassword() === '' ? null : $url->getPassword(),
host: $url->getAsciiHost(),
port: $url->getPort() ?? $httpsUrl?->getPort(),
path: $url->getPath() ?: null,
queryString: $url->getQuery(),
fragment: $url->getFragment(),
);
}

$path = str_starts_with($uri, '/')
? $url->getPath()
: Str\strip_start($url->getPath(), '/');

return new self(
scheme: $parts['scheme'] ?? null,
user: $parts['user'] ?? null,
password: $parts['pass'] ?? null,
host: $parts['host'] ?? null,
port: $parts['port'] ?? null,
path: $parts['path'] ?? null,
queryString: $parts['query'] ?? null,
fragment: $parts['fragment'] ?? null,
path: $path === '' ? null : $path,
queryString: $url->getQuery(),
fragment: $url->getFragment(),
);
}

Expand Down
68 changes: 35 additions & 33 deletions packages/support/tests/Uri/FunctionsTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,15 +31,15 @@
final class FunctionsTest extends TestCase
{
#[Test]
#[TestWith(['https://example.com', ['foo' => 'bar'], 'https://example.com?foo=bar'])]
#[TestWith(['https://example.com?existing=value', ['foo' => 'bar'], 'https://example.com?foo=bar'])]
#[TestWith(['https://example.com', ['foo' => 'bar', 'baz' => 'qux'], 'https://example.com?foo=bar&baz=qux'])]
#[TestWith(['https://example.com', ['foo' => 'bar'], 'https://example.com/?foo=bar'])]
#[TestWith(['https://example.com?existing=value', ['foo' => 'bar'], 'https://example.com/?foo=bar'])]
#[TestWith(['https://example.com', ['foo' => 'bar', 'baz' => 'qux'], 'https://example.com/?foo=bar&baz=qux'])]
#[TestWith(['https://example.com/path', [], 'https://example.com/path'])]
#[TestWith(['malformed-uri', ['foo' => 'bar'], 'malformed-uri?foo=bar'])]
#[TestWith(['https://example.com', ['foo'], 'https://example.com?foo'])]
#[TestWith(['https://example.com', ['foo' => true], 'https://example.com?foo=true'])]
#[TestWith(['https://example.com', ['foo' => false], 'https://example.com?foo=false'])]
#[TestWith(['https://example.com', ['foo' => ['bar' => 'baz']], 'https://example.com?foo%5Bbar%5D=baz'])]
#[TestWith(['https://example.com', ['foo'], 'https://example.com/?foo'])]
#[TestWith(['https://example.com', ['foo' => true], 'https://example.com/?foo=true'])]
#[TestWith(['https://example.com', ['foo' => false], 'https://example.com/?foo=false'])]
#[TestWith(['https://example.com', ['foo' => ['bar' => 'baz']], 'https://example.com/?foo%5Bbar%5D=baz'])]
public function set_query(string $uri, array $query, string $expected): void
{
$this->assertSame($expected, set_query($uri, ...$query));
Expand All @@ -57,8 +57,8 @@ public function get_query(string $uri, array $expected): void
}

#[Test]
#[TestWith(['https://example.com', 'frieren', 'https://example.com#frieren'])]
#[TestWith(['https://example.com#old', 'stark', 'https://example.com#stark'])]
#[TestWith(['https://example.com', 'frieren', 'https://example.com/#frieren'])]
#[TestWith(['https://example.com#old', 'stark', 'https://example.com/#stark'])]
#[TestWith(['https://example.com/path?query=value', 'fern', 'https://example.com/path?query=value#fern'])]
#[TestWith(['malformed-uri', 'fragment', 'malformed-uri#fragment'])]
public function set_fragment(string $uri, string $fragment, string $expected): void
Expand All @@ -77,9 +77,9 @@ public function get_fragment(string $uri, ?string $expected): void
}

#[Test]
#[TestWith(['https://example.com', 'flamme.org', 'https://flamme.org'])]
#[TestWith(['https://example.com', 'flamme.org', 'https://flamme.org/'])]
#[TestWith(['https://old.com/path', 'neue.com', 'https://neue.com/path'])]
#[TestWith(['http://user:pass@old.com:8080', 'serie.com', 'http://user:pass@serie.com:8080'])]
#[TestWith(['http://user:pass@old.com:8080', 'serie.com', 'http://user:pass@serie.com:8080/'])]
#[TestWith(['malformed-uri', 'domain.com', '//domain.com/malformed-uri'])]
public function set_host(string $uri, string $host, string $expected): void
{
Expand All @@ -98,9 +98,9 @@ public function get_host(string $uri, ?string $expected): void
}

#[Test]
#[TestWith(['https://example.com', 'http', 'http://example.com'])]
#[TestWith(['http://example.com', 'https', 'https://example.com'])]
#[TestWith(['ftp://files.example.com', 'sftp', 'sftp://files.example.com'])]
#[TestWith(['https://example.com', 'http', 'http://example.com/'])]
#[TestWith(['http://example.com', 'https', 'https://example.com/'])]
#[TestWith(['ftp://files.example.com', 'sftp', 'sftp://files.example.com/'])]
#[TestWith(['malformed-uri', 'https', 'https:malformed-uri'])]
public function set_scheme(string $uri, string $scheme, string $expected): void
{
Expand All @@ -119,9 +119,9 @@ public function get_scheme(string $uri, ?string $expected): void
}

#[Test]
#[TestWith(['https://example.com', 8080, 'https://example.com:8080'])]
#[TestWith(['https://example.com:80', 9000, 'https://example.com:9000'])]
#[TestWith(['http://user:pass@example.com', 3000, 'http://user:pass@example.com:3000'])]
#[TestWith(['https://example.com', 8080, 'https://example.com:8080/'])]
#[TestWith(['https://example.com:80', 9000, 'https://example.com:9000/'])]
#[TestWith(['http://user:pass@example.com', 3000, 'http://user:pass@example.com:3000/'])]
#[TestWith(['malformed-uri', 8080, 'malformed-uri'])]
public function set_port(string $uri, int $port, string $expected): void
{
Expand All @@ -139,9 +139,9 @@ public function get_port(string $uri, ?int $expected): void
}

#[Test]
#[TestWith(['https://example.com', 'frieren', 'https://frieren@example.com'])]
#[TestWith(['https://old@example.com', 'fern', 'https://fern@example.com'])]
#[TestWith(['https://old:pass@example.com', 'stark', 'https://stark:pass@example.com'])]
#[TestWith(['https://example.com', 'frieren', 'https://frieren@example.com/'])]
#[TestWith(['https://old@example.com', 'fern', 'https://fern@example.com/'])]
#[TestWith(['https://old:pass@example.com', 'stark', 'https://stark:pass@example.com/'])]
#[TestWith(['malformed-uri', 'user', '//user@malformed-uri'])]
public function set_user(string $uri, string $user, string $expected): void
{
Expand All @@ -159,9 +159,9 @@ public function get_user(string $uri, ?string $expected): void
}

#[Test]
#[TestWith(['https://user@example.com', 'magic', 'https://user:magic@example.com'])]
#[TestWith(['https://user:old@example.com', 'spell', 'https://user:spell@example.com'])]
#[TestWith(['https://example.com', 'secret', 'https://:secret@example.com'])]
#[TestWith(['https://user@example.com', 'magic', 'https://user:magic@example.com/'])]
#[TestWith(['https://user:old@example.com', 'spell', 'https://user:spell@example.com/'])]
#[TestWith(['https://example.com', 'secret', 'https://:secret@example.com/'])]
#[TestWith(['malformed-uri', 'pass', '//:pass@malformed-uri'])]
public function set_password(string $uri, string $pass, string $expected): void
{
Expand All @@ -172,6 +172,7 @@ public function set_password(string $uri, string $pass, string $expected): void
#[TestWith(['https://user:magic@example.com', 'magic'])]
#[TestWith(['https://example.com', null])]
#[TestWith(['https://user@example.com', null])]
#[TestWith(['https://user:0@example.com', '0'])]
#[TestWith(['malformed-uri', null])]
public function get_password(string $uri, ?string $expected): void
{
Expand All @@ -190,8 +191,9 @@ public function set_path(string $uri, string $path, string $expected): void

#[Test]
#[TestWith(['https://example.com/frieren/journey', '/frieren/journey'])]
#[TestWith(['https://example.com', null])]
#[TestWith(['https://example.com', '/'])]
#[TestWith(['https://example.com/', '/'])]
#[TestWith(['frieren/journey', 'frieren/journey'])]
#[TestWith(['malformed-uri', 'malformed-uri'])]
public function get_path(string $uri, ?string $expected): void
{
Expand All @@ -211,22 +213,22 @@ public function get_segments(string $uri, array $expected): void
}

#[Test]
#[TestWith(['https://example.com?foo=bar', ['baz' => 'qux'], 'https://example.com?foo=bar&baz=qux'])]
#[TestWith(['https://example.com', ['foo' => 'bar'], 'https://example.com?foo=bar'])]
#[TestWith(['https://example.com?existing=value', ['new' => 'param'], 'https://example.com?existing=value&new=param'])]
#[TestWith(['https://example.com?foo=old', ['foo' => 'new'], 'https://example.com?foo=new'])]
#[TestWith(['https://example.com?foo=bar', ['baz' => 'qux'], 'https://example.com/?foo=bar&baz=qux'])]
#[TestWith(['https://example.com', ['foo' => 'bar'], 'https://example.com/?foo=bar'])]
#[TestWith(['https://example.com?existing=value', ['new' => 'param'], 'https://example.com/?existing=value&new=param'])]
#[TestWith(['https://example.com?foo=old', ['foo' => 'new'], 'https://example.com/?foo=new'])]
#[TestWith(['malformed-uri?foo=bar', ['baz' => 'qux'], 'malformed-uri?foo=bar&baz=qux'])]
public function merge_query(string $uri, array $query, string $expected): void
{
$this->assertSame($expected, merge_query($uri, ...$query));
}

#[Test]
#[TestWith(['https://example.com?foo=bar&baz=qux', ['foo'], 'https://example.com?baz=qux'])]
#[TestWith(['https://example.com?foo=bar&baz=qux', ['foo', 'baz'], 'https://example.com'])]
#[TestWith(['https://example.com?foo=bar&baz=qux', ['foo' => 'bar'], 'https://example.com?baz=qux'])]
#[TestWith(['https://example.com?foo=bar&baz=qux', ['foo' => 'wrong'], 'https://example.com?foo=bar&baz=qux'])]
#[TestWith(['https://example.com', ['nonexistent'], 'https://example.com'])]
#[TestWith(['https://example.com?foo=bar&baz=qux', ['foo'], 'https://example.com/?baz=qux'])]
#[TestWith(['https://example.com?foo=bar&baz=qux', ['foo', 'baz'], 'https://example.com/'])]
#[TestWith(['https://example.com?foo=bar&baz=qux', ['foo' => 'bar'], 'https://example.com/?baz=qux'])]
#[TestWith(['https://example.com?foo=bar&baz=qux', ['foo' => 'wrong'], 'https://example.com/?foo=bar&baz=qux'])]
#[TestWith(['https://example.com', ['nonexistent'], 'https://example.com/'])]
#[TestWith(['malformed-uri?foo=bar', ['foo'], 'malformed-uri'])]
public function without_query(string $uri, array $query, string $expected): void
{
Expand Down
Loading
Loading