Skip to content

Commit

Permalink
#1485 - Ensure proper encoding for request parameter expansion.
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
odrotbohm committed Jul 28, 2021
1 parent d6d816d commit db1cd5d
Show file tree
Hide file tree
Showing 3 changed files with 41 additions and 33 deletions.
Expand Up @@ -54,23 +54,6 @@ public static String encodePath(Object source) {
}
}

/**
* 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.
*
Expand Down
Expand Up @@ -17,7 +17,6 @@

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;
Expand All @@ -30,6 +29,7 @@
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;
Expand Down Expand Up @@ -114,7 +114,9 @@ private static <T extends LinkBuilder> PreparedWebHandler<T> linkTo(Object invoc
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();
Expand All @@ -123,8 +125,8 @@ private static <T extends LinkBuilder> PreparedWebHandler<T> linkTo(Object invoc
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<>();
Expand Down Expand Up @@ -194,24 +196,28 @@ private static void bindRequestParameters(UriComponentsBuilder builder, HandlerM

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 (Entry<String, ?> entry : requestParams.entrySet()) {

String key = entry.getKey();
TemplateVariable variable = TemplateVariable.requestParameter(key);

for (Map.Entry<String, String> requestParamEntry : requestParams.entrySet()) {
builder.queryParam(requestParamEntry.getKey(), encodeParameter(requestParamEntry.getValue()));
builder.queryParam(key, variable.prepareAndEncode(entry.getValue()));
}

return;
Expand All @@ -222,18 +228,17 @@ private static void bindRequestParameters(UriComponentsBuilder builder, HandlerM
}

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));
}
}
}
Expand All @@ -247,7 +252,7 @@ private static void bindRequestParameters(UriComponentsBuilder builder, HandlerM

} else {
if (key != null) {
builder.queryParam(key, encodeParameter(parameter.getValueAsString(arguments, conversionService)));
builder.queryParam(key, variable.prepareAndEncode(parameter.getValueAsString(arguments, conversionService)));
}
}
}
Expand Down
Expand Up @@ -19,6 +19,8 @@
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;
Expand All @@ -28,6 +30,8 @@
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;
Expand Down Expand Up @@ -638,6 +642,17 @@ void buildsNonCompositeRequestParamUri() {
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();
}
Expand Down Expand Up @@ -733,6 +748,11 @@ HttpEntity<Void> methodWithMapRequestParam(@RequestParam Map<String, String> par
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")
Expand Down

0 comments on commit db1cd5d

Please sign in to comment.