Browse Source

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
pull/1916/head
Rossen Stoyanchev 6 years ago
parent
commit
d81ec55a60
  1. 243
      spring-web/src/main/java/org/springframework/web/util/HierarchicalUriComponents.java
  2. 43
      spring-web/src/main/java/org/springframework/web/util/UriComponents.java
  3. 73
      spring-web/src/main/java/org/springframework/web/util/UriComponentsBuilder.java
  4. 60
      spring-web/src/test/java/org/springframework/web/util/UriComponentsTests.java

243
spring-web/src/main/java/org/springframework/web/util/HierarchicalUriComponents.java

@ -26,6 +26,8 @@ import java.util.Arrays; @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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);
}

43
spring-web/src/main/java/org/springframework/web/util/UriComponents.java

@ -24,6 +24,7 @@ import java.util.Arrays; @@ -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 { @@ -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 { @@ -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 { @@ -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();
@ -298,6 +308,11 @@ public abstract class UriComponents implements Serializable { @@ -298,6 +308,11 @@ public abstract class UriComponents implements Serializable {
*/
public interface UriTemplateVariables {
/**
* 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;
/**

73
spring-web/src/main/java/org/springframework/web/util/UriComponentsBuilder.java

@ -17,6 +17,8 @@ @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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();
}

60
spring-web/src/test/java/org/springframework/web/util/UriComponentsTests.java

@ -1,5 +1,5 @@ @@ -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; @@ -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; @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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…
Cancel
Save