diff --git a/core/src/main/java/org/apache/struts2/StrutsConstants.java b/core/src/main/java/org/apache/struts2/StrutsConstants.java index 8a68109213..eb8fe6fcda 100644 --- a/core/src/main/java/org/apache/struts2/StrutsConstants.java +++ b/core/src/main/java/org/apache/struts2/StrutsConstants.java @@ -252,4 +252,7 @@ public final class StrutsConstants { public static final String STRUTS_EXPRESSION_PARSER = "struts.expression.parser"; + /** actions names' whitelist **/ + public static final String STRUTS_ALLOWED_ACTION_NAMES = "struts.allowed.action.names"; + } diff --git a/core/src/main/java/org/apache/struts2/dispatcher/mapper/DefaultActionMapper.java b/core/src/main/java/org/apache/struts2/dispatcher/mapper/DefaultActionMapper.java index 6e7d8a6be7..bac8310b34 100644 --- a/core/src/main/java/org/apache/struts2/dispatcher/mapper/DefaultActionMapper.java +++ b/core/src/main/java/org/apache/struts2/dispatcher/mapper/DefaultActionMapper.java @@ -35,12 +35,7 @@ import org.apache.struts2.util.PrefixTrie; import javax.servlet.http.HttpServletRequest; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; +import java.util.*; /** * @@ -171,6 +166,7 @@ public class DefaultActionMapper implements ActionMapper { protected boolean allowSlashesInActionNames = false; protected boolean alwaysSelectFullNamespace = false; protected PrefixTrie prefixTrie = null; + protected String allowedActionNames = "[a-z]*[A-Z]*[0-9]*[.\\-_!/]*"; protected List extensions = new ArrayList() {{ add("action"); @@ -260,6 +256,11 @@ public void setAlwaysSelectFullNamespace(String val) { this.alwaysSelectFullNamespace = "true".equals(val); } + @Inject(value = StrutsConstants.STRUTS_ALLOWED_ACTION_NAMES, required = false) + public void setAllowedActionNames(String allowedActionNames) { + this.allowedActionNames = allowedActionNames; + } + @Inject public void setContainer(Container container) { this.container = container; @@ -417,7 +418,25 @@ protected void parseNameAndNamespace(String uri, ActionMapping mapping, Configur } mapping.setNamespace(namespace); - mapping.setName(name); + mapping.setName(cleanupActionName(name)); + } + + /** + * Cleans up action name from suspicious characters + * + * @param rawActionName action name extracted from URI + * @return safe action name + */ + protected String cleanupActionName(final String rawActionName) { + if (rawActionName.matches(allowedActionNames)) { + return rawActionName; + } else { + String cleanActionName = rawActionName; + for(String chunk : rawActionName.split(allowedActionNames)) { + cleanActionName = cleanActionName.replace(chunk, ""); + } + return cleanActionName; + } } /** diff --git a/core/src/test/java/org/apache/struts2/dispatcher/mapper/DefaultActionMapperTest.java b/core/src/test/java/org/apache/struts2/dispatcher/mapper/DefaultActionMapperTest.java index e2513b2cae..c6ecd4bfac 100644 --- a/core/src/test/java/org/apache/struts2/dispatcher/mapper/DefaultActionMapperTest.java +++ b/core/src/test/java/org/apache/struts2/dispatcher/mapper/DefaultActionMapperTest.java @@ -747,4 +747,23 @@ public void testSetExtension() throws Exception { } + public void testAllowedActionNames() throws Exception { + DefaultActionMapper mapper = new DefaultActionMapper(); + + String actionName = "action"; + assertEquals(actionName, mapper.cleanupActionName(actionName)); + + actionName = "${action}"; + assertEquals("action", mapper.cleanupActionName(actionName)); + + actionName = "${${%{action}}}"; + assertEquals("action", mapper.cleanupActionName(actionName)); + + actionName = "${#foo='action',#foo}"; + assertEquals("fooactionfoo", mapper.cleanupActionName(actionName)); + + actionName = "test-action"; + assertEquals("test-action", mapper.cleanupActionName(actionName)); + } + }