Skip to content

Commit db1cd5d

Browse files
committed
#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.
1 parent d6d816d commit db1cd5d

File tree

3 files changed

+41
-33
lines changed

3 files changed

+41
-33
lines changed

src/main/java/org/springframework/hateoas/server/core/EncodingUtils.java

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -54,23 +54,6 @@ public static String encodePath(Object source) {
5454
}
5555
}
5656

57-
/**
58-
* Encodes the given request parameter value.
59-
*
60-
* @param source must not be {@literal null}.
61-
* @return
62-
*/
63-
public static String encodeParameter(Object source) {
64-
65-
Assert.notNull(source, "Request parameter value must not be null!");
66-
67-
try {
68-
return UriUtils.encodeQueryParam(source.toString(), ENCODING);
69-
} catch (Throwable e) {
70-
throw new IllegalStateException(e);
71-
}
72-
}
73-
7457
/**
7558
* Encodes the given fragment value.
7659
*

src/main/java/org/springframework/hateoas/server/core/WebHandler.java

Lines changed: 21 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717

1818
import static org.springframework.hateoas.TemplateVariable.VariableType.*;
1919
import static org.springframework.hateoas.TemplateVariables.*;
20-
import static org.springframework.hateoas.server.core.EncodingUtils.*;
2120
import static org.springframework.web.util.UriComponents.UriTemplateVariables.*;
2221

2322
import java.lang.annotation.Annotation;
@@ -30,6 +29,7 @@
3029
import java.util.Iterator;
3130
import java.util.List;
3231
import java.util.Map;
32+
import java.util.Map.Entry;
3333
import java.util.Optional;
3434
import java.util.concurrent.ConcurrentHashMap;
3535
import java.util.function.BiFunction;
@@ -114,7 +114,9 @@ private static <T extends LinkBuilder> PreparedWebHandler<T> linkTo(Object invoc
114114
Iterator<Object> classMappingParameters = invocations.getObjectParameters();
115115

116116
while (classMappingParameters.hasNext()) {
117-
values.put(names.next(), encodePath(classMappingParameters.next()));
117+
String name = names.next();
118+
TemplateVariable variable = TemplateVariable.segment(name);
119+
values.put(name, variable.prepareAndEncode(classMappingParameters.next()));
118120
}
119121

120122
Method method = invocation.getMethod();
@@ -123,8 +125,8 @@ private static <T extends LinkBuilder> PreparedWebHandler<T> linkTo(Object invoc
123125
ConversionService resolved = conversionService;
124126

125127
for (HandlerMethodParameter parameter : parameters.getParameterAnnotatedWith(PathVariable.class, arguments)) {
126-
values.put(parameter.getVariableName(),
127-
encodePath(parameter.getValueAsString(arguments, resolved)));
128+
TemplateVariable variable = TemplateVariable.segment(parameter.getVariableName());
129+
values.put(variable.getName(), variable.prepareAndEncode(parameter.getValueAsString(arguments, resolved)));
128130
}
129131

130132
List<String> optionalEmptyParameters = new ArrayList<>();
@@ -194,24 +196,28 @@ private static void bindRequestParameters(UriComponentsBuilder builder, HandlerM
194196

195197
if (value instanceof MultiValueMap) {
196198

197-
MultiValueMap<String, String> requestParams = (MultiValueMap<String, String>) value;
199+
Map<String, List<?>> requestParams = (Map<String, List<?>>) value;
198200

199-
for (Map.Entry<String, List<String>> multiValueEntry : requestParams.entrySet()) {
200-
for (String singleEntryValue : multiValueEntry.getValue()) {
201-
builder.queryParam(multiValueEntry.getKey(), encodeParameter(singleEntryValue));
201+
for (Entry<String, List<?>> entry : requestParams.entrySet()) {
202+
for (Object element : entry.getValue()) {
203+
TemplateVariable variable = TemplateVariable.pathVariable(entry.getKey());
204+
builder.queryParam(entry.getKey(), variable.prepareAndEncode(element));
202205
}
203206
}
204207

205208
return;
206-
207209
}
208210

209211
if (value instanceof Map) {
210212

211-
Map<String, String> requestParams = (Map<String, String>) value;
213+
Map<String, ?> requestParams = (Map<String, ?>) value;
214+
215+
for (Entry<String, ?> entry : requestParams.entrySet()) {
216+
217+
String key = entry.getKey();
218+
TemplateVariable variable = TemplateVariable.requestParameter(key);
212219

213-
for (Map.Entry<String, String> requestParamEntry : requestParams.entrySet()) {
214-
builder.queryParam(requestParamEntry.getKey(), encodeParameter(requestParamEntry.getValue()));
220+
builder.queryParam(key, variable.prepareAndEncode(entry.getValue()));
215221
}
216222

217223
return;
@@ -222,18 +228,17 @@ private static void bindRequestParameters(UriComponentsBuilder builder, HandlerM
222228
}
223229

224230
String key = parameter.getVariableName();
231+
TemplateVariable variable = TemplateVariable.requestParameter(key);
225232

226233
if (value instanceof Collection) {
227234

228235
if (parameter.isNonComposite()) {
229-
230-
TemplateVariable variable = TemplateVariable.requestParameter(key);
231236
builder.queryParam(key, variable.prepareAndEncode(value));
232237

233238
} else {
234239
for (Object element : (Collection<?>) value) {
235240
if (key != null) {
236-
builder.queryParam(key, encodeParameter(element));
241+
builder.queryParam(key, variable.prepareAndEncode(element));
237242
}
238243
}
239244
}
@@ -247,7 +252,7 @@ private static void bindRequestParameters(UriComponentsBuilder builder, HandlerM
247252

248253
} else {
249254
if (key != null) {
250-
builder.queryParam(key, encodeParameter(parameter.getValueAsString(arguments, conversionService)));
255+
builder.queryParam(key, variable.prepareAndEncode(parameter.getValueAsString(arguments, conversionService)));
251256
}
252257
}
253258
}

src/test/java/org/springframework/hateoas/server/mvc/WebMvcLinkBuilderUnitTest.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.*;
2020

2121
import java.lang.reflect.Method;
22+
import java.time.OffsetDateTime;
23+
import java.time.ZoneId;
2224
import java.util.Arrays;
2325
import java.util.Collection;
2426
import java.util.HashMap;
@@ -28,6 +30,8 @@
2830
import java.util.stream.Stream;
2931

3032
import org.junit.jupiter.api.Test;
33+
import org.springframework.format.annotation.DateTimeFormat;
34+
import org.springframework.format.annotation.DateTimeFormat.ISO;
3135
import org.springframework.hateoas.IanaLinkRelations;
3236
import org.springframework.hateoas.Link;
3337
import org.springframework.hateoas.NonComposite;
@@ -638,6 +642,17 @@ void buildsNonCompositeRequestParamUri() {
638642
assertThat(link.getHref()).endsWith("?foo=first,second");
639643
}
640644

645+
@Test // #1485
646+
void encodesDatesCorrectly() {
647+
648+
OffsetDateTime reference = OffsetDateTime.now(ZoneId.of("CET"));
649+
Link link = linkTo(methodOn(ControllerWithMethods.class).methodWithOffsetDateTime(reference)).withSelfRel();
650+
651+
assertThat(UriComponentsBuilder.fromUriString(link.getHref()).build().getQuery())
652+
.contains("%3A", "%2B")
653+
.doesNotContain(":", "+");
654+
}
655+
641656
private static UriComponents toComponents(Link link) {
642657
return UriComponentsBuilder.fromUriString(link.expand().getHref()).build();
643658
}
@@ -733,6 +748,11 @@ HttpEntity<Void> methodWithMapRequestParam(@RequestParam Map<String, String> par
733748
HttpEntity<Void> nonCompositeRequestParam(@NonComposite @RequestParam("foo") Collection<String> params) {
734749
return null;
735750
}
751+
752+
@RequestMapping("/offset")
753+
HttpEntity<Void> methodWithOffsetDateTime(@RequestParam @DateTimeFormat(iso = ISO.DATE_TIME) OffsetDateTime date) {
754+
return null;
755+
}
736756
}
737757

738758
@RequestMapping("/parent")

0 commit comments

Comments
 (0)