Description
Symfony version(s) affected: 4.1.9, 4.2.1
Description
Having defined a route without trailing slash in the path,
accessing the URL with a trailing slash automatically redirects to the URL without the trailing slash, but also replaces dots with underscores in the query string parameters names (if any).
How to reproduce
TL;DR:
Given my localhost
app defines a route for method GET for path /foos
(not /foos/
)
When a client requests (GET) the URL http://localhost/foos/?nested.prop=bar
Then I expect the client is redirected to http://localhost/foos?nested.prop=bar
but actually the client is redirected to http://localhost/foos?nested_prop=bar
More details:
Given the following controller (note: no trailing slash after "foos"):
src/Controller/FooController.php
<?php
namespace App\Controller;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
class FooController
{
/**
* @Route("/foos", methods={"GET"})
*/
public function getFoos(Request $request)
{
/* Disclaimer: this is quick-and-dirty code. */
$html = '<!DOCTYPE html><html><head><meta charset="UTF-8"></head><body>';
$html .= '<table border="1">';
$html .= '<tr><th>expression</th><th>value</th></tr>';
foreach ([
'$request->getUri()',
'$request->getSchemeAndHttpHost().$request->getRequestUri()',
'$request->getQueryString()',
'$request->server->get("QUERY_STRING")',
] as $expression) {
$value = eval("return $expression;");
[$expression, $value] = array_map('htmlspecialchars', [$expression, $value]);
$html .= "<tr><td><code>$expression</code></td><td><code>$value</code></td></tr>";
}
$html .= '</table>';
$html .= '</body></html>';
return new Response($html);
}
}
Then
-
http://localhost/foos?nested.prop=bar displays:
expression value $request->getUri()
http://localhost/foos?nested_prop=bar
$request->getSchemeAndHttpHost().$request->getRequestUri()
http://localhost/foos?nested.prop=bar
$request->getQueryString()
nested_prop=bar
$request->server->get("QUERY_STRING")
nested.prop=bar
(i.e.
nested.prop
is transformed tonested_prop
but there are ways to get the original query string). -
http://localhost/foos/?nested.prop=bar (note: trailing slash after "foos") redirects (301) to http://localhost/foos?nested_prop=bar with trailing slash removed but also dot replaced with underscore, which thus displays:
expression value $request->getUri()
http://localhost/foos?nested_prop=bar
$request->getSchemeAndHttpHost().$request->getRequestUri()
http://localhost/foos?nested_prop=bar
$request->getQueryString()
nested_prop=bar
$request->server->get("QUERY_STRING")
nested_prop=bar
i.e. we only have
nested_prop
, the original query string has been lost.
Additional context
With 3.4.20,
-
http://localhost/foos?nested.prop=bar displays:
expression value $request->getUri()
http://localhost/foos?nested.prop=bar
$request->getSchemeAndHttpHost().$request->getRequestUri()
http://localhost/foos?nested.prop=bar
$request->getQueryString()
nested.prop=bar
$request->server->get("QUERY_STRING")
nested.prop=bar
(i.e.
nested.prop
was preserved). -
http://localhost/foos/?nested.prop=bar (note: trailing slash after "foos") gives a 404 error
No route found for "GET /foos/"
(no automatic redirect).
In all cases, $request->query->get("nested.prop")
is null
(not defined) (with 4.1, 4.2 and even 3.4), and we have to use $request->query->get("nested_prop")
(or parse the original query string) to get "bar"
.
The real project uses API Platform, which auto-generates resource routes (without trailing slash) and parses the original query string to get nested filters (with dots). The calls with trailing slash are from automated REST clients.