WW-4514 Fixes building query string with empty parameters

This commit is contained in:
Lukasz Lenart
2022-11-04 07:58:47 +01:00
parent 054f1f4cdb
commit 6330bb1ce3
4 changed files with 135 additions and 113 deletions
@@ -31,7 +31,6 @@ import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@@ -170,9 +169,9 @@ public class DefaultUrlHelper implements UrlHelper {
//if the action was not explicitly set grab the params from the request
if (escapeAmp) {
buildParametersString(params, link, AMP, true);
buildParametersString(params, link, AMP);
} else {
buildParametersString(params, link, "&", true);
buildParametersString(params, link, "&");
}
String result = link.toString();
@@ -198,62 +197,69 @@ public class DefaultUrlHelper implements UrlHelper {
}
public void buildParametersString(Map<String, Object> params, StringBuilder link, String paramSeparator) {
buildParametersString(params, link, paramSeparator, true);
}
public void buildParametersString(Map<String, Object> params, StringBuilder link, String paramSeparator, boolean encode) {
if ((params != null) && (params.size() > 0)) {
if (!link.toString().contains("?")) {
link.append("?");
} else {
link.append(paramSeparator);
}
StringBuilder queryString = new StringBuilder();
// Set params
Iterator<Map.Entry<String, Object>> iter = params.entrySet().iterator();
while (iter.hasNext()) {
Map.Entry<String, Object> entry = iter.next();
for (Map.Entry<String, Object> entry : params.entrySet()) {
String name = entry.getKey();
Object value = entry.getValue();
if (value instanceof Iterable) {
for (Iterator<?> iterator = ((Iterable<?>) value).iterator(); iterator.hasNext(); ) {
Object paramValue = iterator.next();
link.append(buildParameterSubstring(name, paramValue != null ? paramValue.toString() : StringUtils.EMPTY, encode));
if (iterator.hasNext()) {
link.append(paramSeparator);
}
for (Object o : (Iterable<?>) value) {
appendParameterSubstring(queryString, paramSeparator, name, o);
}
} else if (value instanceof Object[]) {
Object[] array = (Object[]) value;
for (int i = 0; i < array.length; i++) {
Object paramValue = array[i];
link.append(buildParameterSubstring(name, paramValue != null ? paramValue.toString() : StringUtils.EMPTY, encode));
if (i < array.length - 1) {
link.append(paramSeparator);
}
for (Object o : array) {
appendParameterSubstring(queryString, paramSeparator, name, o);
}
} else {
link.append(buildParameterSubstring(name, value != null ? value.toString() : StringUtils.EMPTY, encode));
}
if (iter.hasNext()) {
link.append(paramSeparator);
appendParameterSubstring(queryString, paramSeparator, name, value);
}
}
if (queryString.length() > 0) {
if (!link.toString().contains("?")) {
link.append("?");
} else {
link.append(paramSeparator);
}
link.append(queryString);
}
}
}
/**
* Builds parameters assigned to url - a query string
* @param params a set of params to assign
* @param link a based url
* @param paramSeparator separator used
* @param encode if true, parameters will be encoded - ignored
* @deprecated since Struts 6.1.0, use {@link #buildParametersString(Map, StringBuilder, String)}
*/
@Deprecated
public void buildParametersString(Map<String, Object> params, StringBuilder link, String paramSeparator, boolean encode) {
buildParametersString(params, link, paramSeparator);
}
protected boolean isValidScheme(String scheme) {
return HTTP_PROTOCOL.equals(scheme) || HTTPS_PROTOCOL.equals(scheme);
}
private String buildParameterSubstring(String name, String value, boolean encode) {
String encodedName = encode ? encoder.encode(name) : name;
String encodedValue = encode ? encoder.encode(value) : value;
return encodedName + '=' + encodedValue;
private void appendParameterSubstring(StringBuilder queryString, String paramSeparator, String name, Object value) {
if (queryString.length() > 0) {
queryString.append(paramSeparator);
}
String encodedName = encoder.encode(name);
queryString.append(encodedName);
queryString.append('=');
if (value != null) {
String encodedValue = encoder.encode(value.toString());
queryString.append(encodedValue);
}
}
/**
@@ -18,6 +18,7 @@
*/
package org.apache.struts2.url;
import org.apache.struts2.StrutsConstants;
import org.junit.Before;
import org.junit.Test;
@@ -105,6 +106,13 @@ public class StrutsUrlDecoderTest {
assertEquals("xxxxA", result);
}
@Test
public void testDecoding() {
String result = decoder.decode("%E6%96%B0%E8%81%9E");
assertEquals(result, "\u65b0\u805e");
}
@Before
public void setUp() throws Exception {
this.decoder = new StrutsUrlDecoder();
@@ -82,6 +82,13 @@ public class StrutsUrlEncoderTest {
assertEquals("%25xxxx", result);
}
@Test
public void testEncoding() {
String result = encoder.encode("\u65b0\u805e");
assertEquals(result, "%E6%96%B0%E8%81%9E");
}
@Before
public void setUp() throws Exception {
this.encoder = new StrutsUrlEncoder();
@@ -18,36 +18,31 @@
*/
package org.apache.struts2.views.util;
import com.mockobjects.dynamic.Mock;
import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.inject.Container;
import com.opensymphony.xwork2.inject.Scope.Strategy;
import org.apache.struts2.StrutsInternalTestCase;
import org.apache.struts2.url.StrutsUrlDecoder;
import org.apache.struts2.url.StrutsUrlEncoder;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.struts2.StrutsConstants;
import org.apache.struts2.StrutsInternalTestCase;
import com.mockobjects.dynamic.Mock;
import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.inject.Container;
import com.opensymphony.xwork2.inject.Scope.Strategy;
import org.apache.struts2.url.StrutsUrlDecoder;
import org.apache.struts2.url.StrutsUrlEncoder;
/**
* Test case for DefaultUrlHelper.
*
*/
public class DefaultUrlHelperTest extends StrutsInternalTestCase {
private StubContainer stubContainer;
private DefaultUrlHelper urlHelper;
public void testForceAddSchemeHostAndPort() throws Exception {
public void testForceAddSchemeHostAndPort() {
String expectedUrl = "http://localhost/contextPath/path1/path2/myAction.action";
Mock mockHttpServletRequest = new Mock(HttpServletRequest.class);
@@ -64,7 +59,7 @@ public class DefaultUrlHelperTest extends StrutsInternalTestCase {
mockHttpServletRequest.verify();
}
public void testDoNotForceAddSchemeHostAndPort() throws Exception {
public void testDoNotForceAddSchemeHostAndPort() {
String expectedUrl = "/contextPath/path1/path2/myAction.action";
Mock mockHttpServletRequest = new Mock(HttpServletRequest.class);
@@ -80,7 +75,7 @@ public class DefaultUrlHelperTest extends StrutsInternalTestCase {
assertEquals(expectedUrl, result);
}
public void testForceAddSchemeHostAndPortWithNonStandardPort() throws Exception {
public void testForceAddSchemeHostAndPortWithNonStandardPort() {
String expectedUrl = "http://localhost:9090/contextPath/path1/path2/myAction.action";
Mock mockHttpServletRequest = new Mock(HttpServletRequest.class);
@@ -97,39 +92,64 @@ public class DefaultUrlHelperTest extends StrutsInternalTestCase {
mockHttpServletRequest.verify();
}
public void testBuildParametersStringWithUrlHavingSomeExistingParameters() throws Exception {
public void testBuildParametersStringWithUrlHavingSomeExistingParameters() {
String expectedUrl = "http://localhost:8080/myContext/myPage.jsp?initParam=initValue&amp;param1=value1&amp;param2=value2&amp;param3%22%3CsCrIpT%3Ealert%281%29%3B%3C%2FsCrIpT%3E=value3";
Map parameters = new LinkedHashMap();
Map<String, Object> parameters = new LinkedHashMap<>();
parameters.put("param1", "value1");
parameters.put("param2", "value2");
parameters.put("param3\"<sCrIpT>alert(1);</sCrIpT>","value3");
parameters.put("param3\"<sCrIpT>alert(1);</sCrIpT>", "value3");
StringBuilder url = new StringBuilder("http://localhost:8080/myContext/myPage.jsp?initParam=initValue");
urlHelper.buildParametersString(parameters, url, UrlHelper.AMP);
assertEquals(
expectedUrl, url.toString());
expectedUrl, url.toString());
}
public void testBuildParametersStringWithJavaScriptInjected() throws Exception {
public void testBuildParametersStringWithJavaScriptInjected() {
String expectedUrl = "http://localhost:8080/myContext/myPage.jsp?initParam=initValue&amp;param1=value1&amp;param2=value2&amp;param3%22%3Cscript+type%3D%22text%2Fjavascript%22%3Ealert%281%29%3B%3C%2Fscript%3E=value3";
Map parameters = new LinkedHashMap();
Map<String, Object> parameters = new LinkedHashMap<>();
parameters.put("param1", "value1");
parameters.put("param2", "value2");
parameters.put("param3\"<script type=\"text/javascript\">alert(1);</script>","value3");
parameters.put("param3\"<script type=\"text/javascript\">alert(1);</script>", "value3");
StringBuilder url = new StringBuilder("http://localhost:8080/myContext/myPage.jsp?initParam=initValue");
urlHelper.buildParametersString(parameters, url, UrlHelper.AMP);
assertEquals(
expectedUrl, url.toString());
expectedUrl, url.toString());
}
public void testForceAddNullSchemeHostAndPort() throws Exception {
public void testBuildParametersStringWithEmptyListParameters() {
String expectedUrl = "https://www.nowhere.com/myworld.html";
Map<String, Object> parameters = new LinkedHashMap<>();
parameters.put("param1", new String[]{});
parameters.put("param2", new ArrayList<>());
StringBuilder url = new StringBuilder("https://www.nowhere.com/myworld.html");
urlHelper.buildParametersString(parameters, url, UrlHelper.AMP);
assertEquals(expectedUrl, url.toString());
}
public void testBuildParametersStringWithListParameters() {
String expectedUrl = "https://www.nowhere.com/myworld.html?param1=x&param2=y&param2=z";
Map<String, Object> parameters = new LinkedHashMap<>();
parameters.put("param1", new String[]{"x"});
parameters.put("param2", new ArrayList<String>() {
{
add("y");
add("z");
}
});
StringBuilder url = new StringBuilder("https://www.nowhere.com/myworld.html");
urlHelper.buildParametersString(parameters, url, "&");
assertEquals(expectedUrl, url.toString());
}
public void testForceAddNullSchemeHostAndPort() {
String expectedUrl = "http://localhost/contextPath/path1/path2/myAction.action";
Mock mockHttpServletRequest = new Mock(HttpServletRequest.class);
@@ -144,14 +164,14 @@ public class DefaultUrlHelperTest extends StrutsInternalTestCase {
expectedUrl);
String result = urlHelper.buildUrl("/path1/path2/myAction.action",
(HttpServletRequest) mockHttpServletRequest.proxy(),
(HttpServletResponse) mockHttpServletResponse.proxy(), null,
null, true, true, true);
(HttpServletRequest) mockHttpServletRequest.proxy(),
(HttpServletResponse) mockHttpServletResponse.proxy(), null,
null, true, true, true);
assertEquals(expectedUrl, result);
mockHttpServletRequest.verify();
}
public void testForceAddNullSchemeHostAndPort2() throws Exception {
public void testForceAddNullSchemeHostAndPort2() {
String expectedUrl = "http://localhost:8080/contextPath/path1/path2/myAction.action";
Mock mockHttpServletRequest = new Mock(HttpServletRequest.class);
@@ -166,9 +186,9 @@ public class DefaultUrlHelperTest extends StrutsInternalTestCase {
expectedUrl);
String result = urlHelper.buildUrl("/path1/path2/myAction.action",
(HttpServletRequest) mockHttpServletRequest.proxy(),
(HttpServletResponse) mockHttpServletResponse.proxy(), null,
null, true, true, true);
(HttpServletRequest) mockHttpServletRequest.proxy(),
(HttpServletResponse) mockHttpServletResponse.proxy(), null,
null, true, true, true);
assertEquals(expectedUrl, result);
mockHttpServletRequest.verify();
}
@@ -184,7 +204,7 @@ public class DefaultUrlHelperTest extends StrutsInternalTestCase {
mockHttpServletResponse.expectAndReturn("encodeURL", expectedUrl, expectedUrl);
String actualUrl = urlHelper.buildUrl(expectedUrl, (HttpServletRequest) mockHttpServletRequest.proxy(),
(HttpServletResponse) mockHttpServletResponse.proxy(), new HashMap());
(HttpServletResponse) mockHttpServletResponse.proxy(), new HashMap<>());
assertEquals(expectedUrl, actualUrl);
}
@@ -199,7 +219,7 @@ public class DefaultUrlHelperTest extends StrutsInternalTestCase {
mockHttpServletResponse.expectAndReturn("encodeURL", expectedString, expectedString);
String actionName = "my.actionName";
TreeMap params = new TreeMap();
TreeMap<String, Object> params = new TreeMap<>();
params.put("hello", "world");
params.put("foo", "bar");
@@ -218,7 +238,7 @@ public class DefaultUrlHelperTest extends StrutsInternalTestCase {
mockHttpServletResponse.expectAndReturn("encodeURL", expectedString, expectedString);
String actionName = "my.actionName";
TreeMap params = new TreeMap();
TreeMap<String, Object> params = new TreeMap<>();
params.put("hello", "world");
params.put("foo", "bar");
@@ -234,7 +254,7 @@ public class DefaultUrlHelperTest extends StrutsInternalTestCase {
mockHttpServletResponse.expectAndReturn("encodeURL", expectedString, expectedString);
String actionName = "my.actionName";
TreeMap params = new TreeMap();
TreeMap<String, Object> params = new TreeMap<>();
params.put("hello", new String[]{"earth", "mars"});
params.put("foo", "bar");
@@ -259,7 +279,7 @@ public class DefaultUrlHelperTest extends StrutsInternalTestCase {
mockHttpServletResponse.expectAndReturn("encodeURL", expectedString, expectedString);
String actionName = "/MyAction.action";
TreeMap params = new TreeMap();
TreeMap<String, Object> params = new TreeMap<>();
params.put("hello", new String[]{"earth", "mars"});
params.put("foo", "bar");
@@ -284,7 +304,7 @@ public class DefaultUrlHelperTest extends StrutsInternalTestCase {
mockHttpServletResponse.expectAndReturn("encodeURL", expectedString, expectedString);
String actionName = "/MyAction.action";
TreeMap params = new TreeMap();
TreeMap<String, Object> params = new TreeMap<>();
params.put("hello", new String[]{"earth", "mars"});
params.put("foo", "bar");
@@ -313,7 +333,7 @@ public class DefaultUrlHelperTest extends StrutsInternalTestCase {
mockHttpServletResponse.expectAndReturn("encodeURL", expectedString, expectedString);
String actionName = "/MyAction.action";
TreeMap params = new TreeMap();
TreeMap<String, Object> params = new TreeMap<>();
params.put("hello", new String[]{"earth", "mars"});
params.put("foo", "bar");
@@ -342,7 +362,7 @@ public class DefaultUrlHelperTest extends StrutsInternalTestCase {
mockHttpServletResponse.expectAndReturn("encodeURL", expectedString, expectedString);
String actionName = "/MyAction.action";
TreeMap params = new TreeMap();
TreeMap<String, Object> params = new TreeMap<>();
params.put("hello", new String[]{"earth", "mars"});
params.put("foo", "bar");
@@ -371,15 +391,15 @@ public class DefaultUrlHelperTest extends StrutsInternalTestCase {
mockHttpServletResponse.expectAndReturn("encodeURL", expectedString, expectedString);
String actionName = "promo.html";
Map params = new TreeMap();
TreeMap<String, Object> params = new TreeMap<>();
String urlString = urlHelper.buildUrl(actionName, (HttpServletRequest) mockHttpServletRequest.proxy(), (HttpServletResponse) mockHttpServletResponse.proxy(), params, "https", true, true);
assertEquals(expectedString, urlString);
}
public void testParseQuery() throws Exception {
Map result = urlHelper.parseQueryString("aaa=aaaval&bbb=bbbval&ccc=&%3Ca%22%3E=%3Cval%3E", false);
public void testParseQuery() {
Map<String, Object> result = urlHelper.parseQueryString("aaa=aaaval&bbb=bbbval&ccc=&%3Ca%22%3E=%3Cval%3E", false);
assertEquals(result.get("aaa"), "aaaval");
assertEquals(result.get("bbb"), "bbbval");
@@ -387,38 +407,21 @@ public class DefaultUrlHelperTest extends StrutsInternalTestCase {
assertEquals(result.get("<a\">"), "<val>");
}
public void testParseEmptyQuery() throws Exception {
Map result = urlHelper.parseQueryString("", false);
public void testParseEmptyQuery() {
Map<String, Object> result = urlHelper.parseQueryString("", false);
assertNotNull(result);
assertEquals(result.size(), 0);
}
public void testParseNullQuery() throws Exception {
Map result = urlHelper.parseQueryString(null, false);
public void testParseNullQuery() {
Map<String, Object> result = urlHelper.parseQueryString(null, false);
assertNotNull(result);
assertEquals(result.size(), 0);
}
public void testEncode() throws Exception {
setProp(StrutsConstants.STRUTS_I18N_ENCODING, "UTF-8");
String result = urlHelper.encode("\u65b0\u805e");
String expectedResult = "%E6%96%B0%E8%81%9E";
assertEquals(result, expectedResult);
}
public void testDecode() throws Exception {
setProp(StrutsConstants.STRUTS_I18N_ENCODING, "UTF-8");
String result = urlHelper.decode("%E6%96%B0%E8%81%9E");
String expectedResult = "\u65b0\u805e";
assertEquals(result, expectedResult);
}
public void testDecodeSpacesInQueryString() throws Exception {
public void testDecodeSpacesInQueryString() {
Map<String, Object> queryParameters = urlHelper.parseQueryString("name=value+with+space", false);
assertTrue(queryParameters.containsKey("name"));
@@ -428,18 +431,14 @@ public class DefaultUrlHelperTest extends StrutsInternalTestCase {
public void setUp() throws Exception {
super.setUp();
stubContainer = new StubContainer(container);
StubContainer stubContainer = new StubContainer(container);
ActionContext.getContext().withContainer(stubContainer);
urlHelper = new DefaultUrlHelper();
urlHelper.setEncoder(new StrutsUrlEncoder());
urlHelper.setDecoder(new StrutsUrlDecoder());
}
private void setProp(String key, String val) {
stubContainer.overrides.put(key, val);
}
class StubContainer implements Container {
static class StubContainer implements Container {
Container parent;
@@ -448,7 +447,9 @@ public class DefaultUrlHelperTest extends StrutsInternalTestCase {
this.parent = parent;
}
public Map<String, Object> overrides = new HashMap<String,Object>();
public Map<String, Object> overrides = new HashMap<>();
@SuppressWarnings("unchecked")
public <T> T getInstance(Class<T> type, String name) {
if (String.class.isAssignableFrom(type) && overrides.containsKey(name)) {
return (T) overrides.get(name);