#1485 - Ensure proper encoding for request parameter expansion.

We now use the encoding rules defined in RFC 6570 (URI templates, API introduced in #1583) to prepare request parameters added to the URIs rendered.
This commit is contained in:
Oliver Drotbohm
2021-07-28 21:37:36 +02:00
parent d6d816d77b
commit db1cd5db16
3 changed files with 41 additions and 33 deletions

View File

@@ -54,23 +54,6 @@ final class EncodingUtils {
}
}
/**
* Encodes the given request parameter value.
*
* @param source must not be {@literal null}.
* @return
*/
public static String encodeParameter(Object source) {
Assert.notNull(source, "Request parameter value must not be null!");
try {
return UriUtils.encodeQueryParam(source.toString(), ENCODING);
} catch (Throwable e) {
throw new IllegalStateException(e);
}
}
/**
* Encodes the given fragment value.
*

View File

@@ -17,7 +17,6 @@ package org.springframework.hateoas.server.core;
import static org.springframework.hateoas.TemplateVariable.VariableType.*;
import static org.springframework.hateoas.TemplateVariables.*;
import static org.springframework.hateoas.server.core.EncodingUtils.*;
import static org.springframework.web.util.UriComponents.UriTemplateVariables.*;
import java.lang.annotation.Annotation;
@@ -30,6 +29,7 @@ import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.BiFunction;
@@ -114,7 +114,9 @@ public class WebHandler {
Iterator<Object> classMappingParameters = invocations.getObjectParameters();
while (classMappingParameters.hasNext()) {
values.put(names.next(), encodePath(classMappingParameters.next()));
String name = names.next();
TemplateVariable variable = TemplateVariable.segment(name);
values.put(name, variable.prepareAndEncode(classMappingParameters.next()));
}
Method method = invocation.getMethod();
@@ -123,8 +125,8 @@ public class WebHandler {
ConversionService resolved = conversionService;
for (HandlerMethodParameter parameter : parameters.getParameterAnnotatedWith(PathVariable.class, arguments)) {
values.put(parameter.getVariableName(),
encodePath(parameter.getValueAsString(arguments, resolved)));
TemplateVariable variable = TemplateVariable.segment(parameter.getVariableName());
values.put(variable.getName(), variable.prepareAndEncode(parameter.getValueAsString(arguments, resolved)));
}
List<String> optionalEmptyParameters = new ArrayList<>();
@@ -194,24 +196,28 @@ public class WebHandler {
if (value instanceof MultiValueMap) {
MultiValueMap<String, String> requestParams = (MultiValueMap<String, String>) value;
Map<String, List<?>> requestParams = (Map<String, List<?>>) value;
for (Map.Entry<String, List<String>> multiValueEntry : requestParams.entrySet()) {
for (String singleEntryValue : multiValueEntry.getValue()) {
builder.queryParam(multiValueEntry.getKey(), encodeParameter(singleEntryValue));
for (Entry<String, List<?>> entry : requestParams.entrySet()) {
for (Object element : entry.getValue()) {
TemplateVariable variable = TemplateVariable.pathVariable(entry.getKey());
builder.queryParam(entry.getKey(), variable.prepareAndEncode(element));
}
}
return;
}
if (value instanceof Map) {
Map<String, String> requestParams = (Map<String, String>) value;
Map<String, ?> requestParams = (Map<String, ?>) value;
for (Map.Entry<String, String> requestParamEntry : requestParams.entrySet()) {
builder.queryParam(requestParamEntry.getKey(), encodeParameter(requestParamEntry.getValue()));
for (Entry<String, ?> entry : requestParams.entrySet()) {
String key = entry.getKey();
TemplateVariable variable = TemplateVariable.requestParameter(key);
builder.queryParam(key, variable.prepareAndEncode(entry.getValue()));
}
return;
@@ -222,18 +228,17 @@ public class WebHandler {
}
String key = parameter.getVariableName();
TemplateVariable variable = TemplateVariable.requestParameter(key);
if (value instanceof Collection) {
if (parameter.isNonComposite()) {
TemplateVariable variable = TemplateVariable.requestParameter(key);
builder.queryParam(key, variable.prepareAndEncode(value));
} else {
for (Object element : (Collection<?>) value) {
if (key != null) {
builder.queryParam(key, encodeParameter(element));
builder.queryParam(key, variable.prepareAndEncode(element));
}
}
}
@@ -247,7 +252,7 @@ public class WebHandler {
} else {
if (key != null) {
builder.queryParam(key, encodeParameter(parameter.getValueAsString(arguments, conversionService)));
builder.queryParam(key, variable.prepareAndEncode(parameter.getValueAsString(arguments, conversionService)));
}
}
}

View File

@@ -19,6 +19,8 @@ import static org.assertj.core.api.Assertions.*;
import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.*;
import java.lang.reflect.Method;
import java.time.OffsetDateTime;
import java.time.ZoneId;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
@@ -28,6 +30,8 @@ import java.util.Optional;
import java.util.stream.Stream;
import org.junit.jupiter.api.Test;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.format.annotation.DateTimeFormat.ISO;
import org.springframework.hateoas.IanaLinkRelations;
import org.springframework.hateoas.Link;
import org.springframework.hateoas.NonComposite;
@@ -638,6 +642,17 @@ class WebMvcLinkBuilderUnitTest extends TestUtils {
assertThat(link.getHref()).endsWith("?foo=first,second");
}
@Test // #1485
void encodesDatesCorrectly() {
OffsetDateTime reference = OffsetDateTime.now(ZoneId.of("CET"));
Link link = linkTo(methodOn(ControllerWithMethods.class).methodWithOffsetDateTime(reference)).withSelfRel();
assertThat(UriComponentsBuilder.fromUriString(link.getHref()).build().getQuery())
.contains("%3A", "%2B")
.doesNotContain(":", "+");
}
private static UriComponents toComponents(Link link) {
return UriComponentsBuilder.fromUriString(link.expand().getHref()).build();
}
@@ -733,6 +748,11 @@ class WebMvcLinkBuilderUnitTest extends TestUtils {
HttpEntity<Void> nonCompositeRequestParam(@NonComposite @RequestParam("foo") Collection<String> params) {
return null;
}
@RequestMapping("/offset")
HttpEntity<Void> methodWithOffsetDateTime(@RequestParam @DateTimeFormat(iso = ISO.DATE_TIME) OffsetDateTime date) {
return null;
}
}
@RequestMapping("/parent")