diff --git a/Slim/Routing/Router.php b/Slim/Routing/Router.php index eee5ed1ce..057be4870 100644 --- a/Slim/Routing/Router.php +++ b/Slim/Routing/Router.php @@ -42,19 +42,17 @@ public function map(array $methods, string $path, callable|string $handler): Rou throw new InvalidArgumentException('HTTP methods array cannot be empty'); } - $routePattern = $this->normalizePath($path); - $route = new Route($methods, $routePattern, $handler, null); + $route = new Route($methods, $path, $handler, null); - $this->collector->addRoute($methods, $routePattern, $route); + $this->collector->addRoute($methods, $path, $route); return $route; } public function group(string $path, callable $handler): RouteGroup { - $routePattern = $this->normalizePath($path); - $routeGroup = new RouteGroup($routePattern, $handler, $this->getRouteCollector()); - $this->collector->addGroup($routePattern, $routeGroup); + $routeGroup = new RouteGroup($path, $handler, $this->getRouteCollector()); + $this->collector->addGroup($path, $routeGroup); return $routeGroup; } @@ -80,27 +78,4 @@ public function handle(ServerRequestInterface $request): ResponseInterface ->withPipeline($this->getMiddleware()) ->handle($request); } - - /** - * Normalizes a path by ensuring: - * - Starts with a forward slash - * - No trailing slash (unless root path) - * - No double slashes - */ - private function normalizePath(string $path): string - { - // If path is empty or just a slash, return single slash - if ($path === '' || $path === '/') { - return '/'; - } - - // Ensure path starts with a slash - $path = '/' . ltrim($path, '/'); - - // Remove trailing slash unless it's the root path - $path = rtrim($path, '/'); - - // Replace multiple consecutive slashes with a single slash - return preg_replace('#/+#', '/', $path) ?? ''; - } } diff --git a/tests/AppTest.php b/tests/AppTest.php index 4e1134ef0..cd14434e5 100644 --- a/tests/AppTest.php +++ b/tests/AppTest.php @@ -277,20 +277,20 @@ public static function routePatternsProvider(): array { return [ // Route pattern -> http uri - // Empty route - ['', '/'], // Single slash route ['/', '/'], - // Route That Does Not Start With A Slash - ['foo', '/foo'], // Route That Does Not End In A Slash ['/foo', '/foo'], // Route That Ends In A Slash - ['/foo/', '/foo'], + ['/foo/', '/foo/'], // Route That Ends In A double Slash - ['/foo//', '/foo'], + ['/foo//', '/foo//'], // Route That contains In A double Slash - ['/foo//bar', '/foo/bar'], + ['/foo//bar', '/foo//bar'], + // FastRoute optional trailing slash segment matches /foo + ['/foo[/]', '/foo'], + // FastRoute optional trailing slash segment matches /foo/ + ['/foo[/]', '/foo/'], ]; } @@ -315,6 +315,47 @@ public function testRoutePatterns(string $pattern, string $uri): void $this->assertSame('Hello World', (string)$response->getBody()); } + public static function strictRouteMismatchProvider(): array + { + return [ + 'foo does not match foo trailing slash' => [ + '/foo', // route pattern + '/foo/', // request URI + ], + 'foo trailing slash does not match foo' => [ + '/foo/', // route pattern + '/foo', // request URI + ], + 'foo does not match FOO uppercase' => [ + '/foo', // route pattern + '/FOO', // request URI + ], + 'route without leading slash does not match' => [ + 'foo', // route pattern + '/foo', // request URI + ], + ]; + } + + #[DataProvider('strictRouteMismatchProvider')] + public function testStrictRouteMismatch(string $routePattern, string $requestUri): void + { + $this->expectException(HttpNotFoundException::class); + + $app = AppFactory::create(); + $app->addRoutingMiddleware(); + + $request = $this + ->getServerRequestFactory($app) + ->createServerRequest('GET', $requestUri); + + $app->get($routePattern, function () { + // noop + }); + + $app->handle($request); + } + /******************************************************************************** * Route Groups *******************************************************************************/