[HttpFoundation] add HeaderUtils::parseQuery()
: it does the same as parse_str()
but preserves dots in variable names
This commit is contained in:
parent
ce8f8a5f18
commit
dd81e32ec1
@ -11,6 +11,7 @@
|
|||||||
|
|
||||||
namespace Symfony\Bundle\FrameworkBundle\Controller;
|
namespace Symfony\Bundle\FrameworkBundle\Controller;
|
||||||
|
|
||||||
|
use Symfony\Component\HttpFoundation\HeaderUtils;
|
||||||
use Symfony\Component\HttpFoundation\RedirectResponse;
|
use Symfony\Component\HttpFoundation\RedirectResponse;
|
||||||
use Symfony\Component\HttpFoundation\Request;
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
use Symfony\Component\HttpFoundation\Response;
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
@ -65,7 +66,7 @@ class RedirectController
|
|||||||
|
|
||||||
if ($keepQueryParams) {
|
if ($keepQueryParams) {
|
||||||
if ($query = $request->server->get('QUERY_STRING')) {
|
if ($query = $request->server->get('QUERY_STRING')) {
|
||||||
$query = self::parseQuery($query);
|
$query = HeaderUtils::parseQuery($query);
|
||||||
} else {
|
} else {
|
||||||
$query = $request->query->all();
|
$query = $request->query->all();
|
||||||
}
|
}
|
||||||
@ -185,49 +186,4 @@ class RedirectController
|
|||||||
|
|
||||||
throw new \RuntimeException(sprintf('The parameter "path" or "route" is required to configure the redirect action in "%s" routing configuration.', $request->attributes->get('_route')));
|
throw new \RuntimeException(sprintf('The parameter "path" or "route" is required to configure the redirect action in "%s" routing configuration.', $request->attributes->get('_route')));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static function parseQuery(string $query)
|
|
||||||
{
|
|
||||||
$q = [];
|
|
||||||
|
|
||||||
foreach (explode('&', $query) as $v) {
|
|
||||||
if (false !== $i = strpos($v, "\0")) {
|
|
||||||
$v = substr($v, 0, $i);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (false === $i = strpos($v, '=')) {
|
|
||||||
$k = urldecode($v);
|
|
||||||
$v = '';
|
|
||||||
} else {
|
|
||||||
$k = urldecode(substr($v, 0, $i));
|
|
||||||
$v = substr($v, $i);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (false !== $i = strpos($k, "\0")) {
|
|
||||||
$k = substr($k, 0, $i);
|
|
||||||
}
|
|
||||||
|
|
||||||
$k = ltrim($k, ' ');
|
|
||||||
|
|
||||||
if (false === $i = strpos($k, '[')) {
|
|
||||||
$q[] = bin2hex($k).$v;
|
|
||||||
} else {
|
|
||||||
$q[] = substr_replace($k, bin2hex(substr($k, 0, $i)), 0, $i).$v;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
parse_str(implode('&', $q), $q);
|
|
||||||
|
|
||||||
$query = [];
|
|
||||||
|
|
||||||
foreach ($q as $k => $v) {
|
|
||||||
if (false !== $i = strpos($k, '_')) {
|
|
||||||
$query[substr_replace($k, hex2bin(substr($k, 0, $i)).'[', 0, 1 + $i)] = $v;
|
|
||||||
} else {
|
|
||||||
$query[hex2bin($k)] = $v;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $query;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -23,7 +23,7 @@
|
|||||||
"symfony/dependency-injection": "^5.2",
|
"symfony/dependency-injection": "^5.2",
|
||||||
"symfony/event-dispatcher": "^5.1",
|
"symfony/event-dispatcher": "^5.1",
|
||||||
"symfony/error-handler": "^4.4.1|^5.0.1",
|
"symfony/error-handler": "^4.4.1|^5.0.1",
|
||||||
"symfony/http-foundation": "^4.4|^5.0",
|
"symfony/http-foundation": "^5.2",
|
||||||
"symfony/http-kernel": "^5.2",
|
"symfony/http-kernel": "^5.2",
|
||||||
"symfony/polyfill-mbstring": "~1.0",
|
"symfony/polyfill-mbstring": "~1.0",
|
||||||
"symfony/polyfill-php80": "^1.15",
|
"symfony/polyfill-php80": "^1.15",
|
||||||
|
@ -1,6 +1,11 @@
|
|||||||
CHANGELOG
|
CHANGELOG
|
||||||
=========
|
=========
|
||||||
|
|
||||||
|
5.2.0
|
||||||
|
-----
|
||||||
|
|
||||||
|
* added `HeaderUtils::parseQuery()`: it does the same as `parse_str()` but preserves dots in variable names
|
||||||
|
|
||||||
5.1.0
|
5.1.0
|
||||||
-----
|
-----
|
||||||
|
|
||||||
|
@ -193,6 +193,64 @@ class HeaderUtils
|
|||||||
return $disposition.'; '.self::toString($params, ';');
|
return $disposition.'; '.self::toString($params, ';');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Like parse_str(), but preserves dots in variable names.
|
||||||
|
*/
|
||||||
|
public static function parseQuery(string $query, bool $ignoreBrackets = false, string $separator = '&'): array
|
||||||
|
{
|
||||||
|
$q = [];
|
||||||
|
|
||||||
|
foreach (explode($separator, $query) as $v) {
|
||||||
|
if (false !== $i = strpos($v, "\0")) {
|
||||||
|
$v = substr($v, 0, $i);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (false === $i = strpos($v, '=')) {
|
||||||
|
$k = urldecode($v);
|
||||||
|
$v = '';
|
||||||
|
} else {
|
||||||
|
$k = urldecode(substr($v, 0, $i));
|
||||||
|
$v = substr($v, $i);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (false !== $i = strpos($k, "\0")) {
|
||||||
|
$k = substr($k, 0, $i);
|
||||||
|
}
|
||||||
|
|
||||||
|
$k = ltrim($k, ' ');
|
||||||
|
|
||||||
|
if ($ignoreBrackets) {
|
||||||
|
$q[$k][] = urldecode(substr($v, 1));
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (false === $i = strpos($k, '[')) {
|
||||||
|
$q[] = bin2hex($k).$v;
|
||||||
|
} else {
|
||||||
|
$q[] = substr_replace($k, bin2hex(substr($k, 0, $i)), 0, $i).$v;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($ignoreBrackets) {
|
||||||
|
return $q;
|
||||||
|
}
|
||||||
|
|
||||||
|
parse_str(implode('&', $q), $q);
|
||||||
|
|
||||||
|
$query = [];
|
||||||
|
|
||||||
|
foreach ($q as $k => $v) {
|
||||||
|
if (false !== $i = strpos($k, '_')) {
|
||||||
|
$query[substr_replace($k, hex2bin(substr($k, 0, $i)).'[', 0, 1 + $i)] = $v;
|
||||||
|
} else {
|
||||||
|
$query[hex2bin($k)] = $v;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $query;
|
||||||
|
}
|
||||||
|
|
||||||
private static function groupParts(array $matches, string $separators): array
|
private static function groupParts(array $matches, string $separators): array
|
||||||
{
|
{
|
||||||
$separator = $separators[0];
|
$separator = $separators[0];
|
||||||
|
@ -399,7 +399,7 @@ class Request
|
|||||||
|
|
||||||
$queryString = '';
|
$queryString = '';
|
||||||
if (isset($components['query'])) {
|
if (isset($components['query'])) {
|
||||||
parse_str(html_entity_decode($components['query']), $qs);
|
$qs = HeaderUtils::parseQuery(html_entity_decode($components['query']));
|
||||||
|
|
||||||
if ($query) {
|
if ($query) {
|
||||||
$query = array_replace($qs, $query);
|
$query = array_replace($qs, $query);
|
||||||
@ -660,7 +660,7 @@ class Request
|
|||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
parse_str($qs, $qs);
|
$qs = HeaderUtils::parseQuery($qs);
|
||||||
ksort($qs);
|
ksort($qs);
|
||||||
|
|
||||||
return http_build_query($qs, '', '&', PHP_QUERY_RFC3986);
|
return http_build_query($qs, '', '&', PHP_QUERY_RFC3986);
|
||||||
|
@ -129,4 +129,41 @@ class HeaderUtilsTest extends TestCase
|
|||||||
['attachment', 'föö.html'],
|
['attachment', 'föö.html'],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dataProvider provideParseQuery
|
||||||
|
*/
|
||||||
|
public function testParseQuery(string $query, string $expected = null)
|
||||||
|
{
|
||||||
|
$this->assertSame($expected ?? $query, http_build_query(HeaderUtils::parseQuery($query), '', '&'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function provideParseQuery()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
['a=b&c=d'],
|
||||||
|
['a.b=c'],
|
||||||
|
['a+b=c'],
|
||||||
|
["a\0b=c", 'a='],
|
||||||
|
['a%00b=c', 'a=c'],
|
||||||
|
['a[b=c', 'a%5Bb=c'],
|
||||||
|
['a]b=c', 'a%5Db=c'],
|
||||||
|
['a[b]=c', 'a%5Bb%5D=c'],
|
||||||
|
['a[b][c.d]=c', 'a%5Bb%5D%5Bc.d%5D=c'],
|
||||||
|
['a%5Bb%5D=c'],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testParseCookie()
|
||||||
|
{
|
||||||
|
$query = 'a.b=c; def%5Bg%5D=h';
|
||||||
|
$this->assertSame($query, http_build_query(HeaderUtils::parseQuery($query, false, ';'), '', '; '));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testParseQueryIgnoreBrackets()
|
||||||
|
{
|
||||||
|
$this->assertSame(['a.b' => ['A', 'B']], HeaderUtils::parseQuery('a.b=A&a.b=B', true));
|
||||||
|
$this->assertSame(['a.b[]' => ['A']], HeaderUtils::parseQuery('a.b[]=A', true));
|
||||||
|
$this->assertSame(['a.b[]' => ['A']], HeaderUtils::parseQuery('a.b%5B%5D=A', true));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -807,7 +807,7 @@ class RequestTest extends TestCase
|
|||||||
['foo=1&foo=2', 'foo=2', 'merges repeated parameters'],
|
['foo=1&foo=2', 'foo=2', 'merges repeated parameters'],
|
||||||
['pa%3Dram=foo%26bar%3Dbaz&test=test', 'pa%3Dram=foo%26bar%3Dbaz&test=test', 'works with encoded delimiters'],
|
['pa%3Dram=foo%26bar%3Dbaz&test=test', 'pa%3Dram=foo%26bar%3Dbaz&test=test', 'works with encoded delimiters'],
|
||||||
['0', '0=', 'allows "0"'],
|
['0', '0=', 'allows "0"'],
|
||||||
['Foo Bar&Foo%20Baz', 'Foo_Bar=&Foo_Baz=', 'normalizes encoding in keys'],
|
['Foo Bar&Foo%20Baz', 'Foo%20Bar=&Foo%20Baz=', 'normalizes encoding in keys'],
|
||||||
['bar=Foo Bar&baz=Foo%20Baz', 'bar=Foo%20Bar&baz=Foo%20Baz', 'normalizes encoding in values'],
|
['bar=Foo Bar&baz=Foo%20Baz', 'bar=Foo%20Bar&baz=Foo%20Baz', 'normalizes encoding in values'],
|
||||||
['foo=bar&&&test&&', 'foo=bar&test=', 'removes unneeded delimiters'],
|
['foo=bar&&&test&&', 'foo=bar&test=', 'removes unneeded delimiters'],
|
||||||
['formula=e=m*c^2', 'formula=e%3Dm%2Ac%5E2', 'correctly treats only the first "=" as delimiter and the next as value'],
|
['formula=e=m*c^2', 'formula=e%3Dm%2Ac%5E2', 'correctly treats only the first "=" as delimiter and the next as value'],
|
||||||
|
Reference in New Issue
Block a user