SPR-6946 - RestTemplate should not encode fragments (#'s)
This commit is contained in:
@@ -17,9 +17,10 @@
|
||||
package org.springframework.web.client;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
@@ -36,11 +37,12 @@ import org.springframework.http.converter.FormHttpMessageConverter;
|
||||
import org.springframework.http.converter.HttpMessageConverter;
|
||||
import org.springframework.http.converter.StringHttpMessageConverter;
|
||||
import org.springframework.http.converter.json.MappingJacksonHttpMessageConverter;
|
||||
import org.springframework.http.converter.xml.SourceHttpMessageConverter;
|
||||
import org.springframework.http.converter.xml.Jaxb2RootElementHttpMessageConverter;
|
||||
import org.springframework.http.converter.xml.SourceHttpMessageConverter;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.web.util.UriTemplate;
|
||||
import org.springframework.web.util.UriUtils;
|
||||
|
||||
/**
|
||||
* <strong>The central class for client-side HTTP access.</strong> It simplifies communication with HTTP servers, and
|
||||
@@ -302,7 +304,7 @@ public class RestTemplate extends HttpAccessor implements RestOperations {
|
||||
public <T> T execute(String url, HttpMethod method, RequestCallback requestCallback,
|
||||
ResponseExtractor<T> responseExtractor, Object... urlVariables) throws RestClientException {
|
||||
|
||||
UriTemplate uriTemplate = new UriTemplate(url);
|
||||
UriTemplate uriTemplate = new HttpUrlTemplate(url);
|
||||
URI expanded = uriTemplate.expand(urlVariables);
|
||||
return doExecute(expanded, method, requestCallback, responseExtractor);
|
||||
}
|
||||
@@ -310,7 +312,7 @@ public class RestTemplate extends HttpAccessor implements RestOperations {
|
||||
public <T> T execute(String url, HttpMethod method, RequestCallback requestCallback,
|
||||
ResponseExtractor<T> responseExtractor, Map<String, ?> urlVariables) throws RestClientException {
|
||||
|
||||
UriTemplate uriTemplate = new UriTemplate(url);
|
||||
UriTemplate uriTemplate = new HttpUrlTemplate(url);
|
||||
URI expanded = uriTemplate.expand(urlVariables);
|
||||
return doExecute(expanded, method, requestCallback, responseExtractor);
|
||||
}
|
||||
@@ -489,4 +491,29 @@ public class RestTemplate extends HttpAccessor implements RestOperations {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* HTTP-specific subclass of UriTemplate, overriding the encode method.
|
||||
*/
|
||||
private static class HttpUrlTemplate extends UriTemplate {
|
||||
|
||||
public HttpUrlTemplate(String uriTemplate) {
|
||||
super(uriTemplate);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected URI encodeUri(String uri) {
|
||||
try {
|
||||
String encoded = UriUtils.encodeHttpUrl(uri, "UTF-8");
|
||||
return new URI(encoded);
|
||||
}
|
||||
catch (UnsupportedEncodingException ex) {
|
||||
// should not happen, UTF-8 is always supported
|
||||
throw new IllegalStateException(ex);
|
||||
}
|
||||
catch (URISyntaxException ex) {
|
||||
throw new IllegalArgumentException("Could not create HTTP URL from [" + uri + "]: " + ex, ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -70,7 +70,7 @@ public class UriTemplate {
|
||||
* Return the names of the variables in the template, in order.
|
||||
* @return the template variable names
|
||||
*/
|
||||
public final List<String> getVariableNames() {
|
||||
public List<String> getVariableNames() {
|
||||
return this.variableNames;
|
||||
}
|
||||
|
||||
@@ -172,7 +172,15 @@ public class UriTemplate {
|
||||
return this.uriTemplate;
|
||||
}
|
||||
|
||||
private static URI encodeUri(String uri) {
|
||||
/**
|
||||
* Encodes the given String as URL.
|
||||
*
|
||||
* <p>Defaults to {@link UriUtils#encodeUri(String, String)}.
|
||||
*
|
||||
* @param uri the URI to encode
|
||||
* @return the encoded URI
|
||||
*/
|
||||
protected URI encodeUri(String uri) {
|
||||
try {
|
||||
String encoded = UriUtils.encodeUri(uri, "UTF-8");
|
||||
return new URI(encoded);
|
||||
@@ -186,7 +194,6 @@ public class UriTemplate {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Static inner class to parse uri template strings into a matching regular expression.
|
||||
*/
|
||||
|
||||
@@ -36,13 +36,13 @@ import org.springframework.util.Assert;
|
||||
* </ul>
|
||||
*
|
||||
* @author Arjen Poutsma
|
||||
* @since 3.0
|
||||
* @see <a href="http://www.ietf.org/rfc/rfc3986.txt">RFC 3986</a>
|
||||
* @since 3.0
|
||||
*/
|
||||
public abstract class UriUtils {
|
||||
|
||||
private static final BitSet SCHEME;
|
||||
|
||||
|
||||
private static final BitSet USER_INFO;
|
||||
|
||||
private static final BitSet HOST;
|
||||
@@ -61,6 +61,8 @@ public abstract class UriUtils {
|
||||
|
||||
private static final String SCHEME_PATTERN = "([^:/?#]+):";
|
||||
|
||||
private static final String HTTP_PATTERN = "(http|https):";
|
||||
|
||||
private static final String USERINFO_PATTERN = "([^@/]*)";
|
||||
|
||||
private static final String HOST_PATTERN = "([^/?#:]*)";
|
||||
@@ -71,15 +73,16 @@ public abstract class UriUtils {
|
||||
|
||||
private static final String QUERY_PATTERN = "([^#]*)";
|
||||
|
||||
private static final String FRAGMENT_PATTERN = "(.*)";
|
||||
private static final String LAST_PATTERN = "(.*)";
|
||||
|
||||
// Regex patterns that matches URIs. See RFC 3986, appendix B
|
||||
private static final Pattern URI_PATTERN =
|
||||
Pattern.compile("^(" + SCHEME_PATTERN + ")?" +
|
||||
"(//(" + USERINFO_PATTERN + "@)?" + HOST_PATTERN + "(:" + PORT_PATTERN + ")?" + ")?"
|
||||
+ PATH_PATTERN +
|
||||
"(\\?" + QUERY_PATTERN + ")?" +
|
||||
"(#" + FRAGMENT_PATTERN + ")?");
|
||||
private static final Pattern URI_PATTERN = Pattern.compile(
|
||||
"^(" + SCHEME_PATTERN + ")?" + "(//(" + USERINFO_PATTERN + "@)?" + HOST_PATTERN + "(:" + PORT_PATTERN +
|
||||
")?" + ")?" + PATH_PATTERN + "(\\?" + QUERY_PATTERN + ")?" + "(#" + LAST_PATTERN + ")?");
|
||||
|
||||
private static final Pattern HTTP_URL_PATTERN = Pattern.compile(
|
||||
"^" + HTTP_PATTERN + "(//(" + USERINFO_PATTERN + "@)?" + HOST_PATTERN + "(:" + PORT_PATTERN + ")?" +
|
||||
")?" + PATH_PATTERN + "(\\?" + LAST_PATTERN + ")?");
|
||||
|
||||
static {
|
||||
// variable names refer to RFC 3986, appendix A
|
||||
@@ -181,10 +184,10 @@ public abstract class UriUtils {
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes the given source URI into an encoded String. All various URI components are encoded according to
|
||||
* their respective valid character sets.
|
||||
* Encodes the given source URI into an encoded String. All various URI components are encoded according to their
|
||||
* respective valid character sets.
|
||||
*
|
||||
* @param uri the URI to be encoded
|
||||
* @param uri the URI to be encoded
|
||||
* @param encoding the character encoding to encode to
|
||||
* @return the encoded URI
|
||||
* @throws IllegalArgumentException when the given uri parameter is not a valid URI
|
||||
@@ -204,50 +207,115 @@ public abstract class UriUtils {
|
||||
String query = m.group(11);
|
||||
String fragment = m.group(13);
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
if (scheme != null) {
|
||||
sb.append(encodeScheme(scheme, encoding));
|
||||
sb.append(':');
|
||||
}
|
||||
|
||||
if (authority != null) {
|
||||
sb.append("//");
|
||||
if (userinfo != null) {
|
||||
sb.append(encodeUserInfo(userinfo, encoding));
|
||||
sb.append('@');
|
||||
}
|
||||
if (host != null) {
|
||||
sb.append(encodeHost(host, encoding));
|
||||
}
|
||||
if (port != null) {
|
||||
sb.append(':');
|
||||
sb.append(encodePort(port, encoding));
|
||||
}
|
||||
}
|
||||
|
||||
sb.append(encodePath(path, encoding));
|
||||
|
||||
if (query != null) {
|
||||
sb.append('?');
|
||||
sb.append(encodeQuery(query, encoding));
|
||||
}
|
||||
|
||||
if (fragment != null) {
|
||||
sb.append('#');
|
||||
sb.append(encodeFragment(fragment, encoding));
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
} else {
|
||||
return encodeUriComponents(scheme, authority, userinfo, host, port, path, query, fragment, encoding);
|
||||
}
|
||||
else {
|
||||
throw new IllegalArgumentException("[" + uri + "] is not a valid URI");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes the given HTTP URI into an encoded String. All various URI components are encoded according to their
|
||||
* respective valid character sets.
|
||||
*
|
||||
* <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.
|
||||
*
|
||||
* @param httpUrl the HTTP URL to be encoded
|
||||
* @param encoding the character encoding to encode to
|
||||
* @return the encoded URL
|
||||
* @throws IllegalArgumentException when the given uri parameter is not a valid URI
|
||||
* @throws UnsupportedEncodingException when the given encoding parameter is not supported
|
||||
*/
|
||||
public static String encodeHttpUrl(String httpUrl, String encoding) throws UnsupportedEncodingException {
|
||||
Assert.notNull(httpUrl, "'httpUrl' must not be null");
|
||||
Assert.hasLength(encoding, "'encoding' must not be empty");
|
||||
Matcher m = HTTP_URL_PATTERN.matcher(httpUrl);
|
||||
if (m.matches()) {
|
||||
String scheme = m.group(1);
|
||||
String authority = m.group(2);
|
||||
String userinfo = m.group(4);
|
||||
String host = m.group(5);
|
||||
String portString = m.group(7);
|
||||
String path = m.group(8);
|
||||
String query = m.group(10);
|
||||
|
||||
return encodeUriComponents(scheme, authority, userinfo, host, portString, path, query, null, encoding);
|
||||
}
|
||||
else {
|
||||
throw new IllegalArgumentException("[" + httpUrl + "] is not a valid HTTP URL");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes the given source URI components into an encoded String. All various URI components are optional, but encoded according
|
||||
* to their respective valid character sets.
|
||||
*
|
||||
* @param scheme the scheme
|
||||
* @param authority the authority
|
||||
* @param userinfo the user info
|
||||
* @param host the host
|
||||
* @param port the port
|
||||
* @param path the path
|
||||
* @param query the query
|
||||
* @param fragment the fragment
|
||||
* @param encoding the character encoding to encode to
|
||||
* @return the encoded URI
|
||||
* @throws IllegalArgumentException when the given uri parameter is not a valid URI
|
||||
* @throws UnsupportedEncodingException when the given encoding parameter is not supported
|
||||
*/
|
||||
public static String encodeUriComponents(String scheme,
|
||||
String authority,
|
||||
String userinfo,
|
||||
String host,
|
||||
String port,
|
||||
String path,
|
||||
String query,
|
||||
String fragment,
|
||||
String encoding) throws UnsupportedEncodingException {
|
||||
|
||||
Assert.hasLength(encoding, "'encoding' must not be empty");
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
if (scheme != null) {
|
||||
sb.append(encodeScheme(scheme, encoding));
|
||||
sb.append(':');
|
||||
}
|
||||
|
||||
if (authority != null) {
|
||||
sb.append("//");
|
||||
if (userinfo != null) {
|
||||
sb.append(encodeUserInfo(userinfo, encoding));
|
||||
sb.append('@');
|
||||
}
|
||||
if (host != null) {
|
||||
sb.append(encodeHost(host, encoding));
|
||||
}
|
||||
if (port != null) {
|
||||
sb.append(':');
|
||||
sb.append(encodePort(port, encoding));
|
||||
}
|
||||
}
|
||||
|
||||
sb.append(encodePath(path, encoding));
|
||||
|
||||
if (query != null) {
|
||||
sb.append('?');
|
||||
sb.append(encodeQuery(query, encoding));
|
||||
}
|
||||
|
||||
if (fragment != null) {
|
||||
sb.append('#');
|
||||
sb.append(encodeFragment(fragment, encoding));
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes the given URI scheme.
|
||||
*
|
||||
* @param scheme the scheme to be encoded
|
||||
* @param scheme the scheme to be encoded
|
||||
* @param encoding the character encoding to encode to
|
||||
* @return the encoded scheme
|
||||
* @throws UnsupportedEncodingException when the given encoding parameter is not supported
|
||||
@@ -271,7 +339,7 @@ public abstract class UriUtils {
|
||||
/**
|
||||
* Encodes the given URI host.
|
||||
*
|
||||
* @param host the host to be encoded
|
||||
* @param host the host to be encoded
|
||||
* @param encoding the character encoding to encode to
|
||||
* @return the encoded host
|
||||
* @throws UnsupportedEncodingException when the given encoding parameter is not supported
|
||||
@@ -283,7 +351,7 @@ public abstract class UriUtils {
|
||||
/**
|
||||
* Encodes the given URI port.
|
||||
*
|
||||
* @param port the port to be encoded
|
||||
* @param port the port to be encoded
|
||||
* @param encoding the character encoding to encode to
|
||||
* @return the encoded port
|
||||
* @throws UnsupportedEncodingException when the given encoding parameter is not supported
|
||||
@@ -295,7 +363,7 @@ public abstract class UriUtils {
|
||||
/**
|
||||
* Encodes the given URI path.
|
||||
*
|
||||
* @param path the path to be encoded
|
||||
* @param path the path to be encoded
|
||||
* @param encoding the character encoding to encode to
|
||||
* @return the encoded path
|
||||
* @throws UnsupportedEncodingException when the given encoding parameter is not supported
|
||||
@@ -307,7 +375,7 @@ public abstract class UriUtils {
|
||||
/**
|
||||
* Encodes the given URI path segment.
|
||||
*
|
||||
* @param segment the segment to be encoded
|
||||
* @param segment the segment to be encoded
|
||||
* @param encoding the character encoding to encode to
|
||||
* @return the encoded segment
|
||||
* @throws UnsupportedEncodingException when the given encoding parameter is not supported
|
||||
@@ -319,7 +387,7 @@ public abstract class UriUtils {
|
||||
/**
|
||||
* Encodes the given URI query.
|
||||
*
|
||||
* @param query the query to be encoded
|
||||
* @param query the query to be encoded
|
||||
* @param encoding the character encoding to encode to
|
||||
* @return the encoded query
|
||||
* @throws UnsupportedEncodingException when the given encoding parameter is not supported
|
||||
@@ -332,7 +400,7 @@ public abstract class UriUtils {
|
||||
* Encodes the given URI query parameter.
|
||||
*
|
||||
* @param queryParam the query parameter to be encoded
|
||||
* @param encoding the character encoding to encode to
|
||||
* @param encoding the character encoding to encode to
|
||||
* @return the encoded query parameter
|
||||
* @throws UnsupportedEncodingException when the given encoding parameter is not supported
|
||||
*/
|
||||
@@ -352,7 +420,8 @@ public abstract class UriUtils {
|
||||
return encode(fragment, encoding, FRAGMENT);
|
||||
}
|
||||
|
||||
private static String encode(String source, String encoding, BitSet notEncoded) throws UnsupportedEncodingException {
|
||||
private static String encode(String source, String encoding, BitSet notEncoded)
|
||||
throws UnsupportedEncodingException {
|
||||
Assert.notNull(source, "'source' must not be null");
|
||||
Assert.hasLength(encoding, "'encoding' must not be empty");
|
||||
ByteArrayOutputStream bos = new ByteArrayOutputStream(source.length() * 2);
|
||||
@@ -388,8 +457,9 @@ public abstract class UriUtils {
|
||||
* <li>A sequence "<code>%<i>xy</i></code>" is interpreted as a hexadecimal
|
||||
* representation of the character.
|
||||
* </ul>
|
||||
* @param source
|
||||
* @param encoding
|
||||
*
|
||||
* @param source the source string
|
||||
* @param encoding the encoding
|
||||
* @return the decoded URI
|
||||
* @throws UnsupportedEncodingException when the given encoding parameter is not supported
|
||||
* @see java.net.URLDecoder#decode(String, String)
|
||||
|
||||
Reference in New Issue
Block a user