diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/tags/UrlTag.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/tags/UrlTag.java index 5bc0c0a0d0..9c4d71a12b 100644 --- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/tags/UrlTag.java +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/tags/UrlTag.java @@ -16,6 +16,7 @@ package org.springframework.web.servlet.tags; +import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.net.URLEncoder; @@ -23,6 +24,7 @@ import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Set; + import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.jsp.JspException; @@ -65,7 +67,7 @@ import org.springframework.web.util.TagUtils; * <spring:param name="variableName" value="more than JSTL c:url" /> * </spring:url> * Results in: - * /currentApplicationContext/url/path/more+than+JSTL+c%3Aurl + * /currentApplicationContext/url/path/more%20than%20JSTL%20c%3Aurl * * @author Scott Andrews * @since 3.0 @@ -269,7 +271,6 @@ public class UrlTag extends HtmlEscapingAwareTag implements ParamAware { */ protected String replaceUriTemplateParams(String uri, List params, Set usedParams) throws JspException { - for (Param param : params) { String template = URL_TEMPLATE_DELIMITER_PREFIX + param.getName() + URL_TEMPLATE_DELIMITER_SUFFIX; if (uri.contains(template)) { @@ -281,7 +282,11 @@ public class UrlTag extends HtmlEscapingAwareTag implements ParamAware { } /** - * URL-encode the providedSstring using the character encoding for the response. + * URL-encode the provided String using the character encoding for the response. + *

This method will not URL-encode according to the + * application/x-www-form-urlencoded MIME format. Spaces will + * encoded as regular character instead of +. In UTF-8 + * a space encodes to %20. * @param value the value to encode * @return the URL encoded value * @throws JspException if the character encoding is invalid @@ -291,13 +296,41 @@ public class UrlTag extends HtmlEscapingAwareTag implements ParamAware { return null; } try { - return URLEncoder.encode(value, pageContext.getResponse().getCharacterEncoding()); + String encoding = pageContext.getResponse().getCharacterEncoding(); + String formUrlEncodedValue = URLEncoder.encode(value, encoding); + if (!formUrlEncodedValue.contains("+")) { + return formUrlEncodedValue; + } + String spaceEncoding = this.urlEncode(' ', encoding); + return formUrlEncodedValue.replace("+", spaceEncoding); } catch (UnsupportedEncodingException ex) { throw new JspException(ex); } } + /* + * based on URLCodec from Apache Commons Codec + */ + protected String urlEncode(Character c, String enc) throws UnsupportedEncodingException { + if (c == null) { + return null; + } + byte[] bytes = c.toString().getBytes(enc); + StringBuilder builder = new StringBuilder(); + for (int i = 0; i < bytes.length; i++) { + int b = bytes[i]; + if (b < 0) { + b = 256 + b; + } + char hex1 = Character.toUpperCase(Character.forDigit((b >> 4) & 0xF, 16)); + char hex2 = Character.toUpperCase(Character.forDigit(b & 0xF, 16)); + builder.append('%'); + builder.append(hex1); + builder.append(hex2); + } + return builder.toString(); + } /** * Internal enum that classifies URLs by type. diff --git a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/tags/UrlTagTests.java b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/tags/UrlTagTests.java index 9477c7947c..e60ea953a4 100644 --- a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/tags/UrlTagTests.java +++ b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/tags/UrlTagTests.java @@ -16,7 +16,9 @@ package org.springframework.web.servlet.tags; +import java.io.UnsupportedEncodingException; import java.lang.reflect.Method; +import java.net.URLDecoder; import java.util.HashSet; import java.util.LinkedList; import java.util.List; @@ -108,7 +110,7 @@ public class UrlTagTests extends AbstractTagTests { tag.doEndTag(); - assertEquals("url/path?n+me=v%26l%3De&name=value2", context + assertEquals("url/path?n%20me=v%26l%3De&name=value2", context .getAttribute("var")); } @@ -131,7 +133,7 @@ public class UrlTagTests extends AbstractTagTests { tag.doEndTag(); - assertEquals("url/path?n+me=v%26l%3De&name=value2", context + assertEquals("url/path?n%20me=v%26l%3De&name=value2", context .getAttribute("var")); } @@ -154,7 +156,7 @@ public class UrlTagTests extends AbstractTagTests { tag.doEndTag(); - assertEquals("url/path?n+me=v%26l%3De&name=value2", context + assertEquals("url/path?n%20me=v%26l%3De&name=value2", context .getAttribute("var")); } @@ -177,7 +179,7 @@ public class UrlTagTests extends AbstractTagTests { tag.doEndTag(); - assertEquals("url\\/path?n+me=v%26l%3De&name=value2", context + assertEquals("url\\/path?n%20me=v%26l%3De&name=value2", context .getAttribute("var")); } @@ -201,7 +203,7 @@ public class UrlTagTests extends AbstractTagTests { tag.doEndTag(); - assertEquals("url\\/path?n+me=v%26l%3De&name=value2", context + assertEquals("url\\/path?n%20me=v%26l%3De&name=value2", context .getAttribute("var")); } @@ -322,7 +324,7 @@ public class UrlTagTests extends AbstractTagTests { String queryString = tag.createQueryString(params, usedParams, true); - assertEquals("?n+me=v%26l%3De&name=value2", queryString); + assertEquals("?n%20me=v%26l%3De&name=value2", queryString); } public void testCreateQueryStringParamNullName() throws JspException { @@ -425,7 +427,7 @@ public class UrlTagTests extends AbstractTagTests { String uri = tag.replaceUriTemplateParams("url/{name}", params, usedParams); - assertEquals("url/v+lue", uri); + assertEquals("url/v%20lue", uri); assertEquals(1, usedParams.size()); assertTrue(usedParams.contains("name")); } @@ -509,7 +511,7 @@ public class UrlTagTests extends AbstractTagTests { String uri = invokeCreateUrl(tag); - assertEquals("url/path?name=value&n+me=v+lue", uri); + assertEquals("url/path?name=value&n%20me=v%20lue", uri); } public void testCreateUrlWithTemplateParams() throws JspException { @@ -529,7 +531,7 @@ public class UrlTagTests extends AbstractTagTests { String uri = invokeCreateUrl(tag); - assertEquals("url/value?n+me=v+lue", uri); + assertEquals("url/value?n%20me=v%20lue", uri); } public void testCreateUrlWithParamAndExsistingQueryString() @@ -549,7 +551,14 @@ public class UrlTagTests extends AbstractTagTests { } public void testUrlEncode() throws JspException { - assertEquals("my+name", tag.urlEncode("my name")); + assertEquals("my%20name%2Bis", tag.urlEncode("my name+is")); + } + + public void testUrlEncode_character() throws UnsupportedEncodingException { + assertEquals("%20", tag.urlEncode(' ', "UTF-8")); + assertEquals(" ", URLDecoder.decode("%20", "UTF-8")); + assertEquals("%FE%FF%00%20", tag.urlEncode(' ', "UTF-16")); + assertEquals("%40", tag.urlEncode(' ', "IBM-Thai")); } public void testUrlEncodeNull() throws JspException {