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:
parent
03ad809a3b
commit
af132384cc
@ -69,6 +69,7 @@ public class QueryStringDecoder {
|
|||||||
private final Charset charset;
|
private final Charset charset;
|
||||||
private final String uri;
|
private final String uri;
|
||||||
private final int maxParams;
|
private final int maxParams;
|
||||||
|
private final boolean semicolonIsNormalChar;
|
||||||
private int pathEndIdx;
|
private int pathEndIdx;
|
||||||
private String path;
|
private String path;
|
||||||
private Map<String, List<String>> params;
|
private Map<String, List<String>> params;
|
||||||
@ -110,9 +111,19 @@ public class QueryStringDecoder {
|
|||||||
* specified charset.
|
* specified charset.
|
||||||
*/
|
*/
|
||||||
public QueryStringDecoder(String uri, Charset charset, boolean hasPath, int maxParams) {
|
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 = requireNonNull(uri, "uri");
|
this.uri = requireNonNull(uri, "uri");
|
||||||
this.charset = requireNonNull(charset, "charset");
|
this.charset = requireNonNull(charset, "charset");
|
||||||
this.maxParams = checkPositive(maxParams, "maxParams");
|
this.maxParams = checkPositive(maxParams, "maxParams");
|
||||||
|
this.semicolonIsNormalChar = semicolonIsNormalChar;
|
||||||
|
|
||||||
// `-1` means that path end index will be initialized lazily
|
// `-1` means that path end index will be initialized lazily
|
||||||
pathEndIdx = hasPath ? -1 : 0;
|
pathEndIdx = hasPath ? -1 : 0;
|
||||||
@ -139,6 +150,14 @@ public class QueryStringDecoder {
|
|||||||
* specified charset.
|
* specified charset.
|
||||||
*/
|
*/
|
||||||
public QueryStringDecoder(URI uri, Charset charset, int maxParams) {
|
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();
|
String rawPath = uri.getRawPath();
|
||||||
if (rawPath == null) {
|
if (rawPath == null) {
|
||||||
rawPath = EMPTY_STRING;
|
rawPath = EMPTY_STRING;
|
||||||
@ -148,6 +167,7 @@ public class QueryStringDecoder {
|
|||||||
this.uri = rawQuery == null? rawPath : rawPath + '?' + rawQuery;
|
this.uri = rawQuery == null? rawPath : rawPath + '?' + rawQuery;
|
||||||
this.charset = requireNonNull(charset, "charset");
|
this.charset = requireNonNull(charset, "charset");
|
||||||
this.maxParams = checkPositive(maxParams, "maxParams");
|
this.maxParams = checkPositive(maxParams, "maxParams");
|
||||||
|
this.semicolonIsNormalChar = semicolonIsNormalChar;
|
||||||
pathEndIdx = rawPath.length();
|
pathEndIdx = rawPath.length();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -178,7 +198,7 @@ public class QueryStringDecoder {
|
|||||||
*/
|
*/
|
||||||
public Map<String, List<String>> parameters() {
|
public Map<String, List<String>> parameters() {
|
||||||
if (params == null) {
|
if (params == null) {
|
||||||
params = decodeParams(uri, pathEndIdx(), charset, maxParams);
|
params = decodeParams(uri, pathEndIdx(), charset, maxParams, semicolonIsNormalChar);
|
||||||
}
|
}
|
||||||
return params;
|
return params;
|
||||||
}
|
}
|
||||||
@ -205,7 +225,8 @@ public class QueryStringDecoder {
|
|||||||
return pathEndIdx;
|
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();
|
int len = s.length();
|
||||||
if (from >= len) {
|
if (from >= len) {
|
||||||
return Collections.emptyMap();
|
return Collections.emptyMap();
|
||||||
@ -227,8 +248,12 @@ public class QueryStringDecoder {
|
|||||||
valueStart = i + 1;
|
valueStart = i + 1;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case '&':
|
|
||||||
case ';':
|
case ';':
|
||||||
|
if (semicolonIsNormalChar) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// fall-through
|
||||||
|
case '&':
|
||||||
if (addParam(s, nameStart, valueStart, i, params, charset)) {
|
if (addParam(s, nameStart, valueStart, i, params, charset)) {
|
||||||
paramsLimit--;
|
paramsLimit--;
|
||||||
if (paramsLimit == 0) {
|
if (paramsLimit == 0) {
|
||||||
|
@ -129,6 +129,13 @@ public class QueryStringDecoderTest {
|
|||||||
assertQueryString("/foo?a=1&a=&a=", "/foo?a=1&a&a=");
|
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
|
@Test
|
||||||
public void testPathSpecific() {
|
public void testPathSpecific() {
|
||||||
// decode escaped characters
|
// decode escaped characters
|
||||||
@ -225,8 +232,14 @@ public class QueryStringDecoderTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static void assertQueryString(String expected, String actual) {
|
private static void assertQueryString(String expected, String actual) {
|
||||||
QueryStringDecoder ed = new QueryStringDecoder(expected, CharsetUtil.UTF_8);
|
assertQueryString(expected, actual, false);
|
||||||
QueryStringDecoder ad = new QueryStringDecoder(actual, CharsetUtil.UTF_8);
|
}
|
||||||
|
|
||||||
|
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.path(), ad.path());
|
||||||
Assert.assertEquals(ed.parameters(), ad.parameters());
|
Assert.assertEquals(ed.parameters(), ad.parameters());
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user