From b6c1e88e4a0d9f658c9af434f0b59237168964c5 Mon Sep 17 00:00:00 2001 From: Arjen Poutsma Date: Thu, 8 Sep 2011 11:01:56 +0000 Subject: [PATCH] SPR-5973: Cleaning it up --- .../springframework/web/util/UriBuilder.java | 159 +++++------ .../web/util/UriComponentTemplate.java | 2 +- .../springframework/web/util/UriUtils.java | 246 +++++++++++------- .../web/util/UriBuilderTests.java | 19 +- 4 files changed, 227 insertions(+), 199 deletions(-) diff --git a/org.springframework.web/src/main/java/org/springframework/web/util/UriBuilder.java b/org.springframework.web/src/main/java/org/springframework/web/util/UriBuilder.java index 9c4d27fadd..fda3bbfc25 100644 --- a/org.springframework.web/src/main/java/org/springframework/web/util/UriBuilder.java +++ b/org.springframework.web/src/main/java/org/springframework/web/util/UriBuilder.java @@ -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 { * @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 { * @return the resulting URI */ public URI build(Map uriVariables) { - return buildFromMap(true, uriVariables); + return buildFromMap(uriVariables, true); } /** @@ -197,72 +169,54 @@ public class UriBuilder { * @return the resulting URI */ public URI buildFromEncoded(Map uriVariables) { - return buildFromMap(false, uriVariables); + return buildFromMap(uriVariables, false); } - private URI buildFromMap(boolean encodeUriVariableValues, Map uriVariables) { + private URI buildFromMap(Map 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 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { } + private String encodeUriComponent(String source, UriComponent uriComponent) { + return UriUtils.encodeUriComponent(source, uriComponent, EnumSet.of(UriUtils.EncodingOption.ALLOW_TEMPLATE_VARS)); + } + + } diff --git a/org.springframework.web/src/main/java/org/springframework/web/util/UriComponentTemplate.java b/org.springframework.web/src/main/java/org/springframework/web/util/UriComponentTemplate.java index d354ca2dcc..9000a627aa 100644 --- a/org.springframework.web/src/main/java/org/springframework/web/util/UriComponentTemplate.java +++ b/org.springframework.web/src/main/java/org/springframework/web/util/UriComponentTemplate.java @@ -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; } } diff --git a/org.springframework.web/src/main/java/org/springframework/web/util/UriUtils.java b/org.springframework.web/src/main/java/org/springframework/web/util/UriUtils.java index 559ec6efc0..8d51aa4022 100644 --- a/org.springframework.web/src/main/java/org/springframework/web/util/UriUtils.java +++ b/org.springframework.web/src/main/java/org/springframework/web/util/UriUtils.java @@ -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; * Utility class for URI encoding and decoding based on RFC 3986. Offers encoding methods for the various URI * components. * - *

All {@code encode*(String, String} methods in this class operate in a similar way:

  • Valid characters for - * the specific URI component as defined in RFC 3986 stay the same.
  • 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 - * "%xy" format.
+ *

All {@code encode*(String, String} methods in this class operate in a similar way: + *

    + *
  • Valid characters for the specific URI component as defined in RFC 3986 stay the same.
  • + *
  • 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 "%xy" format.
  • + *
* * @author Arjen Poutsma * @see RFC 3986 @@ -127,15 +131,13 @@ public abstract class UriUtils { *
  • {@link UriComponent#PORT}
  • *
  • {@link UriComponent#PATH}
  • *
  • {@link UriComponent#QUERY}
  • - *
  • {@link UriComponent#FRAGMENT}
  • * * though the values assigned to these keys is {@code null} if they do not occur in the given source URI. * *

    Note 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. - * - *

    Note 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 { public static String encodeUriComponents(Map uriComponents, String encoding) throws UnsupportedEncodingException { Assert.notEmpty(uriComponents, "'uriComponents' must not be empty"); + Assert.hasLength(encoding, "'encoding' must not be empty"); Map encodedUriComponents = new EnumMap(UriComponent.class); for (Map.Entry 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 { 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 { 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 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 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 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 { * @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 { * @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 { * @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 { * @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 { * @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 { * @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 { * @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 { * @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 { * @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 { * @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:

    • Alphanumeric characters - * {@code "a"} through {@code "z"}, {@code "A"} through {@code "Z"}, and {@code "0"} through {@code "9"} stay the same. - *
    • Special characters {@code "-"}, {@code "_"}, {@code "."}, and {@code "*"} stay the same.
    • 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.
    • A sequence "%xy" is interpreted - * as a hexadecimal representation of the character.
    + * Decodes the given encoded source String into an URI. Based on the following rules: + *
      + *
    • Alphanumeric characters {@code "a"} through {@code "z"}, {@code "A"} through {@code "Z"}, and + * {@code "0"} through {@code "9"} stay the same.
    • + *
    • Special characters {@code "-"}, {@code "_"}, {@code "."}, and {@code "*"} stay the same.
    • + *
    • A sequence "%xy" is interpreted as a hexadecimal representation of the character.
    • + *
    * * @param source the source string * @param encoding the encoding @@ -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 + + } + } diff --git a/org.springframework.web/src/test/java/org/springframework/web/util/UriBuilderTests.java b/org.springframework.web/src/test/java/org/springframework/web/util/UriBuilderTests.java index ee30c36063..15e31d7fa0 100644 --- a/org.springframework.web/src/test/java/org/springframework/web/util/UriBuilderTests.java +++ b/org.springframework.web/src/test/java/org/springframework/web/util/UriBuilderTests.java @@ -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 { } @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 { @Test public void templateVarsMap() throws URISyntaxException { - Map 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 vars = new HashMap(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); }