From f4f3c7a342c5571d71190d4c018362a80ecdb71d Mon Sep 17 00:00:00 2001 From: n0nag0n Date: Mon, 13 Jan 2025 22:57:04 -0700 Subject: [PATCH] Fixed multiple trailing slashes issues. Also fixed server test for 405 and 404 --- flight/Engine.php | 2 +- flight/net/Route.php | 13 ++++++++++--- flight/net/Router.php | 4 ++-- tests/EngineTest.php | 4 ++-- tests/RouterTest.php | 18 +++++++++++++++++- tests/commands/RouteCommandTest.php | 2 +- tests/server/LayoutMiddleware.php | 3 ++- 7 files changed, 35 insertions(+), 11 deletions(-) diff --git a/flight/Engine.php b/flight/Engine.php index 27b44b4c..0a770314 100644 --- a/flight/Engine.php +++ b/flight/Engine.php @@ -179,7 +179,7 @@ public function init(): void } // Set case-sensitivity - $self->router()->case_sensitive = $self->get('flight.case_sensitive'); + $self->router()->caseSensitive = $self->get('flight.case_sensitive'); // Set Content-Length $self->response()->content_length = $self->get('flight.content_length'); // This is to maintain legacy handling of output buffering diff --git a/flight/net/Route.php b/flight/net/Route.php index 05811ec6..8294a19b 100644 --- a/flight/net/Route.php +++ b/flight/net/Route.php @@ -98,17 +98,24 @@ public function __construct(string $pattern, $callback, array $methods, bool $pa * Checks if a URL matches the route pattern. Also parses named parameters in the URL. * * @param string $url Requested URL (original format, not URL decoded) - * @param bool $case_sensitive Case sensitive matching + * @param bool $caseSensitive Case sensitive matching * * @return bool Match status */ - public function matchUrl(string $url, bool $case_sensitive = false): bool + public function matchUrl(string $url, bool $caseSensitive = false): bool { // Wildcard or exact match if ($this->pattern === '*' || $this->pattern === $url) { return true; } + // if the last character of the incoming url is a slash, only allow one trailing slash, not multiple + if (substr($url, -2) === '//') { + // remove all trailing slashes, and then add one back. + $url = rtrim($url, '/') . '/'; + } + + $ids = []; $last_char = substr($this->pattern, -1); @@ -157,7 +164,7 @@ static function ($matches) use (&$ids) { $regex .= $last_char === '/' ? '?' : '/?'; // Attempt to match route and named parameters - if (!preg_match('#^' . $regex . '(?:\?[\s\S]*)?$#' . (($case_sensitive) ? '' : 'i'), $url, $matches)) { + if (!preg_match('#^' . $regex . '(?:\?[\s\S]*)?$#' . (($caseSensitive) ? '' : 'i'), $url, $matches)) { return false; } diff --git a/flight/net/Router.php b/flight/net/Router.php index 50275ac0..80250249 100644 --- a/flight/net/Router.php +++ b/flight/net/Router.php @@ -20,7 +20,7 @@ class Router /** * Case sensitive matching. */ - public bool $case_sensitive = false; + public bool $caseSensitive = false; /** * Mapped routes. @@ -221,7 +221,7 @@ public function group(string $groupPrefix, callable $callback, array $groupMiddl public function route(Request $request) { while ($route = $this->current()) { - $urlMatches = $route->matchUrl($request->url, $this->case_sensitive); + $urlMatches = $route->matchUrl($request->url, $this->caseSensitive); $methodMatches = $route->matchMethod($request->method); if ($urlMatches === true && $methodMatches === true) { $this->executedRoute = $route; diff --git a/tests/EngineTest.php b/tests/EngineTest.php index 82b7294a..e2289c6b 100644 --- a/tests/EngineTest.php +++ b/tests/EngineTest.php @@ -45,7 +45,7 @@ public function getInitializedVar() $engine->request()->url = '/someRoute'; $engine->start(); - $this->assertFalse($engine->router()->case_sensitive); + $this->assertFalse($engine->router()->caseSensitive); $this->assertTrue($engine->response()->content_length); } @@ -64,7 +64,7 @@ public function getInitializedVar() // This is a necessary evil because of how the v2 output buffer works. ob_end_clean(); - $this->assertFalse($engine->router()->case_sensitive); + $this->assertFalse($engine->router()->caseSensitive); $this->assertTrue($engine->response()->content_length); } diff --git a/tests/RouterTest.php b/tests/RouterTest.php index f0ac7653..ebb3fa7b 100644 --- a/tests/RouterTest.php +++ b/tests/RouterTest.php @@ -117,6 +117,14 @@ public function testPathRouteTrailingSlash() $this->check('OK'); } + public function testPathRouteWithUrlTrailingSlash() + { + $this->router->map('/path', [$this, 'ok']); + $this->request->url = '/path/'; + + $this->check('OK'); + } + public function testGetRouteShortcut() { $this->router->get('/path', [$this, 'ok']); @@ -455,7 +463,7 @@ public function testCaseSensitivity() { $this->router->map('/hello', [$this, 'ok']); $this->request->url = '/HELLO'; - $this->router->case_sensitive = true; + $this->router->caseSensitive = true; $this->check('404'); } @@ -752,4 +760,12 @@ public function testGetUrlByAliasWithGroupSimpleParams() $this->assertEquals('/path1/123/abc', $url); } + + public function testStripMultipleSlashesFromUrlAndStillMatch() + { + $this->router->get('/', [ $this, 'ok' ]); + $this->request->url = '///'; + $this->request->method = 'GET'; + $this->check('OK'); + } } diff --git a/tests/commands/RouteCommandTest.php b/tests/commands/RouteCommandTest.php index d58562d2..fd4cc2be 100644 --- a/tests/commands/RouteCommandTest.php +++ b/tests/commands/RouteCommandTest.php @@ -71,7 +71,7 @@ protected function createIndexFile() Flight::delete('/delete', function () {}); Flight::put('/put', function () {}); Flight::patch('/patch', function () {})->addMiddleware('SomeMiddleware'); -Flight::router()->case_sensitive = true; +Flight::router()->caseSensitive = true; Flight::start(); PHP; diff --git a/tests/server/LayoutMiddleware.php b/tests/server/LayoutMiddleware.php index 719d8cc6..8ee68001 100644 --- a/tests/server/LayoutMiddleware.php +++ b/tests/server/LayoutMiddleware.php @@ -69,7 +69,8 @@ public function before()
  • Protected path
  • Template path
  • Query path
  • -
  • 404 Not Found
  • +
  • 404 Not Found
  • +
  • 405 Method Not Found
  • Mega group
  • Error
  • JSON