SPR-6946 - RestTemplate should not encode fragments (#'s)
This commit is contained in:
parent
54d0346084
commit
c91ff130d5
|
|
@ -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,8 +36,8 @@ 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 {
|
||||
|
||||
|
|
@ -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,8 +184,8 @@ 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 encoding the character encoding to encode to
|
||||
|
|
@ -204,6 +207,74 @@ public abstract class UriUtils {
|
|||
String query = m.group(11);
|
||||
String fragment = m.group(13);
|
||||
|
||||
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) {
|
||||
|
|
@ -239,9 +310,6 @@ public abstract class UriUtils {
|
|||
}
|
||||
|
||||
return sb.toString();
|
||||
} else {
|
||||
throw new IllegalArgumentException("[" + uri + "] is not a valid URI");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2009 the original author or authors.
|
||||
* Copyright 2002-2010 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
|
@ -99,6 +99,8 @@ public class UriUtilsTest {
|
|||
public void encodeUri() throws UnsupportedEncodingException {
|
||||
assertEquals("Invalid encoded URI", "http://www.ietf.org/rfc/rfc3986.txt",
|
||||
UriUtils.encodeUri("http://www.ietf.org/rfc/rfc3986.txt", ENC));
|
||||
assertEquals("Invalid encoded URI", "https://www.ietf.org/rfc/rfc3986.txt",
|
||||
UriUtils.encodeUri("https://www.ietf.org/rfc/rfc3986.txt", ENC));
|
||||
assertEquals("Invalid encoded URI", "http://www.google.com/?q=z%FCrich",
|
||||
UriUtils.encodeUri("http://www.google.com/?q=z\u00fcrich", ENC));
|
||||
assertEquals("Invalid encoded URI",
|
||||
|
|
@ -117,8 +119,34 @@ public class UriUtilsTest {
|
|||
assertEquals("Invalid encoded URI", "../../../demo/jfc/SwingSet2/src/SwingSet2.java",
|
||||
UriUtils.encodeUri("../../../demo/jfc/SwingSet2/src/SwingSet2.java", ENC));
|
||||
assertEquals("Invalid encoded URI", "file:///~/calendar", UriUtils.encodeUri("file:///~/calendar", ENC));
|
||||
assertEquals("Invalid encoded URI", "http://example.com/query=foo@bar", UriUtils.encodeUri("http://example.com/query=foo@bar", ENC));
|
||||
assertEquals("Invalid encoded URI", "http://example.com/query=foo@bar",
|
||||
UriUtils.encodeUri("http://example.com/query=foo@bar", ENC));
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void encodeHttpUrl() throws UnsupportedEncodingException {
|
||||
assertEquals("Invalid encoded HTTP URL", "http://www.ietf.org/rfc/rfc3986.txt",
|
||||
UriUtils.encodeHttpUrl("http://www.ietf.org/rfc/rfc3986.txt", ENC));
|
||||
assertEquals("Invalid encoded URI", "https://www.ietf.org/rfc/rfc3986.txt",
|
||||
UriUtils.encodeHttpUrl("https://www.ietf.org/rfc/rfc3986.txt", ENC));
|
||||
assertEquals("Invalid encoded HTTP URL", "http://www.google.com/?q=z%FCrich",
|
||||
UriUtils.encodeHttpUrl("http://www.google.com/?q=z\u00fcrich", ENC));
|
||||
assertEquals("Invalid encoded HTTP URL",
|
||||
"http://arjen:foobar@java.sun.com:80/javase/6/docs/api/java/util/BitSet.html?foo=bar",
|
||||
UriUtils.encodeHttpUrl(
|
||||
"http://arjen:foobar@java.sun.com:80/javase/6/docs/api/java/util/BitSet.html?foo=bar", ENC));
|
||||
assertEquals("Invalid encoded HTTP URL", "http://search.twitter.com/search.atom?q=%23avatar",
|
||||
UriUtils.encodeHttpUrl("http://search.twitter.com/search.atom?q=#avatar", ENC));
|
||||
assertEquals("Invalid encoded HTTP URL", "http://java.sun.com/j2se/1.3/",
|
||||
UriUtils.encodeHttpUrl("http://java.sun.com/j2se/1.3/", ENC));
|
||||
assertEquals("Invalid encoded HTTP URL", "http://example.com/query=foo@bar",
|
||||
UriUtils.encodeHttpUrl("http://example.com/query=foo@bar", ENC));
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void encodeHttpUrlMail() throws UnsupportedEncodingException {
|
||||
UriUtils.encodeHttpUrl("mailto:java-net@java.sun.com", ENC);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue