Slim icon indicating copy to clipboard operation
Slim copied to clipboard

Route arguments get decoded twice

Open assertio-dani opened this issue 3 years ago • 4 comments

Hi!

The arguments get decoded twice, in:

  • \Slim\Routing\RoutingResults::getRouteArguments
  • \Slim\Routing\RouteResolver::computeRoutingResults

That's a problem if the argument contains a "decodable" combination of characters.

Let's say I have an article with id 9%65. The route to retrieve it is /articles/{id}.

If I rawurlencode the article id (i.e. /articles/9%2565), since it gets decoded twice, the argument returned is 9e instead of 9%65.

In order to get the proper argument, I'd need to rawurlencode the article id twice (i.e. /articles/9%252565)

Please advise

assertio-dani avatar Jul 01 '22 17:07 assertio-dani

I have tried to reproduce your posted issue. Here are my test results using postman:

GET /articles/123 Response: 123

GET /articles/9%65 Response: 9e Single encoding: 9e (same)

GET /articles/9%2565 Response: 9e Single encoding should be: 9%2565 (not the same)

GET /articles/9%252565 Response: 9%65 Single encoding should be: 9%2565 (not the same)

Minimal code example:

<?php

use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use Slim\Factory\AppFactory;

require __DIR__ . '/../vendor/autoload.php';

$app = AppFactory::create();

$app->get('/articles/{id}', function (Request $request, Response $response, $args) {
    $response->getBody()->write($args['id']);
    return $response;
});

$app->run();

Generally, I would recommend to use only characters in a range from [a-zA-Z0-9\-] for route arguments. For complex arguments, a query-string might be a better option.

odan avatar Jul 01 '22 19:07 odan

Hi!

Thank you for taking the time to investigate it.

The problem I see with double decoding is that the article ID is 9%65, I mean, that's literally the id stored in the DB.

Then, how can we refer to it in the URL?

I guess the approach should be to url-encode it (e.g. GET /articles/9%2565), but since it's decoded twice, the ID received in the controller is 9e, not 9%65.

For sure it's an edge case. It's definitely not a good idea to use % characters in the id, but... that's how my client articles are stored, and I don't manage that data.

I don't know the intricacies of Slim, but as an outsider opinion, shouldn't it be enough decoding the URL once in \Slim\Routing\RouteResolver::computeRoutingResults ? Doing so, both URL arguments and query params would be decoded correctly, allowing both GET /articles/9%2565 and GET /articles?id=9%2565.

assertio-dani avatar Jul 02 '22 16:07 assertio-dani

The first url-encoding happens before that actual routing. See RouteResolver. This is needed for the FastRouter dispatcher and a correct behavior.

So this: /articles/9%2565 becomes this /articles/9%65

Then, when Slim reads the route arguments, the values will be decoded a second time here:

https://github.com/slimphp/Slim/blob/4.x/Slim/Routing/RoutingResults.php#L99

I don't know the reasons behind it, but maybe this is a bug?

As a quick workaround, you could pass a - instead of % and replace - to % within a middleware.

Example URI path: /articles/9-65. Then use $id = str_replace('-', '%', $args['id']);

odan avatar Jul 02 '22 18:07 odan

We have to do a rawurldecode() before dispatching the URI to the router otherwise it would break when encountering special chars.

If you use getRouteArguments() you can optionally pass false the the method so rawurldecode() isn't called a second time.

I have to say I'm not entirely sure why that's there in the first place, this is probably an artifact from refactoring the routing system from 3.x to 4.x.

I don't know what the correct solution is here. @akrabat thoughts?

l0gicgate avatar Aug 03 '22 02:08 l0gicgate