* Note: Since 2.5.2, this interceptor extends {@link MethodFilterInterceptor}, therefore being
* able to deal with excludeMethods / includeMethods parameters. See [Workflow Interceptor]
* (class {@link DefaultWorkflowInterceptor}) for documentation and examples on how to use this feature.
*
- *
+ *
*
*
*
Interceptor parameters:
@@ -84,7 +84,10 @@
*
*
* @author Jason Carreira
+ *
+ * @deprecated since 6.7.0, use {@link org.apache.struts2.interceptor.ConversionErrorInterceptor} instead.
*/
+@Deprecated
public class ConversionErrorInterceptor extends MethodFilterInterceptor {
public static final String ORIGINAL_PROPERTY_OVERRIDE = "original.property.override";
diff --git a/core/src/main/java/com/opensymphony/xwork2/interceptor/DefaultWorkflowInterceptor.java b/core/src/main/java/com/opensymphony/xwork2/interceptor/DefaultWorkflowInterceptor.java
index d2cbd0b780..913638913b 100644
--- a/core/src/main/java/com/opensymphony/xwork2/interceptor/DefaultWorkflowInterceptor.java
+++ b/core/src/main/java/com/opensymphony/xwork2/interceptor/DefaultWorkflowInterceptor.java
@@ -29,7 +29,7 @@
/**
*
*
- * An interceptor that makes sure there are not validation, conversion or action errors before allowing the interceptor chain to continue.
+ * An interceptor that makes sure there are not validation, conversion or action errors before allowing the interceptor chain to continue.
* If a single FieldError or ActionError (including the ones replicated by the Message Store Interceptor in a redirection) is found, the INPUT result will be triggered.
* This interceptor does not perform any validation.
*
@@ -129,7 +129,10 @@
* @author Alexandru Popescu
* @author Philip Luppens
* @author tm_jee
+ *
+ * @deprecated since 6.7.0, use {@link org.apache.struts2.interceptor.DefaultWorkflowInterceptor} instead.
*/
+@Deprecated
public class DefaultWorkflowInterceptor extends MethodFilterInterceptor {
private static final long serialVersionUID = 7563014655616490865L;
diff --git a/core/src/main/java/com/opensymphony/xwork2/interceptor/ExceptionMappingInterceptor.java b/core/src/main/java/com/opensymphony/xwork2/interceptor/ExceptionMappingInterceptor.java
index e60550ca69..3bb70bcb8a 100644
--- a/core/src/main/java/com/opensymphony/xwork2/interceptor/ExceptionMappingInterceptor.java
+++ b/core/src/main/java/com/opensymphony/xwork2/interceptor/ExceptionMappingInterceptor.java
@@ -20,8 +20,8 @@
import com.opensymphony.xwork2.ActionInvocation;
import com.opensymphony.xwork2.config.entities.ExceptionMappingConfig;
-import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
import org.apache.struts2.dispatcher.HttpParameters;
import java.util.List;
@@ -153,7 +153,10 @@
*
* @author Matthew E. Porter (matthew dot porter at metissian dot com)
* @author Claus Ibsen
+ *
+ * @deprecated since 6.7.0, use {@link org.apache.struts2.interceptor.ExceptionMappingInterceptor} instead.
*/
+@Deprecated
public class ExceptionMappingInterceptor extends AbstractInterceptor {
private static final Logger LOG = LogManager.getLogger(ExceptionMappingInterceptor.class);
diff --git a/core/src/main/java/com/opensymphony/xwork2/interceptor/LoggingInterceptor.java b/core/src/main/java/com/opensymphony/xwork2/interceptor/LoggingInterceptor.java
index 6ba498b3cb..3f012288c2 100644
--- a/core/src/main/java/com/opensymphony/xwork2/interceptor/LoggingInterceptor.java
+++ b/core/src/main/java/com/opensymphony/xwork2/interceptor/LoggingInterceptor.java
@@ -59,7 +59,10 @@
*
*
* @author Jason Carreira
+ *
+ * @deprecated since 6.7.0, use {@link org.apache.struts2.interceptor.LoggingInterceptor} instead.
*/
+@Deprecated
public class LoggingInterceptor extends AbstractInterceptor {
private static final Logger LOG = LogManager.getLogger(LoggingInterceptor.class);
private static final String FINISH_MESSAGE = "Finishing execution stack for action ";
diff --git a/core/src/main/java/com/opensymphony/xwork2/interceptor/ModelDrivenInterceptor.java b/core/src/main/java/com/opensymphony/xwork2/interceptor/ModelDrivenInterceptor.java
index fa90a315ca..a22075d115 100644
--- a/core/src/main/java/com/opensymphony/xwork2/interceptor/ModelDrivenInterceptor.java
+++ b/core/src/main/java/com/opensymphony/xwork2/interceptor/ModelDrivenInterceptor.java
@@ -71,10 +71,13 @@
* </action>
*
*
- *
+ *
* @author tm_jee
* @version $Date$ $Id$
+ *
+ * @deprecated since 6.7.0, use {@link org.apache.struts2.interceptor.ModelDrivenInterceptor} instead.
*/
+@Deprecated
public class ModelDrivenInterceptor extends AbstractInterceptor {
protected boolean refreshModelBeforeResult = false;
diff --git a/core/src/main/java/com/opensymphony/xwork2/interceptor/ParameterRemoverInterceptor.java b/core/src/main/java/com/opensymphony/xwork2/interceptor/ParameterRemoverInterceptor.java
index c0f83765c2..f33ebf6e27 100644
--- a/core/src/main/java/com/opensymphony/xwork2/interceptor/ParameterRemoverInterceptor.java
+++ b/core/src/main/java/com/opensymphony/xwork2/interceptor/ParameterRemoverInterceptor.java
@@ -66,7 +66,10 @@
* ...
* </action>
*
+ *
+ * @deprecated since 6.7.0, use {@link org.apache.struts2.interceptor.ParameterRemoverInterceptor} instead.
*/
+@Deprecated
public class ParameterRemoverInterceptor extends AbstractInterceptor {
private static final Logger LOG = LogManager.getLogger(ParameterRemoverInterceptor.class);
diff --git a/core/src/main/java/com/opensymphony/xwork2/interceptor/PrefixMethodInvocationUtil.java b/core/src/main/java/com/opensymphony/xwork2/interceptor/PrefixMethodInvocationUtil.java
index 040080824e..0ac840c7a8 100644
--- a/core/src/main/java/com/opensymphony/xwork2/interceptor/PrefixMethodInvocationUtil.java
+++ b/core/src/main/java/com/opensymphony/xwork2/interceptor/PrefixMethodInvocationUtil.java
@@ -19,8 +19,8 @@
package com.opensymphony.xwork2.interceptor;
import com.opensymphony.xwork2.ActionInvocation;
-import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
@@ -28,7 +28,7 @@
/**
*
* A utility class for invoking prefixed methods in action class.
- *
+ *
* Interceptors that made use of this class are:
*
*
@@ -37,7 +37,7 @@
*
* *
*
- *
+ *
* In DefaultWorkflowInterceptor
*
applies only when action implements {@link com.opensymphony.xwork2.Validateable}
*
@@ -45,12 +45,12 @@
*
else if the action class have validateDo{MethodName}(), it will be invoked
*
no matter if 1] or 2] is performed, if alwaysInvokeValidate property of the interceptor is "true" (which is by default "true"), validate() will be invoked.
else if the action class have prepareDo(MethodName()}(), it will be invoked
*
no matter if 1] or 2] is performed, if alwaysinvokePrepare property of the interceptor is "true" (which is by default "true"), prepare() will be invoked.
*
- *
+ *
*
- *
+ *
* @author Philip Luppens
* @author tm_jee
*/
public class PrefixMethodInvocationUtil {
-
+
private static final Logger LOG = LogManager.getLogger(PrefixMethodInvocationUtil.class);
private static final String DEFAULT_INVOCATION_METHODNAME = "execute";
@@ -76,7 +76,7 @@ public class PrefixMethodInvocationUtil {
*
* This method will prefix actionInvocation's ActionProxy's
* method with prefixes before invoking the prefixed method.
- * Order of the prefixes is important, as this method will return once
+ * Order of the prefixes is important, as this method will return once
* a prefixed method is found in the action class.
*
*
@@ -89,7 +89,7 @@ public class PrefixMethodInvocationUtil {
*
*
*
- * Assuming actionInvocation.getProxy(),getMethod() returns "submit",
+ * Assuming actionInvocation.getProxy(),getMethod() returns "submit",
* the order of invocation would be as follows:-
*
*
@@ -99,12 +99,12 @@ public class PrefixMethodInvocationUtil {
*
*
*
- * If prepareSubmit() exists, it will be invoked and this method
- * will return, prepareDoSubmit() will NOT be invoked.
+ * If prepareSubmit() exists, it will be invoked and this method
+ * will return, prepareDoSubmit() will NOT be invoked.
*
*
*
- * On the other hand, if prepareDoSubmit() does not exists, and
+ * On the other hand, if prepareDoSubmit() does not exists, and
* prepareDoSubmit() exists, it will be invoked.
*
*
@@ -119,29 +119,32 @@ public class PrefixMethodInvocationUtil {
*/
public static void invokePrefixMethod(ActionInvocation actionInvocation, String[] prefixes) throws InvocationTargetException, IllegalAccessException {
Object action = actionInvocation.getAction();
-
+
String methodName = actionInvocation.getProxy().getMethod();
-
+
if (methodName == null) {
- // if null returns (possible according to the docs), use the default execute
+ // if null returns (possible according to the docs), use the default execute
methodName = DEFAULT_INVOCATION_METHODNAME;
}
-
+
Method method = getPrefixedMethod(prefixes, methodName, action);
if (method != null) {
method.invoke(action, new Object[0]);
}
}
-
-
+
+ public static void invokePrefixMethod(org.apache.struts2.ActionInvocation actionInvocation, String[] prefixes) throws InvocationTargetException, IllegalAccessException {
+ invokePrefixMethod(ActionInvocation.adapt(actionInvocation), prefixes);
+ }
+
/**
- * This method returns a {@link Method} in action. The method
+ * This method returns a {@link Method} in action. The method
* returned is found by searching for method in action whose method name
* is equals to the result of appending each prefixes
* to methodName. Only the first method found will be returned, hence
* the order of prefixes is important. If none is found this method
* will return null.
- *
+ *
* @param prefixes the prefixes to prefix the methodName
* @param methodName the method name to be prefixed with prefixes
* @param action the action class of which the prefixed method is to be search for.
@@ -162,7 +165,7 @@ public static Method getPrefixedMethod(String[] prefixes, String methodName, Obj
}
return null;
}
-
+
/**
*
* This method capitalized the first character of methodName.
diff --git a/core/src/main/java/com/opensymphony/xwork2/interceptor/PrepareInterceptor.java b/core/src/main/java/com/opensymphony/xwork2/interceptor/PrepareInterceptor.java
index 4516d760c6..f2a6aef37c 100644
--- a/core/src/main/java/com/opensymphony/xwork2/interceptor/PrepareInterceptor.java
+++ b/core/src/main/java/com/opensymphony/xwork2/interceptor/PrepareInterceptor.java
@@ -20,8 +20,6 @@
import com.opensymphony.xwork2.ActionInvocation;
import com.opensymphony.xwork2.Preparable;
-import org.apache.logging.log4j.Logger;
-import org.apache.logging.log4j.LogManager;
import java.lang.reflect.InvocationTargetException;
@@ -98,7 +96,10 @@
* @author Philip Luppens
* @author tm_jee
* @see com.opensymphony.xwork2.Preparable
+ *
+ * @deprecated since 6.7.0, use {@link org.apache.struts2.interceptor.PrepareInterceptor} instead.
*/
+@Deprecated
public class PrepareInterceptor extends MethodFilterInterceptor {
private static final long serialVersionUID = -5216969014510719786L;
diff --git a/core/src/main/java/com/opensymphony/xwork2/interceptor/ScopedModelDrivenInterceptor.java b/core/src/main/java/com/opensymphony/xwork2/interceptor/ScopedModelDrivenInterceptor.java
index 22da179d46..0837d0486a 100644
--- a/core/src/main/java/com/opensymphony/xwork2/interceptor/ScopedModelDrivenInterceptor.java
+++ b/core/src/main/java/com/opensymphony/xwork2/interceptor/ScopedModelDrivenInterceptor.java
@@ -35,7 +35,7 @@
*
*
This interceptor only activates on actions that implement the {@link ScopedModelDriven} interface. If
* detected, it will retrieve the model class from the configured scope, then provide it to the Action.
- *
+ *
*
*
*
Interceptor parameters:
@@ -45,7 +45,7 @@
*
*
*
className - The model class name. Defaults to the class name of the object returned by the getModel() method.
- *
+ *
*
name - The key to use when storing or retrieving the instance in a scope. Defaults to the model
* class name.
+ *
+ * @deprecated since 6.7.0, use {@link org.apache.struts2.interceptor.ScopedModelDrivenInterceptor} instead.
*/
+@Deprecated
public class ScopedModelDrivenInterceptor extends AbstractInterceptor {
private static final Class[] EMPTY_CLASS_ARRAY = new Class[0];
-
+
private static final String GET_MODEL = "getModel";
private String scope;
private String name;
private String className;
private ObjectFactory objectFactory;
-
+
@Inject
public void setObjectFactory(ObjectFactory factory) {
this.objectFactory = factory;
}
-
+
protected Object resolveModel(ObjectFactory factory, ActionContext actionContext, String modelClassName, String modelScope, String modelName) throws Exception {
Object model;
Map scopeMap = actionContext.getContextMap();
if ("session".equals(modelScope)) {
scopeMap = actionContext.getSession();
}
-
+
model = scopeMap.get(modelName);
if (model == null) {
model = factory.buildBean(modelClassName, null);
@@ -119,7 +122,7 @@ public String intercept(ActionInvocation invocation) throws Exception {
if (modelDriven.getModel() == null) {
ActionContext ctx = ActionContext.getContext();
ActionConfig config = invocation.getProxy().getConfig();
-
+
String cName = className;
if (cName == null) {
try {
@@ -161,5 +164,5 @@ public void setName(String name) {
*/
public void setScope(String scope) {
this.scope = scope;
- }
+ }
}
diff --git a/core/src/main/java/com/opensymphony/xwork2/interceptor/StaticParametersInterceptor.java b/core/src/main/java/com/opensymphony/xwork2/interceptor/StaticParametersInterceptor.java
index 9d32a8a184..64da2c3233 100644
--- a/core/src/main/java/com/opensymphony/xwork2/interceptor/StaticParametersInterceptor.java
+++ b/core/src/main/java/com/opensymphony/xwork2/interceptor/StaticParametersInterceptor.java
@@ -20,11 +20,11 @@
import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.ActionInvocation;
+import com.opensymphony.xwork2.LocalizedTextProvider;
import com.opensymphony.xwork2.config.entities.ActionConfig;
import com.opensymphony.xwork2.config.entities.Parameterizable;
import com.opensymphony.xwork2.inject.Inject;
import com.opensymphony.xwork2.util.ClearableValueStack;
-import com.opensymphony.xwork2.LocalizedTextProvider;
import com.opensymphony.xwork2.util.TextParseUtil;
import com.opensymphony.xwork2.util.ValueStack;
import com.opensymphony.xwork2.util.ValueStackFactory;
@@ -84,7 +84,10 @@
*
*
* @author Patrick Lightbody
+ *
+ * @deprecated since 6.7.0, use {@link org.apache.struts2.interceptor.StaticParametersInterceptor} instead.
*/
+@Deprecated
public class StaticParametersInterceptor extends AbstractInterceptor {
private boolean parse;
diff --git a/core/src/main/java/com/opensymphony/xwork2/util/TextParseUtil.java b/core/src/main/java/com/opensymphony/xwork2/util/TextParseUtil.java
index 2a2cad1bf9..9220159bff 100644
--- a/core/src/main/java/com/opensymphony/xwork2/util/TextParseUtil.java
+++ b/core/src/main/java/com/opensymphony/xwork2/util/TextParseUtil.java
@@ -51,6 +51,9 @@ public static String translateVariables(String expression, ValueStack stack) {
return translateVariables(new char[]{'$', '%'}, expression, stack, String.class, null).toString();
}
+ public static String translateVariables(String expression, org.apache.struts2.util.ValueStack stack) {
+ return translateVariables(expression, ValueStack.adapt(stack));
+ }
/**
* Function similarly as {@link #translateVariables(char, String, ValueStack)}
diff --git a/core/src/main/java/com/opensymphony/xwork2/util/ValueStackFactory.java b/core/src/main/java/com/opensymphony/xwork2/util/ValueStackFactory.java
index 788c904534..70d072851e 100644
--- a/core/src/main/java/com/opensymphony/xwork2/util/ValueStackFactory.java
+++ b/core/src/main/java/com/opensymphony/xwork2/util/ValueStackFactory.java
@@ -29,7 +29,7 @@ public interface ValueStackFactory {
* @return a new {@link com.opensymphony.xwork2.util.ValueStack}.
*/
ValueStack createValueStack();
-
+
/**
* Get a new instance of {@link com.opensymphony.xwork2.util.ValueStack}
*
@@ -37,5 +37,8 @@ public interface ValueStackFactory {
* @return a new {@link com.opensymphony.xwork2.util.ValueStack}.
*/
ValueStack createValueStack(ValueStack stack);
-
+
+ default ValueStack createValueStack(org.apache.struts2.util.ValueStack stack) {
+ return createValueStack(ValueStack.adapt(stack));
+ }
}
diff --git a/core/src/main/java/org/apache/struts2/interceptor/AliasInterceptor.java b/core/src/main/java/org/apache/struts2/interceptor/AliasInterceptor.java
new file mode 100644
index 0000000000..7fa112a83e
--- /dev/null
+++ b/core/src/main/java/org/apache/struts2/interceptor/AliasInterceptor.java
@@ -0,0 +1,294 @@
+/*
+ * 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 org.apache.struts2.interceptor;
+
+import com.opensymphony.xwork2.LocalizedTextProvider;
+import com.opensymphony.xwork2.config.entities.ActionConfig;
+import com.opensymphony.xwork2.inject.Inject;
+import com.opensymphony.xwork2.interceptor.ParametersInterceptor;
+import com.opensymphony.xwork2.interceptor.ValidationAware;
+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.util.ValueStackFactory;
+import com.opensymphony.xwork2.util.reflection.ReflectionContextState;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.apache.struts2.ActionContext;
+import org.apache.struts2.ActionInvocation;
+import org.apache.struts2.StrutsConstants;
+import org.apache.struts2.dispatcher.HttpParameters;
+import org.apache.struts2.dispatcher.Parameter;
+import org.apache.struts2.util.ValueStack;
+
+import java.util.Map;
+
+
+/**
+ *
+ *
+ * The aim of this Interceptor is to alias a named parameter to a different named parameter. By acting as the glue
+ * between actions sharing similar parameters (but with different names), it can help greatly with action chaining.
+ *
+ *
Action's alias expressions should be in the form of #{ "name1" : "alias1", "name2" : "alias2" }.
+ * This means that assuming an action (or something else in the stack) has a value for the expression named name1 and the
+ * action this interceptor is applied to has a setter named alias1, alias1 will be set with the value from
+ * name1.
+ *
+ *
+ *
+ *
+ *
Interceptor parameters:
+ *
+ *
+ *
+ *
+ *
+ *
aliasesKey (optional) - the name of the action parameter to look for the alias map (by default this is
+ * aliases).
+ *
+ *
+ *
+ *
+ *
+ *
Extending the interceptor:
+ *
+ *
+ *
+ * This interceptor does not have any known extension points.
+ *
+ *
+ *
+ *
Example code:
+ *
+ *
+ *
+ * <action name="someAction" class="com.examples.SomeAction">
+ * <!-- The value for the foo parameter will be applied as if it were named bar -->
+ * <param name="aliases">#{ 'foo' : 'bar' }</param>
+ *
+ * <interceptor-ref name="alias"/>
+ * <interceptor-ref name="basicStack"/>
+ * <result name="success">good_result.ftl</result>
+ * </action>
+ *
+ *
+ * Sets the name of the action parameter to look for the alias map.
+ *
+ *
+ *
+ * Default is aliases.
+ *
+ *
+ * @param aliasesKey the name of the action parameter
+ */
+ public void setAliasesKey(String aliasesKey) {
+ this.aliasesKey = aliasesKey;
+ }
+
+ @Override public String intercept(ActionInvocation invocation) throws Exception {
+
+ ActionConfig config = invocation.getProxy().getConfig();
+ ActionContext ac = invocation.getInvocationContext();
+ Object action = invocation.getAction();
+
+ // get the action's parameters
+ final Map parameters = config.getParams();
+
+ if (parameters.containsKey(aliasesKey)) {
+
+ String aliasExpression = parameters.get(aliasesKey);
+ ValueStack stack = ac.getValueStack();
+ Object obj = stack.findValue(aliasExpression);
+
+ if (obj instanceof Map) {
+ //get secure stack
+ ValueStack newStack = valueStackFactory.createValueStack(stack);
+ boolean clearableStack = newStack instanceof ClearableValueStack;
+ if (clearableStack) {
+ //if the stack's context can be cleared, do that to prevent OGNL
+ //from having access to objects in the stack, see XW-641
+ ((ClearableValueStack)newStack).clearContextValues();
+ Map context = newStack.getContext();
+ ReflectionContextState.setCreatingNullObjects(context, true);
+ ReflectionContextState.setDenyMethodExecution(context, true);
+ ReflectionContextState.setReportingConversionErrors(context, true);
+
+ //keep locale from original context
+ newStack.getActionContext().withLocale(stack.getActionContext().getLocale());
+ }
+
+ // override
+ Map aliases = (Map) obj;
+ 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
+ HttpParameters contextParameters = ActionContext.getContext().getParameters();
+
+ if (null != contextParameters) {
+ Parameter param = contextParameters.get(name);
+ if (param.isDefined()) {
+ value = new Evaluated(param.getValue());
+ }
+ }
+ }
+ if (value.isDefined()) {
+ try {
+ newStack.setValue(alias, value.get());
+ } catch (RuntimeException e) {
+ if (devMode) {
+ String developerNotification = localizedTextProvider.findText(ParametersInterceptor.class, "devmode.notification", ActionContext.getContext().getLocale(), "Developer Notification:\n{0}", new Object[]{
+ "Unexpected Exception caught setting '" + entry.getKey() + "' on '" + action.getClass() + ": " + e.getMessage()
+ });
+ LOG.error(developerNotification);
+ if (action instanceof com.opensymphony.xwork2.interceptor.ValidationAware) {
+ ((ValidationAware) action).addActionMessage(developerNotification);
+ }
+ }
+ }
+ }
+ }
+
+ if (clearableStack) {
+ stack.getActionContext().withConversionErrors(newStack.getActionContext().getConversionErrors());
+ }
+ } else {
+ LOG.debug("invalid alias expression: {}", aliasesKey);
+ }
+ }
+
+ 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/org/apache/struts2/interceptor/ChainingInterceptor.java b/core/src/main/java/org/apache/struts2/interceptor/ChainingInterceptor.java
new file mode 100644
index 0000000000..adbb3a67a1
--- /dev/null
+++ b/core/src/main/java/org/apache/struts2/interceptor/ChainingInterceptor.java
@@ -0,0 +1,275 @@
+/*
+ * 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 org.apache.struts2.interceptor;
+
+import com.opensymphony.xwork2.ActionChainResult;
+import com.opensymphony.xwork2.Unchainable;
+import com.opensymphony.xwork2.inject.Inject;
+import com.opensymphony.xwork2.util.CompoundRoot;
+import com.opensymphony.xwork2.util.ProxyUtil;
+import com.opensymphony.xwork2.util.TextParseUtil;
+import com.opensymphony.xwork2.util.reflection.ReflectionProvider;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.apache.struts2.ActionInvocation;
+import org.apache.struts2.Result;
+import org.apache.struts2.StrutsConstants;
+import org.apache.struts2.util.ValueStack;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+
+
+/**
+ *
+ *
+ * An interceptor that copies all the properties of every object in the value stack to the currently executing object,
+ * except for any object that implements {@link Unchainable}. A collection of optional includes and
+ * excludes may be provided to control how and which parameters are copied. Only includes or excludes may be
+ * specified. Specifying both results in undefined behavior. See the javadocs for {@link ReflectionProvider#copy(Object, Object,
+ * Map, Collection, Collection)} for more information.
+ *
+ *
+ *
+ * Note: It is important to remember that this interceptor does nothing if there are no objects already on the stack.
+ * This means two things:
+ * One, you can safely apply it to all your actions without any worry of adverse affects.
+ * Two, it is up to you to ensure an object exists in the stack prior to invoking this action. The most typical way this is done
+ * is through the use of the chain result type, which combines with this interceptor to make up the action
+ * chaining feature.
+ *
+ *
+ *
+ * Note: By default Errors, Field errors and Message aren't copied during chaining, to change the behaviour you can specify
+ * the below three constants in struts.properties or struts.xml:
+ *
+ *
+ *
+ *
struts.chaining.copyErrors - set to true to copy Action Errors
+ *
struts.chaining.copyFieldErrors - set to true to copy Field Errors
+ *
struts.chaining.copyMessages - set to true to copy Action Messages
+ *
+ *
+ *
+ * @author mrdon
+ * @author tm_jee ( tm_jee(at)yahoo.co.uk )
+ * @see ActionChainResult
+ */
+public class ChainingInterceptor extends AbstractInterceptor {
+
+ private static final Logger LOG = LogManager.getLogger(ChainingInterceptor.class);
+
+ private static final String ACTION_ERRORS = "actionErrors";
+ private static final String FIELD_ERRORS = "fieldErrors";
+ private static final String ACTION_MESSAGES = "actionMessages";
+
+ private boolean copyMessages = false;
+ private boolean copyErrors = false;
+ private boolean copyFieldErrors = false;
+
+ protected Collection excludes;
+
+ protected Collection includes;
+ protected ReflectionProvider reflectionProvider;
+
+ @Inject
+ public void setReflectionProvider(ReflectionProvider prov) {
+ this.reflectionProvider = prov;
+ }
+
+ @Inject(value = StrutsConstants.STRUTS_CHAINING_COPY_ERRORS, required = false)
+ public void setCopyErrors(String copyErrors) {
+ this.copyErrors = "true".equalsIgnoreCase(copyErrors);
+ }
+
+ @Inject(value = StrutsConstants.STRUTS_CHAINING_COPY_FIELD_ERRORS, required = false)
+ public void setCopyFieldErrors(String copyFieldErrors) {
+ this.copyFieldErrors = "true".equalsIgnoreCase(copyFieldErrors);
+ }
+
+ @Inject(value = StrutsConstants.STRUTS_CHAINING_COPY_MESSAGES, required = false)
+ public void setCopyMessages(String copyMessages) {
+ this.copyMessages = "true".equalsIgnoreCase(copyMessages);
+ }
+
+ @Override
+ public String intercept(ActionInvocation invocation) throws Exception {
+ ValueStack stack = invocation.getStack();
+ CompoundRoot root = stack.getRoot();
+ if (shouldCopyStack(invocation, root)) {
+ copyStack(invocation, root);
+ }
+ return invocation.invoke();
+ }
+
+ private void copyStack(ActionInvocation invocation, CompoundRoot root) {
+ List list = prepareList(root);
+ Map ctxMap = invocation.getInvocationContext().getContextMap();
+ for (Object object : list) {
+ if (shouldCopy(object)) {
+ Object action = invocation.getAction();
+ Class> editable = null;
+ if(ProxyUtil.isProxy(action)) {
+ editable = ProxyUtil.ultimateTargetClass(action);
+ }
+ reflectionProvider.copy(object, action, ctxMap, prepareExcludes(), includes, editable);
+ }
+ }
+ }
+
+ private Collection prepareExcludes() {
+ Collection localExcludes = excludes;
+ if (!copyErrors || !copyMessages ||!copyFieldErrors) {
+ if (localExcludes == null) {
+ localExcludes = new HashSet();
+ if (!copyErrors) {
+ localExcludes.add(ACTION_ERRORS);
+ }
+ if (!copyMessages) {
+ localExcludes.add(ACTION_MESSAGES);
+ }
+ if (!copyFieldErrors) {
+ localExcludes.add(FIELD_ERRORS);
+ }
+ }
+ }
+ return localExcludes;
+ }
+
+ private boolean shouldCopy(Object o) {
+ return o != null && !(o instanceof Unchainable);
+ }
+
+ @SuppressWarnings("unchecked")
+ private List prepareList(CompoundRoot root) {
+ List list = new ArrayList(root);
+ list.remove(0);
+ Collections.reverse(list);
+ return list;
+ }
+
+ private boolean shouldCopyStack(ActionInvocation invocation, CompoundRoot root) throws Exception {
+ Result result = invocation.getResult();
+ return root.size() > 1 && (result == null || ActionChainResult.class.isAssignableFrom(result.getClass()));
+ }
+
+ /**
+ * Gets list of parameter names to exclude
+ *
+ * @return the exclude list
+ */
+ public Collection getExcludes() {
+ return excludes;
+ }
+
+ /**
+ * Sets the list of parameter names to exclude from copying (all others will be included).
+ *
+ * @param excludes the excludes list as comma separated String
+ */
+ public void setExcludes(String excludes) {
+ this.excludes = TextParseUtil.commaDelimitedStringToSet(excludes);
+ }
+
+ /**
+ * Sets the list of parameter names to exclude from copying (all others will be included).
+ *
+ * @param excludes the excludes list
+ */
+ public void setExcludesCollection(Collection excludes) {
+ this.excludes = excludes;
+ }
+
+ /**
+ * Gets list of parameter names to include
+ *
+ * @return the include list
+ */
+ public Collection getIncludes() {
+ return includes;
+ }
+
+ /**
+ * Sets the list of parameter names to include when copying (all others will be excluded).
+ *
+ * @param includes the includes list as comma separated String
+ */
+ public void setIncludes(String includes) {
+ this.includes = TextParseUtil.commaDelimitedStringToSet(includes);
+ }
+
+
+ /**
+ * Sets the list of parameter names to include when copying (all others will be excluded).
+ *
+ * @param includes the includes list
+ */
+ public void setIncludesCollection(Collection includes) {
+ this.includes = includes;
+ }
+
+}
diff --git a/core/src/main/java/org/apache/struts2/interceptor/ConversionErrorInterceptor.java b/core/src/main/java/org/apache/struts2/interceptor/ConversionErrorInterceptor.java
new file mode 100644
index 0000000000..0301441e58
--- /dev/null
+++ b/core/src/main/java/org/apache/struts2/interceptor/ConversionErrorInterceptor.java
@@ -0,0 +1,150 @@
+/*
+ * 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 org.apache.struts2.interceptor;
+
+import com.opensymphony.xwork2.conversion.impl.ConversionData;
+import com.opensymphony.xwork2.conversion.impl.XWorkConverter;
+import com.opensymphony.xwork2.interceptor.ValidationAware;
+import org.apache.commons.text.StringEscapeUtils;
+import org.apache.struts2.ActionContext;
+import org.apache.struts2.ActionInvocation;
+import org.apache.struts2.util.ValueStack;
+
+import java.util.HashMap;
+import java.util.Map;
+
+
+/**
+ *
+ * ConversionErrorInterceptor adds conversion errors from the ActionContext to the Action's field errors.
+ *
+ *
+ * This interceptor adds any error found in the {@link ActionContext}'s conversionErrors map as a field error (provided
+ * that the action implements {@link com.opensymphony.xwork2.interceptor.ValidationAware}). In addition, any field that contains a validation error has its
+ * original value saved such that any subsequent requests for that value return the original value rather than the value
+ * in the action. This is important because if the value "abc" is submitted and can't be converted to an int, we want to
+ * display the original string ("abc") again rather than the int value (likely 0, which would make very little sense to
+ * the user).
+ *
+ *
+ *
+ * Note: Since 2.5.2, this interceptor extends {@link com.opensymphony.xwork2.interceptor.MethodFilterInterceptor}, therefore being
+ * able to deal with excludeMethods / includeMethods parameters. See [Workflow Interceptor]
+ * (class {@link DefaultWorkflowInterceptor}) for documentation and examples on how to use this feature.
+ *
+ *
+ *
+ *
+ *
Interceptor parameters:
+ *
+ *
+ *
+ *
+ *
None
+ *
+ *
+ *
+ *
+ *
Extending the interceptor:
+ *
+ *
+ *
+ * Because this interceptor is not web-specific, it abstracts the logic for whether an error should be added. This
+ * allows for web-specific interceptors to use more complex logic in the {@link #shouldAddError} method for when a value
+ * has a conversion error but is null or empty or otherwise indicates that the value was never actually entered by the
+ * user.
+ *
+ *
+ *
+ *