|
|
|
@ -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 |
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
} |
|
|
|
|