diff --git a/Slim/App.php b/Slim/App.php index 2907d4e13..c711f846c 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\RouterInterface; use Slim\Interfaces\ServerRequestCreatorInterface; @@ -169,12 +170,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 ab996f7c8..0f86e1119 100644 --- a/Slim/Middleware/RoutingMiddleware.php +++ b/Slim/Middleware/RoutingMiddleware.php @@ -30,12 +30,16 @@ final class RoutingMiddleware implements MiddlewareInterface private RouterInterface $router; + private bool $decodePath; + public function __construct( DispatcherInterface $dispatcher, - RouterInterface $router + RouterInterface $router, + bool $decodePath = true ) { $this->dispatcher = $dispatcher; $this->router = $router; + $this->decodePath = $decodePath; } public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface @@ -46,7 +50,7 @@ public function process(ServerRequestInterface $request, RequestHandlerInterface $routingResult = $this->dispatcher->dispatch( $request->getMethod(), - rawurldecode($dispatchPath) + $this->decodePath ? rawurldecode($dispatchPath) : $dispatchPath ); $routeMatch = $this->createRouteMatch($routingResult); diff --git a/tests/Middleware/RoutingMiddlewareTest.php b/tests/Middleware/RoutingMiddlewareTest.php index 7da0ebe2e..6ab8e2152 100644 --- a/tests/Middleware/RoutingMiddlewareTest.php +++ b/tests/Middleware/RoutingMiddlewareTest.php @@ -198,6 +198,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 testMethodNotAllowedThrowsRuntimeExceptionWhenAllowedMethodsPayloadIsInvalid(): void { $dispatcher = $this->createMock(DispatcherInterface::class);