diff --git a/flow-server/src/main/java/com/vaadin/flow/router/LocationUtil.java b/flow-server/src/main/java/com/vaadin/flow/router/LocationUtil.java index a76328714a4..0a6c5e72dbb 100644 --- a/flow-server/src/main/java/com/vaadin/flow/router/LocationUtil.java +++ b/flow-server/src/main/java/com/vaadin/flow/router/LocationUtil.java @@ -34,7 +34,6 @@ public class LocationUtil { private static final String PATH_SEPARATOR = "/"; - private static final String PARAMETERS_SEPARATOR = "&"; // Prevent instantiation of util class private LocationUtil() { diff --git a/flow-server/src/main/java/com/vaadin/flow/router/QueryParameters.java b/flow-server/src/main/java/com/vaadin/flow/router/QueryParameters.java index 16bfd0b06a4..79278a905c2 100644 --- a/flow-server/src/main/java/com/vaadin/flow/router/QueryParameters.java +++ b/flow-server/src/main/java/com/vaadin/flow/router/QueryParameters.java @@ -16,6 +16,8 @@ package com.vaadin.flow.router; import java.io.Serializable; +import java.io.UnsupportedEncodingException; +import java.net.URLDecoder; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -97,6 +99,70 @@ private static Map> toFullParameters( entry -> Collections.singletonList(entry.getValue()))); } + /** + * Creates parameters from a query string. + *

+ * Note that no length checking is done for the string. It is the + * responsibility of the caller (or the server) to limit the length of the + * query string. + * + * @param queryString + * the query string + * @return query parameters information + */ + public static QueryParameters fromString(String queryString) { + return new QueryParameters(parseQueryString(queryString)); + } + + private static Map> parseQueryString(String query) { + Map> parsedParams = Arrays + .stream(query.split(PARAMETERS_SEPARATOR)) + .map(QueryParameters::makeQueryParamList) + .collect(Collectors.toMap(list -> list.get(0), + QueryParameters::getParameterValues, + QueryParameters::mergeLists)); + return parsedParams; + } + + private static List makeQueryParamList(String paramAndValue) { + int index = paramAndValue.indexOf('='); + if (index == -1) { + return Collections.singletonList(paramAndValue); + } + String param = paramAndValue.substring(0, index); + String value = paramAndValue.substring(index + 1); + try { + value = URLDecoder.decode(value, "utf-8"); + } catch (UnsupportedEncodingException e) { + throw new RuntimeException( + "Unable to decode parameter value: " + value, e); + } + return Arrays.asList(param, value); + } + + private static List getParameterValues(List paramAndValue) { + if (paramAndValue.size() == 1) { + return Collections.singletonList(""); + } else { + return Collections.singletonList(paramAndValue.get(1)); + } + } + + private static List mergeLists(List list1, + List list2) { + List result = new ArrayList<>(list1); + if (result.isEmpty()) { + result.add(null); + } + if (list2.isEmpty()) { + result.add(null); + } else { + result.addAll(list2); + } + + return result; + } + /** * Returns query parameters information with support for multiple values * corresponding to single parameter name. @@ -125,11 +191,12 @@ public String getQueryString() { private Stream getParameterAndValues( Entry> entry) { - if (entry.getValue().isEmpty()) { + List params = entry.getValue(); + if (params.size() == 1 && "".equals(params.get(0))) { return Stream.of(entry.getKey()); } String param = entry.getKey(); - return entry.getValue().stream().map(value -> value == null ? param + return params.stream().map(value -> "".equals(value) ? param : param + PARAMETER_VALUES_SEPARATOR + value); } } diff --git a/flow-server/src/test/java/com/vaadin/flow/router/LocationTest.java b/flow-server/src/test/java/com/vaadin/flow/router/LocationTest.java index ddec5934c21..53aacc0073b 100644 --- a/flow-server/src/test/java/com/vaadin/flow/router/LocationTest.java +++ b/flow-server/src/test/java/com/vaadin/flow/router/LocationTest.java @@ -70,7 +70,9 @@ public void parseLocationWithQueryString_noValue() { Location location = new Location("path?query"); assertEquals("path", location.getPath()); - assertEquals(Collections.singletonMap("query", Collections.emptyList()), + assertEquals( + Collections.singletonMap("query", + Collections.singletonList("")), location.getQueryParameters().getParameters()); assertEquals("path?query", location.getPathWithQueryParameters()); } @@ -84,7 +86,7 @@ public void parseLocationWithQueryString_emptyValue() { Collections.singletonMap("query", Collections.singletonList("")), location.getQueryParameters().getParameters()); - assertEquals("path?query=", location.getPathWithQueryParameters()); + assertEquals("path?query", location.getPathWithQueryParameters()); } @Test @@ -224,7 +226,7 @@ public void locationWithParamWithAndWithoutValue() { public void locationWithParamAndEmptyValue() { Location location = new Location("foo?param=¶m=bar"); - Assert.assertEquals("param=¶m=bar", + Assert.assertEquals("param¶m=bar", location.getQueryParameters().getQueryString()); } diff --git a/flow-server/src/test/java/com/vaadin/flow/router/QueryParametersTest.java b/flow-server/src/test/java/com/vaadin/flow/router/QueryParametersTest.java index 1c459524828..7b8aced20e7 100644 --- a/flow-server/src/test/java/com/vaadin/flow/router/QueryParametersTest.java +++ b/flow-server/src/test/java/com/vaadin/flow/router/QueryParametersTest.java @@ -30,10 +30,12 @@ import org.junit.Assert; import org.junit.Test; -import com.vaadin.flow.router.QueryParameters; - public class QueryParametersTest { + private String simpleInputQueryString = "one=1&two=2&three=3&four&five=4%2F5%266%2B7"; + + private String complexInputQueryString = "one=1&one=11&two=2&two=22&three=3&four&five=4%2F5%266%2B7"; + private Map getSimpleInputParameters() { Map inputParameters = new HashMap<>(); inputParameters.put("one", "1"); @@ -93,6 +95,20 @@ public void simpleParameters() { assertEquals(expectedFullParams, simpleParams.getParameters()); } + @Test + public void simpleParametersFromQueryString() { + QueryParameters simpleParams = QueryParameters + .fromString(simpleInputQueryString); + + Map> expectedFullParams = new HashMap<>(); + expectedFullParams.put("one", Collections.singletonList("1")); + expectedFullParams.put("two", Collections.singletonList("2")); + expectedFullParams.put("three", Collections.singletonList("3")); + expectedFullParams.put("four", Collections.singletonList("")); + expectedFullParams.put("five", Collections.singletonList("4/5&6+7")); + assertEquals(expectedFullParams, simpleParams.getParameters()); + } + @Test public void simpleParametersToQueryString() { QueryParameters simpleParams = QueryParameters @@ -131,6 +147,20 @@ public void complexParameters() { assertEquals(expectedFullParams, fullParams.getParameters()); } + @Test + public void complexParametersFromQueryString() { + QueryParameters fullParams = QueryParameters + .fromString(complexInputQueryString); + + Map> expectedFullParams = new HashMap<>(); + expectedFullParams.put("one", Arrays.asList("1", "11")); + expectedFullParams.put("two", Arrays.asList("2", "22")); + expectedFullParams.put("three", Collections.singletonList("3")); + expectedFullParams.put("four", Collections.singletonList("")); + expectedFullParams.put("five", Collections.singletonList("4/5&6+7")); + assertEquals(expectedFullParams, fullParams.getParameters()); + } + @Test public void complexParametersToQueryString() { QueryParameters fullParams = QueryParameters @@ -168,15 +198,15 @@ public void underlyingListsUnmodifiable_full() { @Test public void parameterWithoutValue() { QueryParameters params = new QueryParameters( - Collections.singletonMap("foo", Collections.emptyList())); + Collections.singletonMap("foo", Collections.singletonList(""))); Assert.assertEquals("foo", params.getQueryString()); params = new QueryParameters( - Collections.singletonMap("foo", Arrays.asList(null, "bar"))); + Collections.singletonMap("foo", Arrays.asList("", "bar"))); Assert.assertEquals("foo&foo=bar", params.getQueryString()); params = new QueryParameters( - Collections.singletonMap("foo", Arrays.asList("bar", null))); + Collections.singletonMap("foo", Arrays.asList("bar", ""))); Assert.assertEquals("foo=bar&foo", params.getQueryString()); } @@ -184,6 +214,6 @@ public void parameterWithoutValue() { public void parameterWithEmptyValue() { QueryParameters fullParams = new QueryParameters( Collections.singletonMap("foo", Collections.singletonList(""))); - Assert.assertEquals("foo=", fullParams.getQueryString()); + Assert.assertEquals("foo", fullParams.getQueryString()); } }