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:
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user