diff --git a/spring-web/src/main/java/org/springframework/web/util/DefaultUriTemplateHandler.java b/spring-web/src/main/java/org/springframework/web/util/DefaultUriTemplateHandler.java
index 00d62a624ad..3eb27c3009a 100644
--- a/spring-web/src/main/java/org/springframework/web/util/DefaultUriTemplateHandler.java
+++ b/spring-web/src/main/java/org/springframework/web/util/DefaultUriTemplateHandler.java
@@ -16,8 +16,10 @@
package org.springframework.web.util;
+import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URISyntaxException;
+import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -36,6 +38,8 @@ public class DefaultUriTemplateHandler implements UriTemplateHandler {
private boolean parsePath;
+ private boolean strictEncoding;
+
/**
* Configure a base URL to prepend URI templates with. The base URL must
@@ -83,19 +87,45 @@ public class DefaultUriTemplateHandler implements UriTemplateHandler {
return this.parsePath;
}
+ /**
+ * Whether to encode characters outside the unreserved set as defined in
+ * RFC 3986 Section 2.
+ * This ensures a URI variable value will not contain any characters with a
+ * reserved purpose.
+ *
By default this is set to {@code false} in which case only characters
+ * illegal for the given URI component are encoded. For example when expanding
+ * a URI variable into a path segment the "/" character is illegal and
+ * encoded. The ";" character however is legal and not encoded even though
+ * it has a reserved purpose.
+ *
Note: this property supersedes the need to also set
+ * the {@link #setParsePath parsePath} property.
+ * @param strictEncoding whether to perform strict encoding
+ * @since 4.3
+ */
+ public void setStrictEncoding(boolean strictEncoding) {
+ this.strictEncoding = strictEncoding;
+ }
+
+ /**
+ * Whether to strictly encode any character outside the unreserved set.
+ */
+ public boolean isStrictEncoding() {
+ return this.strictEncoding;
+ }
+
@Override
public URI expand(String uriTemplate, Map uriVariables) {
- UriComponentsBuilder builder = initUriComponentsBuilder(uriTemplate);
- UriComponents url = builder.build().expand(uriVariables).encode();
- return insertBaseUrl(url);
+ UriComponentsBuilder uriComponentsBuilder = initUriComponentsBuilder(uriTemplate);
+ UriComponents uriComponents = expandAndEncode(uriComponentsBuilder, uriVariables);
+ return insertBaseUrl(uriComponents);
}
@Override
public URI expand(String uriTemplate, Object... uriVariables) {
- UriComponentsBuilder builder = initUriComponentsBuilder(uriTemplate);
- UriComponents url = builder.build().expand(uriVariables).encode();
- return insertBaseUrl(url);
+ UriComponentsBuilder uriComponentsBuilder = initUriComponentsBuilder(uriTemplate);
+ UriComponents uriComponents = expandAndEncode(uriComponentsBuilder, uriVariables);
+ return insertBaseUrl(uriComponents);
}
/**
@@ -105,7 +135,7 @@ public class DefaultUriTemplateHandler implements UriTemplateHandler {
*/
protected UriComponentsBuilder initUriComponentsBuilder(String uriTemplate) {
UriComponentsBuilder builder = UriComponentsBuilder.fromUriString(uriTemplate);
- if (shouldParsePath()) {
+ if (shouldParsePath() && !isStrictEncoding()) {
List pathSegments = builder.build().getPathSegments();
builder.replacePath(null);
for (String pathSegment : pathSegments) {
@@ -115,6 +145,43 @@ public class DefaultUriTemplateHandler implements UriTemplateHandler {
return builder;
}
+ protected UriComponents expandAndEncode(UriComponentsBuilder builder, Map uriVariables) {
+ if (!isStrictEncoding()) {
+ return builder.build().expand(uriVariables).encode();
+ }
+ else {
+ Map encodedUriVars = new HashMap(uriVariables.size());
+ for (Map.Entry entry : uriVariables.entrySet()) {
+ encodedUriVars.put(entry.getKey(), encodeValue(entry.getValue()));
+ }
+ return builder.build().expand(encodedUriVars);
+ }
+ }
+
+ protected UriComponents expandAndEncode(UriComponentsBuilder builder, Object[] uriVariables) {
+ if (!isStrictEncoding()) {
+ return builder.build().expand(uriVariables).encode();
+ }
+ else {
+ Object[] encodedUriVars = new Object[uriVariables.length];
+ for (int i = 0; i < uriVariables.length; i++) {
+ encodedUriVars[i] = encodeValue(uriVariables[i]);
+ }
+ return builder.build().expand(encodedUriVars);
+ }
+ }
+
+ private String encodeValue(Object value) {
+ String stringValue = (value != null ? value.toString() : "");
+ try {
+ return UriUtils.encode(stringValue, "UTF-8");
+ }
+ catch (UnsupportedEncodingException ex) {
+ // Should never happen
+ throw new IllegalStateException("Failed to encode URI variable", ex);
+ }
+ }
+
/**
* Invoked after the URI template has been expanded and encoded to prepend
* the configured {@link #setBaseUrl(String) baseUrl} if any.
@@ -122,10 +189,10 @@ public class DefaultUriTemplateHandler implements UriTemplateHandler {
* @return the final URI
*/
protected URI insertBaseUrl(UriComponents uriComponents) {
- if (getBaseUrl() == null || uriComponents.getHost() != null) {
- return uriComponents.toUri();
+ String url = uriComponents.toUriString();
+ if (getBaseUrl() != null && uriComponents.getHost() == null) {
+ url = getBaseUrl() + url;
}
- String url = getBaseUrl() + uriComponents.toUriString();
try {
return new URI(url);
}
diff --git a/spring-web/src/test/java/org/springframework/web/util/DefaultUriTemplateHandlerTests.java b/spring-web/src/test/java/org/springframework/web/util/DefaultUriTemplateHandlerTests.java
index dd6c67c3d50..691d6da285f 100644
--- a/spring-web/src/test/java/org/springframework/web/util/DefaultUriTemplateHandlerTests.java
+++ b/spring-web/src/test/java/org/springframework/web/util/DefaultUriTemplateHandlerTests.java
@@ -49,7 +49,8 @@ public class DefaultUriTemplateHandlerTests {
}
@Test
- public void expandWithFullPath() throws Exception {
+ public void parsePathOff() throws Exception {
+ this.handler.setParsePath(false);
Map vars = new HashMap<>(2);
vars.put("hotel", "1");
vars.put("publicpath", "pics/logo.png");
@@ -60,7 +61,7 @@ public class DefaultUriTemplateHandlerTests {
}
@Test
- public void expandWithFullPathAndParsePathEnabled() throws Exception {
+ public void parsePathOn() throws Exception {
this.handler.setParsePath(true);
Map vars = new HashMap<>(2);
vars.put("hotel", "1");
@@ -72,4 +73,35 @@ public class DefaultUriTemplateHandlerTests {
assertEquals(expected, actual);
}
+ @Test
+ public void strictEncodingOff() throws Exception {
+ this.handler.setStrictEncoding(false);
+ Map vars = new HashMap<>(2);
+ vars.put("userId", "john;doe");
+ String template = "http://www.example.com/user/{userId}/dashboard";
+ URI actual = this.handler.expand(template, vars);
+ URI expected = new URI("http://www.example.com/user/john;doe/dashboard");
+ assertEquals(expected, actual);
+ }
+
+ @Test
+ public void strictEncodingOnWithMap() throws Exception {
+ this.handler.setStrictEncoding(true);
+ Map vars = new HashMap<>(2);
+ vars.put("userId", "john;doe");
+ String template = "http://www.example.com/user/{userId}/dashboard";
+ URI actual = this.handler.expand(template, vars);
+ URI expected = new URI("http://www.example.com/user/john%3Bdoe/dashboard");
+ assertEquals(expected, actual);
+ }
+
+ @Test
+ public void strictEncodingOnWithArray() throws Exception {
+ this.handler.setStrictEncoding(true);
+ String template = "http://www.example.com/user/{userId}/dashboard";
+ URI actual = this.handler.expand(template, "john;doe");
+ URI expected = new URI("http://www.example.com/user/john%3Bdoe/dashboard");
+ assertEquals(expected, actual);
+ }
+
}