From bcd8355e61ed784346d4d5259ba8bfb28ea30313 Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Thu, 19 Jan 2012 23:47:31 -0500 Subject: [PATCH] SPR-8974 Fix regression in UriUtils.java --- .../resources/changelog.txt | 5 +- .../springframework/web/util/UriUtils.java | 125 ++++++++++++++++-- .../web/util/UriUtilsTests.java | 3 + 3 files changed, 119 insertions(+), 14 deletions(-) diff --git a/build-spring-framework/resources/changelog.txt b/build-spring-framework/resources/changelog.txt index 827ad75bdd..eff2824e93 100644 --- a/build-spring-framework/resources/changelog.txt +++ b/build-spring-framework/resources/changelog.txt @@ -23,7 +23,10 @@ Changes in version 3.1.1 (2012-02-06) * corrected fix for QuartzJobBean to work with Quartz 2.0/2.1 * JMS CachingConnectionFactory never caches consumers for temporary queues and topics * JMS SimpleMessageListenerContainer silently falls back to lazy registration of consumers - +* fix regresion in UriUtils +* allow adding flash attributes in methods with a ModelAndView return value +* preserve quotes in MediaType parameters +* make flash attributes available in the model of ParameterizableViewController and UrlFilenameViewController Changes in version 3.1 GA (2011-12-12) -------------------------------------- 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 e24781b745..4d6c2a4499 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,6 +18,8 @@ package org.springframework.web.util; import java.io.ByteArrayOutputStream; import java.io.UnsupportedEncodingException; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import org.springframework.util.Assert; @@ -38,38 +40,107 @@ import org.springframework.util.Assert; */ 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 = "([^/?#:]*)"; + + private static final String PORT_PATTERN = "(\\d*)"; + + private static final String PATH_PATTERN = "([^?#]*)"; + + private static final String QUERY_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 + ")?" + "(#" + LAST_PATTERN + ")?"); + + private static final Pattern HTTP_URL_PATTERN = Pattern.compile( + "^" + HTTP_PATTERN + "(//(" + USERINFO_PATTERN + "@)?" + HOST_PATTERN + "(:" + PORT_PATTERN + ")?" + ")?" + + PATH_PATTERN + "(\\?" + LAST_PATTERN + ")?"); // encoding /** * Encodes the given source URI into an encoded String. All various URI components are * encoded according to their respective valid character sets. + *

Note that this method does not attempt to encode "=" and "&" + * characters in query parameter names and query parameter values because they cannot + * be parsed in a reliable way. Instead use: + *

+	 *  UriComponents uriComponents = UriComponentsBuilder.fromUri("/path?name={value}").buildAndExpand("a=b");
+	 *  String encodedUri = uriComponents.encode().toUriString();
+	 * 
* @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 * @throws UnsupportedEncodingException when the given encoding parameter is not supported + * @deprecated in favor of {@link UriComponentsBuilder}; see note about query param encoding */ public static String encodeUri(String uri, String encoding) throws UnsupportedEncodingException { - UriComponents uriComponents = UriComponentsBuilder.fromUriString(uri).build(); - UriComponents encoded = uriComponents.encode(encoding); - return encoded.toUriString(); - } + Assert.notNull(uri, "'uri' must not be null"); + Assert.hasLength(encoding, "'encoding' must not be empty"); + Matcher m = URI_PATTERN.matcher(uri); + if (m.matches()) { + String scheme = m.group(2); + String authority = m.group(3); + String userinfo = m.group(5); + String host = m.group(6); + String port = m.group(8); + String path = m.group(9); + 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. *

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

Note that this method does not attempt to encode "=" and "&" + * characters in query parameter names and query parameter values because they cannot + * be parsed in a reliable way. Instead use: + *

+	 *  UriComponents uriComponents = UriComponentsBuilder.fromHttpUrl("/path?name={value}").buildAndExpand("a=b");
+	 *  String encodedUri = uriComponents.encode().toUriString();
+	 * 
* @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 + * @deprecated in favor of {@link UriComponentsBuilder}; see note about query param encoding */ public static String encodeHttpUrl(String httpUrl, String encoding) throws UnsupportedEncodingException { - UriComponents uriComponents = UriComponentsBuilder.fromHttpUrl(httpUrl).build(); - UriComponents encoded = uriComponents.encode(encoding); - return encoded.toUriString(); + 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"); + } } /** @@ -87,20 +158,48 @@ public abstract class UriUtils { * @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 + * @deprecated in favor of {@link UriComponentsBuilder} */ public static String encodeUriComponents(String scheme, String authority, String userInfo, String host, String port, String path, String query, String fragment, String encoding) throws UnsupportedEncodingException { - int portAsInt = (port != null ? Integer.parseInt(port) : -1); + 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)); - UriComponentsBuilder builder = UriComponentsBuilder.newInstance(); - builder.scheme(scheme).userInfo(userInfo).host(host).port(portAsInt); - builder.path(path).query(query).fragment(fragment); + if (query != null) { + sb.append('?'); + sb.append(encodeQuery(query, encoding)); + } - UriComponents encoded = builder.build().encode(encoding); + if (fragment != null) { + sb.append('#'); + sb.append(encodeFragment(fragment, encoding)); + } - return encoded.toUriString(); + return sb.toString(); } diff --git a/org.springframework.web/src/test/java/org/springframework/web/util/UriUtilsTests.java b/org.springframework.web/src/test/java/org/springframework/web/util/UriUtilsTests.java index 4ccd215f14..444b7ec43c 100644 --- a/org.springframework.web/src/test/java/org/springframework/web/util/UriUtilsTests.java +++ b/org.springframework.web/src/test/java/org/springframework/web/util/UriUtilsTests.java @@ -128,6 +128,9 @@ public class UriUtilsTests { assertEquals("Invalid encoded URI", "http://example.com/query=foo@bar", UriUtils.encodeUri("http://example.com/query=foo@bar", ENC)); + // SPR-8974 + assertEquals("http://example.org?format=json&url=http://another.com?foo=bar", + UriUtils.encodeUri("http://example.org?format=json&url=http://another.com?foo=bar", ENC)); } @Test