STR-3169: Do not match when recursive substitutions are detected
git-svn-id: https://svn.apache.org/repos/asf/struts/struts1/trunk@728474 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/core/src/main/java/org/apache/struts/config/ActionConfigMatcher.java b/core/src/main/java/org/apache/struts/config/ActionConfigMatcher.java
index 3d22251..5c75b94 100755
--- a/core/src/main/java/org/apache/struts/config/ActionConfigMatcher.java
+++ b/core/src/main/java/org/apache/struts/config/ActionConfigMatcher.java
@@ -123,9 +123,18 @@
+ m.getActionConfig().getPath() + "'");
}
- config =
- convertActionConfig(path,
- (ActionConfig) m.getActionConfig(), vars);
+ try {
+ config =
+ convertActionConfig(path,
+ (ActionConfig) m.getActionConfig(), vars);
+ } catch (IllegalStateException e) {
+ log.warn("Path matches pattern '"
+ + m.getActionConfig().getPath() + "' but is "
+ + "incompatible with the matching config due "
+ + "to recursive substitution: "
+ + path);
+ config = null;
+ }
}
}
}
@@ -221,6 +230,8 @@
* @param orig The original properties set with placehold values
* @param props The target properties to store the processed values
* @param vars A Map of wildcard-matched strings
+ * @throws IllegalStateException if a placeholder substitution is
+ * impossible due to recursion
*/
protected void replaceProperties(Properties orig, Properties props, Map vars) {
Map.Entry entry = null;
@@ -239,6 +250,8 @@
* @param val The value to convert
* @param vars A Map of wildcard-matched strings
* @return The new value
+ * @throws IllegalStateException if a placeholder substitution is
+ * impossible due to recursion
*/
protected String convertParam(String val, Map vars) {
if (val == null) {
@@ -250,16 +263,23 @@
Map.Entry entry;
StringBuffer key = new StringBuffer("{0}");
StringBuffer ret = new StringBuffer(val);
- String keyTmp;
+ String keyStr;
int x;
for (Iterator i = vars.entrySet().iterator(); i.hasNext();) {
entry = (Map.Entry) i.next();
key.setCharAt(1, ((String) entry.getKey()).charAt(0));
- keyTmp = key.toString();
-
+ keyStr = key.toString();
+
+ // STR-3169
+ // Prevent an infinite loop by retaining the placeholders
+ // that contain itself in the substitution value
+ if (((String) entry.getValue()).contains(keyStr)) {
+ throw new IllegalStateException();
+ }
+
// Replace all instances of the placeholder
- while ((x = ret.toString().indexOf(keyTmp)) > -1) {
+ while ((x = ret.toString().indexOf(keyStr)) > -1) {
ret.replace(x, x + 3, (String) entry.getValue());
}
}
@@ -311,4 +331,5 @@
return this.config;
}
}
+
}
diff --git a/core/src/test/java/org/apache/struts/config/TestActionConfigMatcher.java b/core/src/test/java/org/apache/struts/config/TestActionConfigMatcher.java
index 9cc6d70..4dc9181 100755
--- a/core/src/test/java/org/apache/struts/config/TestActionConfigMatcher.java
+++ b/core/src/test/java/org/apache/struts/config/TestActionConfigMatcher.java
@@ -27,204 +27,270 @@
import org.apache.struts.action.ActionMapping;
import org.apache.struts.mock.TestMockBase;
+import java.util.HashMap;
+
/**
- * <p>Unit tests for <code>org.apache.struts.util.ActionConfigMatcher</code>.</p>
- *
- * @version $Rev$ $Date: 2005-10-27 23:25:01 -0400 (Thu, 27 Oct 2005)
- * $
+ * <p>
+ * Unit tests for <code>org.apache.struts.util.ActionConfigMatcher</code>.
+ * </p>
+ *
+ * @version $Rev$ $Date$
*/
public class TestActionConfigMatcher extends TestMockBase {
// ----------------------------------------------------------------- Basics
public TestActionConfigMatcher(String name) {
- super(name);
+ super(name);
}
public static void main(String[] args) {
- junit.awtui.TestRunner.main(new String[] {
- TestActionConfigMatcher.class.getName()
- });
+ junit.awtui.TestRunner.main(new String[] { TestActionConfigMatcher.class.getName() });
}
public static Test suite() {
- return (new TestSuite(TestActionConfigMatcher.class));
+ return (new TestSuite(TestActionConfigMatcher.class));
}
// ----------------------------------------------------- Instance Variables
// ----------------------------------------------------- Setup and Teardown
public void setUp() {
- super.setUp();
+ super.setUp();
}
public void tearDown() {
- super.tearDown();
+ super.tearDown();
}
// ------------------------------------------------------- Individual Tests
// ---------------------------------------------------------- match()
public void testNoMatch() {
- ActionConfig[] configs = new ActionConfig[1];
- ActionConfig mapping = buildActionConfig("/foo");
+ ActionConfig[] configs = new ActionConfig[1];
+ ActionConfig mapping = buildActionConfig("/foo");
- configs[0] = mapping;
+ configs[0] = mapping;
- ActionConfigMatcher matcher = new ActionConfigMatcher(configs);
+ ActionConfigMatcher matcher = new ActionConfigMatcher(configs);
- assertNull("ActionConfig shouldn't be matched", matcher.match("/test"));
+ assertNull("ActionConfig shouldn't be matched", matcher.match("/test"));
+ }
+
+ /**
+ * Verifies that a match succeeds when the substituted value contains a
+ * placeholder key.
+ * <p>
+ * See STR-3169.
+ *
+ * @since Struts 1.3.11
+ */
+ public void testMatchWithKeyInPath() {
+ ActionMapping[] mapping = new ActionMapping[1];
+ mapping[0] = new ActionMapping();
+ mapping[0].setPath("/page-*");
+
+ ActionConfigMatcher matcher = new ActionConfigMatcher(mapping);
+ ActionConfig matched = matcher.match("/page-{0}");
+
+ assertNotNull("ActionConfig should be matched", matched);
+ assertEquals("Path hasn't been replaced", "/page-{0}", matched.getPath());
+ }
+
+ /**
+ * Verifies that an infinite loop is prevented and substitution is skipped when
+ * the substituted value equals the placeholder key.
+ * <p>
+ * See STR-3169.
+ *
+ * @since Struts 1.3.11
+ * @see #testMatchWithSubstitutionEqualPlaceholderKey1()
+ */
+ public void testMatchWithSubstitutionEqualPlaceholderKey0() {
+ ActionMapping[] mapping = new ActionMapping[1];
+ mapping[0] = new ActionMapping();
+ mapping[0].setPath("/page-*");
+ mapping[0].setParameter("{0}");
+
+ ActionConfigMatcher matcher = new ActionConfigMatcher(mapping);
+ ActionConfig matched = matcher.match("/page-{1}");
+
+ assertNull("ActionConfig should not be matched", matched);
+ }
+
+ /**
+ * Verifies that an infinite loop is prevented and substitution is skipped when
+ * the substituted value equals the placeholder key.
+ * <p>
+ * See STR-3169.
+ *
+ * @since Struts 1.3.11
+ * @see #testMatchWithSubstitutionEqualPlaceholderKey0()
+ */
+ public void testMatchWithSubstitutionEqualPlaceholderKey1() {
+ ActionMapping[] mapping = new ActionMapping[1];
+ mapping[0] = new ActionMapping();
+ mapping[0].setPath("/page-*");
+ mapping[0].setParameter("{1}");
+
+ ActionConfigMatcher matcher = new ActionConfigMatcher(mapping);
+ ActionConfig matched = matcher.match("/page-{1}");
+
+ assertNull("ActionConfig should not be matched", matched);
+ }
+
+ /**
+ * Verifies that an infinite loop is prevented and substitution is skipped when
+ * the the placeholder key is contained within the substituted value.
+ * <p>
+ * See STR-3169.
+ *
+ * @since Struts 1.3.11
+ */
+ public void testMatchWhenSubstitutionContainsPlaceholderKey() {
+ ActionMapping[] mapping = new ActionMapping[1];
+ mapping[0] = new ActionMapping();
+ mapping[0].setPath("/page-*");
+ mapping[0].setParameter("{1}");
+
+ ActionConfigMatcher matcher = new ActionConfigMatcher(mapping);
+ ActionConfig matched = matcher.match("/page-{0}");
+
+ assertNull("ActionConfig should not be matched", matched);
}
public void testNoWildcardMatch() {
- ActionConfig[] configs = new ActionConfig[1];
- ActionConfig mapping = buildActionConfig("/fooBar");
+ ActionConfig[] configs = new ActionConfig[1];
+ ActionConfig mapping = buildActionConfig("/fooBar");
- configs[0] = mapping;
+ configs[0] = mapping;
- ActionConfigMatcher matcher = new ActionConfigMatcher(configs);
+ ActionConfigMatcher matcher = new ActionConfigMatcher(configs);
- assertNull("ActionConfig shouldn't be matched", matcher.match("/fooBar"));
+ assertNull("ActionConfig shouldn't be matched", matcher.match("/fooBar"));
}
public void testShouldMatch() {
- ActionConfig[] configs = new ActionConfig[1];
- ActionConfig mapping = buildActionConfig("/foo*");
+ ActionConfig[] configs = new ActionConfig[1];
+ ActionConfig mapping = buildActionConfig("/foo*");
- configs[0] = mapping;
+ configs[0] = mapping;
- ActionConfigMatcher matcher = new ActionConfigMatcher(configs);
+ ActionConfigMatcher matcher = new ActionConfigMatcher(configs);
- ActionConfig matched = matcher.match("/fooBar");
+ ActionConfig matched = matcher.match("/fooBar");
- assertNotNull("ActionConfig should be matched", matched);
- assertTrue("ActionConfig should have two action forward",
- matched.findForwardConfigs().length == 2);
- assertTrue("ActionConfig should have two exception forward",
- matched.findExceptionConfigs().length == 2);
- assertTrue("ActionConfig should have properties",
- matched.getProperties().size() == 2);
+ assertNotNull("ActionConfig should be matched", matched);
+ assertTrue("ActionConfig should have two action forward", matched.findForwardConfigs().length == 2);
+ assertTrue("ActionConfig should have two exception forward", matched.findExceptionConfigs().length == 2);
+ assertTrue("ActionConfig should have properties", matched.getProperties().size() == 2);
}
public void testCheckSubstitutionsMatch() {
- ActionConfig[] configs = new ActionConfig[1];
- ActionConfig mapping = buildActionConfig("/foo*");
+ ActionConfig[] configs = new ActionConfig[1];
+ ActionConfig mapping = buildActionConfig("/foo*");
- configs[0] = mapping;
+ configs[0] = mapping;
- ActionConfigMatcher matcher = new ActionConfigMatcher(configs);
- ActionConfig m = matcher.match("/fooBar");
+ ActionConfigMatcher matcher = new ActionConfigMatcher(configs);
+ ActionConfig m = matcher.match("/fooBar");
- assertTrue("Name hasn't been replaced", "name,Bar".equals(m.getName()));
- assertTrue("Path hasn't been replaced", "/fooBar".equals(m.getPath()));
- assertTrue("Scope isn't correct", "request".equals(m.getScope()));
- assertTrue("Unknown isn't correct", !m.getUnknown());
- assertTrue("Validate isn't correct", m.getValidate());
+ assertTrue("Name hasn't been replaced", "name,Bar".equals(m.getName()));
+ assertTrue("Path hasn't been replaced", "/fooBar".equals(m.getPath()));
+ assertTrue("Scope isn't correct", "request".equals(m.getScope()));
+ assertTrue("Unknown isn't correct", !m.getUnknown());
+ assertTrue("Validate isn't correct", m.getValidate());
- assertTrue("Prefix hasn't been replaced",
- "foo,Bar".equals(m.getPrefix()));
- assertTrue("Suffix hasn't been replaced",
- "bar,Bar".equals(m.getSuffix()));
- assertTrue("Type hasn't been replaced",
- "foo.bar.BarAction".equals(m.getType()));
- assertTrue("Roles hasn't been replaced",
- "public,Bar".equals(m.getRoles()));
- assertTrue("Parameter hasn't been replaced",
- "param,Bar".equals(m.getParameter()));
- assertTrue("Attribute hasn't been replaced",
- "attrib,Bar".equals(m.getAttribute()));
- assertTrue("Forward hasn't been replaced",
- "fwd,Bar".equals(m.getForward()));
- assertTrue("Include hasn't been replaced",
- "include,Bar".equals(m.getInclude()));
- assertTrue("Input hasn't been replaced",
- "input,Bar".equals(m.getInput()));
+ assertTrue("Prefix hasn't been replaced", "foo,Bar".equals(m.getPrefix()));
+ assertTrue("Suffix hasn't been replaced", "bar,Bar".equals(m.getSuffix()));
+ assertTrue("Type hasn't been replaced", "foo.bar.BarAction".equals(m.getType()));
+ assertTrue("Roles hasn't been replaced", "public,Bar".equals(m.getRoles()));
+ assertTrue("Parameter hasn't been replaced", "param,Bar".equals(m.getParameter()));
+ assertTrue("Attribute hasn't been replaced", "attrib,Bar".equals(m.getAttribute()));
+ assertTrue("Forward hasn't been replaced", "fwd,Bar".equals(m.getForward()));
+ assertTrue("Include hasn't been replaced", "include,Bar".equals(m.getInclude()));
+ assertTrue("Input hasn't been replaced", "input,Bar".equals(m.getInput()));
- assertTrue("ActionConfig property not replaced",
- "testBar".equals(m.getProperty("testprop2")));
+ assertTrue("ActionConfig property not replaced", "testBar".equals(m.getProperty("testprop2")));
- ForwardConfig[] fConfigs = m.findForwardConfigs();
- boolean found = false;
+ ForwardConfig[] fConfigs = m.findForwardConfigs();
+ boolean found = false;
- for (int x = 0; x < fConfigs.length; x++) {
- ForwardConfig cfg = fConfigs[x];
+ for (int x = 0; x < fConfigs.length; x++) {
+ ForwardConfig cfg = fConfigs[x];
- if ("name".equals(cfg.getName())) {
- found = true;
- assertTrue("Path hasn't been replaced",
- "path,Bar".equals(cfg.getPath()));
- assertTrue("Property foo hasn't been replaced",
- "bar,Bar".equals(cfg.getProperty("foo")));
- assertTrue("Module hasn't been replaced",
- "modBar".equals(cfg.getModule()));
- }
- }
+ if ("name".equals(cfg.getName())) {
+ found = true;
+ assertTrue("Path hasn't been replaced", "path,Bar".equals(cfg.getPath()));
+ assertTrue("Property foo hasn't been replaced", "bar,Bar".equals(cfg.getProperty("foo")));
+ assertTrue("Module hasn't been replaced", "modBar".equals(cfg.getModule()));
+ }
+ }
- assertTrue("The forward config 'name' cannot be found", found);
+ assertTrue("The forward config 'name' cannot be found", found);
}
public void testCheckMultipleSubstitutions() {
- ActionMapping[] mapping = new ActionMapping[1];
+ ActionMapping[] mapping = new ActionMapping[1];
- mapping[0] = new ActionMapping();
- mapping[0].setPath("/foo*");
- mapping[0].setName("name,{1}-{1}");
+ mapping[0] = new ActionMapping();
+ mapping[0].setPath("/foo*");
+ mapping[0].setName("name,{1}-{1}");
- ActionConfigMatcher matcher = new ActionConfigMatcher(mapping);
- ActionConfig m = matcher.match("/fooBar");
+ ActionConfigMatcher matcher = new ActionConfigMatcher(mapping);
+ ActionConfig m = matcher.match("/fooBar");
- assertTrue("Name hasn't been replaced correctly: " + m.getName(),
- "name,Bar-Bar".equals(m.getName()));
+ assertTrue("Name hasn't been replaced correctly: " + m.getName(), "name,Bar-Bar".equals(m.getName()));
}
private ActionConfig buildActionConfig(String path) {
- ActionMapping mapping = new ActionMapping();
+ ActionMapping mapping = new ActionMapping();
- mapping.setName("name,{1}");
- mapping.setPath(path);
- mapping.setScope("request");
- mapping.setUnknown(false);
- mapping.setValidate(true);
+ mapping.setName("name,{1}");
+ mapping.setPath(path);
+ mapping.setScope("request");
+ mapping.setUnknown(false);
+ mapping.setValidate(true);
- mapping.setPrefix("foo,{1}");
- mapping.setSuffix("bar,{1}");
+ mapping.setPrefix("foo,{1}");
+ mapping.setSuffix("bar,{1}");
- mapping.setType("foo.bar.{1}Action");
- mapping.setRoles("public,{1}");
- mapping.setParameter("param,{1}");
- mapping.setAttribute("attrib,{1}");
- mapping.setForward("fwd,{1}");
- mapping.setInclude("include,{1}");
- mapping.setInput("input,{1}");
+ mapping.setType("foo.bar.{1}Action");
+ mapping.setRoles("public,{1}");
+ mapping.setParameter("param,{1}");
+ mapping.setAttribute("attrib,{1}");
+ mapping.setForward("fwd,{1}");
+ mapping.setInclude("include,{1}");
+ mapping.setInput("input,{1}");
- ForwardConfig cfg = new ActionForward();
+ ForwardConfig cfg = new ActionForward();
- cfg.setName("name");
- cfg.setPath("path,{1}");
- cfg.setModule("mod{1}");
- cfg.setProperty("foo", "bar,{1}");
- mapping.addForwardConfig(cfg);
+ cfg.setName("name");
+ cfg.setPath("path,{1}");
+ cfg.setModule("mod{1}");
+ cfg.setProperty("foo", "bar,{1}");
+ mapping.addForwardConfig(cfg);
- cfg = new ActionForward();
- cfg.setName("name2");
- cfg.setPath("path2");
- cfg.setModule("mod{1}");
- mapping.addForwardConfig(cfg);
+ cfg = new ActionForward();
+ cfg.setName("name2");
+ cfg.setPath("path2");
+ cfg.setModule("mod{1}");
+ mapping.addForwardConfig(cfg);
- ExceptionConfig excfg = new ExceptionConfig();
+ ExceptionConfig excfg = new ExceptionConfig();
- excfg.setKey("foo");
- excfg.setType("foo.Bar");
- excfg.setPath("path");
- mapping.addExceptionConfig(excfg);
+ excfg.setKey("foo");
+ excfg.setType("foo.Bar");
+ excfg.setPath("path");
+ mapping.addExceptionConfig(excfg);
- excfg = new ExceptionConfig();
- excfg.setKey("foo2");
- excfg.setType("foo.Bar2");
- excfg.setPath("path2");
- mapping.addExceptionConfig(excfg);
+ excfg = new ExceptionConfig();
+ excfg.setKey("foo2");
+ excfg.setType("foo.Bar2");
+ excfg.setPath("path2");
+ mapping.addExceptionConfig(excfg);
- mapping.setProperty("testprop", "testval");
- mapping.setProperty("testprop2", "test{1}");
+ mapping.setProperty("testprop", "testval");
+ mapping.setProperty("testprop2", "test{1}");
- mapping.freeze();
+ mapping.freeze();
- return mapping;
+ return mapping;
}
}