diff --git a/common/uri/src/main/java/io/helidon/common/uri/UriEncoding.java b/common/uri/src/main/java/io/helidon/common/uri/UriEncoding.java index 70a5e1f213e..f7abb5e1ce8 100644 --- a/common/uri/src/main/java/io/helidon/common/uri/UriEncoding.java +++ b/common/uri/src/main/java/io/helidon/common/uri/UriEncoding.java @@ -43,19 +43,27 @@ private UriEncoding() { /** * Decode a URI segment. + *

+ * Percent characters {@code "%s"} found between brackets {@code "[]"} are not decoded to support IPv6 literal. + * E.g. {@code http://[fe80::1%lo0]:8080}. + *

+ * See RFC 6874, section 2. * * @param uriSegment URI segment with percent encoding * @return decoded string */ public static String decodeUri(String uriSegment) { - if (uriSegment.isEmpty()) { - return ""; - } - if (uriSegment.indexOf('%') == -1 && uriSegment.indexOf('+') == -1) { - return uriSegment; - } + return decodeUri(uriSegment, true); + } - return decode(uriSegment); + /** + * Decode a URI query. + * + * @param uriQuery URI query with percent encoding + * @return decoded string + */ + public static String decodeQuery(String uriQuery) { + return decodeUri(uriQuery, false); } /** @@ -123,7 +131,18 @@ private static void appendEscape(StringBuilder appender, int b) { appender.append(HEX_DIGITS[b & 0x0F]); } - private static String decode(String string) { + private static String decodeUri(String uriSegment, boolean ignorePercentInBrackets) { + if (uriSegment.isEmpty()) { + return ""; + } + if (uriSegment.indexOf('%') == -1 && uriSegment.indexOf('+') == -1) { + return uriSegment; + } + return decode(uriSegment, ignorePercentInBrackets); + } + + // see java.net.URI.decode(String, boolean) + private static String decode(String string, boolean ignorePercentInBrackets) { int len = string.length(); StringBuilder sb = new StringBuilder(len); @@ -141,7 +160,7 @@ private static String decode(String string) { } else if (betweenBrackets && c == ']') { betweenBrackets = false; } - if (c != '%' || betweenBrackets) { + if (c != '%' || (betweenBrackets && ignorePercentInBrackets)) { sb.append(c == '+' && !betweenBrackets ? ' ' : c); // handles '+' decoding if (++i >= len) { break; diff --git a/common/uri/src/main/java/io/helidon/common/uri/UriQueryImpl.java b/common/uri/src/main/java/io/helidon/common/uri/UriQueryImpl.java index dbe14f22650..fd6544cc808 100644 --- a/common/uri/src/main/java/io/helidon/common/uri/UriQueryImpl.java +++ b/common/uri/src/main/java/io/helidon/common/uri/UriQueryImpl.java @@ -31,7 +31,7 @@ import io.helidon.common.mapper.OptionalValue; import io.helidon.common.mapper.Value; -import static io.helidon.common.uri.UriEncoding.decodeUri; +import static io.helidon.common.uri.UriEncoding.decodeQuery; // must be lazily populated to prevent perf overhead when queries are ignored final class UriQueryImpl implements UriQuery { @@ -215,11 +215,11 @@ private void ensureDecoded() { private void addDecoded(Map> newQueryParams, String next) { int eq = next.indexOf('='); if (eq == -1) { - newQueryParams.putIfAbsent(decodeUri(next), new LinkedList<>()); + newQueryParams.putIfAbsent(decodeQuery(next), new LinkedList<>()); } else { String name = next.substring(0, eq); String value = next.substring(eq + 1); - newQueryParams.computeIfAbsent(decodeUri(name), it -> new LinkedList<>()).add(decodeUri(value)); + newQueryParams.computeIfAbsent(decodeQuery(name), it -> new LinkedList<>()).add(decodeQuery(value)); } } diff --git a/common/uri/src/test/java/io/helidon/common/uri/UriEncodingTest.java b/common/uri/src/test/java/io/helidon/common/uri/UriEncodingTest.java index 56d791e35d2..e8c1214d979 100644 --- a/common/uri/src/test/java/io/helidon/common/uri/UriEncodingTest.java +++ b/common/uri/src/test/java/io/helidon/common/uri/UriEncodingTest.java @@ -30,4 +30,9 @@ void testSpaceDecoding() { assertThat(decodeUri("+hello+world+"), is(" hello world ")); assertThat(decodeUri("[+]hello[+]world[+]"), is("[+]hello[+]world[+]")); } + + @Test + void testIPv6Literal() { + assertThat(decodeUri("http://[fe80::1%lo0]:8080"), is("http://[fe80::1%lo0]:8080")); + } } diff --git a/common/uri/src/test/java/io/helidon/common/uri/UriQueryTest.java b/common/uri/src/test/java/io/helidon/common/uri/UriQueryTest.java index d9dc16fed24..cdbee2474a7 100644 --- a/common/uri/src/test/java/io/helidon/common/uri/UriQueryTest.java +++ b/common/uri/src/test/java/io/helidon/common/uri/UriQueryTest.java @@ -16,7 +16,6 @@ package io.helidon.common.uri; -import java.io.UnsupportedEncodingException; import java.net.URI; import java.net.URLEncoder; @@ -41,11 +40,17 @@ void sanityParse() { } @Test - void testEncoded() throws UnsupportedEncodingException { + void testEncoded() { UriQuery uriQuery = UriQuery.create("a=" + URLEncoder.encode("1&b=2", US_ASCII)); assertThat(uriQuery.get("a"), is("1&b=2")); } + @Test + void testEncodedWithinBrackets() { + UriQuery uriQuery = UriQuery.create("msg=[Hello%20World]"); + assertThat(uriQuery.get("msg"), is("[Hello World]")); + } + @Test void testEncodedOtherChars() { UriQuery uriQuery = UriQuery.create("a=b%26c=d&e=f&e=g&h=x%63%23e%3c");