Merge pull request #692 from apache/WW-5310-fragment

[WW-5310] Supports fragment in URL
This commit is contained in:
Lukasz Lenart
2023-06-28 08:55:46 +02:00
committed by GitHub
5 changed files with 158 additions and 39 deletions
@@ -159,10 +159,10 @@ public class ServletUrlRenderer implements UrlRenderer {
}
}
Map<String, Object> actionParams = null;
QueryStringParser.Result queryStringResult = queryStringParser.empty();
if (action != null && action.indexOf('?') > 0) {
String queryString = action.substring(action.indexOf('?') + 1);
actionParams = queryStringParser.parse(queryString, false);
queryStringResult = queryStringParser.parse(queryString);
action = action.substring(0, action.indexOf('?'));
}
@@ -176,7 +176,7 @@ public class ServletUrlRenderer implements UrlRenderer {
ActionMapping mapping = new ActionMapping(actionName, namespace, actionMethod, formComponent.parameters);
String result = urlHelper.buildUrl(formComponent.actionMapper.getUriFromActionMapping(mapping),
formComponent.request, formComponent.response, actionParams, scheme, formComponent.includeContext, true, false, false);
formComponent.request, formComponent.response, queryStringResult.getQueryParams(), scheme, formComponent.includeContext, true, false, false);
formComponent.addParameter("action", result);
// let's try to get the actual action class and name
@@ -213,7 +213,7 @@ public class ServletUrlRenderer implements UrlRenderer {
LOG.warn("No configuration found for the specified action: '{}' in namespace: '{}'. Form action defaulting to 'action' attribute's literal value.", actionName, namespace);
}
String result = urlHelper.buildUrl(action, formComponent.request, formComponent.response, actionParams, scheme, formComponent.includeContext, true);
String result = urlHelper.buildUrl(action, formComponent.request, formComponent.response, queryStringResult.getQueryParams(), scheme, formComponent.includeContext, true);
formComponent.addParameter("action", result);
// namespace: cut out anything between the start and the last /
@@ -291,7 +291,11 @@ public class ServletUrlRenderer implements UrlRenderer {
private void includeGetParameters(UrlProvider urlComponent) {
String query = extractQueryString(urlComponent);
mergeRequestParameters(urlComponent.getValue(), urlComponent.getParameters(), queryStringParser.parse(query, false));
QueryStringParser.Result result = queryStringParser.parse(query);
mergeRequestParameters(urlComponent.getValue(), urlComponent.getParameters(), result.getQueryParams());
if (!result.getQueryFragment().isEmpty()) {
urlComponent.setAnchor(result.getQueryFragment());
}
}
private String extractQueryString(UrlProvider urlComponent) {
@@ -339,7 +343,7 @@ public class ServletUrlRenderer implements UrlRenderer {
if (StringUtils.contains(value, "?")) {
String queryString = value.substring(value.indexOf('?') + 1);
mergedParams = queryStringParser.parse(queryString, false);
mergedParams = new LinkedHashMap<>(queryStringParser.parse(queryString).getQueryParams());
for (Map.Entry<String, ?> entry : contextParameters.entrySet()) {
if (!mergedParams.containsKey(entry.getKey())) {
mergedParams.put(entry.getKey(), entry.getValue());
@@ -33,7 +33,6 @@ import javax.servlet.RequestDispatcher;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.jsp.PageContext;
import java.util.Map;
/**
* <!-- START SNIPPET: description -->
@@ -140,9 +139,9 @@ public class ServletDispatcherResult extends StrutsResultSupport {
if (StringUtils.isNotEmpty(finalLocation) && finalLocation.indexOf('?') > 0) {
String queryString = finalLocation.substring(finalLocation.indexOf('?') + 1);
HttpParameters parameters = getParameters(invocation);
Map<String, Object> queryParams = queryStringParser.parse(queryString, true);
if (queryParams != null && !queryParams.isEmpty()) {
parameters = HttpParameters.create(queryParams).withParent(parameters).build();
QueryStringParser.Result queryParams = queryStringParser.parse(queryString);
if (!queryParams.isEmpty()) {
parameters = HttpParameters.create(queryParams.getQueryParams()).withParent(parameters).build();
invocation.getInvocationContext().withParameters(parameters);
// put to extraContext, see Dispatcher#createContextMap
invocation.getInvocationContext().getContextMap().put("parameters", parameters);
@@ -22,11 +22,47 @@ import java.io.Serializable;
import java.util.Map;
/**
* Used to parse Http Query String into a Map of parameters
* Used to parse Http Query String into a map of parameters with support for fragment
*
* @since Struts 6.1.0
*/
public interface QueryStringParser extends Serializable {
/**
* @deprecated since Struts 6.2.0, use {@link #parse(String)} instead
*/
@Deprecated
Map<String, Object> parse(String queryString, boolean forceValueArray);
/**
* @param queryString a query string to parse
* @return a {@link Result} of parsing the query string
* @since Struts 6.2.0
*/
Result parse(String queryString);
/**
* Return an empty {@link Result}
* @return empty result
*/
Result empty();
/**
* Represents result of parsing query string by implementation of {@link QueryStringParser}
*/
interface Result {
Result addParam(String name, String value);
Result withQueryFragment(String queryFragment);
Map<String, Object> getQueryParams();
String getQueryFragment();
boolean contains(String name);
boolean isEmpty();
}
}
@@ -43,13 +43,18 @@ public class StrutsQueryStringParser implements QueryStringParser {
@Override
public Map<String, Object> parse(String queryString, boolean forceValueArray) {
return parse(queryString).getQueryParams();
}
@Override
public Result parse(String queryString) {
if (StringUtils.isEmpty(queryString)) {
LOG.debug("Query String is empty, returning an empty map");
return Collections.emptyMap();
return this.empty();
}
Map<String, Object> queryParams = new LinkedHashMap<>();
String[] params = queryString.split("&");
Result queryParams = StrutsQueryStringParserResult.create();
String[] params = extractParams(queryString);
for (String param : params) {
if (StringUtils.isBlank(param)) {
LOG.debug("Param [{}] is blank, skipping", param);
@@ -64,32 +69,98 @@ public class StrutsQueryStringParser implements QueryStringParser {
} else {
paramName = param;
}
extractParam(paramName, paramValue, queryParams, forceValueArray);
queryParams = extractParam(paramName, paramValue, queryParams);
}
return queryParams;
return queryParams.withQueryFragment(extractFragment(queryString));
}
private void extractParam(String paramName, String paramValue, Map<String, Object> queryParams, boolean forceValueArray) {
@Override
public Result empty() {
return new StrutsQueryStringParserResult(Collections.emptyMap(), "");
}
private String[] extractParams(String queryString) {
LOG.trace("Extracting params from query string: {}", queryString);
String[] params = queryString.split("&");
int fragmentIndex = queryString.lastIndexOf("#");
if (fragmentIndex > -1) {
LOG.trace("Stripping fragment at index: {}", fragmentIndex);
params = queryString.substring(0, fragmentIndex).split("&");
}
return params;
}
private Result extractParam(String paramName, String paramValue, Result queryParams) {
String decodedParamName = decoder.decode(paramName, true);
String decodedParamValue = decoder.decode(paramValue, true);
return queryParams.addParam(decodedParamName, decodedParamValue);
}
if (queryParams.containsKey(decodedParamName) || forceValueArray) {
// WW-1619 append new param value to existing value(s)
Object currentParam = queryParams.get(decodedParamName);
if (currentParam instanceof String) {
queryParams.put(decodedParamName, new String[]{(String) currentParam, decodedParamValue});
} else {
String[] currentParamValues = (String[]) currentParam;
if (currentParamValues != null) {
List<String> paramList = new ArrayList<>(Arrays.asList(currentParamValues));
paramList.add(decodedParamValue);
queryParams.put(decodedParamName, paramList.toArray(new String[0]));
private String extractFragment(String queryString) {
int fragmentIndex = queryString.lastIndexOf("#");
if (fragmentIndex > -1) {
return queryString.substring(fragmentIndex + 1);
}
return "";
}
public static class StrutsQueryStringParserResult implements Result {
private final Map<String, Object> queryParams;
private String queryFragment;
static Result create() {
return new StrutsQueryStringParserResult(new LinkedHashMap<>(), "");
}
private StrutsQueryStringParserResult(Map<String, Object> queryParams, String queryFragment) {
this.queryParams = queryParams;
this.queryFragment = queryFragment;
}
public Result addParam(String name, String value) {
if (queryParams.containsKey(name)) {
// WW-1619 append new param value to existing value(s)
Object currentParam = queryParams.get(name);
if (currentParam instanceof String) {
queryParams.put(name, new String[]{(String) currentParam, value});
} else {
queryParams.put(decodedParamName, new String[]{decodedParamValue});
String[] currentParamValues = (String[]) currentParam;
if (currentParamValues != null) {
List<String> paramList = new ArrayList<>(Arrays.asList(currentParamValues));
paramList.add(value);
queryParams.put(name, paramList.toArray(new String[0]));
} else {
queryParams.put(name, new String[]{value});
}
}
} else {
queryParams.put(name, value);
}
} else {
queryParams.put(decodedParamName, decodedParamValue);
return this;
}
public Result withQueryFragment(String queryFragment) {
this.queryFragment = queryFragment;
return this;
}
public Map<String, Object> getQueryParams() {
return Collections.unmodifiableMap(queryParams);
}
public String getQueryFragment() {
return queryFragment;
}
public boolean contains(String name) {
return queryParams.containsKey(name);
}
public boolean isEmpty() {
return queryParams.isEmpty();
}
}
}
@@ -35,7 +35,7 @@ public class StrutsQueryStringParserTest {
@Test
public void testParseQuery() {
Map<String, Object> result = parser.parse("aaa=aaaval&bbb=bbbval&ccc=&%3Ca%22%3E=%3Cval%3E", false);
Map<String, Object> result = parser.parse("aaa=aaaval&bbb=bbbval&ccc=&%3Ca%22%3E=%3Cval%3E").getQueryParams();
assertEquals("aaaval", result.get("aaa"));
assertEquals("bbbval", result.get("bbb"));
@@ -45,7 +45,7 @@ public class StrutsQueryStringParserTest {
@Test
public void testParseQueryIntoArray() {
Map<String, Object> result = parser.parse("a=1&a=2&a=3", true);
Map<String, Object> result = parser.parse("a=1&a=2&a=3").getQueryParams();
Object actual = result.get("a");
assertThat(actual).isInstanceOf(String[].class);
@@ -54,7 +54,7 @@ public class StrutsQueryStringParserTest {
@Test
public void testParseEmptyQuery() {
Map<String, Object> result = parser.parse("", false);
Map<String, Object> result = parser.parse("").getQueryParams();
assertNotNull(result);
assertEquals(0, result.size());
@@ -62,7 +62,7 @@ public class StrutsQueryStringParserTest {
@Test
public void testParseNullQuery() {
Map<String, Object> result = parser.parse(null, false);
Map<String, Object> result = parser.parse(null).getQueryParams();
assertNotNull(result);
assertEquals(0, result.size());
@@ -70,7 +70,7 @@ public class StrutsQueryStringParserTest {
@Test
public void testDecodeSpacesInQueryString() {
Map<String, Object> queryParameters = parser.parse("name=value+with+space", false);
Map<String, Object> queryParameters = parser.parse("name=value+with+space").getQueryParams();
assertTrue(queryParameters.containsKey("name"));
assertEquals("value with space", queryParameters.get("name"));
@@ -78,7 +78,7 @@ public class StrutsQueryStringParserTest {
@Test
public void shouldProperlySplitParamsWithDoubleEqualSign() {
Map<String, Object> queryParameters = parser.parse("id1=n123=&id2=n3456", false);
Map<String, Object> queryParameters = parser.parse("id1=n123=&id2=n3456").getQueryParams();
assertTrue(queryParameters.containsKey("id1"));
assertTrue(queryParameters.containsKey("id2"));
@@ -88,7 +88,7 @@ public class StrutsQueryStringParserTest {
@Test
public void shouldHandleParamWithNoValue1() {
Map<String, Object> queryParameters = parser.parse("paramNoValue", false);
Map<String, Object> queryParameters = parser.parse("paramNoValue").getQueryParams();
assertTrue(queryParameters.containsKey("paramNoValue"));
assertEquals("", queryParameters.get("paramNoValue"));
@@ -96,13 +96,22 @@ public class StrutsQueryStringParserTest {
@Test
public void shouldHandleParamWithNoValue2() {
Map<String, Object> queryParameters = parser.parse("paramNoValue&param1=1234", false);
Map<String, Object> queryParameters = parser.parse("paramNoValue&param1=1234").getQueryParams();
assertTrue(queryParameters.containsKey("paramNoValue"));
assertTrue(queryParameters.containsKey("param1"));
assertEquals("1234", queryParameters.get("param1"));
}
@Test
public void shouldHandleParamAndFragment() {
QueryStringParser.Result queryParameters = parser.parse("param1=1234#test");
assertTrue(queryParameters.getQueryParams().containsKey("param1"));
assertEquals("1234", queryParameters.getQueryParams().get("param1"));
assertEquals("test", queryParameters.getQueryFragment());
}
@Before
public void setUp() throws Exception {
this.parser = new StrutsQueryStringParser(new StrutsUrlDecoder());