Additional convenience methods in UriUtils

The generic encode method in UriUtils that encodes any character
outside the reserved character set for a URI is meant for "strict"
encoding of URI variable values. This commit adds a couple more
conveninence methods that accept a Map or array of URI variable
values to encode.

This facilitates the use case where the URI template is assumed to
be encoded while URI variables are encoded strictly to avoid any
possibility for unwanted reserved characters:

Map<String, ?> encodedUriVars = UriUtils.encodeUriVariables(uriVars);
uriComponentsBuilder.build(true).expand(encodedUriVars).toUri();

Issue: SPR-14970
This commit is contained in:
Rossen Stoyanchev 2017-01-18 21:45:33 -05:00
parent f2e293aadf
commit bb3b1f2fe2
2 changed files with 54 additions and 22 deletions

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2016 the original author or authors.
* Copyright 2002-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -16,10 +16,8 @@
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;
@ -127,10 +125,7 @@ public class DefaultUriTemplateHandler extends AbstractUriTemplateHandler {
return builder.buildAndExpand(uriVariables).encode();
}
else {
Map<String, Object> encodedUriVars = new HashMap<>(uriVariables.size());
for (Map.Entry<String, ?> entry : uriVariables.entrySet()) {
encodedUriVars.put(entry.getKey(), applyStrictEncoding(entry.getValue()));
}
Map<String, ?> encodedUriVars = UriUtils.encodeUriVariables(uriVariables);
return builder.buildAndExpand(encodedUriVars);
}
}
@ -140,25 +135,11 @@ public class DefaultUriTemplateHandler extends AbstractUriTemplateHandler {
return builder.buildAndExpand(uriVariables).encode();
}
else {
Object[] encodedUriVars = new Object[uriVariables.length];
for (int i = 0; i < uriVariables.length; i++) {
encodedUriVars[i] = applyStrictEncoding(uriVariables[i]);
}
Object[] encodedUriVars = UriUtils.encodeUriVariables(uriVariables);
return builder.buildAndExpand(encodedUriVars);
}
}
private String applyStrictEncoding(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);
}
}
private URI createUri(UriComponents uriComponents) {
try {
// Avoid further encoding (in the case of strictEncoding=true)

View File

@ -18,6 +18,11 @@ package org.springframework.web.util;
import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.stream.Collectors;
import org.springframework.util.StringUtils;
@ -165,6 +170,52 @@ public abstract class UriUtils {
return HierarchicalUriComponents.encodeUriComponent(source, encoding, type);
}
/**
* Encode characters outside the unreserved character set as defined in
* <a href="https://tools.ietf.org/html/rfc3986#section-2">RFC 3986 Section 2</a>.
* <p>This can be used to ensure the given String will not contain any
* characters with reserved URI meaning regardless of URI component.
* @param source the String to be encoded
* @param charset the character encoding to encode to
* @return the encoded String
*/
public static String encode(String source, Charset charset) {
HierarchicalUriComponents.Type type = HierarchicalUriComponents.Type.URI;
return HierarchicalUriComponents.encodeUriComponent(source, charset, type);
}
/**
* Apply {@link #encode(String, String)} to the values in the given URI
* variables and return a new Map containing the encoded values.
* @param uriVariables the URI variable values to be encoded
* @return the encoded String
* @since 5.0
*/
public static Map<String, String> encodeUriVariables(Map<String, ?> uriVariables) {
Map<String, String> result = new LinkedHashMap<>(uriVariables.size());
uriVariables.entrySet().stream().forEach(entry -> {
String stringValue = (entry.getValue() != null ? entry.getValue().toString() : "");
result.put(entry.getKey(), encode(stringValue, StandardCharsets.UTF_8));
});
return result;
}
/**
* Apply {@link #encode(String, String)} to the values in the given URI
* variables and return a new array containing the encoded values.
* @param uriVariables the URI variable values to be encoded
* @return the encoded String
* @since 5.0
*/
public static Object[] encodeUriVariables(Object... uriVariables) {
return Arrays.stream(uriVariables)
.map(value -> {
String stringValue = (value != null ? value.toString() : "");
return encode(stringValue, StandardCharsets.UTF_8);
})
.collect(Collectors.toList()).toArray();
}
/**
* Decode the given encoded URI component.
* <p>See {@link StringUtils#uriDecode(String, Charset) for the decoding rules.