diff --git a/core/src/main/java/com/opensymphony/xwork2/config/providers/StrutsDefaultConfigurationProvider.java b/core/src/main/java/com/opensymphony/xwork2/config/providers/StrutsDefaultConfigurationProvider.java
index f263df383..4a39a335b 100644
--- a/core/src/main/java/com/opensymphony/xwork2/config/providers/StrutsDefaultConfigurationProvider.java
+++ b/core/src/main/java/com/opensymphony/xwork2/config/providers/StrutsDefaultConfigurationProvider.java
@@ -33,6 +33,7 @@ import com.opensymphony.xwork2.security.DefaultAcceptedPatternsChecker;
import com.opensymphony.xwork2.security.DefaultExcludedPatternsChecker;
import com.opensymphony.xwork2.DefaultTextProvider;
import com.opensymphony.xwork2.DefaultUnknownHandlerManager;
+import com.opensymphony.xwork2.security.DefaultNotExcludedAcceptedPatternsChecker;
import com.opensymphony.xwork2.security.ExcludedPatternsChecker;
import com.opensymphony.xwork2.FileManager;
import com.opensymphony.xwork2.FileManagerFactory;
@@ -54,6 +55,7 @@ import com.opensymphony.xwork2.conversion.impl.CollectionConverter;
import com.opensymphony.xwork2.conversion.impl.DateConverter;
import com.opensymphony.xwork2.conversion.impl.DefaultConversionAnnotationProcessor;
import com.opensymphony.xwork2.conversion.impl.DefaultConversionFileProcessor;
+import com.opensymphony.xwork2.security.NotExcludedAcceptedPatternsChecker;
import org.apache.struts2.conversion.StrutsConversionPropertiesProcessor;
import com.opensymphony.xwork2.conversion.impl.DefaultObjectTypeDeterminer;
import org.apache.struts2.conversion.StrutsTypeConverterCreator;
@@ -212,6 +214,8 @@ public class StrutsDefaultConfigurationProvider implements ConfigurationProvider
.factory(ExcludedPatternsChecker.class, DefaultExcludedPatternsChecker.class, Scope.PROTOTYPE)
.factory(AcceptedPatternsChecker.class, DefaultAcceptedPatternsChecker.class, Scope.PROTOTYPE)
+ .factory(NotExcludedAcceptedPatternsChecker.class, DefaultNotExcludedAcceptedPatternsChecker.class
+ , Scope.SINGLETON)
.factory(ValueSubstitutor.class, EnvsValueSubstitutor.class, Scope.SINGLETON)
;
diff --git a/core/src/main/java/com/opensymphony/xwork2/interceptor/AliasInterceptor.java b/core/src/main/java/com/opensymphony/xwork2/interceptor/AliasInterceptor.java
index 3a41df722..9edafe3fc 100644
--- a/core/src/main/java/com/opensymphony/xwork2/interceptor/AliasInterceptor.java
+++ b/core/src/main/java/com/opensymphony/xwork2/interceptor/AliasInterceptor.java
@@ -22,6 +22,8 @@ import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.ActionInvocation;
import com.opensymphony.xwork2.config.entities.ActionConfig;
import com.opensymphony.xwork2.inject.Inject;
+import com.opensymphony.xwork2.security.AcceptedPatternsChecker;
+import com.opensymphony.xwork2.security.ExcludedPatternsChecker;
import com.opensymphony.xwork2.util.ClearableValueStack;
import com.opensymphony.xwork2.util.Evaluated;
import com.opensymphony.xwork2.LocalizedTextProvider;
@@ -100,6 +102,9 @@ public class AliasInterceptor extends AbstractInterceptor {
protected LocalizedTextProvider localizedTextProvider;
protected boolean devMode = false;
+ private ExcludedPatternsChecker excludedPatterns;
+ private AcceptedPatternsChecker acceptedPatterns;
+
@Inject(StrutsConstants.STRUTS_DEVMODE)
public void setDevMode(String mode) {
this.devMode = Boolean.parseBoolean(mode);
@@ -115,6 +120,16 @@ public class AliasInterceptor extends AbstractInterceptor {
this.localizedTextProvider = localizedTextProvider;
}
+ @Inject
+ public void setExcludedPatterns(ExcludedPatternsChecker excludedPatterns) {
+ this.excludedPatterns = excludedPatterns;
+ }
+
+ @Inject
+ public void setAcceptedPatterns(AcceptedPatternsChecker acceptedPatterns) {
+ this.acceptedPatterns = acceptedPatterns;
+ }
+
/**
*
* Sets the name of the action parameter to look for the alias map.
@@ -145,7 +160,7 @@ public class AliasInterceptor extends AbstractInterceptor {
ValueStack stack = ac.getValueStack();
Object obj = stack.findValue(aliasExpression);
- if (obj != null && obj instanceof Map) {
+ if (obj instanceof Map) {
//get secure stack
ValueStack newStack = valueStackFactory.createValueStack(stack);
boolean clearableStack = newStack instanceof ClearableValueStack;
@@ -167,7 +182,13 @@ public class AliasInterceptor extends AbstractInterceptor {
for (Object o : aliases.entrySet()) {
Map.Entry entry = (Map.Entry) o;
String name = entry.getKey().toString();
+ if (isNotAcceptableExpression(name)) {
+ continue;
+ }
String alias = (String) entry.getValue();
+ if (isNotAcceptableExpression(alias)) {
+ continue;
+ }
Evaluated value = new Evaluated(stack.findValue(name));
if (!value.isDefined()) {
// workaround
@@ -207,5 +228,65 @@ public class AliasInterceptor extends AbstractInterceptor {
return invocation.invoke();
}
-
+
+ protected boolean isAccepted(String paramName) {
+ AcceptedPatternsChecker.IsAccepted result = acceptedPatterns.isAccepted(paramName);
+ if (result.isAccepted()) {
+ return true;
+ }
+
+ LOG.warn("Parameter [{}] didn't match accepted pattern [{}]! See Accepted / Excluded patterns at\n" +
+ "https://struts.apache.org/security/#accepted--excluded-patterns",
+ paramName, result.getAcceptedPattern());
+
+ return false;
+ }
+
+ protected boolean isExcluded(String paramName) {
+ ExcludedPatternsChecker.IsExcluded result = excludedPatterns.isExcluded(paramName);
+ if (!result.isExcluded()) {
+ return false;
+ }
+
+ LOG.warn("Parameter [{}] matches excluded pattern [{}]! See Accepted / Excluded patterns at\n" +
+ "https://struts.apache.org/security/#accepted--excluded-patterns",
+ paramName, result.getExcludedPattern());
+
+ return true;
+ }
+
+ /**
+ * Checks if expression contains vulnerable code
+ *
+ * @param expression of interceptor
+ * @return true|false
+ */
+ protected boolean isNotAcceptableExpression(String expression) {
+ return isExcluded(expression) || !isAccepted(expression);
+ }
+
+ /**
+ * Sets a comma-delimited list of regular expressions to match
+ * parameters that are allowed in the parameter map (aka whitelist).
+ *
+ * Don't change the default unless you know what you are doing in terms
+ * of security implications.
+ *
+ *
+ * @param commaDelim A comma-delimited list of regular expressions
+ */
+ public void setAcceptParamNames(String commaDelim) {
+ acceptedPatterns.setAcceptedPatterns(commaDelim);
+ }
+
+ /**
+ * Sets a comma-delimited list of regular expressions to match
+ * parameters that should be removed from the parameter map.
+ *
+ * @param commaDelim A comma-delimited list of regular expressions
+ */
+ public void setExcludeParams(String commaDelim) {
+ excludedPatterns.setExcludedPatterns(commaDelim);
+ }
+
}
diff --git a/core/src/main/java/com/opensymphony/xwork2/mock/MockResult.java b/core/src/main/java/com/opensymphony/xwork2/mock/MockResult.java
index f85cae4a7..6d3debec3 100644
--- a/core/src/main/java/com/opensymphony/xwork2/mock/MockResult.java
+++ b/core/src/main/java/com/opensymphony/xwork2/mock/MockResult.java
@@ -31,6 +31,8 @@ public class MockResult implements Result {
public static final String DEFAULT_PARAM = "foo";
+ private ActionInvocation invocation;
+
@Override
public boolean equals(Object o) {
if (this == o) {
@@ -41,7 +43,7 @@ public class MockResult implements Result {
}
public void execute(ActionInvocation invocation) throws Exception {
- // no op
+ this.invocation = invocation;
}
@Override
@@ -53,4 +55,7 @@ public class MockResult implements Result {
// no op
}
+ public ActionInvocation getInvocation() {
+ return invocation;
+ }
}
diff --git a/core/src/main/java/com/opensymphony/xwork2/security/AcceptedPatternsChecker.java b/core/src/main/java/com/opensymphony/xwork2/security/AcceptedPatternsChecker.java
index fa92e0b2a..f5a329459 100644
--- a/core/src/main/java/com/opensymphony/xwork2/security/AcceptedPatternsChecker.java
+++ b/core/src/main/java/com/opensymphony/xwork2/security/AcceptedPatternsChecker.java
@@ -22,7 +22,7 @@ import java.util.Set;
import java.util.regex.Pattern;
/**
- * Used across different interceptors to check if given string matches one of the excluded patterns.
+ * Used across different interceptors to check if given string matches one of the accepted patterns.
*/
public interface AcceptedPatternsChecker {
diff --git a/core/src/main/java/com/opensymphony/xwork2/security/DefaultNotExcludedAcceptedPatternsChecker.java b/core/src/main/java/com/opensymphony/xwork2/security/DefaultNotExcludedAcceptedPatternsChecker.java
new file mode 100644
index 000000000..b475da1d0
--- /dev/null
+++ b/core/src/main/java/com/opensymphony/xwork2/security/DefaultNotExcludedAcceptedPatternsChecker.java
@@ -0,0 +1,105 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package com.opensymphony.xwork2.security;
+
+import com.opensymphony.xwork2.inject.Inject;
+
+import java.util.Set;
+import java.util.regex.Pattern;
+
+public class DefaultNotExcludedAcceptedPatternsChecker implements NotExcludedAcceptedPatternsChecker {
+ private ExcludedPatternsChecker excludedPatterns;
+ private AcceptedPatternsChecker acceptedPatterns;
+
+
+ @Inject
+ public void setExcludedPatterns(ExcludedPatternsChecker excludedPatterns) {
+ this.excludedPatterns = excludedPatterns;
+ }
+
+ @Inject
+ public void setAcceptedPatterns(AcceptedPatternsChecker acceptedPatterns) {
+ this.acceptedPatterns = acceptedPatterns;
+ }
+
+ @Override
+ public IsAllowed isAllowed(String value) {
+ IsExcluded isExcluded = isExcluded(value);
+ if (isExcluded.isExcluded()) {
+ return IsAllowed.no(isExcluded.getExcludedPattern());
+ }
+
+ IsAccepted isAccepted = isAccepted(value);
+ if (!isAccepted.isAccepted()) {
+ return IsAllowed.no(isAccepted.getAcceptedPattern());
+ }
+
+ return IsAllowed.yes(isAccepted.getAcceptedPattern());
+ }
+
+ @Override
+ public IsAccepted isAccepted(String value) {
+ return acceptedPatterns.isAccepted(value);
+ }
+
+ @Override
+ public void setAcceptedPatterns(String commaDelimitedPatterns) {
+ acceptedPatterns.setAcceptedPatterns(commaDelimitedPatterns);
+ }
+
+ @Override
+ public void setAcceptedPatterns(String[] patterns) {
+ acceptedPatterns.setAcceptedPatterns(patterns);
+ }
+
+ @Override
+ public void setAcceptedPatterns(Set patterns) {
+ acceptedPatterns.setAcceptedPatterns(patterns);
+ }
+
+ @Override
+ public Set getAcceptedPatterns() {
+ return acceptedPatterns.getAcceptedPatterns();
+ }
+
+ @Override
+ public IsExcluded isExcluded(String value) {
+ return excludedPatterns.isExcluded(value);
+ }
+
+ @Override
+ public void setExcludedPatterns(String commaDelimitedPatterns) {
+ excludedPatterns.setExcludedPatterns(commaDelimitedPatterns);
+ }
+
+ @Override
+ public void setExcludedPatterns(String[] patterns) {
+ excludedPatterns.setExcludedPatterns(patterns);
+ }
+
+ @Override
+ public void setExcludedPatterns(Set patterns) {
+ excludedPatterns.setExcludedPatterns(patterns);
+ }
+
+ @Override
+ public Set getExcludedPatterns() {
+ return excludedPatterns.getExcludedPatterns();
+ }
+}
diff --git a/core/src/main/java/com/opensymphony/xwork2/security/NotExcludedAcceptedPatternsChecker.java b/core/src/main/java/com/opensymphony/xwork2/security/NotExcludedAcceptedPatternsChecker.java
new file mode 100644
index 000000000..030b5b67e
--- /dev/null
+++ b/core/src/main/java/com/opensymphony/xwork2/security/NotExcludedAcceptedPatternsChecker.java
@@ -0,0 +1,70 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package com.opensymphony.xwork2.security;
+
+/**
+ * Used across different places to check if given string is not excluded and is accepted
+ * @see here
+ * @since 2.6
+ */
+public interface NotExcludedAcceptedPatternsChecker extends ExcludedPatternsChecker, AcceptedPatternsChecker {
+
+ /**
+ * Checks if value doesn't match excluded pattern and matches accepted pattern
+ *
+ * @param value to check
+ * @return object containing result of matched pattern and pattern itself
+ */
+ IsAllowed isAllowed(String value);
+
+ final class IsAllowed {
+
+ private final boolean allowed;
+ private final String allowedPattern;
+
+ public static IsAllowed yes(String allowedPattern) {
+ return new IsAllowed(true, allowedPattern);
+ }
+
+ public static IsAllowed no(String allowedPattern) {
+ return new IsAllowed(false, allowedPattern);
+ }
+
+ private IsAllowed(boolean allowed, String allowedPattern) {
+ this.allowed = allowed;
+ this.allowedPattern = allowedPattern;
+ }
+
+ public boolean isAllowed() {
+ return allowed;
+ }
+
+ public String getAllowedPattern() {
+ return allowedPattern;
+ }
+
+ @Override
+ public String toString() {
+ return "IsAllowed { " +
+ "allowed=" + allowed +
+ ", allowedPattern=" + allowedPattern +
+ " }";
+ }
+ }
+}
diff --git a/core/src/main/java/org/apache/struts2/StrutsConstants.java b/core/src/main/java/org/apache/struts2/StrutsConstants.java
index ccba9b101..60007cea7 100644
--- a/core/src/main/java/org/apache/struts2/StrutsConstants.java
+++ b/core/src/main/java/org/apache/struts2/StrutsConstants.java
@@ -334,6 +334,7 @@ public final class StrutsConstants {
/** Dedicated services to check if passed string is excluded/accepted */
public static final String STRUTS_EXCLUDED_PATTERNS_CHECKER = "struts.excludedPatterns.checker";
public static final String STRUTS_ACCEPTED_PATTERNS_CHECKER = "struts.acceptedPatterns.checker";
+ public static final String STRUTS_NOT_EXCLUDED_ACCEPTED_PATTERNS_CHECKER = "struts.notExcludedAcceptedPatterns.checker";
/** Constant is used to override framework's default excluded patterns */
public static final String STRUTS_OVERRIDE_EXCLUDED_PATTERNS = "struts.override.excludedPatterns";
diff --git a/core/src/main/java/org/apache/struts2/components/Component.java b/core/src/main/java/org/apache/struts2/components/Component.java
index e20b4f5e8..748285b76 100644
--- a/core/src/main/java/org/apache/struts2/components/Component.java
+++ b/core/src/main/java/org/apache/struts2/components/Component.java
@@ -19,6 +19,7 @@
package org.apache.struts2.components;
import com.opensymphony.xwork2.inject.Inject;
+import com.opensymphony.xwork2.security.NotExcludedAcceptedPatternsChecker;
import com.opensymphony.xwork2.util.TextParseUtil;
import com.opensymphony.xwork2.util.ValueStack;
import org.apache.commons.lang3.BooleanUtils;
@@ -73,6 +74,8 @@ public class Component {
protected boolean throwExceptionOnELFailure;
private UrlHelper urlHelper;
+ private NotExcludedAcceptedPatternsChecker notExcludedAcceptedPatterns;
+
/**
* Constructor.
*
@@ -117,6 +120,11 @@ public class Component {
this.urlHelper = urlHelper;
}
+ @Inject
+ public void setNotExcludedAcceptedPatterns(NotExcludedAcceptedPatternsChecker notExcludedAcceptedPatterns) {
+ this.notExcludedAcceptedPatterns = notExcludedAcceptedPatterns;
+ }
+
/**
* Gets the OGNL value stack associated with this component.
*
@@ -209,7 +217,6 @@ public class Component {
if (currPosition >= 0) {
int start = componentStack.size() - currPosition - 1;
- //for (int i = componentStack.size() - 2; i >= 0; i--) {
for (int i = start; i >= 0; i--) {
Component component = (Component) componentStack.get(i);
if (clazz.isAssignableFrom(component.getClass()) && component != this) {
@@ -376,16 +383,6 @@ public class Component {
}
}
- /**
- * Detects if expression already contains %{...}
- *
- * @param expression a string to examined
- * @return true if expression contains %{...}
- */
- protected boolean recursion(String expression) {
- return ComponentUtils.containsExpression(expression);
- }
-
/**
* Renders an action URL by consulting the {@link org.apache.struts2.dispatcher.mapper.ActionMapper}.
*
@@ -403,7 +400,7 @@ public class Component {
* @return the action url.
*/
protected String determineActionURL(String action, String namespace, String method,
- HttpServletRequest req, HttpServletResponse res, Map parameters, String scheme,
+ HttpServletRequest req, HttpServletResponse res, Map parameters, String scheme,
boolean includeContext, boolean encodeResult, boolean forceAddSchemeHostAndPort,
boolean escapeAmp) {
String finalAction = findString(action);
@@ -571,4 +568,22 @@ public class Component {
return standardAttributes;
}
+ /**
+ * Checks if expression doesn't contain vulnerable code
+ *
+ * @param expression of the component
+ * @return true|false
+ * @since 2.6
+ */
+ protected boolean isAcceptableExpression(String expression) {
+ NotExcludedAcceptedPatternsChecker.IsAllowed isAllowed = notExcludedAcceptedPatterns.isAllowed(expression);
+ if (isAllowed.isAllowed()) {
+ return true;
+ }
+
+ LOG.warn("Expression [{}] isn't allowed by pattern [{}]! See Accepted / Excluded patterns at\n" +
+ "https://struts.apache.org/security/", expression, isAllowed.getAllowedPattern());
+
+ return false;
+ }
}
diff --git a/core/src/main/java/org/apache/struts2/components/FormButton.java b/core/src/main/java/org/apache/struts2/components/FormButton.java
index 5cf88bc42..d9e75f1e4 100644
--- a/core/src/main/java/org/apache/struts2/components/FormButton.java
+++ b/core/src/main/java/org/apache/struts2/components/FormButton.java
@@ -122,6 +122,7 @@ public abstract class FormButton extends ClosingUIBean {
}
}
addParameter("id", _tmp_id);
+ addParameter("escapedId", escape(_tmp_id));
}
/**
diff --git a/core/src/main/java/org/apache/struts2/components/Param.java b/core/src/main/java/org/apache/struts2/components/Param.java
index 023761ad9..c2c299e7e 100644
--- a/core/src/main/java/org/apache/struts2/components/Param.java
+++ b/core/src/main/java/org/apache/struts2/components/Param.java
@@ -125,23 +125,29 @@ public class Param extends Component {
if (component instanceof UnnamedParametric) {
((UnnamedParametric) component).addParameter(findValue(value));
} else {
- String name = findString(this.name);
+ String translatedName = findString(this.name);
- if (name == null) {
+ if (translatedName == null) {
throw new StrutsException("No name found for following expression: " + this.name);
}
- Object value = findValue(this.value);
+ boolean evaluated = !translatedName.equals(this.name);
+ boolean reevaluate = !evaluated || isAcceptableExpression(translatedName);
+ if (!reevaluate) {
+ throw new StrutsException("Excluded or not accepted name found: " + translatedName);
+ }
+
+ Object foundValue = findValue(this.value);
if (suppressEmptyParameters) {
- if (value != null && StringUtils.isNotBlank(value.toString())) {
- component.addParameter(name, value);
+ if (foundValue != null && StringUtils.isNotBlank(foundValue.toString())) {
+ component.addParameter(translatedName, foundValue);
} else {
- component.addParameter(name, null);
+ component.addParameter(translatedName, null);
}
- } else if (value == null || StringUtils.isBlank(value.toString())) {
- component.addParameter(name, "");
+ } else if (foundValue == null || StringUtils.isBlank(foundValue.toString())) {
+ component.addParameter(translatedName, "");
} else {
- component.addParameter(name, value);
+ component.addParameter(translatedName, foundValue);
}
}
} else {
@@ -158,7 +164,8 @@ public class Param extends Component {
return super.end(writer, "");
}
-
+
+ @Override
public boolean usesBody() {
return true;
}
@@ -193,7 +200,7 @@ public class Param extends Component {
* Adds the given value as a parameter to the outer tag.
* @param value the value
*/
- public void addParameter(Object value);
+ void addParameter(Object value);
}
}
diff --git a/core/src/main/java/org/apache/struts2/components/ServletUrlRenderer.java b/core/src/main/java/org/apache/struts2/components/ServletUrlRenderer.java
index a302e8a77..3fa192584 100644
--- a/core/src/main/java/org/apache/struts2/components/ServletUrlRenderer.java
+++ b/core/src/main/java/org/apache/struts2/components/ServletUrlRenderer.java
@@ -191,7 +191,9 @@ public class ServletUrlRenderer implements UrlRenderer {
// if the id isn't specified, use the action name
if (formComponent.getId() == null && actionName != null) {
- formComponent.addParameter("id", formComponent.escape(actionName));
+ String escapedId = formComponent.escape(actionName);
+ formComponent.addParameter("id", escapedId);
+ formComponent.addParameter("escapedId", escapedId);
}
} else if (action != null) {
// Since we can't find an action alias in the configuration, we just
@@ -226,7 +228,9 @@ public class ServletUrlRenderer implements UrlRenderer {
} else {
id = result.substring(slash + 1);
}
- formComponent.addParameter("id", formComponent.escape(id));
+ String escapedId = formComponent.escape(id);
+ formComponent.addParameter("id", escapedId);
+ formComponent.addParameter("escapedId", escapedId);
}
}
diff --git a/core/src/main/java/org/apache/struts2/components/UIBean.java b/core/src/main/java/org/apache/struts2/components/UIBean.java
index 8ebd50604..24b47fa14 100644
--- a/core/src/main/java/org/apache/struts2/components/UIBean.java
+++ b/core/src/main/java/org/apache/struts2/components/UIBean.java
@@ -563,16 +563,13 @@ public abstract class UIBean extends Component {
protected abstract String getDefaultTemplate();
protected Template buildTemplateName(String myTemplate, String myDefaultTemplate) {
- String template = myDefaultTemplate;
+ String templateName = myDefaultTemplate;
if (myTemplate != null) {
- template = findString(myTemplate);
+ templateName = findString(myTemplate);
}
- String templateDir = getTemplateDir();
- String theme = getTheme();
-
- return new Template(templateDir, theme, template);
+ return new Template(getTemplateDir(), getTheme(), templateName);
}
@@ -589,73 +586,72 @@ public abstract class UIBean extends Component {
}
public String getTemplateDir() {
- String templateDir = null;
+ String result = null;
if (this.templateDir != null) {
- templateDir = findString(this.templateDir);
+ result = findString(this.templateDir);
}
// If templateDir is not explicitly given,
// try to find attribute which states the dir set to use
- if (StringUtils.isBlank(templateDir)) {
- templateDir = stack.findString("#attr.templateDir");
+ if (StringUtils.isBlank(result)) {
+ result = stack.findString("#attr.templateDir");
}
// Default template set
- if (StringUtils.isBlank(templateDir)) {
- templateDir = defaultTemplateDir;
+ if (StringUtils.isBlank(result)) {
+ result = defaultTemplateDir;
}
// Defaults to 'template'
- if (StringUtils.isBlank(templateDir)) {
- templateDir = "template";
+ if (StringUtils.isBlank(result)) {
+ result = "template";
}
- return templateDir;
+ return result;
}
public String getTheme() {
- String theme = null;
+ String result = null;
if (this.theme != null) {
- theme = findString(this.theme);
+ result = findString(this.theme);
}
- if (StringUtils.isBlank(theme)) {
+ if (StringUtils.isBlank(result)) {
Form form = (Form) findAncestor(Form.class);
if (form != null) {
- theme = form.getTheme();
+ result = form.getTheme();
}
}
// If theme set is not explicitly given,
// try to find attribute which states the theme set to use
- if (StringUtils.isBlank(theme)) {
- theme = stack.findString("#attr.theme");
+ if (StringUtils.isBlank(result)) {
+ result = stack.findString("#attr.theme");
}
// Default theme set
- if (StringUtils.isBlank(theme)) {
- theme = defaultUITheme;
+ if (StringUtils.isBlank(result)) {
+ result = defaultUITheme;
}
- return theme;
+ return result;
}
public void evaluateParams() {
- String templateDir = getTemplateDir();
- String theme = getTheme();
+ String gotTheme = getTheme();
- addParameter("templateDir", templateDir);
- addParameter("theme", theme);
+ addParameter("templateDir", getTemplateDir());
+ addParameter("theme", gotTheme);
addParameter("template", template != null ? findString(template) : getDefaultTemplate());
addParameter("dynamicAttributes", dynamicAttributes);
addParameter("themeExpansionToken", uiThemeExpansionToken);
- addParameter("expandTheme", uiThemeExpansionToken + theme);
+ addParameter("expandTheme", uiThemeExpansionToken + gotTheme);
addParameter("staticContentPath", findString(uiStaticContentPath));
- String name = null;
+ String translatedName = null;
String providedLabel = null;
if (this.key != null) {
@@ -671,8 +667,8 @@ public abstract class UIBean extends Component {
}
if (this.name != null) {
- name = findString(this.name);
- addParameter("name", name);
+ translatedName = findString(this.name);
+ addParameter("name", translatedName);
}
if (label != null) {
@@ -796,28 +792,31 @@ public abstract class UIBean extends Component {
// see if the value was specified as a parameter already
+ final String NAME_VALUE = "nameValue";
if (parameters.containsKey("value")) {
- parameters.put("nameValue", parameters.get("value"));
+ parameters.put(NAME_VALUE, parameters.get("value"));
} else {
if (evaluateNameValue()) {
final Class> valueClazz = getValueClassType();
if (valueClazz != null) {
if (value != null) {
- addParameter("nameValue", findValue(value, valueClazz));
- } else if (name != null) {
- String expr = completeExpression(name);
- if (recursion(name)) {
- addParameter("nameValue", expr);
+ addParameter(NAME_VALUE, findValue(value, valueClazz));
+ } else if (translatedName != null) {
+ boolean evaluated = !translatedName.equals(this.name);
+ boolean reevaluate = !evaluated || isAcceptableExpression(translatedName);
+ if (!reevaluate) {
+ addParameter(NAME_VALUE, translatedName);
} else {
- addParameter("nameValue", findValue(expr, valueClazz));
+ String expr = completeExpression(translatedName);
+ addParameter(NAME_VALUE, findValue(expr, valueClazz));
}
}
} else {
if (value != null) {
- addParameter("nameValue", findValue(value));
- } else if (name != null) {
- addParameter("nameValue", findValue(name));
+ addParameter(NAME_VALUE, findValue(value));
+ } else if (translatedName != null) {
+ addParameter(NAME_VALUE, findValue(translatedName));
}
}
}
@@ -831,10 +830,10 @@ public abstract class UIBean extends Component {
if (form != null ) {
addParameter("form", form.getParameters());
- if ( name != null ) {
+ if ( translatedName != null ) {
// list should have been created by the form component
List tags = (List) form.getParameters().get("tagNames");
- tags.add(name);
+ tags.add(translatedName);
}
}
@@ -897,11 +896,9 @@ public abstract class UIBean extends Component {
// to be used with the CSP interceptor - adds the nonce value as a parameter to be accessed from ftl files
Map session = stack.getActionContext().getSession();
- if (session != null) {
- if (session.containsKey("nonce")) {
- String nonceValue = session.get("nonce").toString();
- addParameter("nonce", nonceValue);
- }
+ Object nonceValue = session != null ? session.get("nonce") : null;
+ if (nonceValue != null) {
+ addParameter("nonce", nonceValue.toString());
}
evaluateExtraParams();
@@ -910,7 +907,7 @@ public abstract class UIBean extends Component {
protected String escape(String name) {
// escape any possible values that can make the ID painful to work with in JavaScript
if (name != null) {
- return name.replaceAll("[/.\\[\\]'\"]", "_");
+ return name.replaceAll("[^a-zA-Z0-9_]", "_");
} else {
return null;
}
@@ -961,14 +958,14 @@ public abstract class UIBean extends Component {
protected Map getTooltipConfig(UIBean component) {
Object tooltipConfigObj = component.getParameters().get("tooltipConfig");
- Map tooltipConfig = new LinkedHashMap<>();
+ Map result = new LinkedHashMap<>();
if (tooltipConfigObj instanceof Map) {
// we get this if its configured using
// 1] UI component's tooltipConfig attribute OR
// 2] param tag value attribute
- tooltipConfig = new LinkedHashMap<>((Map) tooltipConfigObj);
+ result = new LinkedHashMap<>((Map) tooltipConfigObj);
} else if (tooltipConfigObj instanceof String) {
// we get this if its configured using
@@ -978,23 +975,23 @@ public abstract class UIBean extends Component {
for (String aTooltipConfigArray : tooltipConfigArray) {
String[] configEntry = aTooltipConfigArray.trim().split("=");
- String key = configEntry[0].trim();
- String value;
+ String configKey = configEntry[0].trim();
+ String configValue;
if (configEntry.length > 1) {
- value = configEntry[1].trim();
- tooltipConfig.put(key, value);
+ configValue = configEntry[1].trim();
+ result.put(configKey, configValue);
} else {
- LOG.warn("component {} tooltip config param {} has no value defined, skipped", component, key);
+ LOG.warn("component {} tooltip config param {} has no value defined, skipped", component, configKey);
}
}
}
if (component.javascriptTooltip != null)
- tooltipConfig.put("jsTooltipEnabled", component.javascriptTooltip);
+ result.put("jsTooltipEnabled", component.javascriptTooltip);
if (component.tooltipIconPath != null)
- tooltipConfig.put("tooltipIcon", component.tooltipIconPath);
+ result.put("tooltipIcon", component.tooltipIconPath);
if (component.tooltipDelay != null)
- tooltipConfig.put("tooltipDelay", component.tooltipDelay);
- return tooltipConfig;
+ result.put("tooltipDelay", component.tooltipDelay);
+ return result;
}
/**
@@ -1275,10 +1272,10 @@ public abstract class UIBean extends Component {
public void setDynamicAttributes(Map tagDynamicAttributes) {
for (Map.Entry entry : tagDynamicAttributes.entrySet()) {
- String key = entry.getKey();
+ String entryKey = entry.getKey();
- if (!isValidTagAttribute(key)) {
- dynamicAttributes.put(key, entry.getValue());
+ if (!isValidTagAttribute(entryKey)) {
+ dynamicAttributes.put(entryKey, entry.getValue());
}
}
}
@@ -1292,9 +1289,9 @@ public abstract class UIBean extends Component {
public void copyParams(Map params) {
super.copyParams(params);
for (Map.Entryentry : params.entrySet()) {
- String key = entry.getKey();
- if (!isValidTagAttribute(key) && !key.equals("dynamicAttributes")) {
- dynamicAttributes.put(key, entry.getValue());
+ String entryKey = entry.getKey();
+ if (!isValidTagAttribute(entryKey) && !entryKey.equals("dynamicAttributes")) {
+ dynamicAttributes.put(entryKey, entry.getValue());
}
}
}
diff --git a/core/src/main/java/org/apache/struts2/config/StrutsBeanSelectionProvider.java b/core/src/main/java/org/apache/struts2/config/StrutsBeanSelectionProvider.java
index 08f46fa86..ade6aa0b0 100644
--- a/core/src/main/java/org/apache/struts2/config/StrutsBeanSelectionProvider.java
+++ b/core/src/main/java/org/apache/struts2/config/StrutsBeanSelectionProvider.java
@@ -49,6 +49,7 @@ import com.opensymphony.xwork2.factory.ResultFactory;
import com.opensymphony.xwork2.factory.ValidatorFactory;
import com.opensymphony.xwork2.inject.ContainerBuilder;
import com.opensymphony.xwork2.inject.Scope;
+import com.opensymphony.xwork2.security.NotExcludedAcceptedPatternsChecker;
import com.opensymphony.xwork2.util.PatternMatcher;
import com.opensymphony.xwork2.util.TextParser;
import com.opensymphony.xwork2.util.ValueStackFactory;
@@ -418,6 +419,8 @@ public class StrutsBeanSelectionProvider extends AbstractBeanSelectionProvider {
/** Checker is used mostly in interceptors, so there be one instance of checker per interceptor with Scope.PROTOTYPE **/
alias(ExcludedPatternsChecker.class, StrutsConstants.STRUTS_EXCLUDED_PATTERNS_CHECKER, builder, props, Scope.PROTOTYPE);
alias(AcceptedPatternsChecker.class, StrutsConstants.STRUTS_ACCEPTED_PATTERNS_CHECKER, builder, props, Scope.PROTOTYPE);
+ alias(NotExcludedAcceptedPatternsChecker.class, StrutsConstants.STRUTS_NOT_EXCLUDED_ACCEPTED_PATTERNS_CHECKER
+ , builder, props, Scope.SINGLETON);
switchDevMode(props);
}
diff --git a/core/src/main/java/org/apache/struts2/config/entities/ConstantConfig.java b/core/src/main/java/org/apache/struts2/config/entities/ConstantConfig.java
index 7729c8d89..df08ac7c1 100644
--- a/core/src/main/java/org/apache/struts2/config/entities/ConstantConfig.java
+++ b/core/src/main/java/org/apache/struts2/config/entities/ConstantConfig.java
@@ -132,6 +132,7 @@ public class ConstantConfig {
private Set devModeExcludedPackageNames;
private BeanConfig excludedPatternsChecker;
private BeanConfig acceptedPatternsChecker;
+ private BeanConfig notExcludedAcceptedPatternsChecker;
private Set overrideExcludedPatterns;
private Set overrideAcceptedPatterns;
private Set additionalExcludedPatterns;
@@ -261,6 +262,7 @@ public class ConstantConfig {
map.put(StrutsConstants.STRUTS_DEV_MODE_EXCLUDED_PACKAGE_NAMES, StringUtils.join(devModeExcludedPackageNames, ','));
map.put(StrutsConstants.STRUTS_EXCLUDED_PATTERNS_CHECKER, beanConfToString(excludedPatternsChecker));
map.put(StrutsConstants.STRUTS_ACCEPTED_PATTERNS_CHECKER, beanConfToString(acceptedPatternsChecker));
+ map.put(StrutsConstants.STRUTS_NOT_EXCLUDED_ACCEPTED_PATTERNS_CHECKER, beanConfToString(notExcludedAcceptedPatternsChecker));
map.put(StrutsConstants.STRUTS_OVERRIDE_EXCLUDED_PATTERNS, StringUtils.join(overrideExcludedPatterns, ','));
map.put(StrutsConstants.STRUTS_OVERRIDE_ACCEPTED_PATTERNS, StringUtils.join(overrideAcceptedPatterns, ','));
map.put(StrutsConstants.STRUTS_ADDITIONAL_EXCLUDED_PATTERNS, StringUtils.join(additionalExcludedPatterns, ','));
@@ -1228,6 +1230,18 @@ public class ConstantConfig {
this.acceptedPatternsChecker = new BeanConfig(clazz, clazz.getName());
}
+ public BeanConfig getNotExcludedAcceptedPatternsChecker() {
+ return notExcludedAcceptedPatternsChecker;
+ }
+
+ public void setNotExcludedAcceptedPatternsChecker(BeanConfig notExcludedAcceptedPatternsChecker) {
+ this.notExcludedAcceptedPatternsChecker = notExcludedAcceptedPatternsChecker;
+ }
+
+ public void setNotExcludedAcceptedPatternsChecker(Class> clazz) {
+ this.notExcludedAcceptedPatternsChecker = new BeanConfig(clazz, clazz.getName());
+ }
+
public Set getOverrideExcludedPatterns() {
return overrideExcludedPatterns;
}
diff --git a/core/src/main/java/org/apache/struts2/result/StreamResult.java b/core/src/main/java/org/apache/struts2/result/StreamResult.java
index 711929f4a..131614dd9 100644
--- a/core/src/main/java/org/apache/struts2/result/StreamResult.java
+++ b/core/src/main/java/org/apache/struts2/result/StreamResult.java
@@ -19,6 +19,8 @@
package org.apache.struts2.result;
import com.opensymphony.xwork2.ActionInvocation;
+import com.opensymphony.xwork2.inject.Inject;
+import com.opensymphony.xwork2.security.NotExcludedAcceptedPatternsChecker;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
@@ -85,6 +87,8 @@ public class StreamResult extends StrutsResultSupport {
protected int bufferSize = 1024;
protected boolean allowCaching = true;
+ private NotExcludedAcceptedPatternsChecker notExcludedAcceptedPatterns;
+
public StreamResult() {
super();
}
@@ -93,6 +97,11 @@ public class StreamResult extends StrutsResultSupport {
this.inputStream = in;
}
+ @Inject
+ public void setNotExcludedAcceptedPatterns(NotExcludedAcceptedPatternsChecker notExcludedAcceptedPatterns) {
+ this.notExcludedAcceptedPatterns = notExcludedAcceptedPatterns;
+ }
+
/**
* @return Returns the whether or not the client should be requested to allow caching of the data stream.
*/
@@ -204,14 +213,17 @@ public class StreamResult extends StrutsResultSupport {
OutputStream oOutput = null;
try {
- if (inputStream == null) {
+ String parsedInputName = conditionalParse(inputName, invocation);
+ boolean evaluated = parsedInputName != null && !parsedInputName.equals(inputName);
+ boolean reevaluate = !evaluated || isAcceptableExpression(parsedInputName);
+ if (inputStream == null && reevaluate) {
LOG.debug("Find the inputstream from the invocation variable stack");
- inputStream = (InputStream) invocation.getStack().findValue(conditionalParse(inputName, invocation));
+ inputStream = (InputStream) invocation.getStack().findValue(parsedInputName);
}
if (inputStream == null) {
- String msg = ("Can not find a java.io.InputStream with the name [" + inputName + "] in the invocation stack. " +
- "Check the tag specified for this action.");
+ String msg = ("Can not find a java.io.InputStream with the name [" + parsedInputName + "] in the invocation stack. " +
+ "Check the tag specified for this action is correct, not excluded and accepted.");
LOG.error(msg);
throw new IllegalArgumentException(msg);
}
@@ -228,15 +240,16 @@ public class StreamResult extends StrutsResultSupport {
LOG.debug("Set the content length: {}", contentLength);
if (contentLength != null) {
- String _contentLength = conditionalParse(contentLength, invocation);
- int _contentLengthAsInt;
+ String translatedContentLength = conditionalParse(contentLength, invocation);
+ int contentLengthAsInt;
try {
- _contentLengthAsInt = Integer.parseInt(_contentLength);
- if (_contentLengthAsInt >= 0) {
- oResponse.setContentLength(_contentLengthAsInt);
+ contentLengthAsInt = Integer.parseInt(translatedContentLength);
+ if (contentLengthAsInt >= 0) {
+ oResponse.setContentLength(contentLengthAsInt);
}
} catch (NumberFormatException e) {
- LOG.warn("failed to recognize {} as a number, contentLength header will not be set", _contentLength, e);
+ LOG.warn("failed to recognize {} as a number, contentLength header will not be set",
+ translatedContentLength, e);
}
}
@@ -277,4 +290,22 @@ public class StreamResult extends StrutsResultSupport {
}
}
+ /**
+ * Checks if expression doesn't contain vulnerable code
+ *
+ * @param expression of result
+ * @return true|false
+ * @since 2.6
+ */
+ protected boolean isAcceptableExpression(String expression) {
+ NotExcludedAcceptedPatternsChecker.IsAllowed isAllowed = notExcludedAcceptedPatterns.isAllowed(expression);
+ if (isAllowed.isAllowed()) {
+ return true;
+ }
+
+ LOG.warn("Expression [{}] isn't allowed by pattern [{}]! See Accepted / Excluded patterns at\n" +
+ "https://struts.apache.org/security/", expression, isAllowed.getAllowedPattern());
+
+ return false;
+ }
}
diff --git a/core/src/main/java/org/apache/struts2/util/StrutsUtil.java b/core/src/main/java/org/apache/struts2/util/StrutsUtil.java
index b694250b9..7d8379c89 100644
--- a/core/src/main/java/org/apache/struts2/util/StrutsUtil.java
+++ b/core/src/main/java/org/apache/struts2/util/StrutsUtil.java
@@ -18,9 +18,7 @@
*/
package org.apache.struts2.util;
-import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.ObjectFactory;
-import com.opensymphony.xwork2.inject.Container;
import com.opensymphony.xwork2.util.ClassLoaderUtil;
import com.opensymphony.xwork2.util.TextParseUtil;
import com.opensymphony.xwork2.util.ValueStack;
@@ -102,7 +100,7 @@ public class StrutsUtil {
return responseWrapper.getData();
}
catch (Exception e) {
- LOG.debug("Cannot include {}", aName.toString(), e);
+ LOG.debug("Cannot include {}", aName, e);
throw e;
}
}
@@ -125,7 +123,7 @@ public class StrutsUtil {
}
public String getText(String text) {
- return (String) stack.findValue("getText('" + text + "')");
+ return (String) stack.findValue("getText('" + text.replace('\'', '"') + "')");
}
/*
diff --git a/core/src/main/resources/struts-default.xml b/core/src/main/resources/struts-default.xml
index 22d11ae6b..9dd8fbfa3 100644
--- a/core/src/main/resources/struts-default.xml
+++ b/core/src/main/resources/struts-default.xml
@@ -224,6 +224,7 @@
+
diff --git a/core/src/main/resources/template/css_xhtml/form-validate.ftl b/core/src/main/resources/template/css_xhtml/form-validate.ftl
index a45aa7189..da7b24080 100644
--- a/core/src/main/resources/template/css_xhtml/form-validate.ftl
+++ b/core/src/main/resources/template/css_xhtml/form-validate.ftl
@@ -21,8 +21,8 @@
<#if parameters.validate!false == true>
<#if parameters.onsubmit??>
- ${tag.addParameter('onsubmit', "${parameters.onsubmit}; return validateForm_${parameters.id}();")}
+ ${tag.addParameter('onsubmit', "${parameters.onsubmit}; return validateForm_${parameters.escapedId}();")}
<#else>
- ${tag.addParameter('onsubmit', "return validateForm_${parameters.id}();")}
+ ${tag.addParameter('onsubmit', "return validateForm_${parameters.escapedId}();")}
#if>
#if>
diff --git a/core/src/main/resources/template/simple/combobox.ftl b/core/src/main/resources/template/simple/combobox.ftl
index 390e20df2..761b57772 100644
--- a/core/src/main/resources/template/simple/combobox.ftl
+++ b/core/src/main/resources/template/simple/combobox.ftl
@@ -21,7 +21,7 @@
>
function autoPopulate_${parameters.escapedId}(targetElement) {
<#if parameters.headerKey?? && parameters.headerValue??>
- if (targetElement.options[targetElement.selectedIndex].value == '${parameters.headerKey}') {
+ if (targetElement.options[targetElement.selectedIndex].value == '${parameters.headerKey?js_string}') {
return;
}
#if>
@@ -30,7 +30,7 @@
return;
}
#if>
- targetElement.form.elements['${parameters.name}'].value=targetElement.options[targetElement.selectedIndex].value;
+ targetElement.form.elements['${parameters.name?js_string}'].value=targetElement.options[targetElement.selectedIndex].value;
}
<#include "/${parameters.templateDir}/simple/text.ftl" />
diff --git a/core/src/main/resources/template/simple/doubleselect.ftl b/core/src/main/resources/template/simple/doubleselect.ftl
index 039e1420a..c064f29ae 100644
--- a/core/src/main/resources/template/simple/doubleselect.ftl
+++ b/core/src/main/resources/template/simple/doubleselect.ftl
@@ -75,9 +75,9 @@
#if>
>
<#assign itemCount = startCount/>
- var ${parameters.id}Group = new Array(${parameters.listSize} + ${startCount});
- for (var i = 0; i < (${parameters.listSize} + ${startCount}); i++) {
- ${parameters.id}Group[i] = [];
+ var ${parameters.escapedId}Group = new Array(${parameters.listSize?number?c} + ${startCount});
+ for (var i = 0; i < (${parameters.listSize?number?c} + ${startCount}); i++) {
+ ${parameters.escapedId}Group[i] = [];
}
<@s.iterator value="parameters.list">
@@ -93,11 +93,11 @@
#if>
<#assign doubleItemCount = 0/>
<#if parameters.doubleHeaderKey?? && parameters.doubleHeaderValue??>
- ${parameters.id}Group[${itemCount}][${doubleItemCount}] = new Option("${parameters.doubleHeaderValue?js_string}", "${parameters.doubleHeaderKey?js_string}");
+ ${parameters.escapedId}Group[${itemCount}][${doubleItemCount}] = new Option("${parameters.doubleHeaderValue?js_string}", "${parameters.doubleHeaderKey?js_string}");
<#assign doubleItemCount = doubleItemCount + 1/>
#if>
<#if parameters.doubleEmptyOption??>
- ${parameters.id}Group[${itemCount}][${doubleItemCount}] = new Option("", "");
+ ${parameters.escapedId}Group[${itemCount}][${doubleItemCount}] = new Option("", "");
<#assign doubleItemCount = doubleItemCount + 1/>
#if>
<@s.iterator value="${parameters.doubleList}">
@@ -133,15 +133,15 @@
<#assign itemDoubleTitle = ''/>
#if>
#if>
- ${parameters.id}Group[${itemCount}][${doubleItemCount}] = new Option("${doubleItemValue?js_string}", "${doubleItemKeyStr?js_string}");
+ ${parameters.escapedId}Group[${itemCount}][${doubleItemCount}] = new Option("${doubleItemValue?js_string}", "${doubleItemKeyStr?js_string}");
<#if itemDoubleCssClass??>
- ${parameters.id}Group[${itemCount}][${doubleItemCount}].setAttribute("class","${itemDoubleCssClass}");
+ ${parameters.escapedId}Group[${itemCount}][${doubleItemCount}].setAttribute("class","${itemDoubleCssClass}");
#if>
<#if itemDoubleCssStyle??>
- ${parameters.id}Group[${itemCount}][${doubleItemCount}].setAttribute("style","${itemDoubleCssStyle}");
+ ${parameters.escapedId}Group[${itemCount}][${doubleItemCount}].setAttribute("style","${itemDoubleCssStyle}");
#if>
<#if itemDoubleTitle??>
- ${parameters.id}Group[${itemCount}][${doubleItemCount}].setAttribute("title","${itemDoubleTitle}");
+ ${parameters.escapedId}Group[${itemCount}][${doubleItemCount}].setAttribute("title","${itemDoubleTitle}");
#if>
<#assign doubleItemCount = doubleItemCount + 1/>
@@ -149,7 +149,7 @@
<#assign itemCount = itemCount + 1/>
@s.iterator>
- var ${parameters.id}Temp = document.${parameters.formName}.${parameters.doubleId};
+ var ${parameters.escapedId}Temp = document.${parameters.formName}.${parameters.doubleId};
<#assign itemCount = startCount/>
<#assign redirectTo = 0/>
<@s.iterator value="parameters.list">
@@ -163,34 +163,34 @@
#if>
<#assign itemCount = itemCount + 1/>
@s.iterator>
- ${parameters.id}Redirect(${redirectTo});
- function ${parameters.id}Redirect(x) {
+ ${parameters.escapedId}Redirect(${redirectTo});
+ function ${parameters.escapedId}Redirect(x) {
var selected = false;
- for (var m = ${parameters.id}Temp.options.length - 1; m >= 0; m--) {
- ${parameters.id}Temp.remove(m);
+ for (var m = ${parameters.escapedId}Temp.options.length - 1; m >= 0; m--) {
+ ${parameters.escapedId}Temp.remove(m);
}
- for (var i = 0; i < ${parameters.id}Group[x].length; i++) {
- ${parameters.id}Temp.options[i] = new Option(${parameters.id}Group[x][i].text, ${parameters.id}Group[x][i].value);
+ for (var i = 0; i < ${parameters.escapedId}Group[x].length; i++) {
+ ${parameters.escapedId}Temp.options[i] = new Option(${parameters.escapedId}Group[x][i].text, ${parameters.escapedId}Group[x][i].value);
<#if parameters.doubleNameValue??>
<#if parameters.doubleMultiple??>
for (var j = 0; j < ${parameters.doubleNameValue}.length; j++) {
- if (${parameters.id}Temp.options[i].value == ${parameters.doubleNameValue?js_string}[j]) {
- ${parameters.id}Temp.options[i].selected = true;
+ if (${parameters.escapedId}Temp.options[i].value == ${parameters.doubleNameValue?js_string}[j]) {
+ ${parameters.escapedId}Temp.options[i].selected = true;
selected = true;
}
}
<#else>
- if (${parameters.id}Temp.options[i].value == '${parameters.doubleNameValue?js_string}') {
- ${parameters.id}Temp.options[i].selected = true;
+ if (${parameters.escapedId}Temp.options[i].value == '${parameters.doubleNameValue?js_string}') {
+ ${parameters.escapedId}Temp.options[i].selected = true;
selected = true;
}
#if>
#if>
}
- if ((${parameters.id}Temp.options.length > 0) && (! selected)) {
- ${parameters.id}Temp.options[0].selected = true;
+ if ((${parameters.escapedId}Temp.options.length > 0) && (! selected)) {
+ ${parameters.escapedId}Temp.options[0].selected = true;
}
}
diff --git a/core/src/main/resources/template/simple/form-close.ftl b/core/src/main/resources/template/simple/form-close.ftl
index 192932929..c2285b2e6 100644
--- a/core/src/main/resources/template/simple/form-close.ftl
+++ b/core/src/main/resources/template/simple/form-close.ftl
@@ -27,15 +27,15 @@
submission.
-->
<#if (parameters.optiontransferselectIds!?size > 0)>
- var containingForm = document.getElementById("${parameters.id}");
+ var containingForm = document.getElementById("${parameters.id?js_string}");
<#assign selectObjIds = parameters.optiontransferselectIds.keySet() />
<#list selectObjIds as selectObjectId>
StrutsUtils.addEventListener(containingForm, "submit",
function(evt) {
- var selectObj = document.getElementById("${selectObjectId}");
+ var selectObj = document.getElementById("${selectObjectId?js_string}");
<#if parameters.optiontransferselectIds.get(selectObjectId)??>
<#assign selectTagHeaderKey = parameters.optiontransferselectIds.get(selectObjectId)/>
- selectAllOptionsExceptSome(selectObj, "key", "${selectTagHeaderKey}");
+ selectAllOptionsExceptSome(selectObj, "key", "${selectTagHeaderKey?js_string}");
<#else>
selectAllOptionsExceptSome(selectObj, "key", "");
#if>
@@ -43,15 +43,15 @@
#list>
#if>
<#if (parameters.inputtransferselectIds!?size > 0)>
- var containingForm = document.getElementById("${parameters.id}");
+ var containingForm = document.getElementById("${parameters.id?js_string}");
<#assign selectObjIds = parameters.inputtransferselectIds.keySet() />
<#list selectObjIds as selectObjectId>
StrutsUtils.addEventListener(containingForm, "submit",
function(evt) {
- var selectObj = document.getElementById("${selectObjectId}");
+ var selectObj = document.getElementById("${selectObjectId?js_string}");
<#if parameters.inputtransferselectIds.get(selectObjectId)??>
<#assign selectTagHeaderKey = parameters.inputtransferselectIds.get(selectObjectId)/>
- selectAllOptionsExceptSome(selectObj, "key", "${selectTagHeaderKey}");
+ selectAllOptionsExceptSome(selectObj, "key", "${selectTagHeaderKey?js_string}");
<#else>
selectAllOptionsExceptSome(selectObj, "key", "");
#if>
@@ -59,15 +59,15 @@
#list>
#if>
<#if (parameters.optiontransferselectDoubleIds!?size > 0)>
- var containingForm = document.getElementById("${parameters.id}");
+ var containingForm = document.getElementById("${parameters.id?js_string}");
<#assign selectDoubleObjIds = parameters.optiontransferselectDoubleIds.keySet() />
<#list selectDoubleObjIds as selectObjId>
StrutsUtils.addEventListener(containingForm, "submit",
function(evt) {
- var selectObj = document.getElementById("${selectObjId}");
+ var selectObj = document.getElementById("${selectObjId?js_string}");
<#if parameters.optiontransferselectDoubleIds.get(selectObjId)??>
<#assign selectTagHeaderKey = parameters.optiontransferselectDoubleIds.get(selectObjId)/>
- selectAllOptionsExceptSome(selectObj, "key", "${selectTagHeaderKey}");
+ selectAllOptionsExceptSome(selectObj, "key", "${selectTagHeaderKey?js_string}");
<#else>
selectAllOptionsExceptSome(selectObj, "key", "");
#if>
@@ -81,15 +81,15 @@
submission
-->
<#if (parameters.updownselectIds!?size > 0)>
- var containingForm = document.getElementById("${parameters.id}");
+ var containingForm = document.getElementById("${parameters.id?js_string}");
<#assign tmpIds = parameters.updownselectIds.keySet() />
<#list tmpIds as tmpId>
StrutsUtils.addEventListener(containingForm, "submit",
function(evt) {
- var updownselectObj = document.getElementById("${tmpId}");
+ var updownselectObj = document.getElementById("${tmpId?js_string}");
<#if parameters.updownselectIds.get(tmpId)??>
<#assign tmpHeaderKey = parameters.updownselectIds.get(tmpId) />
- selectAllOptionsExceptSome(updownselectObj, "key", "${tmpHeaderKey}");
+ selectAllOptionsExceptSome(updownselectObj, "key", "${tmpHeaderKey?js_string}");
<#else>
selectAllOptionsExceptSome(updownselectObj, "key", "");
#if>
diff --git a/core/src/main/resources/template/xhtml/form-close-validate.ftl b/core/src/main/resources/template/xhtml/form-close-validate.ftl
index 0a17aac84..afa1c50ae 100644
--- a/core/src/main/resources/template/xhtml/form-close-validate.ftl
+++ b/core/src/main/resources/template/xhtml/form-close-validate.ftl
@@ -33,7 +33,7 @@ END SNIPPET: supported-validators
-->
<#if ((parameters.validate!false == true) && (parameters.performValidation!false == true))>
>
- function validateForm_${parameters.id?replace('[^a-zA-Z0-9_]', '_', 'r')}() {
+ function validateForm_${parameters.escapedId}() {
<#--
In case of multiselect fields return only the first value.
-->
@@ -54,7 +54,7 @@ END SNIPPET: supported-validators
}
return field.value;
}
- form = document.getElementById("${parameters.id}");
+ form = document.getElementById("${parameters.id?js_string}");
clearErrorMessages(form);
clearErrorLabels(form);
@@ -62,10 +62,10 @@ END SNIPPET: supported-validators
var continueValidation = true;
<#list parameters.tagNames as tagName>
<#list tag.getValidators("${tagName}") as aValidator>
- // field name: ${aValidator.fieldName}
+ // field name: ${aValidator.fieldName?js_string}
// validator name: ${aValidator.validatorType}
- if (form.elements['${aValidator.fieldName}']) {
- field = form.elements['${aValidator.fieldName}'];
+ if (form.elements['${aValidator.fieldName?js_string}']) {
+ field = form.elements['${aValidator.fieldName?js_string}'];
<#if aValidator.validatorType = "field-visitor">
<#assign validator = aValidator.fieldValidator >
//visitor validator switched to: ${validator.validatorType}
diff --git a/core/src/main/resources/template/xhtml/form-close.ftl b/core/src/main/resources/template/xhtml/form-close.ftl
index 0992f8e41..b8a90fc79 100644
--- a/core/src/main/resources/template/xhtml/form-close.ftl
+++ b/core/src/main/resources/template/xhtml/form-close.ftl
@@ -24,7 +24,7 @@
<#if parameters.focusElement??>
>
StrutsUtils.addOnLoad(function() {
- var element = document.getElementById("${parameters.focusElement}");
+ var element = document.getElementById("${parameters.focusElement?js_string}");
if(element) {
element.focus();
}
diff --git a/core/src/main/resources/template/xhtml/form-validate.ftl b/core/src/main/resources/template/xhtml/form-validate.ftl
index 33e8b5507..2880ffbf1 100644
--- a/core/src/main/resources/template/xhtml/form-validate.ftl
+++ b/core/src/main/resources/template/xhtml/form-validate.ftl
@@ -21,8 +21,8 @@
<#if parameters.validate!false == true>
>
<#if parameters.onsubmit??>
- ${tag.addParameter('onsubmit', "${parameters.onsubmit}; return validateForm_${parameters.id?replace('[^a-zA-Z0-9_]', '_', 'r')}();")}
+ ${tag.addParameter('onsubmit', "${parameters.onsubmit}; return validateForm_${parameters.escapedId}();")}
<#else>
- ${tag.addParameter('onsubmit', "return validateForm_${parameters.id?replace('[^a-zA-Z0-9_]', '_', 'r')}();")}
+ ${tag.addParameter('onsubmit', "return validateForm_${parameters.escapedId}();")}
#if>
#if>
diff --git a/core/src/test/java/com/opensymphony/xwork2/ChainResultTest.java b/core/src/test/java/com/opensymphony/xwork2/ChainResultTest.java
index 84fa0afa1..2806de198 100644
--- a/core/src/test/java/com/opensymphony/xwork2/ChainResultTest.java
+++ b/core/src/test/java/com/opensymphony/xwork2/ChainResultTest.java
@@ -20,6 +20,7 @@ package com.opensymphony.xwork2;
import com.mockobjects.dynamic.Mock;
import com.opensymphony.xwork2.config.providers.XmlConfigurationProvider;
+import com.opensymphony.xwork2.mock.MockResult;
import com.opensymphony.xwork2.util.ValueStack;
import junit.framework.TestCase;
import org.apache.struts2.StrutsException;
@@ -124,7 +125,18 @@ public class ChainResultTest extends XWorkTestCase {
}
}
- private class NamespaceActionNameTestActionProxyFactory implements ActionProxyFactory {
+ public void testNamespaceChain() throws Exception {
+ ActionProxy proxy = actionProxyFactory.createActionProxy(null, "chain_with_namespace", null, null);
+ ((SimpleAction)proxy.getAction()).setBlah("%{foo}");
+
+ proxy.execute();
+
+ assertTrue(proxy.getInvocation().getResult() instanceof MockResult);
+ MockResult result = (MockResult) proxy.getInvocation().getResult();
+ assertEquals("%{foo}", result.getInvocation().getProxy().getNamespace());
+ }
+
+ private static class NamespaceActionNameTestActionProxyFactory implements ActionProxyFactory {
private final ActionProxy returnVal;
private final String expectedActionName;
private final String expectedNamespace;
diff --git a/core/src/test/java/com/opensymphony/xwork2/interceptor/AliasInterceptorTest.java b/core/src/test/java/com/opensymphony/xwork2/interceptor/AliasInterceptorTest.java
index 3671de9b7..00e91411e 100644
--- a/core/src/test/java/com/opensymphony/xwork2/interceptor/AliasInterceptorTest.java
+++ b/core/src/test/java/com/opensymphony/xwork2/interceptor/AliasInterceptorTest.java
@@ -34,6 +34,10 @@ import org.apache.struts2.dispatcher.HttpParameters;
import java.util.HashMap;
import java.util.Map;
+import static com.opensymphony.xwork2.security.DefaultAcceptedPatternsCheckerTest.ACCEPT_ALL_PATTERNS_CHECKER;
+import static com.opensymphony.xwork2.security.DefaultExcludedPatternsCheckerTest.NO_EXCLUSION_PATTERNS_CHECKER;
+import static org.junit.Assert.assertNotEquals;
+
/**
* AliasInterceptorTest
@@ -73,6 +77,92 @@ public class AliasInterceptorTest extends XWorkTestCase {
assertNull(actionOne.getBlah()); // WW-5087
}
+ public void testNameNotAccepted() throws Exception {
+ Map params = new HashMap<>();
+ params.put("aliasSource", "source here");
+
+ Map httpParams = new HashMap<>();
+ httpParams.put("name", "getAliasSource()");
+ httpParams.put("value", "aliasDest");
+ params.put("parameters", HttpParameters.create(httpParams).build());
+
+
+ XmlConfigurationProvider provider = new StrutsXmlConfigurationProvider("xwork-sample.xml");
+ container.inject(provider);
+ loadConfigurationProviders(provider);
+ ActionProxy proxy = actionProxyFactory.createActionProxy("", "dynamicAliasTest", null, params);
+ SimpleAction actionOne = (SimpleAction) proxy.getAction();
+ actionOne.setAliasSource("name to be copied");
+
+ // prevent ERROR result
+ actionOne.setFoo(-1);
+ actionOne.setBar(1);
+
+ proxy.execute();
+ assertEquals("name to be copied", actionOne.getAliasSource());
+ assertNotEquals(actionOne.getAliasSource(), actionOne.getAliasDest());
+
+ proxy = actionProxyFactory.createActionProxy("", "dynamicAliasTest", null, params);
+ ((AliasInterceptor)proxy.getConfig().getInterceptors().get(1).getInterceptor())
+ .setExcludedPatterns(NO_EXCLUSION_PATTERNS_CHECKER);
+ ((AliasInterceptor)proxy.getConfig().getInterceptors().get(1).getInterceptor())
+ .setAcceptedPatterns(ACCEPT_ALL_PATTERNS_CHECKER);
+
+ actionOne = (SimpleAction) proxy.getAction();
+ actionOne.setAliasSource("name to be copied");
+
+ // prevent ERROR result
+ actionOne.setFoo(-1);
+ actionOne.setBar(1);
+
+ proxy.execute();
+ assertEquals("name to be copied", actionOne.getAliasSource());
+ assertEquals(actionOne.getAliasSource(), actionOne.getAliasDest());
+ }
+
+ public void testValueNotAccepted() throws Exception {
+ Map params = new HashMap<>();
+ params.put("aliasSource", "source here");
+
+ Map httpParams = new HashMap<>();
+ httpParams.put("name", "aliasSource");
+ httpParams.put("value", "[0].aliasDest");
+ params.put("parameters", HttpParameters.create(httpParams).build());
+
+
+ XmlConfigurationProvider provider = new StrutsXmlConfigurationProvider("xwork-sample.xml");
+ container.inject(provider);
+ loadConfigurationProviders(provider);
+ ActionProxy proxy = actionProxyFactory.createActionProxy("", "dynamicAliasTest", null, params);
+ SimpleAction actionOne = (SimpleAction) proxy.getAction();
+ actionOne.setAliasSource("name to be copied");
+
+ // prevent ERROR result
+ actionOne.setFoo(-1);
+ actionOne.setBar(1);
+
+ proxy.execute();
+ assertEquals("name to be copied", actionOne.getAliasSource());
+ assertNotEquals(actionOne.getAliasSource(), actionOne.getAliasDest());
+
+ proxy = actionProxyFactory.createActionProxy("", "dynamicAliasTest", null, params);
+ ((AliasInterceptor) proxy.getConfig().getInterceptors().get(1).getInterceptor())
+ .setExcludedPatterns(NO_EXCLUSION_PATTERNS_CHECKER);
+ ((AliasInterceptor) proxy.getConfig().getInterceptors().get(1).getInterceptor())
+ .setAcceptedPatterns(ACCEPT_ALL_PATTERNS_CHECKER);
+
+ actionOne = (SimpleAction) proxy.getAction();
+ actionOne.setAliasSource("name to be copied");
+
+ // prevent ERROR result
+ actionOne.setFoo(-1);
+ actionOne.setBar(1);
+
+ proxy.execute();
+ assertEquals("name to be copied", actionOne.getAliasSource());
+ assertEquals(actionOne.getAliasSource(), actionOne.getAliasDest());
+ }
+
public void testNotExisting() throws Exception {
Map params = new HashMap<>();
Map httpParams = new HashMap<>();
diff --git a/core/src/test/java/com/opensymphony/xwork2/security/DefaultAcceptedPatternsCheckerTest.java b/core/src/test/java/com/opensymphony/xwork2/security/DefaultAcceptedPatternsCheckerTest.java
index 7100f6cf2..050e56897 100644
--- a/core/src/test/java/com/opensymphony/xwork2/security/DefaultAcceptedPatternsCheckerTest.java
+++ b/core/src/test/java/com/opensymphony/xwork2/security/DefaultAcceptedPatternsCheckerTest.java
@@ -233,4 +233,32 @@ public class DefaultAcceptedPatternsCheckerTest extends XWorkTestCase {
// then
assertFalse("Dash was accepted", accepted.isAccepted());
}
+
+
+ public static final AcceptedPatternsChecker ACCEPT_ALL_PATTERNS_CHECKER = new AcceptedPatternsChecker() {
+ @Override
+ public IsAccepted isAccepted(String value) {
+ return IsAccepted.yes(".*");
+ }
+
+ @Override
+ public void setAcceptedPatterns(String commaDelimitedPatterns) {
+
+ }
+
+ @Override
+ public void setAcceptedPatterns(String[] patterns) {
+
+ }
+
+ @Override
+ public void setAcceptedPatterns(Set patterns) {
+
+ }
+
+ @Override
+ public Set getAcceptedPatterns() {
+ return null;
+ }
+ };
}
diff --git a/core/src/test/java/com/opensymphony/xwork2/security/DefaultExcludedPatternsCheckerTest.java b/core/src/test/java/com/opensymphony/xwork2/security/DefaultExcludedPatternsCheckerTest.java
index 8b88360cb..738def5d6 100644
--- a/core/src/test/java/com/opensymphony/xwork2/security/DefaultExcludedPatternsCheckerTest.java
+++ b/core/src/test/java/com/opensymphony/xwork2/security/DefaultExcludedPatternsCheckerTest.java
@@ -22,6 +22,7 @@ import com.opensymphony.xwork2.XWorkTestCase;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.regex.Pattern;
@@ -216,4 +217,32 @@ public class DefaultExcludedPatternsCheckerTest extends XWorkTestCase {
// Expected result
}
}
+
+
+ public static final ExcludedPatternsChecker NO_EXCLUSION_PATTERNS_CHECKER = new ExcludedPatternsChecker() {
+ @Override
+ public IsExcluded isExcluded(String value) {
+ return IsExcluded.no(new HashSet<>());
+ }
+
+ @Override
+ public void setExcludedPatterns(String commaDelimitedPatterns) {
+
+ }
+
+ @Override
+ public void setExcludedPatterns(String[] patterns) {
+
+ }
+
+ @Override
+ public void setExcludedPatterns(Set patterns) {
+
+ }
+
+ @Override
+ public Set getExcludedPatterns() {
+ return null;
+ }
+ };
}
diff --git a/core/src/test/java/com/opensymphony/xwork2/security/DefaultNotExcludedAcceptedPatternsCheckerTest.java b/core/src/test/java/com/opensymphony/xwork2/security/DefaultNotExcludedAcceptedPatternsCheckerTest.java
new file mode 100644
index 000000000..85f5b71b9
--- /dev/null
+++ b/core/src/test/java/com/opensymphony/xwork2/security/DefaultNotExcludedAcceptedPatternsCheckerTest.java
@@ -0,0 +1,91 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package com.opensymphony.xwork2.security;
+
+import com.opensymphony.xwork2.XWorkTestCase;
+
+import java.util.Set;
+import java.util.regex.Pattern;
+
+import static org.junit.Assert.*;
+
+public class DefaultNotExcludedAcceptedPatternsCheckerTest extends XWorkTestCase {
+
+ public void testNoExclusionAcceptAllPatternsChecker() {
+ assertTrue(NO_EXCLUSION_ACCEPT_ALL_PATTERNS_CHECKER.isAllowed("%{1+1}").isAllowed());
+ }
+
+ public static final NotExcludedAcceptedPatternsChecker NO_EXCLUSION_ACCEPT_ALL_PATTERNS_CHECKER
+ = new NotExcludedAcceptedPatternsChecker() {
+ @Override
+ public IsAllowed isAllowed(String value) {
+ return IsAllowed.yes("*");
+ }
+
+ @Override
+ public IsAccepted isAccepted(String value) {
+ return null;
+ }
+
+ @Override
+ public void setAcceptedPatterns(String commaDelimitedPatterns) {
+
+ }
+
+ @Override
+ public void setAcceptedPatterns(String[] patterns) {
+
+ }
+
+ @Override
+ public void setAcceptedPatterns(Set patterns) {
+
+ }
+
+ @Override
+ public Set getAcceptedPatterns() {
+ return null;
+ }
+
+ @Override
+ public IsExcluded isExcluded(String value) {
+ return null;
+ }
+
+ @Override
+ public void setExcludedPatterns(String commaDelimitedPatterns) {
+
+ }
+
+ @Override
+ public void setExcludedPatterns(String[] patterns) {
+
+ }
+
+ @Override
+ public void setExcludedPatterns(Set patterns) {
+
+ }
+
+ @Override
+ public Set getExcludedPatterns() {
+ return null;
+ }
+ };
+}
\ No newline at end of file
diff --git a/core/src/test/java/org/apache/struts2/TestConfigurationProvider.java b/core/src/test/java/org/apache/struts2/TestConfigurationProvider.java
index 9e093f86a..ce6bcc10c 100644
--- a/core/src/test/java/org/apache/struts2/TestConfigurationProvider.java
+++ b/core/src/test/java/org/apache/struts2/TestConfigurationProvider.java
@@ -33,7 +33,9 @@ import com.opensymphony.xwork2.inject.ContainerBuilder;
import com.opensymphony.xwork2.interceptor.ParametersInterceptor;
import com.opensymphony.xwork2.mock.MockResult;
import com.opensymphony.xwork2.security.DefaultExcludedPatternsChecker;
+import com.opensymphony.xwork2.security.DefaultNotExcludedAcceptedPatternsChecker;
import com.opensymphony.xwork2.security.ExcludedPatternsChecker;
+import com.opensymphony.xwork2.security.NotExcludedAcceptedPatternsChecker;
import com.opensymphony.xwork2.util.location.LocatableProperties;
import com.opensymphony.xwork2.validator.ValidationInterceptor;
import org.apache.struts2.result.ServletDispatcherResult;
@@ -42,6 +44,7 @@ import org.apache.struts2.interceptor.TokenSessionStoreInterceptor;
import org.apache.struts2.views.jsp.ui.DoubleValidationAction;
import java.util.HashMap;
+import java.util.Map;
/**
@@ -74,7 +77,7 @@ public class TestConfigurationProvider implements ConfigurationProvider {
*/
public void loadPackages() {
- HashMap successParams = new HashMap();
+ Map successParams = new HashMap<>();
successParams.put("propertyName", "executionCount");
successParams.put("expectedValue", "1");
@@ -149,9 +152,7 @@ public class TestConfigurationProvider implements ConfigurationProvider {
}
/**
- * Tells whether the ConfigurationProvider should reload its configuration
- *
- * @return
+ * @return whether the ConfigurationProvider should reload its configuration
*/
public boolean needsReload() {
return false;
@@ -167,5 +168,8 @@ public class TestConfigurationProvider implements ConfigurationProvider {
if (!builder.contains(ExcludedPatternsChecker.class)) {
builder.factory(ExcludedPatternsChecker.class, DefaultExcludedPatternsChecker.class);
}
+ if (!builder.contains(NotExcludedAcceptedPatternsChecker.class)) {
+ builder.factory(NotExcludedAcceptedPatternsChecker.class, DefaultNotExcludedAcceptedPatternsChecker.class);
+ }
}
}
diff --git a/core/src/test/java/org/apache/struts2/components/UIBeanTest.java b/core/src/test/java/org/apache/struts2/components/UIBeanTest.java
index e4b0c6373..90575f341 100644
--- a/core/src/test/java/org/apache/struts2/components/UIBeanTest.java
+++ b/core/src/test/java/org/apache/struts2/components/UIBeanTest.java
@@ -25,7 +25,6 @@ import org.apache.struts2.StrutsInternalTestCase;
import org.apache.struts2.components.template.Template;
import org.apache.struts2.components.template.TemplateEngine;
import org.apache.struts2.components.template.TemplateEngineManager;
-import org.apache.struts2.dispatcher.DefaultStaticContentLoader;
import org.apache.struts2.dispatcher.StaticContentLoader;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
@@ -35,9 +34,11 @@ import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
+import static com.opensymphony.xwork2.security.DefaultNotExcludedAcceptedPatternsCheckerTest.NO_EXCLUSION_ACCEPT_ALL_PATTERNS_CHECKER;
+
public class UIBeanTest extends StrutsInternalTestCase {
- public void testPopulateComponentHtmlId1() throws Exception {
+ public void testPopulateComponentHtmlId1() {
ValueStack stack = ActionContext.getContext().getValueStack();
MockHttpServletRequest req = new MockHttpServletRequest();
MockHttpServletResponse res = new MockHttpServletResponse();
@@ -53,7 +54,7 @@ public class UIBeanTest extends StrutsInternalTestCase {
assertEquals("txtFldId", txtFld.getParameters().get("id"));
}
- public void testPopulateComponentHtmlIdWithOgnl() throws Exception {
+ public void testPopulateComponentHtmlIdWithOgnl() {
ValueStack stack = ActionContext.getContext().getValueStack();
MockHttpServletRequest req = new MockHttpServletRequest();
MockHttpServletResponse res = new MockHttpServletResponse();
@@ -69,7 +70,7 @@ public class UIBeanTest extends StrutsInternalTestCase {
assertEquals("formId_txtFldName1", txtFld.getParameters().get("id"));
}
- public void testPopulateComponentHtmlId2() throws Exception {
+ public void testPopulateComponentHtmlId2() {
ValueStack stack = ActionContext.getContext().getValueStack();
MockHttpServletRequest req = new MockHttpServletRequest();
MockHttpServletResponse res = new MockHttpServletResponse();
@@ -85,7 +86,7 @@ public class UIBeanTest extends StrutsInternalTestCase {
assertEquals("formId_txtFldName", txtFld.getParameters().get("id"));
}
- public void testPopulateComponentHtmlWithoutNameAndId() throws Exception {
+ public void testPopulateComponentHtmlWithoutNameAndId() {
ValueStack stack = ActionContext.getContext().getValueStack();
MockHttpServletRequest req = new MockHttpServletRequest();
MockHttpServletResponse res = new MockHttpServletResponse();
@@ -97,10 +98,10 @@ public class UIBeanTest extends StrutsInternalTestCase {
txtFld.populateComponentHtmlId(form);
- assertEquals(null, txtFld.getParameters().get("id"));
+ assertNull(txtFld.getParameters().get("id"));
}
- public void testEscape() throws Exception {
+ public void testEscape() {
ValueStack stack = ActionContext.getContext().getValueStack();
MockHttpServletRequest req = new MockHttpServletRequest();
MockHttpServletResponse res = new MockHttpServletResponse();
@@ -113,11 +114,11 @@ public class UIBeanTest extends StrutsInternalTestCase {
assertEquals(bean.escape("hello[world"), "hello_world");
assertEquals(bean.escape("hello.world"), "hello_world");
assertEquals(bean.escape("hello]world"), "hello_world");
- assertEquals(bean.escape("hello!world"), "hello!world");
- assertEquals(bean.escape("hello!@#$%^&*()world"), "hello!@#$%^&*()world");
+ assertEquals(bean.escape("hello!world"), "hello_world");
+ assertEquals(bean.escape("hello!@#$%^&*()world"), "hello__________world");
}
- public void testEscapeId() throws Exception {
+ public void testEscapeId() {
ValueStack stack = ActionContext.getContext().getValueStack();
MockHttpServletRequest req = new MockHttpServletRequest();
MockHttpServletResponse res = new MockHttpServletResponse();
@@ -131,7 +132,7 @@ public class UIBeanTest extends StrutsInternalTestCase {
assertEquals("formId_foo_bar", txtFld.getParameters().get("id"));
}
- public void testGetThemeFromForm() throws Exception {
+ public void testGetThemeFromForm() {
ValueStack stack = ActionContext.getContext().getValueStack();
MockHttpServletRequest req = new MockHttpServletRequest();
MockHttpServletResponse res = new MockHttpServletResponse();
@@ -143,29 +144,29 @@ public class UIBeanTest extends StrutsInternalTestCase {
assertEquals("foo", txtFld.getTheme());
}
- public void testGetThemeFromContext() throws Exception {
+ public void testGetThemeFromContext() {
ValueStack stack = ActionContext.getContext().getValueStack();
MockHttpServletRequest req = new MockHttpServletRequest();
MockHttpServletResponse res = new MockHttpServletResponse();
- Map context = Collections.singletonMap("theme", "bar");
+ Map context = Collections.singletonMap("theme", "bar");
ActionContext.getContext().put("attr", context);
TextField txtFld = new TextField(stack, req, res);
assertEquals("bar", txtFld.getTheme());
}
- public void testGetThemeFromContextNonString() throws Exception {
+ public void testGetThemeFromContextNonString() {
ValueStack stack = ActionContext.getContext().getValueStack();
MockHttpServletRequest req = new MockHttpServletRequest();
MockHttpServletResponse res = new MockHttpServletResponse();
- Map context = Collections.singletonMap("theme", 12);
+ Map context = Collections.singletonMap("theme", 12);
ActionContext.getContext().put("attr", context);
TextField txtFld = new TextField(stack, req, res);
assertEquals("12", txtFld.getTheme());
}
- public void testMergeTemplateNullEngineException() throws Exception {
+ public void testMergeTemplateNullEngineException() {
ValueStack stack = ActionContext.getContext().getValueStack();
MockHttpServletRequest req = new MockHttpServletRequest();
MockHttpServletResponse res = new MockHttpServletResponse();
@@ -187,7 +188,7 @@ public class UIBeanTest extends StrutsInternalTestCase {
}
}
- public void testBuildTemplate() throws Exception {
+ public void testBuildTemplate() {
String defaultTemplateName = "default";
String customTemplateName = "custom";
ValueStack stack = ActionContext.getContext().getValueStack();
@@ -203,14 +204,14 @@ public class UIBeanTest extends StrutsInternalTestCase {
assertEquals(customTemplateName, customTemplate.getName());
}
- public void testGetTemplateDirExplicit() throws Exception {
+ public void testGetTemplateDirExplicit() {
String explicitTemplateDir = "explicitTemplateDirectory";
String attrTemplateDir = "attrTemplateDirectory";
String defaultTemplateDir = "defaultTemplateDirectory";
ValueStack stack = ActionContext.getContext().getValueStack();
MockHttpServletRequest req = new MockHttpServletRequest();
MockHttpServletResponse res = new MockHttpServletResponse();
- Map context = Collections.singletonMap("templateDir", attrTemplateDir);
+ Map context = Collections.singletonMap("templateDir", attrTemplateDir);
ActionContext.getContext().put("attr", context);
TextField txtFld = new TextField(stack, req, res);
@@ -220,13 +221,13 @@ public class UIBeanTest extends StrutsInternalTestCase {
assertEquals(explicitTemplateDir, txtFld.getTemplateDir());
}
- public void testGetTemplateDirAttr() throws Exception {
+ public void testGetTemplateDirAttr() {
String attrTemplateDir = "attrTemplateDirectory";
String defaultTemplateDir = "defaultTemplateDirectory";
ValueStack stack = ActionContext.getContext().getValueStack();
MockHttpServletRequest req = new MockHttpServletRequest();
MockHttpServletResponse res = new MockHttpServletResponse();
- Map context = Collections.singletonMap("templateDir", attrTemplateDir);
+ Map context = Collections.singletonMap("templateDir", attrTemplateDir);
ActionContext.getContext().put("attr", context);
TextField txtFld = new TextField(stack, req, res);
@@ -235,7 +236,7 @@ public class UIBeanTest extends StrutsInternalTestCase {
assertEquals(attrTemplateDir, txtFld.getTemplateDir());
}
- public void testGetTemplateDirDefault() throws Exception {
+ public void testGetTemplateDirDefault() {
String defaultTemplateDir = "defaultTemplateDirectory";
ValueStack stack = ActionContext.getContext().getValueStack();
MockHttpServletRequest req = new MockHttpServletRequest();
@@ -247,7 +248,7 @@ public class UIBeanTest extends StrutsInternalTestCase {
assertEquals(defaultTemplateDir, txtFld.getTemplateDir());
}
- public void testGetTemplateDirNoneSet() throws Exception {
+ public void testGetTemplateDirNoneSet() {
ValueStack stack = ActionContext.getContext().getValueStack();
MockHttpServletRequest req = new MockHttpServletRequest();
MockHttpServletResponse res = new MockHttpServletResponse();
@@ -298,10 +299,58 @@ public class UIBeanTest extends StrutsInternalTestCase {
});
TextField txtFld = new TextField(stack, req, res);
+ container.inject(txtFld);
txtFld.setName("%{myValue}");
txtFld.evaluateParams();
assertEquals("%{myBad}", txtFld.getParameters().get("nameValue"));
+ assertEquals("%{myBad}", txtFld.getParameters().get("name"));
+ }
+
+ public void testValueNameParameterNotAccepted() {
+ ValueStack stack = ActionContext.getContext().getValueStack();
+ MockHttpServletRequest req = new MockHttpServletRequest();
+ MockHttpServletResponse res = new MockHttpServletResponse();
+
+ stack.push(new Object() {
+ public String getMyValueName() {
+ return "getMyValue()";
+ }
+ public String getMyValue() {
+ return "value";
+ }
+ });
+
+ TextField txtFld = new TextField(stack, req, res);
+ container.inject(txtFld);
+ txtFld.setName("%{myValueName}");
+ txtFld.evaluateParams();
+ assertEquals("getMyValue()", txtFld.getParameters().get("name"));
+ assertEquals("getMyValue()", txtFld.getParameters().get("nameValue"));
+
+ txtFld.setNotExcludedAcceptedPatterns(NO_EXCLUSION_ACCEPT_ALL_PATTERNS_CHECKER);
+ txtFld.evaluateParams();
+ assertEquals("getMyValue()", txtFld.getParameters().get("name"));
+ assertEquals("value", txtFld.getParameters().get("nameValue"));
+ }
+
+ public void testValueNameParameterGetterAccepted() {
+ ValueStack stack = ActionContext.getContext().getValueStack();
+ MockHttpServletRequest req = new MockHttpServletRequest();
+ MockHttpServletResponse res = new MockHttpServletResponse();
+
+ stack.push(new Object() {
+ public String getMyValue() {
+ return "value";
+ }
+ });
+
+ TextField txtFld = new TextField(stack, req, res);
+ container.inject(txtFld);
+ txtFld.setName("getMyValue()");
+ txtFld.evaluateParams();
+ assertEquals("getMyValue()", txtFld.getParameters().get("name"));
+ assertEquals("value", txtFld.getParameters().get("nameValue"));
}
public void testSetClass() {
diff --git a/core/src/test/java/org/apache/struts2/result/PostbackResultTest.java b/core/src/test/java/org/apache/struts2/result/PostbackResultTest.java
index 1cc6d24d4..42d9330fa 100644
--- a/core/src/test/java/org/apache/struts2/result/PostbackResultTest.java
+++ b/core/src/test/java/org/apache/struts2/result/PostbackResultTest.java
@@ -93,6 +93,46 @@ public class PostbackResultTest extends StrutsInternalTestCase {
control.verify();
}
+ public void testExpressionNamespace() throws Exception {
+
+ ActionContext context = ActionContext.getContext();
+ context.getContextMap().put("namespaceName", "${1-1}");
+ context.getContextMap().put("actionName", "${1-1}");
+ context.getContextMap().put("methodName", "${1-1}");
+ ValueStack stack = context.getValueStack();
+ MockHttpServletRequest req = new MockHttpServletRequest();
+ MockHttpServletResponse res = new MockHttpServletResponse();
+ context.put(ServletActionContext.HTTP_REQUEST, req);
+ context.put(ServletActionContext.HTTP_RESPONSE, res);
+
+ PostbackResult result = new PostbackResult();
+ result.setNamespace("/myNamespace${#namespaceName}");
+ result.setActionName("myAction${#actionName}");
+ result.setMethod("myMethod${#methodName}");
+ result.setPrependServletContext(false);
+
+ IMocksControl control = createControl();
+ ActionInvocation mockInvocation = control.createMock(ActionInvocation.class);
+ expect(mockInvocation.getInvocationContext()).andReturn(context).anyTimes();
+ expect(mockInvocation.getStack()).andReturn(stack).anyTimes();
+
+ control.replay();
+ result.setActionMapper(container.getInstance(ActionMapper.class));
+ result.execute(mockInvocation);
+ assertEquals("