diff --git a/Slim/App.php b/Slim/App.php index 5d808461f..66ab4aff3 100644 --- a/Slim/App.php +++ b/Slim/App.php @@ -16,6 +16,7 @@ use Psr\Http\Server\MiddlewareInterface; use Psr\Http\Server\RequestHandlerInterface; use Psr\Log\LoggerInterface; +use Slim\Interfaces\DispatcherInterface; use Slim\Interfaces\EmitterInterface; use Slim\Interfaces\RouteInterface; use Slim\Interfaces\RouterInterface; @@ -170,12 +171,23 @@ public function addMiddleware(MiddlewareInterface $middleware): self /** * Add routing middleware. * + * @param bool $decodePath Whether the request path should be URL-decoded before dispatch. + * Disable to preserve encoded reserved characters inside route parameters. + * * @return self */ - public function addRoutingMiddleware(): self + public function addRoutingMiddleware(bool $decodePath = true): self { + $routingMiddleware = $decodePath + ? RoutingMiddleware::class + : new RoutingMiddleware( + $this->container->get(DispatcherInterface::class), + $this->router, + false, + ); + return $this - ->add(RoutingMiddleware::class) + ->add($routingMiddleware) ->add(EndpointMiddleware::class); } diff --git a/Slim/Middleware/RoutingMiddleware.php b/Slim/Middleware/RoutingMiddleware.php index 5b496d056..4ecc5a060 100644 --- a/Slim/Middleware/RoutingMiddleware.php +++ b/Slim/Middleware/RoutingMiddleware.php @@ -31,10 +31,16 @@ final class RoutingMiddleware implements MiddlewareInterface private RouterInterface $router; - public function __construct(DispatcherInterface $dispatcher, RouterInterface $router) - { + private bool $decodePath; + + public function __construct( + DispatcherInterface $dispatcher, + RouterInterface $router, + bool $decodePath = true + ) { $this->dispatcher = $dispatcher; $this->router = $router; + $this->decodePath = $decodePath; } public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface @@ -50,7 +56,7 @@ public function process(ServerRequestInterface $request, RequestHandlerInterface $routingResult = $this->dispatcher->dispatch( $request->getMethod(), - $dispatchPath + $this->decodePath ? rawurldecode($dispatchPath) : $dispatchPath ); $routeMatch = $this->createRouteMatch($routingResult); diff --git a/Slim/Routing/FastRouteDispatcher.php b/Slim/Routing/FastRouteDispatcher.php index ab9419531..57972ac97 100644 --- a/Slim/Routing/FastRouteDispatcher.php +++ b/Slim/Routing/FastRouteDispatcher.php @@ -27,7 +27,7 @@ public function __construct(RouterInterface $router) public function dispatch(string $httpMethod, string $uri): array { - return $this->getDispatcher()->dispatch($httpMethod, rawurldecode(($uri))); + return $this->getDispatcher()->dispatch($httpMethod, $uri); } private function getDispatcher(): GroupCountBased diff --git a/tests/Middleware/RoutingMiddlewareTest.php b/tests/Middleware/RoutingMiddlewareTest.php index d4b3742fe..8a254bafb 100644 --- a/tests/Middleware/RoutingMiddlewareTest.php +++ b/tests/Middleware/RoutingMiddlewareTest.php @@ -197,6 +197,31 @@ public function testRoutingWithBasePath(): void $this->assertSame('/api/users/123?page=2', $response->getHeaderLine('X-fullUrlFor')); } + public function testRoutePreservesEncodedReservedCharactersWhenPathDecodingDisabled(): void + { + $app = AppFactory::create(); + $app->addRoutingMiddleware(false); + + $app->get('/something/{magic}/{foo}', function ( + ServerRequestInterface $request, + ResponseInterface $response, + array $args + ) { + $response->getBody()->write($args['magic'] . '|' . $args['foo']); + + return $response; + }); + + $request = $this + ->getServerRequestFactory($app) + ->createServerRequest('GET', '/something/magic/foo%2Fbar'); + + $response = $app->handle($request); + + $this->assertSame(200, $response->getStatusCode()); + $this->assertSame('magic|foo%2Fbar', (string)$response->getBody()); + } + public function testRoutingWithUriDoesNotStartWithBasePath(): void { $this->expectException(HttpNotFoundException::class);