Support strict URI variable encoding

The DefaulUriTemplateHandler now provides a strictEncoding property
which if turned on encodes everything outside the reserved char set.

This is in contrast to the default policy of encoding only illegal
charaters depending on the URI component type.

Issue: SPR-11652
This commit is contained in:
Rossen Stoyanchev
2016-03-03 14:37:48 -05:00
parent e98738d8ba
commit 6f2c968925
2 changed files with 111 additions and 12 deletions

View File

@@ -16,8 +16,10 @@
package org.springframework.web.util; package org.springframework.web.util;
import java.io.UnsupportedEncodingException;
import java.net.URI; import java.net.URI;
import java.net.URISyntaxException; import java.net.URISyntaxException;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@@ -36,6 +38,8 @@ public class DefaultUriTemplateHandler implements UriTemplateHandler {
private boolean parsePath; private boolean parsePath;
private boolean strictEncoding;
/** /**
* Configure a base URL to prepend URI templates with. The base URL must * 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; return this.parsePath;
} }
/**
* Whether to encode characters outside the unreserved set as defined in
* <a href="https://tools.ietf.org/html/rfc3986#section-2">RFC 3986 Section 2</a>.
* This ensures a URI variable value will not contain any characters with a
* reserved purpose.
* <p>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.
* <p><strong>Note:</strong> 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 @Override
public URI expand(String uriTemplate, Map<String, ?> uriVariables) { public URI expand(String uriTemplate, Map<String, ?> uriVariables) {
UriComponentsBuilder builder = initUriComponentsBuilder(uriTemplate); UriComponentsBuilder uriComponentsBuilder = initUriComponentsBuilder(uriTemplate);
UriComponents url = builder.build().expand(uriVariables).encode(); UriComponents uriComponents = expandAndEncode(uriComponentsBuilder, uriVariables);
return insertBaseUrl(url); return insertBaseUrl(uriComponents);
} }
@Override @Override
public URI expand(String uriTemplate, Object... uriVariables) { public URI expand(String uriTemplate, Object... uriVariables) {
UriComponentsBuilder builder = initUriComponentsBuilder(uriTemplate); UriComponentsBuilder uriComponentsBuilder = initUriComponentsBuilder(uriTemplate);
UriComponents url = builder.build().expand(uriVariables).encode(); UriComponents uriComponents = expandAndEncode(uriComponentsBuilder, uriVariables);
return insertBaseUrl(url); return insertBaseUrl(uriComponents);
} }
/** /**
@@ -105,7 +135,7 @@ public class DefaultUriTemplateHandler implements UriTemplateHandler {
*/ */
protected UriComponentsBuilder initUriComponentsBuilder(String uriTemplate) { protected UriComponentsBuilder initUriComponentsBuilder(String uriTemplate) {
UriComponentsBuilder builder = UriComponentsBuilder.fromUriString(uriTemplate); UriComponentsBuilder builder = UriComponentsBuilder.fromUriString(uriTemplate);
if (shouldParsePath()) { if (shouldParsePath() && !isStrictEncoding()) {
List<String> pathSegments = builder.build().getPathSegments(); List<String> pathSegments = builder.build().getPathSegments();
builder.replacePath(null); builder.replacePath(null);
for (String pathSegment : pathSegments) { for (String pathSegment : pathSegments) {
@@ -115,6 +145,43 @@ public class DefaultUriTemplateHandler implements UriTemplateHandler {
return builder; return builder;
} }
protected UriComponents expandAndEncode(UriComponentsBuilder builder, Map<String, ?> uriVariables) {
if (!isStrictEncoding()) {
return builder.build().expand(uriVariables).encode();
}
else {
Map<String, Object> encodedUriVars = new HashMap<String, Object>(uriVariables.size());
for (Map.Entry<String, ?> 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 * Invoked after the URI template has been expanded and encoded to prepend
* the configured {@link #setBaseUrl(String) baseUrl} if any. * the configured {@link #setBaseUrl(String) baseUrl} if any.
@@ -122,10 +189,10 @@ public class DefaultUriTemplateHandler implements UriTemplateHandler {
* @return the final URI * @return the final URI
*/ */
protected URI insertBaseUrl(UriComponents uriComponents) { protected URI insertBaseUrl(UriComponents uriComponents) {
if (getBaseUrl() == null || uriComponents.getHost() != null) { String url = uriComponents.toUriString();
return uriComponents.toUri(); if (getBaseUrl() != null && uriComponents.getHost() == null) {
url = getBaseUrl() + url;
} }
String url = getBaseUrl() + uriComponents.toUriString();
try { try {
return new URI(url); return new URI(url);
} }

View File

@@ -49,7 +49,8 @@ public class DefaultUriTemplateHandlerTests {
} }
@Test @Test
public void expandWithFullPath() throws Exception { public void parsePathOff() throws Exception {
this.handler.setParsePath(false);
Map<String, String> vars = new HashMap<>(2); Map<String, String> vars = new HashMap<>(2);
vars.put("hotel", "1"); vars.put("hotel", "1");
vars.put("publicpath", "pics/logo.png"); vars.put("publicpath", "pics/logo.png");
@@ -60,7 +61,7 @@ public class DefaultUriTemplateHandlerTests {
} }
@Test @Test
public void expandWithFullPathAndParsePathEnabled() throws Exception { public void parsePathOn() throws Exception {
this.handler.setParsePath(true); this.handler.setParsePath(true);
Map<String, String> vars = new HashMap<>(2); Map<String, String> vars = new HashMap<>(2);
vars.put("hotel", "1"); vars.put("hotel", "1");
@@ -72,4 +73,35 @@ public class DefaultUriTemplateHandlerTests {
assertEquals(expected, actual); assertEquals(expected, actual);
} }
@Test
public void strictEncodingOff() throws Exception {
this.handler.setStrictEncoding(false);
Map<String, String> 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<String, String> 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);
}
} }