Skip to content

Commit e98cdc3

Browse files
[HttpFoundation] Add parse_str()-based methods for query strings normalization
1 parent b0facfe commit e98cdc3

File tree

2 files changed

+99
-2
lines changed

2 files changed

+99
-2
lines changed

src/Symfony/Component/HttpFoundation/Request.php

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -524,9 +524,11 @@ public function __toString()
524524
*/
525525
public function overrideGlobals()
526526
{
527-
$this->server->set('QUERY_STRING', static::normalizeQueryString(http_build_query($this->query->all(), null, '&')));
527+
$query = $this->query->all();
528+
ksort($query);
529+
$this->server->set('QUERY_STRING', http_build_query($query, null, '&', PHP_QUERY_RFC3986));
528530

529-
$_GET = $this->query->all();
531+
$_GET = $query;
530532
$_POST = $this->request->all();
531533
$_SERVER = $this->server->all();
532534
$_COOKIE = $this->cookies->all();
@@ -656,6 +658,24 @@ public static function normalizeQueryString($qs)
656658
return implode('&', $parts);
657659
}
658660

661+
/**
662+
* Normalizes a query string using `parse_str()`.
663+
*
664+
* It builds a normalized query string, where root-keys/value pairs are alphabetized,
665+
* have consistent escaping and unneeded delimiters are removed.
666+
*/
667+
public static function normalizeQueryStringForPhp(string $qs): string
668+
{
669+
if ('' == $qs) {
670+
return '';
671+
}
672+
673+
parse_str($qs, $qs);
674+
ksort($qs);
675+
676+
return http_build_query($qs, '', '&', PHP_QUERY_RFC3986);
677+
}
678+
659679
/**
660680
* Enables support for the _method request parameter to determine the intended HTTP method.
661681
*
@@ -1032,6 +1052,20 @@ public function getUri()
10321052
return $this->getSchemeAndHttpHost().$this->getBaseUrl().$this->getPathInfo().$qs;
10331053
}
10341054

1055+
/**
1056+
* Generates a `parse_str()`-normalized URI (URL) for the Request.
1057+
*
1058+
* @see getQueryStringForPhp()
1059+
*/
1060+
public function getUriForPhp(): string
1061+
{
1062+
if (null !== $qs = $this->getQueryStringForPhp()) {
1063+
$qs = '?'.$qs;
1064+
}
1065+
1066+
return $this->getSchemeAndHttpHost().$this->getBaseUrl().$this->getPathInfo().$qs;
1067+
}
1068+
10351069
/**
10361070
* Generates a normalized URI for the given path.
10371071
*
@@ -1114,6 +1148,19 @@ public function getQueryString()
11141148
return '' === $qs ? null : $qs;
11151149
}
11161150

1151+
/**
1152+
* Generates the query string for the Request, using `parse_str()` it value.
1153+
*
1154+
* It builds a normalized query string, where root-keys/value pairs are alphabetized
1155+
* and have consistent escaping.
1156+
*/
1157+
public function getQueryStringForPhp(): ?string
1158+
{
1159+
$qs = static::normalizeQueryStringForPhp($this->server->get('QUERY_STRING'));
1160+
1161+
return '' === $qs ? null : $qs;
1162+
}
1163+
11171164
/**
11181165
* Checks whether the request is secure or not.
11191166
*

src/Symfony/Component/HttpFoundation/Tests/RequestTest.php

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -696,6 +696,56 @@ public function getQueryStringNormalizationData()
696696
// Ignore pairs with empty key, even if there was a value, e.g. "=value", as such nameless values cannot be retrieved anyway.
697697
// PHP also does not include them when building _GET.
698698
array('foo=bar&=a=b&=x=y', 'foo=bar', 'removes params with empty key'),
699+
700+
// Also reorder nested query string keys
701+
array('foo[]=Z&foo[]=A', 'foo%5B%5D=A&foo%5B%5D=Z', 'reorders values'),
702+
array('foo[Z]=B&foo[A]=B', 'foo%5BA%5D=B&foo%5BZ%5D=B', 'reorders keys'),
703+
704+
array('utf8=✓', 'utf8=%E2%9C%93', 'encodes UTF-8'),
705+
);
706+
}
707+
708+
/**
709+
* @dataProvider getQueryStringNormalizationDataForPhp
710+
*/
711+
public function testGetQueryStringForPhp($query, $expectedQuery, $msg)
712+
{
713+
$request = new Request();
714+
715+
$request->server->set('QUERY_STRING', $query);
716+
$this->assertSame($expectedQuery, $request->getQueryStringForPhp(), $msg);
717+
}
718+
719+
public function getQueryStringNormalizationDataForPhp()
720+
{
721+
return array(
722+
array('foo', 'foo=', 'works with valueless parameters'),
723+
array('foo=', 'foo=', 'includes a dangling equal sign'),
724+
array('bar=&foo=bar', 'bar=&foo=bar', '->works with empty parameters'),
725+
array('foo=bar&bar=', 'bar=&foo=bar', 'sorts keys alphabetically'),
726+
727+
// GET parameters, that are submitted from a HTML form, encode spaces as "+" by default (as defined in enctype application/x-www-form-urlencoded).
728+
// PHP also converts "+" to spaces when filling the global _GET or when using the function parse_str.
729+
array('him=John%20Doe&her=Jane+Doe', 'her=Jane%20Doe&him=John%20Doe', 'normalizes spaces in both encodings "%20" and "+"'),
730+
731+
array('foo[]=1&foo[]=2', 'foo%5B0%5D=1&foo%5B1%5D=2', 'allows array notation'),
732+
array('foo=1&foo=2', 'foo=2', 'merges repeated parameters'),
733+
array('pa%3Dram=foo%26bar%3Dbaz&test=test', 'pa%3Dram=foo%26bar%3Dbaz&test=test', 'works with encoded delimiters'),
734+
array('0', '0=', 'allows "0"'),
735+
array('Jane Doe&John%20Doe', 'Jane_Doe=&John_Doe=', 'normalizes encoding in keys'),
736+
array('her=Jane Doe&him=John%20Doe', 'her=Jane%20Doe&him=John%20Doe', 'normalizes encoding in values'),
737+
array('foo=bar&&&test&&', 'foo=bar&test=', 'removes unneeded delimiters'),
738+
array('formula=e=m*c^2', 'formula=e%3Dm%2Ac%5E2', 'correctly treats only the first "=" as delimiter and the next as value'),
739+
740+
// Ignore pairs with empty key, even if there was a value, e.g. "=value", as such nameless values cannot be retrieved anyway.
741+
// PHP also does not include them when building _GET.
742+
array('foo=bar&=a=b&=x=y', 'foo=bar', 'removes params with empty key'),
743+
744+
// Don't reorder nested query string keys
745+
array('foo[]=Z&foo[]=A', 'foo%5B0%5D=Z&foo%5B1%5D=A', 'keeps order of values'),
746+
array('foo[Z]=B&foo[A]=B', 'foo%5BZ%5D=B&foo%5BA%5D=B', 'keeps order of keys'),
747+
748+
array('utf8=✓', 'utf8=%E2%9C%93', 'encodes UTF-8'),
699749
);
700750
}
701751

0 commit comments

Comments
 (0)