From af132384cced3c07a45d4ca75eac85fafdbb15d0 Mon Sep 17 00:00:00 2001 From: Norman Maurer Date: Wed, 23 Oct 2019 23:44:37 -0700 Subject: [PATCH] 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 --- .../codec/http/QueryStringDecoder.java | 31 +++++++++++++++++-- .../codec/http/QueryStringDecoderTest.java | 17 ++++++++-- 2 files changed, 43 insertions(+), 5 deletions(-) diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/QueryStringDecoder.java b/codec-http/src/main/java/io/netty/handler/codec/http/QueryStringDecoder.java index 06f5e8c694..845bc1940d 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/QueryStringDecoder.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/QueryStringDecoder.java @@ -69,6 +69,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> params; @@ -110,9 +111,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 = requireNonNull(uri, "uri"); this.charset = requireNonNull(charset, "charset"); this.maxParams = checkPositive(maxParams, "maxParams"); + this.semicolonIsNormalChar = semicolonIsNormalChar; // `-1` means that path end index will be initialized lazily pathEndIdx = hasPath ? -1 : 0; @@ -139,6 +150,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; @@ -148,6 +167,7 @@ public class QueryStringDecoder { this.uri = rawQuery == null? rawPath : rawPath + '?' + rawQuery; this.charset = requireNonNull(charset, "charset"); this.maxParams = checkPositive(maxParams, "maxParams"); + this.semicolonIsNormalChar = semicolonIsNormalChar; pathEndIdx = rawPath.length(); } @@ -178,7 +198,7 @@ public class QueryStringDecoder { */ public Map> parameters() { if (params == null) { - params = decodeParams(uri, pathEndIdx(), charset, maxParams); + params = decodeParams(uri, pathEndIdx(), charset, maxParams, semicolonIsNormalChar); } return params; } @@ -205,7 +225,8 @@ public class QueryStringDecoder { return pathEndIdx; } - private static Map> decodeParams(String s, int from, Charset charset, int paramsLimit) { + private static Map> decodeParams(String s, int from, Charset charset, int paramsLimit, + boolean semicolonIsNormalChar) { int len = s.length(); if (from >= len) { return Collections.emptyMap(); @@ -227,8 +248,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) { diff --git a/codec-http/src/test/java/io/netty/handler/codec/http/QueryStringDecoderTest.java b/codec-http/src/test/java/io/netty/handler/codec/http/QueryStringDecoderTest.java index a0071c4a70..5937d66bf2 100644 --- a/codec-http/src/test/java/io/netty/handler/codec/http/QueryStringDecoderTest.java +++ b/codec-http/src/test/java/io/netty/handler/codec/http/QueryStringDecoderTest.java @@ -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()); }