diff --git a/core/src/main/java/org/apache/struts2/dispatcher/mapper/RestfulActionMapper.java b/core/src/main/java/org/apache/struts2/dispatcher/mapper/RestfulActionMapper.java index 1709834a13..56b111aa34 100644 --- a/core/src/main/java/org/apache/struts2/dispatcher/mapper/RestfulActionMapper.java +++ b/core/src/main/java/org/apache/struts2/dispatcher/mapper/RestfulActionMapper.java @@ -23,12 +23,14 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.struts2.RequestUtils; +import org.apache.struts2.StrutsConstants; import org.apache.struts2.url.UrlDecoder; import javax.servlet.http.HttpServletRequest; import java.util.HashMap; import java.util.Map; import java.util.StringTokenizer; +import java.util.regex.Pattern; /** * Simple Restfull Action Mapper to support REST application @@ -41,11 +43,31 @@ public class RestfulActionMapper implements ActionMapper { private UrlDecoder decoder; + /** + * Matches action names allowed in the request URI, aligned with {@link DefaultActionMapper}. + */ + private Pattern allowedActionNames = Pattern.compile("[a-zA-Z0-9._!/\\-]*"); + + /** + * Action name used when the name extracted from the URI is not allowed, aligned with {@link DefaultActionMapper}. + */ + private String defaultActionName = "index"; + @Inject public void setDecoder(UrlDecoder decoder) { this.decoder = decoder; } + @Inject(value = StrutsConstants.STRUTS_ALLOWED_ACTION_NAMES, required = false) + public void setAllowedActionNames(String allowedActionNames) { + this.allowedActionNames = Pattern.compile(allowedActionNames); + } + + @Inject(value = StrutsConstants.STRUTS_DEFAULT_ACTION_NAME, required = false) + public void setDefaultActionName(String defaultActionName) { + this.defaultActionName = defaultActionName; + } + /* (non-Javadoc) * @see org.apache.struts2.dispatcher.mapper.ActionMapper#getMapping(javax.servlet.http.HttpServletRequest) */ @@ -57,7 +79,7 @@ public ActionMapping getMapping(HttpServletRequest request, ConfigurationManager return null; } - String actionName = uri.substring(1, nextSlash); + String actionName = cleanupActionName(uri.substring(1, nextSlash)); Map parameters = new HashMap<>(); try { StringTokenizer st = new StringTokenizer(uri.substring(nextSlash), "/"); @@ -96,6 +118,22 @@ public ActionMapping getMappingFromActionName(String actionName) { return new ActionMapping(actionName, null, null, null); } + /** + * Checks action name against the allowed pattern; if it does not match, returns the default action name. + * Mirrors {@link DefaultActionMapper#cleanupActionName(String)}. + * + * @param rawActionName action name extracted from the URI + * @return safe action name + */ + protected String cleanupActionName(final String rawActionName) { + if (allowedActionNames.matcher(rawActionName).matches()) { + return rawActionName; + } else { + LOG.warn("{} did not match allowed action names {} - default action {} will be used!", rawActionName, allowedActionNames, defaultActionName); + return defaultActionName; + } + } + /* (non-Javadoc) * @see org.apache.struts2.dispatcher.mapper.ActionMapper#getUriFromActionMapping(org.apache.struts2.dispatcher.mapper.ActionMapping) */ diff --git a/core/src/test/java/org/apache/struts2/dispatcher/mapper/RestfulActionMapperTest.java b/core/src/test/java/org/apache/struts2/dispatcher/mapper/RestfulActionMapperTest.java index 0ee3b1956b..ecc284cd0c 100644 --- a/core/src/test/java/org/apache/struts2/dispatcher/mapper/RestfulActionMapperTest.java +++ b/core/src/test/java/org/apache/struts2/dispatcher/mapper/RestfulActionMapperTest.java @@ -105,6 +105,22 @@ public void testGetMapping3() { assertEquals("europe", am.getParams().get("region")); } + public void testGetMappingRejectsActionNameWithDisallowedCharacters() { + StrutsMockHttpServletRequest request = new StrutsMockHttpServletRequest(); + request.setupGetServletPath("/%{1+1}/x"); + + ActionMapping am = mapper.getMapping(request, null); + assertEquals("index", am.getName()); + } + + public void testGetMappingAcceptsRegularActionName() { + StrutsMockHttpServletRequest request = new StrutsMockHttpServletRequest(); + request.setupGetServletPath("/my-app.action/x"); + + ActionMapping am = mapper.getMapping(request, null); + assertEquals("my-app.action", am.getName()); + } + protected void setUp() throws Exception { super.setUp(); mapper = new RestfulActionMapper();