Browse Source

SPR-5973: Cleaning it up

pull/7/head
Arjen Poutsma 13 years ago
parent
commit
b6c1e88e4a
  1. 159
      org.springframework.web/src/main/java/org/springframework/web/util/UriBuilder.java
  2. 2
      org.springframework.web/src/main/java/org/springframework/web/util/UriComponentTemplate.java
  3. 246
      org.springframework.web/src/main/java/org/springframework/web/util/UriUtils.java
  4. 19
      org.springframework.web/src/test/java/org/springframework/web/util/UriBuilderTests.java

159
org.springframework.web/src/main/java/org/springframework/web/util/UriBuilder.java

@ -19,6 +19,7 @@ package org.springframework.web.util; @@ -19,6 +19,7 @@ package org.springframework.web.util;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumSet;
import java.util.List;
import java.util.Map;
@ -120,56 +121,27 @@ public class UriBuilder { @@ -120,56 +121,27 @@ public class UriBuilder {
* @return the resulting URI
*/
public URI build() {
StringBuilder uriBuilder = new StringBuilder();
if (scheme != null) {
uriBuilder.append(scheme);
uriBuilder.append(':');
}
if (userInfo != null || host != null || port != -1) {
uriBuilder.append("//");
if (StringUtils.hasLength(userInfo)) {
uriBuilder.append(userInfo);
uriBuilder.append('@');
}
if (host != null) {
uriBuilder.append(host);
}
if (port != -1) {
uriBuilder.append(':');
uriBuilder.append(port);
}
}
String port = portAsString();
String path = null;
if (!pathSegments.isEmpty()) {
StringBuilder pathBuilder = new StringBuilder();
for (String pathSegment : pathSegments) {
boolean startsWithSlash = pathSegment.charAt(0) == '/';
boolean endsWithSlash = uriBuilder.length() > 0 && uriBuilder.charAt(uriBuilder.length() - 1) == '/';
boolean endsWithSlash = pathBuilder.length() > 0 && pathBuilder.charAt(pathBuilder.length() - 1) == '/';
if (!endsWithSlash && !startsWithSlash) {
uriBuilder.append('/');
pathBuilder.append('/');
}
else if (endsWithSlash && startsWithSlash) {
pathSegment = pathSegment.substring(1);
}
uriBuilder.append(pathSegment);
pathBuilder.append(pathSegment);
}
path = pathBuilder.toString();
}
String query = queryAsString();
if (queryBuilder.length() > 0) {
uriBuilder.append('?');
uriBuilder.append(queryBuilder);
}
if (StringUtils.hasLength(fragment)) {
uriBuilder.append('#');
uriBuilder.append(fragment);
}
String uri = uriBuilder.toString();
String uri = UriUtils.buildUri(scheme, null, userInfo, host, port, path, query, fragment);
uri = StringUtils.replace(uri, "{", "%7B");
uri = StringUtils.replace(uri, "}", "%7D");
@ -186,7 +158,7 @@ public class UriBuilder { @@ -186,7 +158,7 @@ public class UriBuilder {
* @return the resulting URI
*/
public URI build(Map<String, ?> uriVariables) {
return buildFromMap(true, uriVariables);
return buildFromMap(uriVariables, true);
}
/**
@ -197,72 +169,54 @@ public class UriBuilder { @@ -197,72 +169,54 @@ public class UriBuilder {
* @return the resulting URI
*/
public URI buildFromEncoded(Map<String, ?> uriVariables) {
return buildFromMap(false, uriVariables);
return buildFromMap(uriVariables, false);
}
private URI buildFromMap(boolean encodeUriVariableValues, Map<String, ?> uriVariables) {
private URI buildFromMap(Map<String, ?> uriVariables, boolean encodeUriVariableValues) {
if (CollectionUtils.isEmpty(uriVariables)) {
return build();
}
StringBuilder uriBuilder = new StringBuilder();
UriTemplate template;
if (scheme != null) {
template = new UriComponentTemplate(scheme, UriComponent.SCHEME, encodeUriVariableValues);
uriBuilder.append(template.expandAsString(uriVariables));
uriBuilder.append(':');
}
if (userInfo != null || host != null || port != -1) {
uriBuilder.append("//");
if (StringUtils.hasLength(userInfo)) {
template = new UriComponentTemplate(userInfo, UriComponent.USER_INFO, encodeUriVariableValues);
uriBuilder.append(template.expandAsString(uriVariables));
uriBuilder.append('@');
}
if (host != null) {
template = new UriComponentTemplate(host, UriComponent.HOST, encodeUriVariableValues);
uriBuilder.append(template.expandAsString(uriVariables));
}
if (port != -1) {
uriBuilder.append(':');
uriBuilder.append(port);
}
}
if (!pathSegments.isEmpty()) {
for (String pathSegment : pathSegments) {
String scheme = expand(this.scheme, UriComponent.SCHEME, uriVariables, encodeUriVariableValues);
String userInfo = expand(this.userInfo, UriComponent.USER_INFO, uriVariables, encodeUriVariableValues);
String host = expand(this.host, UriComponent.HOST, uriVariables, encodeUriVariableValues);
String port = expand(this.portAsString(), UriComponent.PORT, uriVariables, encodeUriVariableValues);
String path = null;
if (!this.pathSegments.isEmpty()) {
StringBuilder pathBuilder = new StringBuilder();
for (String pathSegment : this.pathSegments) {
boolean startsWithSlash = pathSegment.charAt(0) == '/';
boolean endsWithSlash = uriBuilder.length() > 0 && uriBuilder.charAt(uriBuilder.length() - 1) == '/';
boolean endsWithSlash = pathBuilder.length() > 0 && pathBuilder.charAt(pathBuilder.length() - 1) == '/';
if (!endsWithSlash && !startsWithSlash) {
uriBuilder.append('/');
pathBuilder.append('/');
}
else if (endsWithSlash && startsWithSlash) {
pathSegment = pathSegment.substring(1);
}
template = new UriComponentTemplate(pathSegment, UriComponent.PATH_SEGMENT, encodeUriVariableValues);
uriBuilder.append(template.expandAsString(uriVariables));
pathSegment = expand(pathSegment, UriComponent.PATH_SEGMENT, uriVariables, encodeUriVariableValues);
pathBuilder.append(pathSegment);
}
path = pathBuilder.toString();
}
if (queryBuilder.length() > 0) {
uriBuilder.append('?');
template = new UriComponentTemplate(queryBuilder.toString(), UriComponent.QUERY, encodeUriVariableValues);
uriBuilder.append(template.expandAsString(uriVariables));
}
String query = expand(this.queryAsString(), UriComponent.QUERY, uriVariables, encodeUriVariableValues);
String fragment = expand(this.fragment, UriComponent.FRAGMENT, uriVariables, encodeUriVariableValues);
if (StringUtils.hasLength(fragment)) {
uriBuilder.append('#');
template = new UriComponentTemplate(fragment, UriComponent.FRAGMENT, encodeUriVariableValues);
uriBuilder.append(template.expandAsString(uriVariables));
}
String uri = UriUtils.buildUri(scheme, null, userInfo, host, port, path, query, fragment);
return URI.create(uri);
}
return URI.create(uriBuilder.toString());
private String expand(String source,
UriComponent uriComponent,
Map<String, ?> uriVariables,
boolean encodeUriVariableValues) {
if (source == null) {
return null;
}
if (source.indexOf('{') == -1) {
return source;
}
UriTemplate template = new UriComponentTemplate(source, uriComponent, encodeUriVariableValues);
return template.expandAsString(uriVariables);
}
/**
@ -403,7 +357,7 @@ public class UriBuilder { @@ -403,7 +357,7 @@ public class UriBuilder {
public UriBuilder scheme(String scheme) {
if (scheme != null) {
Assert.hasLength(scheme, "'scheme' must not be empty");
this.scheme = UriUtils.encode(scheme, UriComponent.SCHEME, true);
this.scheme = encodeUriComponent(scheme, UriComponent.SCHEME);
}
else {
this.scheme = null;
@ -421,7 +375,7 @@ public class UriBuilder { @@ -421,7 +375,7 @@ public class UriBuilder {
public UriBuilder userInfo(String userInfo) {
if (userInfo != null) {
Assert.hasLength(userInfo, "'userInfo' must not be empty");
this.userInfo = UriUtils.encode(userInfo, UriComponent.USER_INFO, true);
this.userInfo = encodeUriComponent(userInfo, UriComponent.USER_INFO);
}
else {
this.userInfo = null;
@ -439,7 +393,7 @@ public class UriBuilder { @@ -439,7 +393,7 @@ public class UriBuilder {
public UriBuilder host(String host) {
if (host != null) {
Assert.hasLength(host, "'host' must not be empty");
this.host = UriUtils.encode(host, UriComponent.HOST, true);
this.host = encodeUriComponent(host, UriComponent.HOST);
}
else {
this.host = null;
@ -459,6 +413,10 @@ public class UriBuilder { @@ -459,6 +413,10 @@ public class UriBuilder {
return this;
}
private String portAsString() {
return this.port != -1 ? Integer.toString(this.port) : null;
}
/**
* Appends the given path to the existing path of this builder. The given path may contain URI template variables.
*
@ -482,7 +440,7 @@ public class UriBuilder { @@ -482,7 +440,7 @@ public class UriBuilder {
public UriBuilder pathSegment(String... segments) throws IllegalArgumentException {
Assert.notNull(segments, "'segments' must not be null");
for (String segment : segments) {
this.pathSegments.add(UriUtils.encode(segment, UriComponent.PATH_SEGMENT, true));
this.pathSegments.add(encodeUriComponent(segment, UriComponent.PATH_SEGMENT));
}
return this;
@ -500,7 +458,7 @@ public class UriBuilder { @@ -500,7 +458,7 @@ public class UriBuilder {
public UriBuilder queryParam(String name, Object... values) {
Assert.notNull(name, "'name' must not be null");
String encodedName = UriUtils.encode(name, UriComponent.QUERY_PARAM, true);
String encodedName = encodeUriComponent(name, UriComponent.QUERY_PARAM);
if (ObjectUtils.isEmpty(values)) {
if (queryBuilder.length() != 0) {
@ -518,7 +476,7 @@ public class UriBuilder { @@ -518,7 +476,7 @@ public class UriBuilder {
String valueAsString = value != null ? value.toString() : "";
if (valueAsString.length() != 0) {
queryBuilder.append('=');
queryBuilder.append(UriUtils.encode(valueAsString, UriComponent.QUERY_PARAM, true));
queryBuilder.append(encodeUriComponent(valueAsString, UriComponent.QUERY_PARAM));
}
}
@ -526,6 +484,10 @@ public class UriBuilder { @@ -526,6 +484,10 @@ public class UriBuilder {
return this;
}
private String queryAsString() {
return queryBuilder.length() != 0 ? queryBuilder.toString() : null;
}
/**
* Sets the URI fragment. The given fragment may contain URI template variables, and may also be {@code null} to clear
* the fragment of this builder.
@ -536,7 +498,7 @@ public class UriBuilder { @@ -536,7 +498,7 @@ public class UriBuilder {
public UriBuilder fragment(String fragment) {
if (fragment != null) {
Assert.hasLength(fragment, "'fragment' must not be empty");
this.fragment = UriUtils.encode(fragment, UriComponent.FRAGMENT, true);
this.fragment = encodeUriComponent(fragment, UriComponent.FRAGMENT);
}
else {
this.fragment = null;
@ -545,4 +507,9 @@ public class UriBuilder { @@ -545,4 +507,9 @@ public class UriBuilder {
}
private String encodeUriComponent(String source, UriComponent uriComponent) {
return UriUtils.encodeUriComponent(source, uriComponent, EnumSet.of(UriUtils.EncodingOption.ALLOW_TEMPLATE_VARS));
}
}

2
org.springframework.web/src/main/java/org/springframework/web/util/UriComponentTemplate.java

@ -40,7 +40,7 @@ class UriComponentTemplate extends UriTemplate { @@ -40,7 +40,7 @@ class UriComponentTemplate extends UriTemplate {
@Override
protected String getVariableValueAsString(Object variableValue) {
String variableValueString = super.getVariableValueAsString(variableValue);
return encodeUriVariableValues ? UriUtils.encode(variableValueString, uriComponent, false) :
return encodeUriVariableValues ? UriUtils.encodeUriComponent(variableValueString, uriComponent) :
variableValueString;
}
}

246
org.springframework.web/src/main/java/org/springframework/web/util/UriUtils.java

@ -18,8 +18,10 @@ package org.springframework.web.util; @@ -18,8 +18,10 @@ package org.springframework.web.util;
import java.io.ByteArrayOutputStream;
import java.io.UnsupportedEncodingException;
import java.util.Collections;
import java.util.EnumMap;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@ -29,10 +31,12 @@ import org.springframework.util.Assert; @@ -29,10 +31,12 @@ import org.springframework.util.Assert;
* Utility class for URI encoding and decoding based on RFC 3986. Offers encoding methods for the various URI
* components.
*
* <p>All {@code encode*(String, String} methods in this class operate in a similar way: <ul> <li>Valid characters for
* the specific URI component as defined in RFC 3986 stay the same. <li>All other characters are converted into one or
* more bytes in the given encoding scheme. Each of the resulting bytes is written as a hexadecimal string in the
* "<code>%<i>xy</i></code>" format. </ul>
* <p>All {@code encode*(String, String} methods in this class operate in a similar way:
* <ul>
* <li>Valid characters for the specific URI component as defined in RFC 3986 stay the same.</li>
* <li>All other characters are converted into one or more bytes in the given encoding scheme. Each of the
* resulting bytes is written as a hexadecimal string in the "<code>%<i>xy</i></code>" format.</li>
* </ul>
*
* @author Arjen Poutsma
* @see <a href="http://www.ietf.org/rfc/rfc3986.txt">RFC 3986</a>
@ -127,15 +131,13 @@ public abstract class UriUtils { @@ -127,15 +131,13 @@ public abstract class UriUtils {
* <li>{@link UriComponent#PORT}</li>
* <li>{@link UriComponent#PATH}</li>
* <li>{@link UriComponent#QUERY}</li>
* <li>{@link UriComponent#FRAGMENT}</li>
* </ul>
* though the values assigned to these keys is {@code null} if they do not occur in the given source URI.
*
* <p><strong>Note</strong> that the returned map will never contain mappings for {@link UriComponent#PATH_SEGMENT},
* nor {@link UriComponent#QUERY_PARAM}, since those components can occur multiple times in the URI.
*
* <p><strong>Note</strong> that this method does not support fragments ({@code #}), as these are not supposed to be
* sent to the server, but retained by the client.
* nor {@link UriComponent#QUERY_PARAM}, since those components can occur multiple times in the URI. Nor does it
* contain a mapping for {@link UriComponent#FRAGMENT}, as fragments are not supposed to be sent to the server, but
* retained by the client.
*
* @param httpUrl the source URI
* @return the URI components of the URI
@ -292,11 +294,12 @@ public abstract class UriUtils { @@ -292,11 +294,12 @@ public abstract class UriUtils {
public static String encodeUriComponents(Map<UriComponent, String> uriComponents,
String encoding) throws UnsupportedEncodingException {
Assert.notEmpty(uriComponents, "'uriComponents' must not be empty");
Assert.hasLength(encoding, "'encoding' must not be empty");
Map<UriComponent, String> encodedUriComponents = new EnumMap<UriComponent, String>(UriComponent.class);
for (Map.Entry<UriComponent, String> entry : uriComponents.entrySet()) {
if (entry.getValue() != null) {
String encodedValue = encode(entry.getValue(), encoding, entry.getKey(), false);
String encodedValue = encodeUriComponent(entry.getValue(), encoding, entry.getKey(), null);
encodedUriComponents.put(entry.getKey(), encodedValue);
}
}
@ -329,7 +332,6 @@ public abstract class UriUtils { @@ -329,7 +332,6 @@ public abstract class UriUtils {
String query,
String fragment,
String encoding) throws UnsupportedEncodingException {
Assert.hasLength(encoding, "'encoding' must not be empty");
if (scheme != null) {
@ -359,6 +361,116 @@ public abstract class UriUtils { @@ -359,6 +361,116 @@ public abstract class UriUtils {
return buildUri(scheme, authority, userInfo, host, port, path, query, fragment);
}
/**
* Encodes the given source into an encoded String using the rules specified by the given component.
*
* @param source the source string
* @param uriComponent the URI component for the source
* @return the encoded URI
* @throws IllegalArgumentException when the given uri parameter is not a valid URI
*/
public static String encodeUriComponent(String source, UriComponent uriComponent) {
return encodeUriComponent(source, uriComponent, null);
}
/**
* Encodes the given source into an encoded String using the rules specified by the given component and with the
* given options.
*
* @param source the source string
* @param encoding the encoding of the source string
* @param uriComponent the URI component for the source
* @param encodingOptions the options used when encoding. May be {@code null}.
* @return the encoded URI
* @throws IllegalArgumentException when the given uri parameter is not a valid URI
* @see EncodingOption
*/
public static String encodeUriComponent(String source,
UriComponent uriComponent,
Set<EncodingOption> encodingOptions) {
try {
return encodeUriComponent(source, DEFAULT_ENCODING, uriComponent, encodingOptions);
}
catch (UnsupportedEncodingException ex) {
throw new InternalError("\"" + DEFAULT_ENCODING + "\" not supported");
}
}
/**
* Encodes the given source into an encoded String using the rules specified by the given component.
*
* @param source the source string
* @param encoding the encoding of the source string
* @param uriComponent the URI component for the source
* @return the encoded URI
* @throws IllegalArgumentException when the given uri parameter is not a valid URI
*/
public static String encodeUriComponent(String source,
String encoding,
UriComponent uriComponent) throws UnsupportedEncodingException {
return encodeUriComponent(source, encoding, uriComponent, null);
}
/**
* Encodes the given source into an encoded String using the rules specified by the given component and with the
* given options.
*
* @param source the source string
* @param encoding the encoding of the source string
* @param uriComponent the URI component for the source
* @param encodingOptions the options used when encoding. May be {@code null}.
* @return the encoded URI
* @throws IllegalArgumentException when the given uri parameter is not a valid URI
* @see EncodingOption
*/
public static String encodeUriComponent(String source,
String encoding,
UriComponent uriComponent,
Set<EncodingOption> encodingOptions) throws UnsupportedEncodingException {
Assert.hasLength(encoding, "'encoding' must not be empty");
byte[] bytes = encodeInternal(source.getBytes(encoding), uriComponent, encodingOptions);
return new String(bytes, "US-ASCII");
}
private static byte[] encodeInternal(byte[] source,
UriComponent uriComponent,
Set<EncodingOption> encodingOptions) {
Assert.notNull(source, "'source' must not be null");
Assert.notNull(uriComponent, "'uriComponent' must not be null");
if (encodingOptions == null) {
encodingOptions = Collections.emptySet();
}
ByteArrayOutputStream bos = new ByteArrayOutputStream(source.length);
for (int i = 0; i < source.length; i++) {
int b = source[i];
if (b < 0) {
b += 256;
}
if (uriComponent.isAllowed(b)) {
bos.write(b);
}
else if (encodingOptions.contains(EncodingOption.ALLOW_TEMPLATE_VARS) && (b == '{' || b == '}')) {
bos.write(b);
}
else {
bos.write('%');
char hex1 = Character.toUpperCase(Character.forDigit((b >> 4) & 0xF, 16));
char hex2 = Character.toUpperCase(Character.forDigit(b & 0xF, 16));
bos.write(hex1);
bos.write(hex2);
}
}
return bos.toByteArray();
}
// encoding convenience methods
/**
* Encodes the given URI scheme with the given encoding.
*
@ -368,7 +480,7 @@ public abstract class UriUtils { @@ -368,7 +480,7 @@ public abstract class UriUtils {
* @throws UnsupportedEncodingException when the given encoding parameter is not supported
*/
public static String encodeScheme(String scheme, String encoding) throws UnsupportedEncodingException {
return encode(scheme, encoding, UriComponent.SCHEME, false);
return encodeUriComponent(scheme, encoding, UriComponent.SCHEME, null);
}
/**
@ -380,7 +492,7 @@ public abstract class UriUtils { @@ -380,7 +492,7 @@ public abstract class UriUtils {
* @throws UnsupportedEncodingException when the given encoding parameter is not supported
*/
public static String encodeAuthority(String authority, String encoding) throws UnsupportedEncodingException {
return encode(authority, encoding, UriComponent.AUTHORITY, false);
return encodeUriComponent(authority, encoding, UriComponent.AUTHORITY, null);
}
/**
@ -392,7 +504,7 @@ public abstract class UriUtils { @@ -392,7 +504,7 @@ public abstract class UriUtils {
* @throws UnsupportedEncodingException when the given encoding parameter is not supported
*/
public static String encodeUserInfo(String userInfo, String encoding) throws UnsupportedEncodingException {
return encode(userInfo, encoding, UriComponent.USER_INFO, false);
return encodeUriComponent(userInfo, encoding, UriComponent.USER_INFO, null);
}
/**
@ -404,7 +516,7 @@ public abstract class UriUtils { @@ -404,7 +516,7 @@ public abstract class UriUtils {
* @throws UnsupportedEncodingException when the given encoding parameter is not supported
*/
public static String encodeHost(String host, String encoding) throws UnsupportedEncodingException {
return encode(host, encoding, UriComponent.HOST, false);
return encodeUriComponent(host, encoding, UriComponent.HOST, null);
}
/**
@ -416,7 +528,7 @@ public abstract class UriUtils { @@ -416,7 +528,7 @@ public abstract class UriUtils {
* @throws UnsupportedEncodingException when the given encoding parameter is not supported
*/
public static String encodePort(String port, String encoding) throws UnsupportedEncodingException {
return encode(port, encoding, UriComponent.PORT, false);
return encodeUriComponent(port, encoding, UriComponent.PORT, null);
}
/**
@ -428,7 +540,7 @@ public abstract class UriUtils { @@ -428,7 +540,7 @@ public abstract class UriUtils {
* @throws UnsupportedEncodingException when the given encoding parameter is not supported
*/
public static String encodePath(String path, String encoding) throws UnsupportedEncodingException {
return encode(path, encoding, UriComponent.PATH, false);
return encodeUriComponent(path, encoding, UriComponent.PATH, null);
}
/**
@ -440,7 +552,7 @@ public abstract class UriUtils { @@ -440,7 +552,7 @@ public abstract class UriUtils {
* @throws UnsupportedEncodingException when the given encoding parameter is not supported
*/
public static String encodePathSegment(String segment, String encoding) throws UnsupportedEncodingException {
return encode(segment, encoding, UriComponent.PATH_SEGMENT, false);
return encodeUriComponent(segment, encoding, UriComponent.PATH_SEGMENT, null);
}
/**
@ -452,7 +564,7 @@ public abstract class UriUtils { @@ -452,7 +564,7 @@ public abstract class UriUtils {
* @throws UnsupportedEncodingException when the given encoding parameter is not supported
*/
public static String encodeQuery(String query, String encoding) throws UnsupportedEncodingException {
return encode(query, encoding, UriComponent.QUERY, false);
return encodeUriComponent(query, encoding, UriComponent.QUERY, null);
}
/**
@ -464,7 +576,7 @@ public abstract class UriUtils { @@ -464,7 +576,7 @@ public abstract class UriUtils {
* @throws UnsupportedEncodingException when the given encoding parameter is not supported
*/
public static String encodeQueryParam(String queryParam, String encoding) throws UnsupportedEncodingException {
return encode(queryParam, encoding, UriComponent.QUERY_PARAM, false);
return encodeUriComponent(queryParam, encoding, UriComponent.QUERY_PARAM, null);
}
/**
@ -476,84 +588,20 @@ public abstract class UriUtils { @@ -476,84 +588,20 @@ public abstract class UriUtils {
* @throws UnsupportedEncodingException when the given encoding parameter is not supported
*/
public static String encodeFragment(String fragment, String encoding) throws UnsupportedEncodingException {
return encode(fragment, encoding, UriComponent.FRAGMENT, false);
}
/**
* Encodes the given source into an encoded String using the rules specified by the given component. This method
* encodes with the default encoding (i.e. UTF-8).
*
* @param source the source string
* @param uriComponent the URI component for the source
* @param allowTemplateVars whether URI template variables are allowed. If {@code true}, '{' and '}' characters are not
* encoded, even though they might not be valid for the component
* @return the encoded URI
* @throws IllegalArgumentException when the given uri parameter is not a valid URI
*/
public static String encode(String source, UriComponent uriComponent, boolean allowTemplateVars) {
try {
return encode(source, DEFAULT_ENCODING, uriComponent, allowTemplateVars);
}
catch (UnsupportedEncodingException e) {
throw new InternalError("'" + DEFAULT_ENCODING + "' encoding not supported");
}
}
/**
* Encodes the given source into an encoded String using the rules specified by the given component.
*
* @param source the source string
* @param encoding the encoding of the source string
* @param uriComponent the URI component for the source
* @param allowTemplateVars whether URI template variables are allowed. If {@code true}, '{' and '}' characters are not
* encoded, even though they might not be valid for the component
* @return the encoded URI
* @throws IllegalArgumentException when the given uri parameter is not a valid URI
*/
public static String encode(String source, String encoding, UriComponent uriComponent, boolean allowTemplateVars)
throws UnsupportedEncodingException {
Assert.hasLength(encoding, "'encoding' must not be empty");
byte[] bytes = encodeInternal(source.getBytes(encoding), uriComponent, allowTemplateVars);
return new String(bytes, "US-ASCII");
return encodeUriComponent(fragment, encoding, UriComponent.FRAGMENT, null);
}
private static byte[] encodeInternal(byte[] source, UriComponent uriComponent, boolean allowTemplateVars) {
Assert.notNull(source, "'source' must not be null");
Assert.notNull(uriComponent, "'uriComponent' must not be null");
ByteArrayOutputStream bos = new ByteArrayOutputStream(source.length);
for (int i = 0; i < source.length; i++) {
int b = source[i];
if (b < 0) {
b += 256;
}
if (uriComponent.isAllowed(b)) {
bos.write(b);
}
else if (allowTemplateVars && (b == '{' || b == '}')) {
bos.write(b);
}
else {
bos.write('%');
char hex1 = Character.toUpperCase(Character.forDigit((b >> 4) & 0xF, 16));
char hex2 = Character.toUpperCase(Character.forDigit(b & 0xF, 16));
bos.write(hex1);
bos.write(hex2);
}
}
return bos.toByteArray();
}
// decoding
/**
* Decodes the given encoded source String into an URI. Based on the following rules: <ul> <li>Alphanumeric characters
* {@code "a"} through {@code "z"}, {@code "A"} through {@code "Z"}, and {@code "0"} through {@code "9"} stay the same.
* <li>Special characters {@code "-"}, {@code "_"}, {@code "."}, and {@code "*"} stay the same. <li>All other
* characters are converted into one or more bytes using the given encoding scheme. Each of the resulting bytes is
* written as a hexadecimal string in the {@code %xy} format. <li>A sequence "<code>%<i>xy</i></code>" is interpreted
* as a hexadecimal representation of the character. </ul>
* Decodes the given encoded source String into an URI. Based on the following rules:
* <ul>
* <li>Alphanumeric characters {@code "a"} through {@code "z"}, {@code "A"} through {@code "Z"}, and
* {@code "0"} through {@code "9"} stay the same.</li>
* <li>Special characters {@code "-"}, {@code "_"}, {@code "."}, and {@code "*"} stay the same.</li>
* <li>A sequence "<code>%<i>xy</i></code>" is interpreted as a hexadecimal representation of the character.</li>
* </ul>
*
* @param source the source string
* @param encoding the encoding
@ -590,4 +638,16 @@ public abstract class UriUtils { @@ -590,4 +638,16 @@ public abstract class UriUtils {
return changed ? new String(bos.toByteArray(), encoding) : source;
}
/**
* Enumeration used to control how URIs are encoded.
*/
public enum EncodingOption {
/**
* Allow for URI template variables to occur in the URI component (i.e. '{foo}')
*/
ALLOW_TEMPLATE_VARS
}
}

19
org.springframework.web/src/test/java/org/springframework/web/util/UriBuilderTests.java

@ -19,9 +19,10 @@ package org.springframework.web.util; @@ -19,9 +19,10 @@ package org.springframework.web.util;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import org.junit.Ignore;
import org.junit.Test;
import static org.junit.Assert.*;
@ -55,11 +56,11 @@ public class UriBuilderTests { @@ -55,11 +56,11 @@ public class UriBuilderTests {
}
@Test
@Ignore("Working on it")
public void templateVarsVarArgs() throws URISyntaxException {
UriBuilder builder = UriBuilder.newInstance();
URI result = builder.scheme("http").host("example.com").path("{foo}").build("bar");
URI result = UriBuilder.fromPath("/{foo}/{bar}").build("baz", "qux");
URI expected = new URI("http://example.com/bar");
URI expected = new URI("http://example.com/baz/qux");
assertEquals("Invalid result URI", expected, result);
}
@ -82,11 +83,11 @@ public class UriBuilderTests { @@ -82,11 +83,11 @@ public class UriBuilderTests {
@Test
public void templateVarsMap() throws URISyntaxException {
Map<String, String> vars = Collections.singletonMap("foo", "bar");
UriBuilder builder = UriBuilder.newInstance();
URI result = builder.scheme("http").host("example.com").path("{foo}").build(vars);
URI expected = new URI("http://example.com/bar");
Map<String, String> vars = new HashMap<String, String>(2);
vars.put("bar", "qux");
vars.put("foo", "baz");
URI result = UriBuilder.fromPath("/{foo}/{bar}").build(vars);
URI expected = new URI("/baz/qux");
assertEquals("Invalid result URI", expected, result);
}

Loading…
Cancel
Save