Skip to content

Commit c2203e8

Browse files
Merge pull request #171 from utopia-php/fix-null-path
Fix null path matching
2 parents e1c7ab4 + 804be9e commit c2203e8

5 files changed

Lines changed: 80 additions & 6 deletions

File tree

src/App.php

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -344,7 +344,7 @@ public static function error(): Hook
344344
* @param string|null $default
345345
* @return string|null
346346
*/
347-
public static function getEnv(string $key, string $default = null): ?string
347+
public static function getEnv(string $key, ?string $default = null): ?string
348348
{
349349
return $_SERVER[$key] ?? $default;
350350
}
@@ -561,6 +561,11 @@ public function match(Request $request, bool $fresh = false): ?Route
561561
}
562562

563563
$url = \parse_url($request->getURI(), PHP_URL_PATH);
564+
565+
if ($url === null || $url === false) {
566+
$url = '/'; // Default to root path for malformed URLs
567+
}
568+
564569
$method = $request->getMethod();
565570
$method = (self::REQUEST_METHOD_HEAD == $method) ? self::REQUEST_METHOD_GET : $method;
566571

src/Request.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ public function getRawPayload(): string
136136
* @param string|null $default
137137
* @return string|null
138138
*/
139-
public function getServer(string $key, string $default = null): ?string
139+
public function getServer(string $key, ?string $default = null): ?string
140140
{
141141
return $_SERVER[$key] ?? $default;
142142
}

src/Response.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -533,7 +533,7 @@ public function getHeaders(): array
533533
* @param bool $httponly
534534
* @param string $sameSite
535535
*/
536-
public function addCookie(string $name, string $value = null, int $expire = null, string $path = null, string $domain = null, bool $secure = null, bool $httponly = null, string $sameSite = null): static
536+
public function addCookie(string $name, ?string $value = null, ?int $expire = null, ?string $path = null, ?string $domain = null, ?bool $secure = null, ?bool $httponly = null, ?string $sameSite = null): static
537537
{
538538
$name = strtolower($name);
539539

@@ -665,7 +665,7 @@ protected function write(string $content): void
665665
* @param string $content
666666
* @return void
667667
*/
668-
protected function end(string $content = null): void
668+
protected function end(?string $content = null): void
669669
{
670670
if (!is_null($content)) {
671671
echo $content;

tests/AppTest.php

Lines changed: 54 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -221,7 +221,7 @@ public function testCanExecuteRoute(): void
221221
->param('x', 'x-def', new Text(1, min: 0), 'x param', false)
222222
->param('y', 'y-def', new Text(1, min: 0), 'y param', false)
223223
->action(function ($x, $y) {
224-
echo $x . '-', $y;
224+
echo $x . '-' . $y;
225225
});
226226

227227
\ob_start();
@@ -467,7 +467,7 @@ public function providerRouteMatching(): array
467467
/**
468468
* @dataProvider providerRouteMatching
469469
*/
470-
public function testCanMatchRoute(string $method, string $path, string $url = null): void
470+
public function testCanMatchRoute(string $method, string $path, ?string $url = null): void
471471
{
472472
$url ??= $path;
473473
$expected = null;
@@ -497,6 +497,58 @@ public function testCanMatchRoute(string $method, string $path, string $url = nu
497497
$this->assertEquals($expected, $this->app->getRoute());
498498
}
499499

500+
public function testMatchWithNullPath(): void
501+
{
502+
// Create a route for root path
503+
$expected = App::get('/');
504+
505+
// Test case where parse_url returns null (malformed URL)
506+
$_SERVER['REQUEST_METHOD'] = 'GET';
507+
$_SERVER['REQUEST_URI'] = '?param=1'; // This will cause parse_url to return null for PATH component
508+
509+
$matched = $this->app->match(new Request());
510+
$this->assertEquals($expected, $matched);
511+
}
512+
513+
public function testMatchWithEmptyPath(): void
514+
{
515+
// Create a route for root path
516+
$expected = App::get('/');
517+
518+
// Test case where URI has no path component
519+
$_SERVER['REQUEST_METHOD'] = 'GET';
520+
$_SERVER['REQUEST_URI'] = 'https://example.com'; // No path component
521+
522+
$matched = $this->app->match(new Request());
523+
$this->assertEquals($expected, $matched);
524+
}
525+
526+
public function testMatchWithMalformedURL(): void
527+
{
528+
// Create a route for root path
529+
$expected = App::get('/');
530+
531+
// Test case where parse_url returns false (severely malformed URL)
532+
$_SERVER['REQUEST_METHOD'] = 'GET';
533+
$_SERVER['REQUEST_URI'] = '#fragment'; // Malformed scheme
534+
535+
$matched = $this->app->match(new Request());
536+
$this->assertEquals($expected, $matched);
537+
}
538+
539+
public function testMatchWithOnlyQueryString(): void
540+
{
541+
// Create a route for root path
542+
$expected = App::get('/');
543+
544+
// Test case where URI has only query string (no path)
545+
$_SERVER['REQUEST_METHOD'] = 'GET';
546+
$_SERVER['REQUEST_URI'] = '?param=value'; // Only query string, no path
547+
548+
$matched = $this->app->match(new Request());
549+
$this->assertEquals($expected, $matched);
550+
}
551+
500552
public function testNoMismatchRoute(): void
501553
{
502554
$requests = [

tests/e2e/ResponseTest.php

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,23 @@ public function testEarlyResponse()
5757
$this->assertEquals('Init response. Actioned before: no', $response['body']);
5858
}
5959

60+
public function testNullPathHandling()
61+
{
62+
// Test that malformed URLs default to root path
63+
$response = $this->client->call(Client::METHOD_GET, '/');
64+
$this->assertEquals('Hello World!', $response['body']);
65+
$this->assertEquals(200, $response['headers']['status-code']);
66+
}
67+
68+
public function testRootPathFallback()
69+
{
70+
// Test that when path parsing fails, it falls back to root
71+
// This is tested by ensuring the root route works correctly
72+
$response = $this->client->call(Client::METHOD_GET, '/');
73+
$this->assertEquals('Hello World!', $response['body']);
74+
$this->assertEquals(200, $response['headers']['status-code']);
75+
}
76+
6077
public function testAliasWithParameter(): void
6178
{
6279
$response = $this->client->call(Client::METHOD_POST, '/functions/deployment', [

0 commit comments

Comments
 (0)