Support semicolons in query parameters as explain in the W3C recommentation (#9701)

Motivation:

Support semicolons in query parameters as explain in the W3C recommentation:
https://www.w3.org/TR/2014/REC-html5-20141028/forms.html#url-encoded-form-data

Modification:

- Add a new constructor arg that can be used to "switch" modes for decoding ;
- Add unit test

Result:

Fixes #8855
This commit is contained in:
Norman Maurer 2019-10-23 23:44:37 -07:00 committed by GitHub
parent e8e7a206b3
commit 7d6d953153
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 43 additions and 5 deletions

View File

@ -68,6 +68,7 @@ public class QueryStringDecoder {
private final Charset charset;
private final String uri;
private final int maxParams;
private final boolean semicolonIsNormalChar;
private int pathEndIdx;
private String path;
private Map<String, List<String>> params;
@ -109,9 +110,19 @@ public class QueryStringDecoder {
* specified charset.
*/
public QueryStringDecoder(String uri, Charset charset, boolean hasPath, int maxParams) {
this(uri, charset, hasPath, maxParams, false);
}
/**
* Creates a new decoder that decodes the specified URI encoded in the
* specified charset.
*/
public QueryStringDecoder(String uri, Charset charset, boolean hasPath,
int maxParams, boolean semicolonIsNormalChar) {
this.uri = checkNotNull(uri, "uri");
this.charset = checkNotNull(charset, "charset");
this.maxParams = checkPositive(maxParams, "maxParams");
this.semicolonIsNormalChar = semicolonIsNormalChar;
// `-1` means that path end index will be initialized lazily
pathEndIdx = hasPath ? -1 : 0;
@ -138,6 +149,14 @@ public class QueryStringDecoder {
* specified charset.
*/
public QueryStringDecoder(URI uri, Charset charset, int maxParams) {
this(uri, charset, maxParams, false);
}
/**
* Creates a new decoder that decodes the specified URI encoded in the
* specified charset.
*/
public QueryStringDecoder(URI uri, Charset charset, int maxParams, boolean semicolonIsNormalChar) {
String rawPath = uri.getRawPath();
if (rawPath == null) {
rawPath = EMPTY_STRING;
@ -147,6 +166,7 @@ public class QueryStringDecoder {
this.uri = rawQuery == null? rawPath : rawPath + '?' + rawQuery;
this.charset = checkNotNull(charset, "charset");
this.maxParams = checkPositive(maxParams, "maxParams");
this.semicolonIsNormalChar = semicolonIsNormalChar;
pathEndIdx = rawPath.length();
}
@ -177,7 +197,7 @@ public class QueryStringDecoder {
*/
public Map<String, List<String>> parameters() {
if (params == null) {
params = decodeParams(uri, pathEndIdx(), charset, maxParams);
params = decodeParams(uri, pathEndIdx(), charset, maxParams, semicolonIsNormalChar);
}
return params;
}
@ -204,7 +224,8 @@ public class QueryStringDecoder {
return pathEndIdx;
}
private static Map<String, List<String>> decodeParams(String s, int from, Charset charset, int paramsLimit) {
private static Map<String, List<String>> decodeParams(String s, int from, Charset charset, int paramsLimit,
boolean semicolonIsNormalChar) {
int len = s.length();
if (from >= len) {
return Collections.emptyMap();
@ -226,8 +247,12 @@ public class QueryStringDecoder {
valueStart = i + 1;
}
break;
case '&':
case ';':
if (semicolonIsNormalChar) {
continue;
}
// fall-through
case '&':
if (addParam(s, nameStart, valueStart, i, params, charset)) {
paramsLimit--;
if (paramsLimit == 0) {

View File

@ -129,6 +129,13 @@ public class QueryStringDecoderTest {
assertQueryString("/foo?a=1&a=&a=", "/foo?a=1&a&a=");
}
@Test
public void testSemicolon() {
assertQueryString("/foo?a=1;2", "/foo?a=1;2", false);
// ";" should be treated as a normal character, see #8855
assertQueryString("/foo?a=1;2", "/foo?a=1%3B2", true);
}
@Test
public void testPathSpecific() {
// decode escaped characters
@ -225,8 +232,14 @@ public class QueryStringDecoderTest {
}
private static void assertQueryString(String expected, String actual) {
QueryStringDecoder ed = new QueryStringDecoder(expected, CharsetUtil.UTF_8);
QueryStringDecoder ad = new QueryStringDecoder(actual, CharsetUtil.UTF_8);
assertQueryString(expected, actual, false);
}
private static void assertQueryString(String expected, String actual, boolean semicolonIsNormalChar) {
QueryStringDecoder ed = new QueryStringDecoder(expected, CharsetUtil.UTF_8, true,
1024, semicolonIsNormalChar);
QueryStringDecoder ad = new QueryStringDecoder(actual, CharsetUtil.UTF_8, true,
1024, semicolonIsNormalChar);
Assert.assertEquals(ed.path(), ad.path());
Assert.assertEquals(ed.parameters(), ad.parameters());
}