Support for encode() in UriComponentsBuilder
The ability to request to encode before `build()`, and more importantly before expanding, allows stricter encoding to be applied to URI vars and consequently to neutralize the effect of characters with reserved meaning in a URI. Issue: SPR-17039
This commit is contained in:
parent
fe2eeb43f1
commit
5fb4982026
|
@ -26,6 +26,8 @@ import java.util.Arrays;
|
|||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.UnaryOperator;
|
||||
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
@ -70,7 +72,7 @@ final class HierarchicalUriComponents extends UriComponents {
|
|||
}
|
||||
|
||||
@Override
|
||||
public PathComponent encode(Charset charset) {
|
||||
public PathComponent encode(BiFunction<String, Type, String> encoder) {
|
||||
return this;
|
||||
}
|
||||
|
||||
|
@ -79,7 +81,7 @@ final class HierarchicalUriComponents extends UriComponents {
|
|||
}
|
||||
|
||||
@Override
|
||||
public PathComponent expand(UriTemplateVariables uriVariables) {
|
||||
public PathComponent expand(UriTemplateVariables uriVariables, @Nullable UnaryOperator<String> encoder) {
|
||||
return this;
|
||||
}
|
||||
|
||||
|
@ -112,7 +114,10 @@ final class HierarchicalUriComponents extends UriComponents {
|
|||
|
||||
private final MultiValueMap<String, String> queryParams;
|
||||
|
||||
private final boolean encoded;
|
||||
private final EncodeState encodeState;
|
||||
|
||||
@Nullable
|
||||
private UnaryOperator<String> variableEncoder;
|
||||
|
||||
|
||||
/**
|
||||
|
@ -125,11 +130,10 @@ final class HierarchicalUriComponents extends UriComponents {
|
|||
* @param queryParams the query parameters
|
||||
* @param fragment the fragment
|
||||
* @param encoded whether the components are already encoded
|
||||
* @param verify whether the components need to be checked for illegal characters
|
||||
*/
|
||||
HierarchicalUriComponents(@Nullable String scheme, @Nullable String fragment, @Nullable String userInfo,
|
||||
@Nullable String host, @Nullable String port, @Nullable PathComponent path,
|
||||
@Nullable MultiValueMap<String, String> queryParams, boolean encoded, boolean verify) {
|
||||
@Nullable MultiValueMap<String, String> queryParams, boolean encoded) {
|
||||
|
||||
super(scheme, fragment);
|
||||
|
||||
|
@ -139,13 +143,31 @@ final class HierarchicalUriComponents extends UriComponents {
|
|||
this.path = (path != null ? path : NULL_PATH_COMPONENT);
|
||||
this.queryParams = CollectionUtils.unmodifiableMultiValueMap(
|
||||
queryParams != null ? queryParams : new LinkedMultiValueMap<>(0));
|
||||
this.encoded = encoded;
|
||||
this.encodeState = encoded ? EncodeState.FULLY_ENCODED : EncodeState.RAW;
|
||||
|
||||
if (verify) {
|
||||
// Check for illegal characters..
|
||||
if (encoded) {
|
||||
verify();
|
||||
}
|
||||
}
|
||||
|
||||
private HierarchicalUriComponents(@Nullable String scheme, @Nullable String fragment, @Nullable String userInfo,
|
||||
@Nullable String host, @Nullable String port, @Nullable PathComponent path,
|
||||
@Nullable MultiValueMap<String, String> queryParams, EncodeState encodeState,
|
||||
@Nullable UnaryOperator<String> variableEncoder) {
|
||||
|
||||
super(scheme, fragment);
|
||||
|
||||
this.userInfo = userInfo;
|
||||
this.host = host;
|
||||
this.port = port;
|
||||
this.path = (path != null ? path : NULL_PATH_COMPONENT);
|
||||
this.queryParams = CollectionUtils.unmodifiableMultiValueMap(
|
||||
queryParams != null ? queryParams : new LinkedMultiValueMap<>(0));
|
||||
this.encodeState = encodeState;
|
||||
this.variableEncoder = variableEncoder;
|
||||
}
|
||||
|
||||
|
||||
// Component getters
|
||||
|
||||
|
@ -232,15 +254,30 @@ final class HierarchicalUriComponents extends UriComponents {
|
|||
|
||||
// Encoding
|
||||
|
||||
/**
|
||||
* Encode all URI components using their specific encoding rules and return
|
||||
* the result as a new {@code UriComponents} instance.
|
||||
* @param charset the encoding of the values
|
||||
* @return the encoded URI components
|
||||
*/
|
||||
HierarchicalUriComponents encodeTemplate(Charset charset) {
|
||||
|
||||
if (this.encodeState.isEncoded()) {
|
||||
return this;
|
||||
}
|
||||
|
||||
// Remember the charset to encode URI variables later..
|
||||
this.variableEncoder = value -> encodeUriComponent(value, charset, Type.URI);
|
||||
|
||||
UriTemplateEncoder encoder = new UriTemplateEncoder(charset);
|
||||
String schemeTo = (getScheme() != null ? encoder.apply(getScheme(), Type.SCHEME) : null);
|
||||
String fragmentTo = (getFragment() != null ? encoder.apply(getFragment(), Type.FRAGMENT) : null);
|
||||
String userInfoTo = (getUserInfo() != null ? encoder.apply(getUserInfo(), Type.USER_INFO) : null);
|
||||
String hostTo = (getHost() != null ? encoder.apply(getHost(), getHostType()) : null);
|
||||
PathComponent pathTo = this.path.encode(encoder);
|
||||
MultiValueMap<String, String> paramsTo = encodeQueryParams(encoder);
|
||||
|
||||
return new HierarchicalUriComponents(schemeTo, fragmentTo, userInfoTo,
|
||||
hostTo, this.port, pathTo, paramsTo, EncodeState.TEMPLATE_ENCODED, this.variableEncoder);
|
||||
}
|
||||
|
||||
@Override
|
||||
public HierarchicalUriComponents encode(Charset charset) {
|
||||
if (this.encoded) {
|
||||
if (this.encodeState.isEncoded()) {
|
||||
return this;
|
||||
}
|
||||
String scheme = getScheme();
|
||||
|
@ -249,20 +286,22 @@ final class HierarchicalUriComponents extends UriComponents {
|
|||
String fragmentTo = (fragment != null ? encodeUriComponent(fragment, charset, Type.FRAGMENT) : null);
|
||||
String userInfoTo = (this.userInfo != null ? encodeUriComponent(this.userInfo, charset, Type.USER_INFO) : null);
|
||||
String hostTo = (this.host != null ? encodeUriComponent(this.host, charset, getHostType()) : null);
|
||||
PathComponent pathTo = this.path.encode(charset);
|
||||
MultiValueMap<String, String> paramsTo = encodeQueryParams(charset);
|
||||
return new HierarchicalUriComponents(
|
||||
schemeTo, fragmentTo, userInfoTo, hostTo, this.port, pathTo, paramsTo, true, false);
|
||||
BiFunction<String, Type, String> encoder = (s, type) -> encodeUriComponent(s, charset, type);
|
||||
PathComponent pathTo = this.path.encode(encoder);
|
||||
MultiValueMap<String, String> paramsTo = encodeQueryParams(encoder);
|
||||
|
||||
return new HierarchicalUriComponents(schemeTo, fragmentTo, userInfoTo,
|
||||
hostTo, this.port, pathTo, paramsTo, EncodeState.FULLY_ENCODED, null);
|
||||
}
|
||||
|
||||
private MultiValueMap<String, String> encodeQueryParams(Charset charset) {
|
||||
private MultiValueMap<String, String> encodeQueryParams(BiFunction<String, Type, String> encoder) {
|
||||
int size = this.queryParams.size();
|
||||
MultiValueMap<String, String> result = new LinkedMultiValueMap<>(size);
|
||||
this.queryParams.forEach((key, values) -> {
|
||||
String name = encodeUriComponent(key, charset, Type.QUERY_PARAM);
|
||||
String name = encoder.apply(key, Type.QUERY_PARAM);
|
||||
List<String> encodedValues = new ArrayList<>(values.size());
|
||||
for (String value : values) {
|
||||
encodedValues.add(encodeUriComponent(value, charset, Type.QUERY_PARAM));
|
||||
encodedValues.add(encoder.apply(value, Type.QUERY_PARAM));
|
||||
}
|
||||
result.put(name, encodedValues);
|
||||
});
|
||||
|
@ -324,18 +363,13 @@ final class HierarchicalUriComponents extends UriComponents {
|
|||
return (this.host != null && this.host.startsWith("[") ? Type.HOST_IPV6 : Type.HOST_IPV4);
|
||||
}
|
||||
|
||||
|
||||
// Verifying
|
||||
|
||||
/**
|
||||
* Verifies all URI components to determine whether they contain any illegal
|
||||
* characters, throwing an {@code IllegalArgumentException} if so.
|
||||
* Check if any of the URI components contain any illegal characters.
|
||||
* @throws IllegalArgumentException if any component has illegal characters
|
||||
*/
|
||||
private void verify() {
|
||||
if (!this.encoded) {
|
||||
return;
|
||||
}
|
||||
verifyUriComponent(getScheme(), Type.SCHEME);
|
||||
verifyUriComponent(this.userInfo, Type.USER_INFO);
|
||||
verifyUriComponent(this.host, getHostType());
|
||||
|
@ -385,18 +419,20 @@ final class HierarchicalUriComponents extends UriComponents {
|
|||
|
||||
@Override
|
||||
protected HierarchicalUriComponents expandInternal(UriTemplateVariables uriVariables) {
|
||||
Assert.state(!this.encoded, "Cannot expand an already encoded UriComponents object");
|
||||
|
||||
String schemeTo = expandUriComponent(getScheme(), uriVariables);
|
||||
String fragmentTo = expandUriComponent(getFragment(), uriVariables);
|
||||
String userInfoTo = expandUriComponent(this.userInfo, uriVariables);
|
||||
String hostTo = expandUriComponent(this.host, uriVariables);
|
||||
String portTo = expandUriComponent(this.port, uriVariables);
|
||||
PathComponent pathTo = this.path.expand(uriVariables);
|
||||
Assert.state(!this.encodeState.equals(EncodeState.FULLY_ENCODED),
|
||||
"URI components already encoded, and could not possibly contain '{' or '}'.");
|
||||
|
||||
String schemeTo = expandUriComponent(getScheme(), uriVariables, this.variableEncoder);
|
||||
String fragmentTo = expandUriComponent(getFragment(), uriVariables, this.variableEncoder);
|
||||
String userInfoTo = expandUriComponent(this.userInfo, uriVariables, this.variableEncoder);
|
||||
String hostTo = expandUriComponent(this.host, uriVariables, this.variableEncoder);
|
||||
String portTo = expandUriComponent(this.port, uriVariables, this.variableEncoder);
|
||||
PathComponent pathTo = this.path.expand(uriVariables, this.variableEncoder);
|
||||
MultiValueMap<String, String> paramsTo = expandQueryParams(uriVariables);
|
||||
|
||||
return new HierarchicalUriComponents(
|
||||
schemeTo, fragmentTo, userInfoTo, hostTo, portTo, pathTo, paramsTo, false, false);
|
||||
return new HierarchicalUriComponents(schemeTo, fragmentTo, userInfoTo,
|
||||
hostTo, portTo, pathTo, paramsTo, this.encodeState, this.variableEncoder);
|
||||
}
|
||||
|
||||
private MultiValueMap<String, String> expandQueryParams(UriTemplateVariables variables) {
|
||||
|
@ -404,10 +440,10 @@ final class HierarchicalUriComponents extends UriComponents {
|
|||
MultiValueMap<String, String> result = new LinkedMultiValueMap<>(size);
|
||||
UriTemplateVariables queryVariables = new QueryUriTemplateVariables(variables);
|
||||
this.queryParams.forEach((key, values) -> {
|
||||
String name = expandUriComponent(key, queryVariables);
|
||||
String name = expandUriComponent(key, queryVariables, this.variableEncoder);
|
||||
List<String> expandedValues = new ArrayList<>(values.size());
|
||||
for (String value : values) {
|
||||
expandedValues.add(expandUriComponent(value, queryVariables));
|
||||
expandedValues.add(expandUriComponent(value, queryVariables, this.variableEncoder));
|
||||
}
|
||||
result.put(name, expandedValues);
|
||||
});
|
||||
|
@ -417,8 +453,9 @@ final class HierarchicalUriComponents extends UriComponents {
|
|||
@Override
|
||||
public UriComponents normalize() {
|
||||
String normalizedPath = StringUtils.cleanPath(getPath());
|
||||
FullPathComponent path = new FullPathComponent(normalizedPath);
|
||||
return new HierarchicalUriComponents(getScheme(), getFragment(), this.userInfo, this.host, this.port,
|
||||
new FullPathComponent(normalizedPath), this.queryParams, this.encoded, false);
|
||||
path, this.queryParams, this.encodeState, this.variableEncoder);
|
||||
}
|
||||
|
||||
|
||||
|
@ -462,7 +499,7 @@ final class HierarchicalUriComponents extends UriComponents {
|
|||
@Override
|
||||
public URI toUri() {
|
||||
try {
|
||||
if (this.encoded) {
|
||||
if (this.encodeState.isEncoded()) {
|
||||
return new URI(toUriString());
|
||||
}
|
||||
else {
|
||||
|
@ -689,6 +726,102 @@ final class HierarchicalUriComponents extends UriComponents {
|
|||
}
|
||||
|
||||
|
||||
private enum EncodeState {
|
||||
|
||||
/**
|
||||
* Not encoded.
|
||||
*/
|
||||
RAW,
|
||||
|
||||
/**
|
||||
* URI vars expanded first and then each URI component encoded by
|
||||
* quoting only illegal characters within that URI component.
|
||||
*/
|
||||
FULLY_ENCODED,
|
||||
|
||||
/**
|
||||
* URI template encoded first by quoting illegal characters only, and
|
||||
* then URI vars encoded more strictly when expanded, by quoting both
|
||||
* illegal chars and chars with reserved meaning.
|
||||
*/
|
||||
TEMPLATE_ENCODED;
|
||||
|
||||
|
||||
public boolean isEncoded() {
|
||||
return this.equals(FULLY_ENCODED) || this.equals(TEMPLATE_ENCODED);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static class UriTemplateEncoder implements BiFunction<String, Type, String> {
|
||||
|
||||
private final Charset charset;
|
||||
|
||||
private final StringBuilder currentLiteral = new StringBuilder();
|
||||
|
||||
private final StringBuilder output = new StringBuilder();
|
||||
|
||||
|
||||
public UriTemplateEncoder(Charset charset) {
|
||||
this.charset = charset;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String apply(String source, Type type) {
|
||||
|
||||
// Only URI variable, nothing to encode..
|
||||
if (source.length() > 1 && source.charAt(0) == '{' && source.charAt(source.length() -1) == '}') {
|
||||
return source;
|
||||
}
|
||||
|
||||
// Only literal, encode all..
|
||||
if (source.indexOf('{') == -1) {
|
||||
return encodeUriComponent(source, this.charset, type);
|
||||
}
|
||||
|
||||
// Mixed, encode all except for URI variables..
|
||||
|
||||
int level = 0;
|
||||
clear(this.currentLiteral);
|
||||
clear(this.output);
|
||||
|
||||
for (char c : source.toCharArray()) {
|
||||
if (c == '{') {
|
||||
level++;
|
||||
if (level == 1) {
|
||||
encodeAndAppendCurrentLiteral(type);
|
||||
}
|
||||
}
|
||||
if (c == '}') {
|
||||
level--;
|
||||
Assert.isTrue(level >=0, "Mismatched open and close braces");
|
||||
}
|
||||
if (level > 0 || (level == 0 && c == '}')) {
|
||||
this.output.append(c);
|
||||
}
|
||||
else {
|
||||
this.currentLiteral.append(c);
|
||||
}
|
||||
}
|
||||
|
||||
Assert.isTrue(level == 0, "Mismatched open and close braces");
|
||||
encodeAndAppendCurrentLiteral(type);
|
||||
|
||||
return this.output.toString();
|
||||
}
|
||||
|
||||
private void encodeAndAppendCurrentLiteral(Type type) {
|
||||
this.output.append(encodeUriComponent(this.currentLiteral.toString(), this.charset, type));
|
||||
clear(this.currentLiteral);
|
||||
}
|
||||
|
||||
private void clear(StringBuilder sb) {
|
||||
sb.delete(0, sb.length());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Defines the contract for path (segments).
|
||||
*/
|
||||
|
@ -698,11 +831,11 @@ final class HierarchicalUriComponents extends UriComponents {
|
|||
|
||||
List<String> getPathSegments();
|
||||
|
||||
PathComponent encode(Charset charset);
|
||||
PathComponent encode(BiFunction<String, Type, String> encoder);
|
||||
|
||||
void verify();
|
||||
|
||||
PathComponent expand(UriTemplateVariables uriVariables);
|
||||
PathComponent expand(UriTemplateVariables uriVariables, @Nullable UnaryOperator<String> encoder);
|
||||
|
||||
void copyToUriComponentsBuilder(UriComponentsBuilder builder);
|
||||
}
|
||||
|
@ -731,8 +864,8 @@ final class HierarchicalUriComponents extends UriComponents {
|
|||
}
|
||||
|
||||
@Override
|
||||
public PathComponent encode(Charset charset) {
|
||||
String encodedPath = encodeUriComponent(getPath(), charset, Type.PATH);
|
||||
public PathComponent encode(BiFunction<String, Type, String> encoder) {
|
||||
String encodedPath = encoder.apply(getPath(), Type.PATH);
|
||||
return new FullPathComponent(encodedPath);
|
||||
}
|
||||
|
||||
|
@ -742,8 +875,8 @@ final class HierarchicalUriComponents extends UriComponents {
|
|||
}
|
||||
|
||||
@Override
|
||||
public PathComponent expand(UriTemplateVariables uriVariables) {
|
||||
String expandedPath = expandUriComponent(getPath(), uriVariables);
|
||||
public PathComponent expand(UriTemplateVariables uriVariables, @Nullable UnaryOperator<String> encoder) {
|
||||
String expandedPath = expandUriComponent(getPath(), uriVariables, encoder);
|
||||
return new FullPathComponent(expandedPath);
|
||||
}
|
||||
|
||||
|
@ -797,11 +930,11 @@ final class HierarchicalUriComponents extends UriComponents {
|
|||
}
|
||||
|
||||
@Override
|
||||
public PathComponent encode(Charset charset) {
|
||||
public PathComponent encode(BiFunction<String, Type, String> encoder) {
|
||||
List<String> pathSegments = getPathSegments();
|
||||
List<String> encodedPathSegments = new ArrayList<>(pathSegments.size());
|
||||
for (String pathSegment : pathSegments) {
|
||||
String encodedPathSegment = encodeUriComponent(pathSegment, charset, Type.PATH_SEGMENT);
|
||||
String encodedPathSegment = encoder.apply(pathSegment, Type.PATH_SEGMENT);
|
||||
encodedPathSegments.add(encodedPathSegment);
|
||||
}
|
||||
return new PathSegmentComponent(encodedPathSegments);
|
||||
|
@ -815,11 +948,11 @@ final class HierarchicalUriComponents extends UriComponents {
|
|||
}
|
||||
|
||||
@Override
|
||||
public PathComponent expand(UriTemplateVariables uriVariables) {
|
||||
public PathComponent expand(UriTemplateVariables uriVariables, @Nullable UnaryOperator<String> encoder) {
|
||||
List<String> pathSegments = getPathSegments();
|
||||
List<String> expandedPathSegments = new ArrayList<>(pathSegments.size());
|
||||
for (String pathSegment : pathSegments) {
|
||||
String expandedPathSegment = expandUriComponent(pathSegment, uriVariables);
|
||||
String expandedPathSegment = expandUriComponent(pathSegment, uriVariables, encoder);
|
||||
expandedPathSegments.add(expandedPathSegment);
|
||||
}
|
||||
return new PathSegmentComponent(expandedPathSegments);
|
||||
|
@ -874,10 +1007,10 @@ final class HierarchicalUriComponents extends UriComponents {
|
|||
}
|
||||
|
||||
@Override
|
||||
public PathComponent encode(Charset charset) {
|
||||
public PathComponent encode(BiFunction<String, Type, String> encoder) {
|
||||
List<PathComponent> encodedComponents = new ArrayList<>(this.pathComponents.size());
|
||||
for (PathComponent pathComponent : this.pathComponents) {
|
||||
encodedComponents.add(pathComponent.encode(charset));
|
||||
encodedComponents.add(pathComponent.encode(encoder));
|
||||
}
|
||||
return new PathComponentComposite(encodedComponents);
|
||||
}
|
||||
|
@ -890,10 +1023,10 @@ final class HierarchicalUriComponents extends UriComponents {
|
|||
}
|
||||
|
||||
@Override
|
||||
public PathComponent expand(UriTemplateVariables uriVariables) {
|
||||
public PathComponent expand(UriTemplateVariables uriVariables, @Nullable UnaryOperator<String> encoder) {
|
||||
List<PathComponent> expandedComponents = new ArrayList<>(this.pathComponents.size());
|
||||
for (PathComponent pathComponent : this.pathComponents) {
|
||||
expandedComponents.add(pathComponent.expand(uriVariables));
|
||||
expandedComponents.add(pathComponent.expand(uriVariables, encoder));
|
||||
}
|
||||
return new PathComponentComposite(expandedComponents);
|
||||
}
|
||||
|
|
|
@ -24,6 +24,7 @@ import java.util.Arrays;
|
|||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.UnaryOperator;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
|
@ -128,21 +129,22 @@ public abstract class UriComponents implements Serializable {
|
|||
|
||||
|
||||
/**
|
||||
* A variant of {@link #encode(Charset)} that uses "UTF-8" as the charset.
|
||||
* @return a new {@code UriComponents} instance with encoded values
|
||||
* Invoke this <em>after</em> expanding URI variables to encode the
|
||||
* resulting URI component values.
|
||||
* <p>In comparison to {@link UriComponentsBuilder#encode()}, this method
|
||||
* quotes <em>only</em> illegal characters within a given URI component type,
|
||||
* but not all characters with reserved meaning. For most cases, prefer
|
||||
* using {@link UriComponentsBuilder#encode()} over this method.
|
||||
* @see UriComponentsBuilder#encode()
|
||||
*/
|
||||
public final UriComponents encode() {
|
||||
return encode(StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode each URI component by percent encoding illegal characters, which
|
||||
* includes non-US-ASCII characters, and also characters that are otherwise
|
||||
* illegal within a given URI component type, as defined in RFC 3986. The
|
||||
* effect of this method, with regards to encoding, is comparable to using
|
||||
* the multi-argument constructor of {@link URI}.
|
||||
* @param charset the encoding of the values contained in this map
|
||||
* @return a new {@code UriComponents} instance with encoded values
|
||||
* A variant of {@link #encode()} with a charset other than "UTF-8".
|
||||
* @param charset the charset to use for encoding
|
||||
* @see UriComponentsBuilder#encode(Charset)
|
||||
*/
|
||||
public abstract UriComponents encode(Charset charset);
|
||||
|
||||
|
@ -235,6 +237,13 @@ public abstract class UriComponents implements Serializable {
|
|||
|
||||
@Nullable
|
||||
static String expandUriComponent(@Nullable String source, UriTemplateVariables uriVariables) {
|
||||
return expandUriComponent(source, uriVariables, null);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
static String expandUriComponent(@Nullable String source, UriTemplateVariables uriVariables,
|
||||
@Nullable UnaryOperator<String> variableEncoder) {
|
||||
|
||||
if (source == null) {
|
||||
return null;
|
||||
}
|
||||
|
@ -249,13 +258,14 @@ public abstract class UriComponents implements Serializable {
|
|||
while (matcher.find()) {
|
||||
String match = matcher.group(1);
|
||||
String variableName = getVariableName(match);
|
||||
Object variableValue = uriVariables.getValue(variableName);
|
||||
if (UriTemplateVariables.SKIP_VALUE.equals(variableValue)) {
|
||||
Object variablesValue = uriVariables.getValue(variableName);
|
||||
if (UriTemplateVariables.SKIP_VALUE.equals(variablesValue)) {
|
||||
continue;
|
||||
}
|
||||
String variableValueString = getVariableValueAsString(variableValue);
|
||||
String replacement = Matcher.quoteReplacement(variableValueString);
|
||||
matcher.appendReplacement(sb, replacement);
|
||||
String formattedValue = getVariableValueAsString(variablesValue);
|
||||
formattedValue = Matcher.quoteReplacement(formattedValue);
|
||||
formattedValue = variableEncoder != null ? variableEncoder.apply(formattedValue) : formattedValue;
|
||||
matcher.appendReplacement(sb, formattedValue);
|
||||
}
|
||||
matcher.appendTail(sb);
|
||||
return sb.toString();
|
||||
|
@ -299,7 +309,9 @@ public abstract class UriComponents implements Serializable {
|
|||
public interface UriTemplateVariables {
|
||||
|
||||
/**
|
||||
* Indicates a skipped value.
|
||||
* Constant for a value that indicates a URI variable name should be
|
||||
* ignored and left as is. This is useful for partial expanding of some
|
||||
* but not all URI variables.
|
||||
*/
|
||||
Object SKIP_VALUE = UriTemplateVariables.class;
|
||||
|
||||
|
|
|
@ -17,6 +17,8 @@
|
|||
package org.springframework.web.util;
|
||||
|
||||
import java.net.URI;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
@ -119,6 +121,10 @@ public class UriComponentsBuilder implements UriBuilder, Cloneable {
|
|||
@Nullable
|
||||
private String fragment;
|
||||
|
||||
private boolean encodeTemplate;
|
||||
|
||||
private Charset charset = StandardCharsets.UTF_8;
|
||||
|
||||
|
||||
/**
|
||||
* Default constructor. Protected to prevent direct instantiation.
|
||||
|
@ -319,6 +325,43 @@ public class UriComponentsBuilder implements UriBuilder, Cloneable {
|
|||
}
|
||||
|
||||
|
||||
// encode methods
|
||||
|
||||
/**
|
||||
* Request to have the URI template encoded first at build time, and
|
||||
* URI variables encoded later when expanded.
|
||||
*
|
||||
* <p>In comparison to {@link UriComponents#encode()}, this method has the
|
||||
* same effect on the URI template, i.e. each URI component is encoded by
|
||||
* quoting <em>only</em> illegal characters within that URI component type.
|
||||
* However URI variables are encoded more strictly, by quoting both illegal
|
||||
* characters and characters with reserved meaning.
|
||||
*
|
||||
* <p>For most cases, prefer this method over {@link UriComponents#encode()}
|
||||
* which is useful only if intentionally expanding variables with reserved
|
||||
* characters. For example ';' is legal in a path, but also has reserved
|
||||
* meaning as a separator. When expanding a variable that contains ';' it
|
||||
* probably should be encoded, unless the intent is to insert path params
|
||||
* through the expanded variable.
|
||||
*
|
||||
* @since 5.0.8
|
||||
*/
|
||||
public final UriComponentsBuilder encode() {
|
||||
return encode(StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
/**
|
||||
* A variant of {@link #encode()} with a charset other than "UTF-8".
|
||||
* @param charset the charset to use for encoding
|
||||
* @since 5.0.8
|
||||
*/
|
||||
public UriComponentsBuilder encode(Charset charset) {
|
||||
this.encodeTemplate = true;
|
||||
this.charset = charset;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
// build methods
|
||||
|
||||
/**
|
||||
|
@ -341,8 +384,11 @@ public class UriComponentsBuilder implements UriBuilder, Cloneable {
|
|||
return new OpaqueUriComponents(this.scheme, this.ssp, this.fragment);
|
||||
}
|
||||
else {
|
||||
return new HierarchicalUriComponents(this.scheme, this.fragment, this.userInfo,
|
||||
this.host, this.port, this.pathBuilder.build(), this.queryParams, encoded, true);
|
||||
HierarchicalUriComponents uriComponents =
|
||||
new HierarchicalUriComponents(this.scheme, this.fragment, this.userInfo,
|
||||
this.host, this.port, this.pathBuilder.build(), this.queryParams, encoded);
|
||||
|
||||
return this.encodeTemplate ? uriComponents.encodeTemplate(this.charset) : uriComponents;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -354,7 +400,7 @@ public class UriComponentsBuilder implements UriBuilder, Cloneable {
|
|||
* @return the URI components with expanded values
|
||||
*/
|
||||
public UriComponents buildAndExpand(Map<String, ?> uriVariables) {
|
||||
return build(false).expand(uriVariables);
|
||||
return build().expand(uriVariables);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -365,30 +411,17 @@ public class UriComponentsBuilder implements UriBuilder, Cloneable {
|
|||
* @return the URI components with expanded values
|
||||
*/
|
||||
public UriComponents buildAndExpand(Object... uriVariableValues) {
|
||||
return build(false).expand(uriVariableValues);
|
||||
return build().expand(uriVariableValues);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Build a {@link URI} instance and replaces URI template variables
|
||||
* with the values from an array.
|
||||
* @param uriVariables the map of URI variables
|
||||
* @return the URI
|
||||
*/
|
||||
@Override
|
||||
public URI build(Object... uriVariables) {
|
||||
return buildAndExpand(uriVariables).encode().toUri();
|
||||
return encode().buildAndExpand(uriVariables).toUri();
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a {@link URI} instance and replaces URI template variables
|
||||
* with the values from a map.
|
||||
* @param uriVariables the map of URI variables
|
||||
* @return the URI
|
||||
*/
|
||||
@Override
|
||||
public URI build(Map<String, ?> uriVariables) {
|
||||
return buildAndExpand(uriVariables).encode().toUri();
|
||||
return encode().buildAndExpand(uriVariables).toUri();
|
||||
}
|
||||
|
||||
|
||||
|
@ -400,7 +433,7 @@ public class UriComponentsBuilder implements UriBuilder, Cloneable {
|
|||
* @see UriComponents#toUriString()
|
||||
*/
|
||||
public String toUriString() {
|
||||
return build(false).encode().toUriString();
|
||||
return build().encode().toUriString();
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2015 the original author or authors.
|
||||
* Copyright 2002-2018 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.
|
||||
|
@ -24,9 +24,13 @@ import java.net.URI;
|
|||
import java.net.URISyntaxException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.web.util.UriComponents.UriTemplateVariables;
|
||||
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.instanceOf;
|
||||
import static org.hamcrest.Matchers.not;
|
||||
|
@ -35,16 +39,50 @@ import static org.junit.Assert.assertThat;
|
|||
import static org.springframework.web.util.UriComponentsBuilder.fromUriString;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link UriComponents}.
|
||||
*
|
||||
* @author Arjen Poutsma
|
||||
* @author Phillip Webb
|
||||
* @author Rossen Stoyanchev
|
||||
*/
|
||||
public class UriComponentsTests {
|
||||
|
||||
@Test
|
||||
public void encode() {
|
||||
UriComponents uriComponents = UriComponentsBuilder.fromPath("/hotel list").build();
|
||||
UriComponents encoded = uriComponents.encode();
|
||||
assertEquals("/hotel%20list", encoded.getPath());
|
||||
public void expandAndEncode() {
|
||||
|
||||
UriComponents uri = UriComponentsBuilder
|
||||
.fromPath("/hotel list/{city} specials").queryParam("q", "{value}").build()
|
||||
.expand("Z\u00fcrich", "a+b").encode();
|
||||
|
||||
assertEquals("/hotel%20list/Z%C3%BCrich%20specials?q=a+b", uri.toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void encodeAndExpand() {
|
||||
|
||||
UriComponents uri = UriComponentsBuilder
|
||||
.fromPath("/hotel list/{city} specials").queryParam("q", "{value}").encode().build()
|
||||
.expand("Z\u00fcrich", "a+b");
|
||||
|
||||
assertEquals("/hotel%20list/Z%C3%BCrich%20specials?q=a%2Bb", uri.toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void encodeAndExpandPartially() {
|
||||
|
||||
Map<String, Object> uriVars = new HashMap<>();
|
||||
uriVars.put("city", "Z\u00fcrich");
|
||||
|
||||
UriComponents uri = UriComponentsBuilder
|
||||
.fromPath("/hotel list/{city} specials").queryParam("q", "{value}").encode().build()
|
||||
.expand(name -> uriVars.getOrDefault(name, UriTemplateVariables.SKIP_VALUE));
|
||||
|
||||
assertEquals("/hotel%20list/Z%C3%BCrich%20specials?q={value}", uri.toString());
|
||||
|
||||
uriVars.put("value", "a+b");
|
||||
uri = uri.expand(uriVars);
|
||||
|
||||
assertEquals("/hotel%20list/Z%C3%BCrich%20specials?q=a%2Bb", uri.toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -86,9 +124,7 @@ public class UriComponentsTests {
|
|||
assertEquals("http://example.com/1 2 3 4", uriComponents.toUriString());
|
||||
}
|
||||
|
||||
// SPR-13311
|
||||
|
||||
@Test
|
||||
@Test // SPR-13311
|
||||
public void expandWithRegexVar() {
|
||||
String template = "/myurl/{name:[a-z]{1,5}}/show";
|
||||
UriComponents uriComponents = UriComponentsBuilder.fromUriString(template).build();
|
||||
|
@ -96,9 +132,7 @@ public class UriComponentsTests {
|
|||
assertEquals("/myurl/test/show", uriComponents.getPath());
|
||||
}
|
||||
|
||||
// SPR-12123
|
||||
|
||||
@Test
|
||||
@Test // SPR-12123
|
||||
public void port() {
|
||||
UriComponents uri1 = fromUriString("http://example.com:8080/bar").build();
|
||||
UriComponents uri2 = fromUriString("http://example.com/bar").port(8080).build();
|
||||
|
@ -158,7 +192,7 @@ public class UriComponentsTests {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void equalsHierarchicalUriComponents() throws Exception {
|
||||
public void equalsHierarchicalUriComponents() {
|
||||
String url = "http://example.com";
|
||||
UriComponents uric1 = UriComponentsBuilder.fromUriString(url).path("/{foo}").query("bar={baz}").build();
|
||||
UriComponents uric2 = UriComponentsBuilder.fromUriString(url).path("/{foo}").query("bar={baz}").build();
|
||||
|
@ -170,7 +204,7 @@ public class UriComponentsTests {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void equalsOpaqueUriComponents() throws Exception {
|
||||
public void equalsOpaqueUriComponents() {
|
||||
String baseUrl = "http:example.com";
|
||||
UriComponents uric1 = UriComponentsBuilder.fromUriString(baseUrl + "/foo/bar").build();
|
||||
UriComponents uric2 = UriComponentsBuilder.fromUriString(baseUrl + "/foo/bar").build();
|
||||
|
|
Loading…
Reference in New Issue