diff --git a/core/src/main/java/org/apache/struts2/components/Component.java b/core/src/main/java/org/apache/struts2/components/Component.java
index fb0f2bd66..66769638b 100644
--- a/core/src/main/java/org/apache/struts2/components/Component.java
+++ b/core/src/main/java/org/apache/struts2/components/Component.java
@@ -338,12 +338,13 @@ public class Component {
*/
protected String determineActionURL(String action, String namespace, String method,
HttpServletRequest req, HttpServletResponse res, Map parameters, String scheme,
- boolean includeContext, boolean encodeResult) {
+ boolean includeContext, boolean encodeResult, boolean forceAddSchemeHostAndPort,
+ boolean escapeAmp) {
String finalAction = findString(action);
String finalNamespace = determineNamespace(namespace, getStack(), req);
ActionMapping mapping = new ActionMapping(finalAction, finalNamespace, method, parameters);
String uri = actionMapper.getUriFromActionMapping(mapping);
- return UrlHelper.buildUrl(uri, req, res, parameters, scheme, includeContext, encodeResult);
+ return UrlHelper.buildUrl(uri, req, res, parameters, scheme, includeContext, encodeResult, forceAddSchemeHostAndPort, escapeAmp);
}
/**
diff --git a/core/src/main/java/org/apache/struts2/components/URL.java b/core/src/main/java/org/apache/struts2/components/URL.java
index 204469062..a210d3645 100644
--- a/core/src/main/java/org/apache/struts2/components/URL.java
+++ b/core/src/main/java/org/apache/struts2/components/URL.java
@@ -53,6 +53,12 @@ import com.opensymphony.xwork2.util.ValueStack;
* additional request parameters.
*
* NOTE:
+ * By default request parameters will be separated using escaped ampersands (i.e., &).
+ * This is necessary for XHTML compliance, however, when using the URL generated by this tag
+ * with the <s:property> tag, the escapeAmp attribute should be used to disable
+ * ampersand escaping.
+ *
+ * NOTE:
* When includeParams is 'all' or 'get', the parameter defined in param tag will take
* precedence and will not be overriden if they exists in the parameter submitted. For
* example, in Example 3 below, if there is a id parameter in the url where the page this
@@ -68,16 +74,20 @@ import com.opensymphony.xwork2.util.ValueStack;
*
* - action (String) - (value or action choose either one, if both exist value takes precedence) action's name (alias)
-
*
- value (String) - (value or action choose either one, if both exist value takes precedence) the url itself
- * - scheme (String) - http scheme (http, https) default to the scheme this request is in
+ * - scheme (String) - http scheme (http, https) defaults to the scheme this request is in
* - namespace - action's namespace
- * - method (String) - action's method, default to execute()
- * - encode (Boolean) - url encode the generated url. Default is true
- * - includeParams (String) - The includeParams attribute may have the value 'none', 'get' or 'all'. Default is 'get'.
+ *
- method (String) - action's method name, defaults to 'execute'
+ * - encode (Boolean) - url encode the generated url. Defaults to 'true'.
+ * - includeParams (String) - The includeParams attribute may have the value 'none', 'get' or 'all'. Defaults to 'get'.
* none - include no parameters in the URL
* get - include only GET parameters in the URL (default)
* all - include both GET and POST parameters in the URL
*
- * - includeContext (Boolean) - determine wheather to include the web app context path. Default is true.
+ * - includeContext (Boolean) - Specifies whether to include the web app context path. Defaults to 'true'.
+ * - escapeAmp (Boolean) - Specifies whether to escape ampersand (&) to (&) or not. Defaults to 'true'.
+ * - portletMode (String) - The resulting portlet mode.
+ * - windowState (String) - The resulting portlet window state.
+ * - portletUrlType (String) - Specifies if this should be a portlet render or action URL.
*
*
*
@@ -135,6 +145,7 @@ public class URL extends Component {
protected String method;
protected boolean encode = true;
protected boolean includeContext = true;
+ protected boolean escapeAmp = true;
protected String portletMode;
protected String windowState;
protected String portletUrlType;
@@ -240,7 +251,7 @@ public class URL extends Component {
result = PortletUrlHelper.buildUrl(action, namespace, parameters, portletUrlType, portletMode, windowState);
}
else {
- result = determineActionURL(action, namespace, method, req, res, parameters, scheme, includeContext, encode);
+ result = determineActionURL(action, namespace, method, req, res, parameters, scheme, includeContext, encode, false, escapeAmp);
}
} else {
if(Dispatcher.getInstance().isPortletSupportActive() && PortletActionContext.isPortletRequest()) {
@@ -254,7 +265,7 @@ public class URL extends Component {
if (_value != null && _value.indexOf("?") > 0) {
_value = _value.substring(0, _value.indexOf("?"));
}
- result = UrlHelper.buildUrl(_value, req, res, parameters, scheme, includeContext, encode);
+ result = UrlHelper.buildUrl(_value, req, res, parameters, scheme, includeContext, encode, false, escapeAmp);
}
}
if ( anchor != null && anchor.length() > 0 ) {
@@ -338,6 +349,11 @@ public class URL extends Component {
this.anchor = anchor;
}
+ @StrutsTagAttribute(description="Specifies whether to escape ampersand (&) to (&) or not", type="Boolean", defaultValue="true")
+ public void setEscapeAmp(boolean escapeAmp) {
+ this.escapeAmp = escapeAmp;
+ }
+
/**
* Merge request parameters into current parameters. If a parameter is
diff --git a/core/src/main/java/org/apache/struts2/views/jsp/URLTag.java b/core/src/main/java/org/apache/struts2/views/jsp/URLTag.java
index f8fbb5381..6ec2c338f 100644
--- a/core/src/main/java/org/apache/struts2/views/jsp/URLTag.java
+++ b/core/src/main/java/org/apache/struts2/views/jsp/URLTag.java
@@ -44,6 +44,7 @@ public class URLTag extends ComponentTagSupport {
protected String method;
protected String encode;
protected String includeContext;
+ protected String escapeAmp;
protected String portletMode;
protected String windowState;
protected String portletUrlType;
@@ -74,6 +75,9 @@ public class URLTag extends ComponentTagSupport {
if (includeContext != null) {
url.setIncludeContext(Boolean.valueOf(includeContext).booleanValue());
}
+ if (escapeAmp != null) {
+ url.setEscapeAmp(Boolean.valueOf(escapeAmp).booleanValue());
+ }
}
public void setEncode(String encode) {
@@ -84,6 +88,10 @@ public class URLTag extends ComponentTagSupport {
this.includeContext = includeContext;
}
+ public void setEscapeAmp(String escapeAmp) {
+ this.escapeAmp = escapeAmp;
+ }
+
public void setIncludeParams(String name) {
includeParams = name;
}
diff --git a/core/src/main/java/org/apache/struts2/views/util/UrlHelper.java b/core/src/main/java/org/apache/struts2/views/util/UrlHelper.java
index a9aacd7c9..1070e98ad 100644
--- a/core/src/main/java/org/apache/struts2/views/util/UrlHelper.java
+++ b/core/src/main/java/org/apache/struts2/views/util/UrlHelper.java
@@ -91,6 +91,10 @@ public class UrlHelper {
}
public static String buildUrl(String action, HttpServletRequest request, HttpServletResponse response, Map params, String scheme, boolean includeContext, boolean encodeResult, boolean forceAddSchemeHostAndPort) {
+ return buildUrl(action, request, response, params, scheme, includeContext, encodeResult, forceAddSchemeHostAndPort, true);
+ }
+
+ public static String buildUrl(String action, HttpServletRequest request, HttpServletResponse response, Map params, String scheme, boolean includeContext, boolean encodeResult, boolean forceAddSchemeHostAndPort, boolean escapeAmp) {
StringBuffer link = new StringBuffer();
boolean changedScheme = false;
@@ -169,7 +173,11 @@ public class UrlHelper {
}
//if the action was not explicitly set grab the params from the request
- buildParametersString(params, link);
+ if (escapeAmp) {
+ buildParametersString(params, link);
+ } else {
+ buildParametersString(params, link, "&");
+ }
String result;
diff --git a/core/src/site/resources/tags/url.html b/core/src/site/resources/tags/url.html
index f26eca008..ab7fdb46f 100644
--- a/core/src/site/resources/tags/url.html
+++ b/core/src/site/resources/tags/url.html
@@ -51,6 +51,14 @@ Please do not edit it directly.
Boolean |
Whether to encode parameters |
+
+ | escapeAmp |
+ false |
+ true |
+ true |
+ Boolean |
+ Specifies whether to escape ampersand (&) to (&) or not |
+
| id |
false |
diff --git a/core/src/test/java/org/apache/struts2/views/jsp/URLTagTest.java b/core/src/test/java/org/apache/struts2/views/jsp/URLTagTest.java
index ac7992dcd..abc0b1aa0 100644
--- a/core/src/test/java/org/apache/struts2/views/jsp/URLTagTest.java
+++ b/core/src/test/java/org/apache/struts2/views/jsp/URLTagTest.java
@@ -348,6 +348,19 @@ public class URLTagTest extends AbstractUITagTest {
assertEquals("/team.action?section=team&company=acme+inc", writer.toString());
}
+ public void testRequestURIActionIncludeGetDoNotEscapeAmp() throws Exception {
+ request.setRequestURI("/public/about");
+ request.setQueryString("section=team&company=acme inc");
+
+ tag.setAction("team");
+ tag.setIncludeParams("get");
+ tag.setEscapeAmp("false");
+ tag.doStartTag();
+ tag.doEndTag();
+
+ assertEquals("/team.action?section=team&company=acme+inc", writer.toString());
+ }
+
public void testRequestURINoActionIncludeNone() throws Exception {
request.setRequestURI("/public/about");
request.setQueryString("section=team&company=acme inc");
diff --git a/core/src/test/java/org/apache/struts2/views/util/UrlHelperTest.java b/core/src/test/java/org/apache/struts2/views/util/UrlHelperTest.java
index 5d2cc5453..5062ebef3 100644
--- a/core/src/test/java/org/apache/struts2/views/util/UrlHelperTest.java
+++ b/core/src/test/java/org/apache/struts2/views/util/UrlHelperTest.java
@@ -144,6 +144,22 @@ public class UrlHelperTest extends StrutsTestCase {
assertEquals(expectedString, urlString);
}
+ public void testBuildUrlCorrectlyAddsDoNotEscapeAmp() {
+ String expectedString = "my.actionName?foo=bar&hello=world";
+ Mock mockHttpServletRequest = new Mock(HttpServletRequest.class);
+ mockHttpServletRequest.expectAndReturn("getScheme", "http");
+ Mock mockHttpServletResponse = new Mock(HttpServletResponse.class);
+ mockHttpServletResponse.expectAndReturn("encodeURL", expectedString, expectedString);
+
+ String actionName = "my.actionName";
+ TreeMap params = new TreeMap();
+ params.put("hello", "world");
+ params.put("foo", "bar");
+
+ String urlString = UrlHelper.buildUrl(actionName, (HttpServletRequest) mockHttpServletRequest.proxy(), (HttpServletResponse) mockHttpServletResponse.proxy(), params, null, true, true, false, false);
+ assertEquals(expectedString, urlString);
+ }
+
public void testBuildUrlWithStringArray() {
String expectedString = "my.actionName?foo=bar&hello=earth&hello=mars";
Mock mockHttpServletRequest = new Mock(HttpServletRequest.class);