Merge pull request #833 from apache/merge-master-to-7xx-2024-01-04

[Struts 7] Merge master to 7xx
This commit is contained in:
Lukasz Lenart
2024-01-05 07:20:45 +01:00
committed by GitHub
94 changed files with 1106 additions and 890 deletions
+3 -3
View File
@@ -52,12 +52,12 @@ jobs:
java-version: 17
cache: 'maven'
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
uses: github/codeql-action/init@v3
with:
languages: ${{ matrix.language }}
- name: Autobuild
uses: github/codeql-action/autobuild@v2
uses: github/codeql-action/autobuild@v3
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2
uses: github/codeql-action/analyze@v3
with:
category: "/language:${{matrix.language}}"
+2 -2
View File
@@ -58,13 +58,13 @@ jobs:
publish_results: true
- name: "Upload artifact"
uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # 3.1.3
uses: actions/upload-artifact@c7d193f32edcb7bfad88892161225aeda64e9392 # 4.0.0
with:
name: SARIF file
path: results.sarif
retention-days: 5
- name: "Upload to code-scanning"
uses: github/codeql-action/upload-sarif@b398f525a5587552e573b247ac661067fafa920b # 2.1.22
uses: github/codeql-action/upload-sarif@03e7845b7bfcd5e7fb63d1ae8c61b0e791134fab # 2.22.11
with:
sarif_file: results.sarif
@@ -72,7 +72,7 @@
<s:script src="%{jqueryJs}"/>
<s:url var="bootstrapJs" value='/js/bootstrap.min.js' encode='false' includeParams='none'/>
<s:script src="%{bootstrapJs}"/>
<s:script type="text/javascript">
<s:script>
$(function () {
var alerts = $('ul.alert').wrap('<div />');
alerts.prepend('<a class="close" data-dismiss="alert" href="#">&times;</a>');
@@ -72,7 +72,7 @@
</div>
</div>
<s:script type="text/javascript">
<s:script>
/********************************************************************
* JS just used on this page.
* Usually this would be placed in a JS file
+4 -4
View File
@@ -1,6 +1,6 @@
#set ($stylebase = "https://struts.apache.org/css")
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<!--
<!--
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
@@ -16,7 +16,7 @@ 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.
under the License.
-->
<html>
<head>
@@ -43,12 +43,12 @@ under the License.
<script src='http://struts.apache.org/highlighter/js/$hscript' type='text/javascript'></script>
#end
<script type="text/javascript">
<script>
SyntaxHighlighter.defaults['toolbar'] = false;
SyntaxHighlighter.all();
</script>
#end
<script type="text/javascript" language="javascript">
<script>
var hide = null;
var show = null;
var children = null;
@@ -89,7 +89,7 @@ import com.opensymphony.xwork2.ognl.OgnlUtil;
import com.opensymphony.xwork2.ognl.OgnlValueStackFactory;
import com.opensymphony.xwork2.ognl.SecurityMemberAccess;
import com.opensymphony.xwork2.ognl.accessor.CompoundRootAccessor;
import com.opensymphony.xwork2.util.CompoundRoot;
import com.opensymphony.xwork2.ognl.accessor.RootAccessor;
import com.opensymphony.xwork2.util.OgnlTextParser;
import com.opensymphony.xwork2.util.PatternMatcher;
import com.opensymphony.xwork2.util.StrutsLocalizedTextProvider;
@@ -100,8 +100,6 @@ import com.opensymphony.xwork2.util.fs.DefaultFileManager;
import com.opensymphony.xwork2.util.fs.DefaultFileManagerFactory;
import com.opensymphony.xwork2.util.location.LocatableProperties;
import com.opensymphony.xwork2.util.reflection.ReflectionProvider;
import ognl.ClassResolver;
import ognl.PropertyAccessor;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
@@ -390,8 +388,7 @@ public class DefaultConfiguration implements Configuration {
.factory(TextParser.class, OgnlTextParser.class, Scope.SINGLETON)
.factory(ObjectTypeDeterminer.class, DefaultObjectTypeDeterminer.class, Scope.SINGLETON)
.factory(PropertyAccessor.class, CompoundRoot.class.getName(), CompoundRootAccessor.class, Scope.SINGLETON)
.factory(ClassResolver.class, CompoundRoot.class.getName(), CompoundRootAccessor.class, Scope.SINGLETON)
.factory(RootAccessor.class, CompoundRootAccessor.class, Scope.SINGLETON)
.factory(ExpressionCacheFactory.class, DefaultOgnlExpressionCacheFactory.class, Scope.SINGLETON)
.factory(BeanInfoCacheFactory.class, DefaultOgnlBeanInfoCacheFactory.class, Scope.SINGLETON)
@@ -38,7 +38,6 @@ import com.opensymphony.xwork2.inject.ContainerBuilder;
import com.opensymphony.xwork2.inject.Scope;
import com.opensymphony.xwork2.ognl.ObjectProxy;
import com.opensymphony.xwork2.ognl.OgnlReflectionContextFactory;
import com.opensymphony.xwork2.ognl.accessor.CompoundRootAccessor;
import com.opensymphony.xwork2.ognl.accessor.HttpParametersPropertyAccessor;
import com.opensymphony.xwork2.ognl.accessor.ObjectAccessor;
import com.opensymphony.xwork2.ognl.accessor.ObjectProxyPropertyAccessor;
@@ -55,7 +54,6 @@ 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.CompoundRoot;
import com.opensymphony.xwork2.util.PatternMatcher;
import com.opensymphony.xwork2.util.WildcardHelper;
import com.opensymphony.xwork2.util.fs.DefaultFileManagerFactory;
@@ -145,7 +143,6 @@ public class StrutsDefaultConfigurationProvider implements ConfigurationProvider
.factory(PropertyAccessor.class, Parameter.class.getName(), ParameterPropertyAccessor.class, Scope.SINGLETON)
.factory(MethodAccessor.class, Object.class.getName(), XWorkMethodAccessor.class, Scope.SINGLETON)
.factory(MethodAccessor.class, CompoundRoot.class.getName(), CompoundRootAccessor.class, Scope.SINGLETON)
.factory(NullHandler.class, Object.class.getName(), InstantiatingNullHandler.class, Scope.SINGLETON)
.factory(ActionValidatorManager.class, AnnotationActionValidatorManager.class, Scope.SINGLETON)
@@ -33,5 +33,5 @@ package com.opensymphony.xwork2.interceptor;
* @deprecated since Struts 6.2.0, use {@link org.apache.struts2.action.NoParameters}
*/
@Deprecated
public interface NoParameters {
public interface NoParameters extends org.apache.struts2.action.NoParameters {
}
@@ -26,7 +26,6 @@ import org.apache.logging.log4j.Logger;
import org.apache.struts2.dispatcher.HttpParameters;
import java.util.Collection;
import java.util.HashSet;
import java.util.Map;
import java.util.TreeMap;
@@ -98,7 +97,10 @@ import java.util.TreeMap;
* </pre>
*
* @author Gabe
*
* @deprecated since 6.4.0, use {@link org.apache.struts2.interceptor.parameter.ParametersInterceptor}.
*/
@Deprecated
public class ParameterFilterInterceptor extends AbstractInterceptor {
private static final Logger LOG = LogManager.getLogger(ParameterFilterInterceptor.class);
@@ -19,19 +19,8 @@
package com.opensymphony.xwork2.interceptor;
/**
* This interface is implemented by actions that want to declare acceptable parameters. Works in conjunction with {@link
* ParametersInterceptor}. For example, actions may want to create a white list of parameters they will accept or a
* blacklist of parameters they will reject to prevent clients from setting other unexpected (and possibly dangerous)
* parameters.
* @deprecated since 6.4.0, use {@link org.apache.struts2.action.ParameterNameAware}.
*/
public interface ParameterNameAware {
/**
* Tests if the the action will accept the parameter with the given name.
*
* @param parameterName the parameter name
* @return <tt>true</tt> if accepted, <tt>false</tt> otherwise
*/
boolean acceptableParameterName(String parameterName);
@Deprecated
public interface ParameterNameAware extends org.apache.struts2.action.ParameterNameAware {
}
@@ -19,19 +19,8 @@
package com.opensymphony.xwork2.interceptor;
/**
* This interface is implemented by actions that want to declare acceptable parameter value. Works in conjunction with {@link
* ParametersInterceptor}. For example, actions may want to create a white list of parameter values they will accept or a
* blacklist of parameter values they will reject to prevent clients from setting other unexpected (and possibly dangerous)
* parameter values.
* @deprecated since 6.4.0, use {@link org.apache.struts2.action.ParameterValueAware}.
*/
public interface ParameterValueAware {
/**
* Tests if the the action will accept the parameter with the given value.
*
* @param parameterValue the parameter value
* @return <tt>true</tt> if accepted, <tt>false</tt> otherwise
*/
boolean acceptableParameterValue(String parameterValue);
@Deprecated
public interface ParameterValueAware extends org.apache.struts2.action.ParameterValueAware {
}
@@ -18,537 +18,8 @@
*/
package com.opensymphony.xwork2.interceptor;
import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.ActionInvocation;
import com.opensymphony.xwork2.TextProvider;
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.MemberAccessValueStack;
import com.opensymphony.xwork2.util.TextParseUtil;
import com.opensymphony.xwork2.util.ValueStack;
import com.opensymphony.xwork2.util.ValueStackFactory;
import com.opensymphony.xwork2.util.reflection.ReflectionContextState;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.struts2.StrutsConstants;
import org.apache.struts2.dispatcher.HttpParameters;
import org.apache.struts2.dispatcher.Parameter;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.regex.Pattern;
/**
* This interceptor sets all parameters on the value stack.
* @deprecated since 6.4.0, use {@link org.apache.struts2.interceptor.parameter.ParametersInterceptor}.
*/
public class ParametersInterceptor extends MethodFilterInterceptor {
private static final Logger LOG = LogManager.getLogger(ParametersInterceptor.class);
protected static final int PARAM_NAME_MAX_LENGTH = 100;
private static final Pattern DMI_IGNORED_PATTERN = Pattern.compile("^(action|method):.*", Pattern.CASE_INSENSITIVE);
private int paramNameMaxLength = PARAM_NAME_MAX_LENGTH;
private boolean devMode = false;
private boolean dmiEnabled = false;
protected boolean ordered = false;
private ValueStackFactory valueStackFactory;
private ExcludedPatternsChecker excludedPatterns;
private AcceptedPatternsChecker acceptedPatterns;
private Set<Pattern> excludedValuePatterns = null;
private Set<Pattern> acceptedValuePatterns = null;
@Inject
public void setValueStackFactory(ValueStackFactory valueStackFactory) {
this.valueStackFactory = valueStackFactory;
}
@Inject(StrutsConstants.STRUTS_DEVMODE)
public void setDevMode(String mode) {
this.devMode = BooleanUtils.toBoolean(mode);
}
@Inject
public void setExcludedPatterns(ExcludedPatternsChecker excludedPatterns) {
this.excludedPatterns = excludedPatterns;
}
@Inject
public void setAcceptedPatterns(AcceptedPatternsChecker acceptedPatterns) {
this.acceptedPatterns = acceptedPatterns;
}
@Inject(value = StrutsConstants.STRUTS_ENABLE_DYNAMIC_METHOD_INVOCATION, required = false)
protected void setDynamicMethodInvocation(String dmiEnabled) {
this.dmiEnabled = Boolean.parseBoolean(dmiEnabled);
}
/**
* If the param name exceeds the configured maximum length it will not be
* accepted.
*
* @param paramNameMaxLength Maximum length of param names
*/
public void setParamNameMaxLength(int paramNameMaxLength) {
this.paramNameMaxLength = paramNameMaxLength;
}
static private int countOGNLCharacters(String s) {
int count = 0;
for (int i = s.length() - 1; i >= 0; i--) {
char c = s.charAt(i);
if (c == '.' || c == '[') count++;
}
return count;
}
/**
* Compares based on number of '.' and '[' characters (fewer is higher)
*/
static final Comparator<String> rbCollator = (s1, s2) -> {
int l1 = countOGNLCharacters(s1);
int l2 = countOGNLCharacters(s2);
return l1 < l2 ? -1 : (l2 < l1 ? 1 : s1.compareTo(s2));
};
@Override
public String doIntercept(ActionInvocation invocation) throws Exception {
Object action = invocation.getAction();
if (!(action instanceof NoParameters)) {
ActionContext ac = invocation.getInvocationContext();
HttpParameters parameters = retrieveParameters(ac);
if (LOG.isDebugEnabled()) {
LOG.debug("Setting params {}", getParameterLogMap(parameters));
}
if (parameters != null) {
Map<String, Object> contextMap = ac.getContextMap();
try {
ReflectionContextState.setCreatingNullObjects(contextMap, true);
ReflectionContextState.setDenyMethodExecution(contextMap, true);
ReflectionContextState.setReportingConversionErrors(contextMap, true);
ValueStack stack = ac.getValueStack();
setParameters(action, stack, parameters);
} finally {
ReflectionContextState.setCreatingNullObjects(contextMap, false);
ReflectionContextState.setDenyMethodExecution(contextMap, false);
ReflectionContextState.setReportingConversionErrors(contextMap, false);
}
}
}
return invocation.invoke();
}
/**
* Gets the parameter map to apply from wherever appropriate
*
* @param ac The action context
* @return The parameter map to apply
*/
protected HttpParameters retrieveParameters(ActionContext ac) {
return ac.getParameters();
}
/**
* Adds the parameters into context's ParameterMap
*
* @param ac The action context
* @param newParams The parameter map to apply
* <p>
* In this class this is a no-op, since the parameters were fetched from the same location.
* In subclasses both retrieveParameters() and addParametersToContext() should be overridden.
* </p>
*/
protected void addParametersToContext(ActionContext ac, Map<String, ?> newParams) {
}
protected void setParameters(final Object action, ValueStack stack, HttpParameters parameters) {
HttpParameters params;
Map<String, Parameter> acceptableParameters;
if (ordered) {
params = HttpParameters.create().withComparator(getOrderedComparator()).withParent(parameters).build();
acceptableParameters = new TreeMap<>(getOrderedComparator());
} else {
params = HttpParameters.create().withParent(parameters).build();
acceptableParameters = new TreeMap<>();
}
for (Map.Entry<String, Parameter> entry : params.entrySet()) {
String parameterName = entry.getKey();
boolean isAcceptableParameter = isAcceptableParameter(parameterName, action);
isAcceptableParameter &= isAcceptableParameterValue(entry.getValue(), action);
if (isAcceptableParameter) {
acceptableParameters.put(parameterName, entry.getValue());
}
}
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<String, Object> 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()).withValueStack(stack);
}
boolean memberAccessStack = newStack instanceof MemberAccessValueStack;
if (memberAccessStack) {
//block or allow access to properties
//see WW-2761 for more details
MemberAccessValueStack accessValueStack = (MemberAccessValueStack) newStack;
accessValueStack.useAcceptProperties(acceptedPatterns.getAcceptedPatterns());
accessValueStack.useExcludeProperties(excludedPatterns.getExcludedPatterns());
}
for (Map.Entry<String, Parameter> entry : acceptableParameters.entrySet()) {
String name = entry.getKey();
Parameter value = entry.getValue();
try {
newStack.setParameter(name, value.getObject());
} catch (RuntimeException e) {
if (devMode) {
notifyDeveloperParameterException(action, name, e.getMessage());
}
}
}
if (clearableStack) {
stack.getActionContext().withConversionErrors(newStack.getActionContext().getConversionErrors());
}
addParametersToContext(ActionContext.getContext(), acceptableParameters);
}
protected void notifyDeveloperParameterException(Object action, String property, String message) {
String developerNotification = "Unexpected Exception caught setting '" + property + "' on '" + action.getClass() + ": " + message;
if (action instanceof TextProvider) {
TextProvider tp = (TextProvider) action;
developerNotification = tp.getText("devmode.notification",
"Developer Notification:\n{0}",
new String[]{developerNotification}
);
}
LOG.error(developerNotification);
if (action instanceof ValidationAware) {
// see https://issues.apache.org/jira/browse/WW-4066
Collection<String> messages = ((ValidationAware) action).getActionMessages();
messages.add(message);
((ValidationAware) action).setActionMessages(messages);
}
}
/**
* Checks if name of parameter can be accepted or thrown away
*
* @param name parameter name
* @param action current action
* @return true if parameter is accepted
*/
protected boolean isAcceptableParameter(String name, Object action) {
ParameterNameAware parameterNameAware = (action instanceof ParameterNameAware) ? (ParameterNameAware) action : null;
return acceptableName(name) && (parameterNameAware == null || parameterNameAware.acceptableParameterName(name));
}
/**
* Checks if parameter value can be accepted or thrown away
*
* @param param the parameter
* @param action current action
* @return true if parameter is accepted
*/
protected boolean isAcceptableParameterValue(Parameter param, Object action) {
ParameterValueAware parameterValueAware = (action instanceof ParameterValueAware) ? (ParameterValueAware) action : null;
boolean acceptableParamValue = (parameterValueAware == null || parameterValueAware.acceptableParameterValue(param.getValue()));
if (hasParamValuesToExclude() || hasParamValuesToAccept()) {
// Additional validations to process
acceptableParamValue &= acceptableValue(param.getName(), param.getValue());
}
return acceptableParamValue;
}
/**
* Gets an instance of the comparator to use for the ordered sorting. Override this
* method to customize the ordering of the parameters as they are set to the
* action.
*
* @return A comparator to sort the parameters
*/
protected Comparator<String> getOrderedComparator() {
return rbCollator;
}
protected String getParameterLogMap(HttpParameters parameters) {
if (parameters == null) {
return "NONE";
}
StringBuilder logEntry = new StringBuilder();
for (Map.Entry<String, Parameter> entry : parameters.entrySet()) {
logEntry.append(entry.getKey());
logEntry.append(" => ");
logEntry.append(entry.getValue().getValue());
logEntry.append(" ");
}
return logEntry.toString();
}
/**
* Validates the name passed is:
* * Within the max length of a parameter name
* * Is not excluded
* * Is accepted
*
* @param name - Name to check
* @return true if accepted
*/
protected boolean acceptableName(String name) {
if (isIgnoredDMI(name)) {
LOG.trace("DMI is enabled, ignoring DMI method: {}", name);
return false;
}
boolean accepted = isWithinLengthLimit(name) && !isExcluded(name) && isAccepted(name);
if (devMode && accepted) { // notify only when in devMode
LOG.debug("Parameter [{}] was accepted and will be appended to action!", name);
}
return accepted;
}
private boolean isIgnoredDMI(String name) {
if (dmiEnabled) {
return DMI_IGNORED_PATTERN.matcher(name).matches();
} else {
return false;
}
}
/**
* Validates:
* * Value is null/blank
* * Value is not excluded
* * Value is accepted
*
* @param name - Param name (for logging)
* @param value - value to check
* @return true if accepted
*/
protected boolean acceptableValue(String name, String value) {
boolean accepted = (value == null || value.isEmpty() || (!isParamValueExcluded(value) && isParamValueAccepted(value)));
if (!accepted) {
String message = "Value [{}] of parameter [{}] was not accepted and will be dropped!";
if (devMode) {
LOG.warn(message, value, name);
} else {
LOG.debug(message, value, name);
}
}
return accepted;
}
protected boolean isWithinLengthLimit(String name) {
boolean matchLength = name.length() <= paramNameMaxLength;
if (!matchLength) {
if (devMode) { // warn only when in devMode
LOG.warn("Parameter [{}] is too long, allowed length is [{}]. Use Interceptor Parameter Overriding " +
"to override the limit, see more at\n" +
"https://struts.apache.org/core-developers/interceptors.html#interceptor-parameter-overriding",
name, paramNameMaxLength);
} else {
LOG.warn("Parameter [{}] is too long, allowed length is [{}]", name, paramNameMaxLength);
}
}
return matchLength;
}
protected boolean isAccepted(String paramName) {
AcceptedPatternsChecker.IsAccepted result = acceptedPatterns.isAccepted(paramName);
if (result.isAccepted()) {
return true;
} else if (devMode) { // warn only when in devMode
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());
} else {
LOG.debug("Parameter [{}] didn't match accepted pattern [{}]!", paramName, result.getAcceptedPattern());
}
return false;
}
protected boolean isExcluded(String paramName) {
ExcludedPatternsChecker.IsExcluded result = excludedPatterns.isExcluded(paramName);
if (result.isExcluded()) {
if (devMode) { // warn only when in devMode
LOG.warn("Parameter [{}] matches excluded pattern [{}]! See Accepted / Excluded patterns at\n" +
"https://struts.apache.org/security/#accepted--excluded-patterns",
paramName, result.getExcludedPattern());
} else {
LOG.debug("Parameter [{}] matches excluded pattern [{}]!", paramName, result.getExcludedPattern());
}
return true;
}
return false;
}
protected boolean isParamValueExcluded(String value) {
if (!hasParamValuesToExclude()) {
LOG.debug("'excludedValuePatterns' not defined so anything is allowed");
return false;
}
for (Pattern excludedValuePattern : excludedValuePatterns) {
if (excludedValuePattern.matcher(value).matches()) {
if (devMode) {
LOG.warn("Parameter value [{}] matches excluded pattern [{}]! See Accepting/Excluding parameter values at\n" +
"https://struts.apache.org/core-developers/parameters-interceptor#excluding-parameter-values",
value, excludedValuePatterns);
} else {
LOG.debug("Parameter value [{}] matches excluded pattern [{}]", value, excludedValuePattern);
}
return true;
}
}
return false;
}
protected boolean isParamValueAccepted(String value) {
if (!hasParamValuesToAccept()) {
LOG.debug("'acceptedValuePatterns' not defined so anything is allowed");
return true;
}
for (Pattern acceptedValuePattern : acceptedValuePatterns) {
if (acceptedValuePattern.matcher(value).matches()) {
return true;
}
}
if (devMode) {
LOG.warn("Parameter value [{}] didn't match accepted pattern [{}]! See Accepting/Excluding parameter values at\n" +
"https://struts.apache.org/core-developers/parameters-interceptor#excluding-parameter-values",
value, acceptedValuePatterns);
} else {
LOG.debug("Parameter value [{}] was not accepted!", value);
}
return false;
}
private boolean hasParamValuesToExclude() {
return excludedValuePatterns != null && excludedValuePatterns.size() > 0;
}
private boolean hasParamValuesToAccept() {
return acceptedValuePatterns != null && acceptedValuePatterns.size() > 0;
}
/**
* Whether to order the parameters or not
*
* @return True to order
*/
public boolean isOrdered() {
return ordered;
}
/**
* Set whether to order the parameters by object depth or not
*
* @param ordered True to order them
*/
public void setOrdered(boolean ordered) {
this.ordered = ordered;
}
/**
* Sets a comma-delimited list of regular expressions to match
* parameters that are allowed in the parameter map (aka whitelist).
* <p>
* Don't change the default unless you know what you are doing in terms
* of security implications.
* </p>
*
* @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);
}
/**
* Sets a comma-delimited list of regular expressions to match
* values of parameters that should be accepted and included in the parameter map.
*
* @param commaDelimitedPatterns A comma-delimited set of regular expressions
*/
public void setAcceptedValuePatterns(String commaDelimitedPatterns) {
Set<String> patterns = TextParseUtil.commaDelimitedStringToSet(commaDelimitedPatterns);
if (acceptedValuePatterns == null) {
// Limit unwanted log entries (for 1st call, acceptedValuePatterns null)
LOG.debug("Sets accepted value patterns to [{}], note this may impact the safety of your application!", patterns);
} else {
LOG.warn("Replacing accepted patterns [{}] with [{}], be aware that this may impact safety of your application!",
acceptedValuePatterns, patterns);
}
acceptedValuePatterns = new HashSet<>(patterns.size());
try {
for (String pattern : patterns) {
acceptedValuePatterns.add(Pattern.compile(pattern, Pattern.CASE_INSENSITIVE));
}
} finally {
acceptedValuePatterns = Collections.unmodifiableSet(acceptedValuePatterns);
}
}
/**
* Sets a comma-delimited list of regular expressions to match
* values of parameters that should be removed from the parameter map.
*
* @param commaDelimitedPatterns A comma-delimited set of regular expressions
*/
public void setExcludedValuePatterns(String commaDelimitedPatterns) {
Set<String> patterns = TextParseUtil.commaDelimitedStringToSet(commaDelimitedPatterns);
if (excludedValuePatterns == null) {
// Limit unwanted log entries (for 1st call, excludedValuePatterns null)
LOG.debug("Setting excluded value patterns to [{}]", patterns);
} else {
LOG.warn("Replacing excluded value patterns [{}] with [{}], be aware that this may impact safety of your application!",
excludedValuePatterns, patterns);
}
excludedValuePatterns = new HashSet<>(patterns.size());
try {
for (String pattern : patterns) {
excludedValuePatterns.add(Pattern.compile(pattern, Pattern.CASE_INSENSITIVE));
}
} finally {
excludedValuePatterns = Collections.unmodifiableSet(excludedValuePatterns);
}
}
public class ParametersInterceptor extends org.apache.struts2.interceptor.parameter.ParametersInterceptor {
}
@@ -21,6 +21,7 @@ package com.opensymphony.xwork2.ognl;
import com.opensymphony.xwork2.conversion.impl.XWorkConverter;
import com.opensymphony.xwork2.inject.Container;
import com.opensymphony.xwork2.inject.Inject;
import com.opensymphony.xwork2.ognl.accessor.RootAccessor;
import com.opensymphony.xwork2.util.CompoundRoot;
import com.opensymphony.xwork2.util.reflection.ReflectionException;
import ognl.ClassResolver;
@@ -857,7 +858,7 @@ public class OgnlUtil {
protected Map<String, Object> createDefaultContext(Object root, ClassResolver resolver) {
if (resolver == null) {
resolver = container.getInstance(ClassResolver.class, CompoundRoot.class.getName());
resolver = container.getInstance(RootAccessor.class);
if (resolver == null) {
throw new IllegalStateException("Cannot find ClassResolver");
}
@@ -24,6 +24,7 @@ import com.opensymphony.xwork2.conversion.impl.XWorkConverter;
import com.opensymphony.xwork2.inject.Container;
import com.opensymphony.xwork2.inject.Inject;
import com.opensymphony.xwork2.ognl.accessor.CompoundRootAccessor;
import com.opensymphony.xwork2.ognl.accessor.RootAccessor;
import com.opensymphony.xwork2.util.ClearableValueStack;
import com.opensymphony.xwork2.util.CompoundRoot;
import com.opensymphony.xwork2.util.MemberAccessValueStack;
@@ -34,7 +35,6 @@ import ognl.NoSuchPropertyException;
import ognl.Ognl;
import ognl.OgnlContext;
import ognl.OgnlException;
import ognl.PropertyAccessor;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
@@ -76,13 +76,14 @@ public class OgnlValueStack implements Serializable, ValueStack, ClearableValueS
private transient XWorkConverter converter;
private boolean devMode;
private boolean logMissingProperties;
private boolean shouldFallbackToContext = true;
/**
* @since 6.4.0
*/
protected OgnlValueStack(ValueStack vs,
XWorkConverter xworkConverter,
CompoundRootAccessor accessor,
RootAccessor accessor,
TextProvider prov,
SecurityMemberAccess securityMemberAccess) {
setRoot(xworkConverter,
@@ -97,19 +98,19 @@ public class OgnlValueStack implements Serializable, ValueStack, ClearableValueS
/**
* @since 6.4.0
*/
protected OgnlValueStack(XWorkConverter xworkConverter, CompoundRootAccessor accessor, TextProvider prov, SecurityMemberAccess securityMemberAccess) {
protected OgnlValueStack(XWorkConverter xworkConverter, RootAccessor accessor, TextProvider prov, SecurityMemberAccess securityMemberAccess) {
this(null, xworkConverter, accessor, prov, securityMemberAccess);
}
/**
* @since 6.4.0
*/
protected OgnlValueStack(ValueStack vs, XWorkConverter xworkConverter, CompoundRootAccessor accessor, SecurityMemberAccess securityMemberAccess) {
protected OgnlValueStack(ValueStack vs, XWorkConverter xworkConverter, RootAccessor accessor, SecurityMemberAccess securityMemberAccess) {
this(vs, xworkConverter, accessor, null, securityMemberAccess);
}
/**
* @deprecated since 6.4.0, use {@link #OgnlValueStack(ValueStack, XWorkConverter, CompoundRootAccessor, TextProvider, SecurityMemberAccess)} instead.
* @deprecated since 6.4.0, use {@link #OgnlValueStack(ValueStack, XWorkConverter, RootAccessor, TextProvider, SecurityMemberAccess)} instead.
*/
@Deprecated
protected OgnlValueStack(ValueStack vs,
@@ -121,7 +122,7 @@ public class OgnlValueStack implements Serializable, ValueStack, ClearableValueS
}
/**
* @deprecated since 6.4.0, use {@link #OgnlValueStack(XWorkConverter, CompoundRootAccessor, TextProvider, SecurityMemberAccess)} instead.
* @deprecated since 6.4.0, use {@link #OgnlValueStack(XWorkConverter, RootAccessor, TextProvider, SecurityMemberAccess)} instead.
*/
@Deprecated
protected OgnlValueStack(XWorkConverter xworkConverter, CompoundRootAccessor accessor, TextProvider prov, boolean allowStaticFieldAccess) {
@@ -129,7 +130,7 @@ public class OgnlValueStack implements Serializable, ValueStack, ClearableValueS
}
/**
* @deprecated since 6.4.0, use {@link #OgnlValueStack(ValueStack, XWorkConverter, CompoundRootAccessor, SecurityMemberAccess)} instead.
* @deprecated since 6.4.0, use {@link #OgnlValueStack(ValueStack, XWorkConverter, RootAccessor, SecurityMemberAccess)} instead.
*/
@Deprecated
protected OgnlValueStack(ValueStack vs, XWorkConverter xworkConverter, CompoundRootAccessor accessor, boolean allowStaticFieldAccess) {
@@ -144,7 +145,7 @@ public class OgnlValueStack implements Serializable, ValueStack, ClearableValueS
/**
* @since 6.4.0
*/
protected void setRoot(XWorkConverter xworkConverter, CompoundRootAccessor accessor, CompoundRoot compoundRoot, SecurityMemberAccess securityMemberAccess) {
protected void setRoot(XWorkConverter xworkConverter, RootAccessor accessor, CompoundRoot compoundRoot, SecurityMemberAccess securityMemberAccess) {
this.root = compoundRoot;
this.securityMemberAccess = securityMemberAccess;
this.context = Ognl.createDefaultContext(this.root, securityMemberAccess, accessor, new OgnlTypeConverterWrapper(xworkConverter));
@@ -155,7 +156,7 @@ public class OgnlValueStack implements Serializable, ValueStack, ClearableValueS
}
/**
* @deprecated since 6.4.0, use {@link #setRoot(XWorkConverter, CompoundRootAccessor, CompoundRoot, SecurityMemberAccess)} instead.
* @deprecated since 6.4.0, use {@link #setRoot(XWorkConverter, RootAccessor, CompoundRoot, SecurityMemberAccess)} instead.
*/
@Deprecated
protected void setRoot(XWorkConverter xworkConverter, CompoundRootAccessor accessor, CompoundRoot compoundRoot, boolean allowStaticFieldAccess) {
@@ -172,6 +173,11 @@ public class OgnlValueStack implements Serializable, ValueStack, ClearableValueS
this.logMissingProperties = BooleanUtils.toBoolean(logMissingProperties);
}
@Inject(value = StrutsConstants.STRUTS_OGNL_VALUE_STACK_FALLBACK_TO_CONTEXT, required = false)
protected void setShouldFallbackToContext(String shouldFallbackToContext) {
this.shouldFallbackToContext = BooleanUtils.toBoolean(shouldFallbackToContext);
}
/**
* @see com.opensymphony.xwork2.util.ValueStack#getContext()
*/
@@ -337,34 +343,16 @@ public class OgnlValueStack implements Serializable, ValueStack, ClearableValueS
}
private Object tryFindValue(String expr) throws OgnlException {
Object value;
expr = lookupForOverrides(expr);
if (defaultType != null) {
value = findValue(expr, defaultType);
} else {
value = getValueUsingOgnl(expr);
if (value == null) {
value = findInContext(expr);
}
}
return value;
return tryFindValue(expr, defaultType);
}
private String lookupForOverrides(String expr) {
if ((overrides != null) && overrides.containsKey(expr)) {
if (overrides != null && overrides.containsKey(expr)) {
expr = (String) overrides.get(expr);
}
return expr;
}
private Object getValueUsingOgnl(String expr) throws OgnlException {
try {
return ognlUtil.getValue(expr, context, root);
} finally {
context.remove(THROW_EXCEPTION_ON_FAILURE);
}
}
public Object findValue(String expr) {
return findValue(expr, false);
}
@@ -419,25 +407,25 @@ public class OgnlValueStack implements Serializable, ValueStack, ClearableValueS
}
private Object tryFindValue(String expr, Class asType) throws OgnlException {
Object value = null;
try {
expr = lookupForOverrides(expr);
value = getValue(expr, asType);
Object value = ognlUtil.getValue(expr, context, root, asType);
if (value == null) {
value = findInContext(expr);
return converter.convertValue(getContext(), value, asType);
if (value != null && asType != null) {
value = converter.convertValue(getContext(), value, asType);
}
}
return value;
} finally {
context.remove(THROW_EXCEPTION_ON_FAILURE);
}
return value;
}
private Object getValue(String expr, Class asType) throws OgnlException {
return ognlUtil.getValue(expr, context, root, asType);
}
protected Object findInContext(String name) {
if (!shouldFallbackToContext) {
return null;
}
return getContext().get(name);
}
@@ -525,7 +513,7 @@ public class OgnlValueStack implements Serializable, ValueStack, ClearableValueS
ActionContext ac = ActionContext.getContext();
Container cont = ac.getContainer();
XWorkConverter xworkConverter = cont.getInstance(XWorkConverter.class);
CompoundRootAccessor accessor = (CompoundRootAccessor) cont.getInstance(PropertyAccessor.class, CompoundRoot.class.getName());
RootAccessor accessor = cont.getInstance(RootAccessor.class);
TextProvider prov = cont.getInstance(TextProvider.class, "system");
SecurityMemberAccess sma = cont.getInstance(SecurityMemberAccess.class);
OgnlValueStack aStack = new OgnlValueStack(xworkConverter, accessor, prov, sma);
@@ -23,10 +23,10 @@ import com.opensymphony.xwork2.conversion.NullHandler;
import com.opensymphony.xwork2.conversion.impl.XWorkConverter;
import com.opensymphony.xwork2.inject.Container;
import com.opensymphony.xwork2.inject.Inject;
import com.opensymphony.xwork2.ognl.accessor.CompoundRootAccessor;
import com.opensymphony.xwork2.ognl.accessor.RootAccessor;
import com.opensymphony.xwork2.util.CompoundRoot;
import com.opensymphony.xwork2.util.ValueStack;
import com.opensymphony.xwork2.util.ValueStackFactory;
import ognl.ClassResolver;
import ognl.MethodAccessor;
import ognl.OgnlRuntime;
import ognl.PropertyAccessor;
@@ -41,7 +41,7 @@ import java.util.Set;
public class OgnlValueStackFactory implements ValueStackFactory {
protected XWorkConverter xworkConverter;
protected CompoundRootAccessor compoundRootAccessor;
protected RootAccessor compoundRootAccessor;
protected TextProvider textProvider;
protected Container container;
@@ -50,9 +50,11 @@ public class OgnlValueStackFactory implements ValueStackFactory {
this.xworkConverter = converter;
}
@Inject(value = "com.opensymphony.xwork2.util.CompoundRoot")
protected void setClassResolver(ClassResolver classResolver) {
this.compoundRootAccessor = (CompoundRootAccessor) classResolver;
@Inject
protected void setCompoundRootAccessor(RootAccessor compoundRootAccessor) {
this.compoundRootAccessor = compoundRootAccessor;
OgnlRuntime.setPropertyAccessor(CompoundRoot.class, compoundRootAccessor);
OgnlRuntime.setMethodAccessor(CompoundRoot.class, compoundRootAccessor);
}
@Inject("system")
@@ -60,18 +62,21 @@ public class OgnlValueStackFactory implements ValueStackFactory {
this.textProvider = textProvider;
}
@Override
public ValueStack createValueStack() {
ValueStack stack = new OgnlValueStack(
xworkConverter, compoundRootAccessor, textProvider, container.getInstance(SecurityMemberAccess.class));
container.inject(stack);
return stack.getActionContext().withContainer(container).withValueStack(stack).getValueStack();
return createValueStack(null, true);
}
@Override
public ValueStack createValueStack(ValueStack stack) {
ValueStack result = new OgnlValueStack(
stack, xworkConverter, compoundRootAccessor, container.getInstance(SecurityMemberAccess.class));
container.inject(result);
return result.getActionContext().withContainer(container).withValueStack(result).getValueStack();
return createValueStack(stack, false);
}
protected ValueStack createValueStack(ValueStack stack, boolean useTextProvider) {
ValueStack newStack = new OgnlValueStack(
stack, xworkConverter, compoundRootAccessor, useTextProvider ? textProvider : null, container.getInstance(SecurityMemberAccess.class));
container.inject(newStack);
return newStack.getActionContext().withContainer(container).withValueStack(newStack).getValueStack();
}
@Inject
@@ -60,15 +60,18 @@ public class SecurityMemberAccess implements MemberAccess {
private static final Logger LOG = LogManager.getLogger(SecurityMemberAccess.class);
private static final Set<String> ALLOWLIST_REQUIRED_PACKAGES = unmodifiableSet(new HashSet<>(Arrays.asList(
"com.opensymphony.xwork2.validator.validators",
"org.apache.struts2.components",
"org.apache.struts2.views.jsp",
"com.opensymphony.xwork2.validator.validators"
"org.apache.struts2.views.jsp"
)));
private static final Set<Class<?>> ALLOWLIST_REQUIRED_CLASSES = unmodifiableSet(new HashSet<>(Arrays.asList(
java.lang.Enum.class,
java.lang.String.class,
java.util.Date.class,
java.util.HashMap.class
java.util.HashMap.class,
java.util.Map.class,
java.util.Map.Entry.class
)));
private final ProviderAllowlist providerAllowlist;
@@ -22,15 +22,12 @@ import com.opensymphony.xwork2.inject.Inject;
import com.opensymphony.xwork2.ognl.OgnlValueStack;
import com.opensymphony.xwork2.util.CompoundRoot;
import com.opensymphony.xwork2.util.ValueStack;
import ognl.ClassResolver;
import ognl.MethodAccessor;
import ognl.MethodFailedException;
import ognl.NoSuchPropertyException;
import ognl.Ognl;
import ognl.OgnlContext;
import ognl.OgnlException;
import ognl.OgnlRuntime;
import ognl.PropertyAccessor;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
@@ -56,11 +53,12 @@ import static org.apache.commons.lang3.BooleanUtils.toBoolean;
* @author Rainer Hermanns
* @version $Revision$
*/
public class CompoundRootAccessor implements PropertyAccessor, MethodAccessor, ClassResolver {
public class CompoundRootAccessor implements RootAccessor {
/**
* Used by OGNl to generate bytecode
*/
@Override
public String getSourceAccessor(OgnlContext context, Object target, Object index) {
return null;
}
@@ -68,6 +66,7 @@ public class CompoundRootAccessor implements PropertyAccessor, MethodAccessor, C
/**
* Used by OGNl to generate bytecode
*/
@Override
public String getSourceSetter(OgnlContext context, Object target, Object index) {
return null;
}
@@ -88,6 +87,7 @@ public class CompoundRootAccessor implements PropertyAccessor, MethodAccessor, C
this.disallowCustomOgnlMap = BooleanUtils.toBoolean(disallowCustomOgnlMap);
}
@Override
public void setProperty(Map context, Object target, Object name, Object value) throws OgnlException {
CompoundRoot root = (CompoundRoot) target;
OgnlContext ognlContext = (OgnlContext) context;
@@ -136,6 +136,7 @@ public class CompoundRootAccessor implements PropertyAccessor, MethodAccessor, C
}
}
@Override
public Object getProperty(Map context, Object target, Object name) throws OgnlException {
CompoundRoot root = (CompoundRoot) target;
OgnlContext ognlContext = (OgnlContext) context;
@@ -181,6 +182,7 @@ public class CompoundRootAccessor implements PropertyAccessor, MethodAccessor, C
}
}
@Override
public Object callMethod(Map context, Object target, String name, Object[] objects) throws MethodFailedException {
CompoundRoot root = (CompoundRoot) target;
@@ -273,10 +275,12 @@ public class CompoundRootAccessor implements PropertyAccessor, MethodAccessor, C
return null;
}
@Override
public Object callStaticMethod(Map transientVars, Class aClass, String s, Object[] objects) throws MethodFailedException {
return null;
}
@Override
public Class classForName(String className, Map context) throws ClassNotFoundException {
Object root = Ognl.getRoot(context);
@@ -0,0 +1,29 @@
/*
* 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.ognl.accessor;
import ognl.ClassResolver;
import ognl.MethodAccessor;
import ognl.PropertyAccessor;
/**
* @since 6.4.0
*/
public interface RootAccessor extends PropertyAccessor, MethodAccessor, ClassResolver {
}
@@ -27,24 +27,8 @@ import java.util.regex.Pattern;
*/
public interface MemberAccessValueStack {
/**
* @deprecated please use {@link #useExcludeProperties(Set)}
*/
@Deprecated
default void setExcludeProperties(Set<Pattern> excludeProperties) {
useExcludeProperties(excludeProperties);
}
void useExcludeProperties(Set<Pattern> excludeProperties);
/**
* @deprecated please use {@link #useAcceptProperties(Set)}
*/
@Deprecated
default void setAcceptProperties(Set<Pattern> acceptedProperties) {
useAcceptProperties(acceptedProperties);
}
void useAcceptProperties(Set<Pattern> acceptedProperties);
}
@@ -220,6 +220,9 @@ public final class StrutsConstants {
public static final String STRUTS_FREEMARKER_WRAPPER_ALT_MAP = "struts.freemarker.wrapper.altMap";
/** Extension point for the Struts CompoundRootAccessor */
public static final String STRUTS_COMPOUND_ROOT_ACCESSOR = "struts.compoundRootAccessor";
/** The name of the xwork converter implementation */
public static final String STRUTS_XWORKCONVERTER = "struts.xworkConverter";
@@ -308,6 +311,14 @@ public final class StrutsConstants {
*/
public static final String STRUTS_OGNL_LOG_MISSING_PROPERTIES = "struts.ognl.logMissingProperties";
/**
* Determines whether lookups on the ValueStack should fallback to looking in the context if the OGNL expression
* fails or returns null.
*
* @since 6.4.0
*/
public static final String STRUTS_OGNL_VALUE_STACK_FALLBACK_TO_CONTEXT = "struts.ognl.valueStackFallbackToContext";
/**
* Logs properties that are not found (very verbose)
* @deprecated as of 6.0.0. Use {@link #STRUTS_OGNL_LOG_MISSING_PROPERTIES} instead.
@@ -42,7 +42,7 @@ import static org.apache.struts2.interceptor.csp.CspSettings.CSP_REPORT_TYPE;
* &lt;package name="csp-reports" namespace="/" extends="struts-default"&gt;
* &lt;action name="csp-reports" class="org.apache.struts2.action.DefaultCspReportAction"&gt;
* &lt;result type="httpheader"&gt;
* &lt;param name="statusCode">200&lt;/param&gt;
* &lt;param name="status">204&lt;/param&gt;
* &lt;/result&gt;
* &lt;/action&gt;
* &lt;/package&gt;
@@ -23,7 +23,6 @@ package org.apache.struts2.action;
* them automatically. This may be useful if one is using the action tag and want to supply
* the parameters to the action manually using the param tag. It may also be useful if one for
* security reasons wants to make sure that parameters cannot be set by malicious users.
*
*/
public interface NoParameters extends com.opensymphony.xwork2.interceptor.NoParameters {
public interface NoParameters {
}
@@ -0,0 +1,39 @@
/*
* 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.action;
import org.apache.struts2.interceptor.parameter.ParametersInterceptor;
/**
* This interface is implemented by actions that want to declare acceptable parameters. Works in conjunction with {@link
* ParametersInterceptor}. For example, actions may want to create a white list of parameters they will accept or a
* blacklist of parameters they will reject to prevent clients from setting other unexpected (and possibly dangerous)
* parameters.
*/
public interface ParameterNameAware {
/**
* Tests if the action will accept the parameter with the given name.
*
* @param parameterName the parameter name
* @return <code>true</code> if accepted, <code>false</code> otherwise
*/
boolean acceptableParameterName(String parameterName);
}
@@ -0,0 +1,39 @@
/*
* 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.action;
import org.apache.struts2.interceptor.parameter.ParametersInterceptor;
/**
* This interface is implemented by actions that want to declare acceptable parameter values. Works in conjunction with
* {@link ParametersInterceptor}. For example, actions may want to create a white list of parameter values they will
* accept or a blacklist of parameter values they will reject to prevent clients from setting other unexpected (and
* possibly dangerous) parameter values.
*/
public interface ParameterValueAware {
/**
* Tests if the action will accept the parameter with the given value.
*
* @param parameterValue the parameter value
* @return <code>true</code> if accepted, <code>false</code> otherwise
*/
boolean acceptableParameterValue(String parameterValue);
}
@@ -50,6 +50,7 @@ import com.opensymphony.xwork2.inject.Scope;
import com.opensymphony.xwork2.ognl.BeanInfoCacheFactory;
import com.opensymphony.xwork2.ognl.ExpressionCacheFactory;
import com.opensymphony.xwork2.ognl.SecurityMemberAccess;
import com.opensymphony.xwork2.ognl.accessor.RootAccessor;
import com.opensymphony.xwork2.security.AcceptedPatternsChecker;
import com.opensymphony.xwork2.security.ExcludedPatternsChecker;
import com.opensymphony.xwork2.security.NotExcludedAcceptedPatternsChecker;
@@ -387,6 +388,8 @@ public class StrutsBeanSelectionProvider extends AbstractBeanSelectionProvider {
alias(FileManagerFactory.class, StrutsConstants.STRUTS_FILE_MANAGER_FACTORY, builder, props, Scope.SINGLETON);
alias(RootAccessor.class, StrutsConstants.STRUTS_COMPOUND_ROOT_ACCESSOR, builder, props);
alias(XWorkConverter.class, StrutsConstants.STRUTS_XWORKCONVERTER, builder, props);
alias(CollectionConverter.class, StrutsConstants.STRUTS_CONVERTER_COLLECTION, builder, props);
alias(ArrayConverter.class, StrutsConstants.STRUTS_CONVERTER_ARRAY, builder, props);
@@ -428,8 +431,7 @@ 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);
alias(NotExcludedAcceptedPatternsChecker.class, StrutsConstants.STRUTS_NOT_EXCLUDED_ACCEPTED_PATTERNS_CHECKER, builder, props, Scope.SINGLETON);
alias(DateFormatter.class, StrutsConstants.STRUTS_DATE_FORMATTER, builder, props, Scope.SINGLETON);
@@ -19,10 +19,10 @@
package org.apache.struts2.interceptor;
import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.interceptor.ParametersInterceptor;
import org.apache.struts2.ServletActionContext;
import org.apache.struts2.action.ParameterNameAware;
import org.apache.struts2.dispatcher.HttpParameters;
import org.apache.struts2.dispatcher.mapper.ActionMapping;
import org.apache.struts2.interceptor.parameter.ParametersInterceptor;
import java.util.Map;
@@ -53,7 +53,7 @@ import java.util.Map;
* <!-- START SNIPPET: extending -->
*
* <p>
* The best way to add behavior to this interceptor is to utilize the {@link com.opensymphony.xwork2.interceptor.ParameterNameAware} interface in your
* The best way to add behavior to this interceptor is to utilize the {@link ParameterNameAware} interface in your
* actions. However, if you wish to apply a global rule that isn't implemented in your action, then you could extend
* this interceptor and override the {@link #acceptableName(String)} method.
* </p>
@@ -0,0 +1,561 @@
/*
* 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.parameter;
import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.ActionInvocation;
import com.opensymphony.xwork2.TextProvider;
import com.opensymphony.xwork2.inject.Inject;
import com.opensymphony.xwork2.interceptor.MethodFilterInterceptor;
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.MemberAccessValueStack;
import com.opensymphony.xwork2.util.TextParseUtil;
import com.opensymphony.xwork2.util.ValueStack;
import com.opensymphony.xwork2.util.ValueStackFactory;
import com.opensymphony.xwork2.util.reflection.ReflectionContextState;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.struts2.StrutsConstants;
import org.apache.struts2.action.NoParameters;
import org.apache.struts2.action.ParameterNameAware;
import org.apache.struts2.action.ParameterValueAware;
import org.apache.struts2.dispatcher.HttpParameters;
import org.apache.struts2.dispatcher.Parameter;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.regex.Pattern;
import static org.apache.commons.lang3.StringUtils.normalizeSpace;
/**
* This interceptor sets all parameters on the value stack.
*/
public class ParametersInterceptor extends MethodFilterInterceptor {
private static final Logger LOG = LogManager.getLogger(ParametersInterceptor.class);
protected static final int PARAM_NAME_MAX_LENGTH = 100;
private static final Pattern DMI_IGNORED_PATTERN = Pattern.compile("^(action|method):.*", Pattern.CASE_INSENSITIVE);
private int paramNameMaxLength = PARAM_NAME_MAX_LENGTH;
private boolean devMode = false;
private boolean dmiEnabled = false;
protected boolean ordered = false;
private ValueStackFactory valueStackFactory;
private ExcludedPatternsChecker excludedPatterns;
private AcceptedPatternsChecker acceptedPatterns;
private Set<Pattern> excludedValuePatterns = null;
private Set<Pattern> acceptedValuePatterns = null;
@Inject
public void setValueStackFactory(ValueStackFactory valueStackFactory) {
this.valueStackFactory = valueStackFactory;
}
@Inject(StrutsConstants.STRUTS_DEVMODE)
public void setDevMode(String mode) {
this.devMode = BooleanUtils.toBoolean(mode);
}
@Inject
public void setExcludedPatterns(ExcludedPatternsChecker excludedPatterns) {
this.excludedPatterns = excludedPatterns;
}
@Inject
public void setAcceptedPatterns(AcceptedPatternsChecker acceptedPatterns) {
this.acceptedPatterns = acceptedPatterns;
}
@Inject(value = StrutsConstants.STRUTS_ENABLE_DYNAMIC_METHOD_INVOCATION, required = false)
protected void setDynamicMethodInvocation(String dmiEnabled) {
this.dmiEnabled = Boolean.parseBoolean(dmiEnabled);
}
/**
* If the param name exceeds the configured maximum length it will not be
* accepted.
*
* @param paramNameMaxLength Maximum length of param names
*/
public void setParamNameMaxLength(int paramNameMaxLength) {
this.paramNameMaxLength = paramNameMaxLength;
}
static private int countOGNLCharacters(String s) {
int count = 0;
for (int i = s.length() - 1; i >= 0; i--) {
char c = s.charAt(i);
if (c == '.' || c == '[') count++;
}
return count;
}
/**
* Compares based on number of '.' and '[' characters (fewer is higher)
*/
static final Comparator<String> rbCollator = (s1, s2) -> {
int l1 = countOGNLCharacters(s1);
int l2 = countOGNLCharacters(s2);
return l1 < l2 ? -1 : (l2 < l1 ? 1 : s1.compareTo(s2));
};
@Override
public String doIntercept(ActionInvocation invocation) throws Exception {
Object action = invocation.getAction();
if (!(action instanceof NoParameters)) {
ActionContext ac = invocation.getInvocationContext();
HttpParameters parameters = retrieveParameters(ac);
if (LOG.isDebugEnabled()) {
LOG.debug("Setting params {}", normalizeSpace(getParameterLogMap(parameters)));
}
if (parameters != null) {
Map<String, Object> contextMap = ac.getContextMap();
try {
ReflectionContextState.setCreatingNullObjects(contextMap, true);
ReflectionContextState.setDenyMethodExecution(contextMap, true);
ReflectionContextState.setReportingConversionErrors(contextMap, true);
ValueStack stack = ac.getValueStack();
setParameters(action, stack, parameters);
} finally {
ReflectionContextState.setCreatingNullObjects(contextMap, false);
ReflectionContextState.setDenyMethodExecution(contextMap, false);
ReflectionContextState.setReportingConversionErrors(contextMap, false);
}
}
}
return invocation.invoke();
}
/**
* Gets the parameter map to apply from wherever appropriate
*
* @param ac The action context
* @return The parameter map to apply
*/
protected HttpParameters retrieveParameters(ActionContext ac) {
return ac.getParameters();
}
/**
* Adds the parameters into context's ParameterMap
*
* @param ac The action context
* @param newParams The parameter map to apply
* <p>
* In this class this is a no-op, since the parameters were fetched from the same location.
* In subclasses both retrieveParameters() and addParametersToContext() should be overridden.
* </p>
*/
protected void addParametersToContext(ActionContext ac, Map<String, ?> newParams) {
}
protected void setParameters(final Object action, ValueStack stack, HttpParameters parameters) {
HttpParameters params;
Map<String, Parameter> acceptableParameters;
if (ordered) {
params = HttpParameters.create().withComparator(getOrderedComparator()).withParent(parameters).build();
acceptableParameters = new TreeMap<>(getOrderedComparator());
} else {
params = HttpParameters.create().withParent(parameters).build();
acceptableParameters = new TreeMap<>();
}
for (Map.Entry<String, Parameter> entry : params.entrySet()) {
String parameterName = entry.getKey();
boolean isAcceptableParameter = isAcceptableParameter(parameterName, action);
isAcceptableParameter &= isAcceptableParameterValue(entry.getValue(), action);
if (isAcceptableParameter) {
acceptableParameters.put(parameterName, entry.getValue());
}
}
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<String, Object> 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()).withValueStack(stack);
}
boolean memberAccessStack = newStack instanceof MemberAccessValueStack;
if (memberAccessStack) {
//block or allow access to properties
//see WW-2761 for more details
MemberAccessValueStack accessValueStack = (MemberAccessValueStack) newStack;
accessValueStack.useAcceptProperties(acceptedPatterns.getAcceptedPatterns());
accessValueStack.useExcludeProperties(excludedPatterns.getExcludedPatterns());
}
for (Map.Entry<String, Parameter> entry : acceptableParameters.entrySet()) {
String name = entry.getKey();
Parameter value = entry.getValue();
try {
newStack.setParameter(name, value.getObject());
} catch (RuntimeException e) {
if (devMode) {
notifyDeveloperParameterException(action, name, e.getMessage());
}
}
}
if (clearableStack) {
stack.getActionContext().withConversionErrors(newStack.getActionContext().getConversionErrors());
}
addParametersToContext(ActionContext.getContext(), acceptableParameters);
}
protected void notifyDeveloperParameterException(Object action, String property, String message) {
String developerNotification = "Unexpected Exception caught setting '" + property + "' on '" + action.getClass() + ": " + message;
if (action instanceof TextProvider) {
TextProvider tp = (TextProvider) action;
developerNotification = tp.getText("devmode.notification",
"Developer Notification:\n{0}",
new String[]{developerNotification}
);
}
LOG.error(developerNotification);
if (action instanceof ValidationAware) {
// see https://issues.apache.org/jira/browse/WW-4066
Collection<String> messages = ((ValidationAware) action).getActionMessages();
messages.add(message);
((ValidationAware) action).setActionMessages(messages);
}
}
/**
* Checks if name of parameter can be accepted or thrown away
*
* @param name parameter name
* @param action current action
* @return true if parameter is accepted
*/
protected boolean isAcceptableParameter(String name, Object action) {
ParameterNameAware parameterNameAware = (action instanceof ParameterNameAware) ? (ParameterNameAware) action : null;
return acceptableName(name) && (parameterNameAware == null || parameterNameAware.acceptableParameterName(name));
}
/**
* Checks if parameter value can be accepted or thrown away
*
* @param param the parameter
* @param action current action
* @return true if parameter is accepted
*/
protected boolean isAcceptableParameterValue(Parameter param, Object action) {
ParameterValueAware parameterValueAware = (action instanceof ParameterValueAware) ? (ParameterValueAware) action : null;
boolean acceptableParamValue = (parameterValueAware == null || parameterValueAware.acceptableParameterValue(param.getValue()));
if (hasParamValuesToExclude() || hasParamValuesToAccept()) {
// Additional validations to process
acceptableParamValue &= acceptableValue(param.getName(), param.getValue());
}
return acceptableParamValue;
}
/**
* Gets an instance of the comparator to use for the ordered sorting. Override this
* method to customize the ordering of the parameters as they are set to the
* action.
*
* @return A comparator to sort the parameters
*/
protected Comparator<String> getOrderedComparator() {
return rbCollator;
}
protected String getParameterLogMap(HttpParameters parameters) {
if (parameters == null) {
return "NONE";
}
StringBuilder logEntry = new StringBuilder();
for (Map.Entry<String, Parameter> entry : parameters.entrySet()) {
logEntry.append(entry.getKey());
logEntry.append(" => ");
logEntry.append(entry.getValue().getValue());
logEntry.append(" ");
}
return logEntry.toString();
}
/**
* Validates the name passed is:
* * Within the max length of a parameter name
* * Is not excluded
* * Is accepted
*
* @param name - Name to check
* @return true if accepted
*/
protected boolean acceptableName(String name) {
if (isIgnoredDMI(name)) {
LOG.trace("DMI is enabled, ignoring DMI method: {}", name);
return false;
}
boolean accepted = isWithinLengthLimit(name) && !isExcluded(name) && isAccepted(name);
if (devMode && accepted) { // notify only when in devMode
LOG.debug("Parameter [{}] was accepted and will be appended to action!", name);
}
return accepted;
}
private boolean isIgnoredDMI(String name) {
if (dmiEnabled) {
return DMI_IGNORED_PATTERN.matcher(name).matches();
} else {
return false;
}
}
/**
* Validates:
* * Value is null/blank
* * Value is not excluded
* * Value is accepted
*
* @param name - Param name (for logging)
* @param value - value to check
* @return true if accepted
*/
protected boolean acceptableValue(String name, String value) {
boolean accepted = (value == null || value.isEmpty() || (!isParamValueExcluded(value) && isParamValueAccepted(value)));
if (!accepted) {
String message = "Value [{}] of parameter [{}] was not accepted and will be dropped!";
if (devMode) {
LOG.warn(message, normalizeSpace(value), normalizeSpace(name));
} else {
LOG.debug(message, normalizeSpace(value), normalizeSpace(name));
}
}
return accepted;
}
protected boolean isWithinLengthLimit(String name) {
boolean matchLength = name.length() <= paramNameMaxLength;
if (!matchLength) {
if (devMode) { // warn only when in devMode
LOG.warn("Parameter [{}] is too long, allowed length is [{}]. Use Interceptor Parameter Overriding " +
"to override the limit, see more at\n" +
"https://struts.apache.org/core-developers/interceptors.html#interceptor-parameter-overriding",
name, paramNameMaxLength);
} else {
LOG.warn("Parameter [{}] is too long, allowed length is [{}]", name, paramNameMaxLength);
}
}
return matchLength;
}
protected boolean isAccepted(String paramName) {
AcceptedPatternsChecker.IsAccepted result = acceptedPatterns.isAccepted(paramName);
if (result.isAccepted()) {
return true;
} else if (devMode) { // warn only when in devMode
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());
} else {
LOG.debug("Parameter [{}] didn't match accepted pattern [{}]!", paramName, result.getAcceptedPattern());
}
return false;
}
protected boolean isExcluded(String paramName) {
ExcludedPatternsChecker.IsExcluded result = excludedPatterns.isExcluded(paramName);
if (result.isExcluded()) {
if (devMode) { // warn only when in devMode
LOG.warn("Parameter [{}] matches excluded pattern [{}]! See Accepted / Excluded patterns at\n" +
"https://struts.apache.org/security/#accepted--excluded-patterns",
paramName, result.getExcludedPattern());
} else {
LOG.debug("Parameter [{}] matches excluded pattern [{}]!", paramName, result.getExcludedPattern());
}
return true;
}
return false;
}
protected boolean isParamValueExcluded(String value) {
if (!hasParamValuesToExclude()) {
LOG.debug("'excludedValuePatterns' not defined so anything is allowed");
return false;
}
for (Pattern excludedValuePattern : excludedValuePatterns) {
if (excludedValuePattern.matcher(value).matches()) {
if (devMode) {
LOG.warn("Parameter value [{}] matches excluded pattern [{}]! See Accepting/Excluding parameter values at\n" +
"https://struts.apache.org/core-developers/parameters-interceptor#excluding-parameter-values",
value, excludedValuePatterns);
} else {
LOG.debug("Parameter value [{}] matches excluded pattern [{}]", value, excludedValuePattern);
}
return true;
}
}
return false;
}
protected boolean isParamValueAccepted(String value) {
if (!hasParamValuesToAccept()) {
LOG.debug("'acceptedValuePatterns' not defined so anything is allowed");
return true;
}
for (Pattern acceptedValuePattern : acceptedValuePatterns) {
if (acceptedValuePattern.matcher(value).matches()) {
return true;
}
}
if (devMode) {
LOG.warn("Parameter value [{}] didn't match accepted pattern [{}]! See Accepting/Excluding parameter values at\n" +
"https://struts.apache.org/core-developers/parameters-interceptor#excluding-parameter-values",
value, acceptedValuePatterns);
} else {
LOG.debug("Parameter value [{}] was not accepted!", value);
}
return false;
}
private boolean hasParamValuesToExclude() {
return excludedValuePatterns != null && excludedValuePatterns.size() > 0;
}
private boolean hasParamValuesToAccept() {
return acceptedValuePatterns != null && acceptedValuePatterns.size() > 0;
}
/**
* Whether to order the parameters or not
*
* @return True to order
*/
public boolean isOrdered() {
return ordered;
}
/**
* Set whether to order the parameters by object depth or not
*
* @param ordered True to order them
*/
public void setOrdered(boolean ordered) {
this.ordered = ordered;
}
/**
* Sets a comma-delimited list of regular expressions to match
* parameters that are allowed in the parameter map (aka whitelist).
* <p>
* Don't change the default unless you know what you are doing in terms
* of security implications.
* </p>
*
* @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);
}
/**
* Sets a comma-delimited list of regular expressions to match
* values of parameters that should be accepted and included in the parameter map.
*
* @param commaDelimitedPatterns A comma-delimited set of regular expressions
*/
public void setAcceptedValuePatterns(String commaDelimitedPatterns) {
Set<String> patterns = TextParseUtil.commaDelimitedStringToSet(commaDelimitedPatterns);
if (acceptedValuePatterns == null) {
// Limit unwanted log entries (for 1st call, acceptedValuePatterns null)
LOG.debug("Sets accepted value patterns to [{}], note this may impact the safety of your application!", patterns);
} else {
LOG.warn("Replacing accepted patterns [{}] with [{}], be aware that this may impact safety of your application!",
acceptedValuePatterns, patterns);
}
acceptedValuePatterns = new HashSet<>(patterns.size());
try {
for (String pattern : patterns) {
acceptedValuePatterns.add(Pattern.compile(pattern, Pattern.CASE_INSENSITIVE));
}
} finally {
acceptedValuePatterns = Collections.unmodifiableSet(acceptedValuePatterns);
}
}
/**
* Sets a comma-delimited list of regular expressions to match
* values of parameters that should be removed from the parameter map.
*
* @param commaDelimitedPatterns A comma-delimited set of regular expressions
*/
public void setExcludedValuePatterns(String commaDelimitedPatterns) {
Set<String> patterns = TextParseUtil.commaDelimitedStringToSet(commaDelimitedPatterns);
if (excludedValuePatterns == null) {
// Limit unwanted log entries (for 1st call, excludedValuePatterns null)
LOG.debug("Setting excluded value patterns to [{}]", patterns);
} else {
LOG.warn("Replacing excluded value patterns [{}] with [{}], be aware that this may impact safety of your application!",
excludedValuePatterns, patterns);
}
excludedValuePatterns = new HashSet<>(patterns.size());
try {
for (String pattern : patterns) {
excludedValuePatterns.add(Pattern.compile(pattern, Pattern.CASE_INSENSITIVE));
}
} finally {
excludedValuePatterns = Collections.unmodifiableSet(excludedValuePatterns);
}
}
}
@@ -0,0 +1,30 @@
/*
* 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.util;
import com.opensymphony.xwork2.util.ValueStack;
/**
* @since 6.4.0
*/
public interface ValueStackProvider {
ValueStack getValueStack();
}
@@ -20,39 +20,39 @@
-->
<!DOCTYPE html>
<html>
<style>
<@s.style>
.debugTable {
border-style: solid;
border-width: 1px;
}
.debugTable td {
border-style: solid;
border-width: 1px;
}
.nameColumn {
background-color:#CCDDFF;
}
.valueColumn {
background-color: #CCFFCC;
}
.nullValue {
background-color: #FF0000;
}
.typeColumn {
background-color: white;
}
.emptyCollection {
background-color: #EEEEEE;
}
</style>
</@s.style>
<script>
<@s.script>
function expand(src, path) {
var baseUrl = location.href;
var i = baseUrl.indexOf("&object=");
@@ -83,7 +83,7 @@
};
request.send();
}
</script>
</@s.script>
<body>
${debugHtml?no_esc}
@@ -21,7 +21,7 @@
<!DOCTYPE html>
<html>
<head>
<@s.script type="text/javascript">
<@s.script>
var baseUrl = "<@s.url value="/static" includeParams="none"/>";
window.open(baseUrl+"/webconsole.html", 'OGNL Console','width=500,height=450,status=no,toolbar=no,menubar=no');
</@s.script>
+9 -3
View File
@@ -174,11 +174,19 @@
<bean type="com.opensymphony.xwork2.util.TextParser" name="struts"
class="com.opensymphony.xwork2.util.OgnlTextParser" scope="singleton"/>
<bean type="ognl.ClassResolver" name="com.opensymphony.xwork2.util.CompoundRoot"
<bean type="com.opensymphony.xwork2.ognl.accessor.RootAccessor" name="struts"
class="com.opensymphony.xwork2.ognl.accessor.CompoundRootAccessor"/>
<!-- DEPRECATED since 6.4.0 - The following 3 beans are retained for backwards compatibility
with custom beans that may expect them to be present. Inject the DEFAULT bean of type
'com.opensymphony.xwork2.ognl.accessor.RootAccessor' above instead. -->
<bean type="ognl.ClassResolver" name="com.opensymphony.xwork2.util.CompoundRoot"
class="com.opensymphony.xwork2.ognl.accessor.CompoundRootAccessor"/>
<bean type="ognl.PropertyAccessor" name="com.opensymphony.xwork2.util.CompoundRoot"
class="com.opensymphony.xwork2.ognl.accessor.CompoundRootAccessor"/>
<bean type="ognl.MethodAccessor" name="com.opensymphony.xwork2.util.CompoundRoot"
class="com.opensymphony.xwork2.ognl.accessor.CompoundRootAccessor"/>
<bean type="ognl.PropertyAccessor" name="java.lang.Object"
class="com.opensymphony.xwork2.ognl.accessor.ObjectAccessor"/>
<bean type="ognl.PropertyAccessor" name="java.util.Iterator"
@@ -202,8 +210,6 @@
<bean type="ognl.MethodAccessor" name="java.lang.Object"
class="com.opensymphony.xwork2.ognl.accessor.XWorkMethodAccessor"/>
<bean type="ognl.MethodAccessor" name="com.opensymphony.xwork2.util.CompoundRoot"
class="com.opensymphony.xwork2.ognl.accessor.CompoundRootAccessor"/>
<bean type="org.apache.struts2.dispatcher.StaticContentLoader"
class="org.apache.struts2.dispatcher.DefaultStaticContentLoader" name="struts"/>
@@ -19,7 +19,7 @@
*/
-->
<#if parameters.validate!false == true>
<script type="text/javascript" src="${base}${parameters.staticContentPath}/css_xhtml/validation.js"></script>
<@s.script src="${base}${parameters.staticContentPath}/css_xhtml/validation.js"/>
<#if parameters.onsubmit??>
${tag.addParameter('onsubmit', "${parameters.onsubmit}; return validateForm_${parameters.escapedId}();")}
<#else>
@@ -18,7 +18,7 @@
* under the License.
*/
-->
<@s.script type="text/javascript">
<@s.script>
function autoPopulate_${parameters.escapedId}(targetElement) {
<#if parameters.headerKey?? && parameters.headerValue??>
if (targetElement.options[targetElement.selectedIndex].value == '${parameters.headerKey?js_string}') {
@@ -114,7 +114,7 @@
</#if>
/><#rt/>
</#if>
<@s.script type="text/javascript">
<@s.script>
<#assign itemCount = startCount/>
var ${parameters.escapedId}Group = new Array(${parameters.listSize?number?c} + ${startCount});
for (var i = 0; i < (${parameters.listSize?number?c} + ${startCount}); i++) {
@@ -24,6 +24,6 @@
--><#t/>
<#if (parameters.hasTooltip!false)><#t/>
<#lt/><!-- javascript that is needed for tooltips -->
<#lt/><@s.script type="text/javascript" src="${base}${parameters.staticContentPath}/domTT.js" />
<#lt/><@s.script src="${base}${parameters.staticContentPath}/domTT.js" />
<#lt/><@s.link rel="stylesheet" type="text/css" href="${base}${parameters.staticContentPath}/domTT.css" />
</#if><#t/>
@@ -21,7 +21,7 @@
</form>
<#if (parameters.customOnsubmitEnabled??)>
<@s.script type="text/javascript">
<@s.script>
<#--
Enable auto-select of optiontransferselect tag's entries upon containing form's
submission.
@@ -18,4 +18,4 @@
* under the License.
*/
-->
<@s.script src="${base}${parameters.staticContentPath}/utils.js" type="text/javascript" />
<@s.script src="${base}${parameters.staticContentPath}/utils.js" />
@@ -19,7 +19,7 @@
*/
-->
<#if !stack.findValue("#inputtransferselect_js_included")??><#t/>
<@s.script type="text/javascript" src="${base}${parameters.staticContentPath}/inputtransferselect.js"/>
<@s.script src="${base}${parameters.staticContentPath}/inputtransferselect.js"/>
<#assign temporaryVariable = stack.setValue("#inputtransferselect_js_included", "true") /><#t/>
</#if><#t/>
<table>
@@ -19,7 +19,7 @@
*/
-->
<#if !stack.findValue("#optiontransferselect_js_included")??><#t/>
<@s.script type="text/javascript" src="${base}${parameters.staticContentPath}/optiontransferselect.js" />
<@s.script src="${base}${parameters.staticContentPath}/optiontransferselect.js" />
<#assign temporaryVariable = stack.setValue("#optiontransferselect_js_included", "true") /><#t/>
</#if><#t/>
<table>
@@ -19,7 +19,7 @@
*/
-->
<#if !stack.findValue("#optiontransferselect_js_included")??><#t/>
<@s.script type="text/javascript" src="${base}${parameters.staticContentPath}/optiontransferselect.js" /><#t/>
<@s.script src="${base}${parameters.staticContentPath}/optiontransferselect.js" /><#t/>
<#assign temporaryVariable = stack.setValue("#optiontransferselect_js_included", "true") /><#t/>
</#if><#t/>
<table>
@@ -32,7 +32,7 @@ Only the following validators are supported:
END SNIPPET: supported-validators
-->
<#if ((parameters.validate!false == true) && (parameters.performValidation!false == true))>
<@s.script type="text/javascript">
<@s.script>
function validateForm_${parameters.escapedId}() {
<#--
In case of multiselect fields return only the first value.
@@ -22,7 +22,7 @@
<#include "/${parameters.templateDir}/simple/form-close.ftl" />
<#include "/${parameters.templateDir}/${parameters.expandTheme}/form-close-validate.ftl" />
<#if parameters.focusElement??>
<@s.script type="text/javascript">
<@s.script>
StrutsUtils.addOnLoad(function() {
var element = document.getElementById("${parameters.focusElement?js_string}");
if(element) {
@@ -19,7 +19,7 @@
*/
-->
<#if parameters.validate!false == true>
<@s.script type="text/javascript" src="${base}${parameters.staticContentPath}/xhtml/validation.js" />
<@s.script src="${base}${parameters.staticContentPath}/xhtml/validation.js" />
<#if parameters.onsubmit??>
${tag.addParameter('onsubmit', "${parameters.onsubmit}; return validateForm_${parameters.escapedId}();")}
<#else>
@@ -49,7 +49,7 @@ under the License.
color: #666;
}
</style>
<script type="text/javascript" language="javascript">
<script>
var hide = null;
var show = null;
var children = null;
@@ -27,6 +27,7 @@ import com.opensymphony.xwork2.conversion.impl.XWorkConverter;
import com.opensymphony.xwork2.inject.ContainerBuilder;
import com.opensymphony.xwork2.interceptor.ChainingInterceptor;
import com.opensymphony.xwork2.ognl.accessor.CompoundRootAccessor;
import com.opensymphony.xwork2.ognl.accessor.RootAccessor;
import com.opensymphony.xwork2.test.StubConfigurationProvider;
import com.opensymphony.xwork2.test.User;
import com.opensymphony.xwork2.util.Bar;
@@ -36,7 +37,6 @@ import com.opensymphony.xwork2.util.Owner;
import com.opensymphony.xwork2.util.ValueStack;
import com.opensymphony.xwork2.util.location.LocatableProperties;
import com.opensymphony.xwork2.util.reflection.ReflectionContextState;
import ognl.ClassResolver;
import ognl.InappropriateExpressionException;
import ognl.MethodFailedException;
import ognl.NoSuchPropertyException;
@@ -1629,7 +1629,7 @@ public class OgnlUtilTest extends XWorkTestCase {
String vulnerableExpr = "#@com.opensymphony.xwork2.ognl.MyCustomMap@{}.get(\"ye\")";
assertEquals("System compromised", ognlUtil.getValue(vulnerableExpr, ognlUtil.createDefaultContext(null), null));
((CompoundRootAccessor) container.getInstance(ClassResolver.class, CompoundRoot.class.getName()))
((CompoundRootAccessor) container.getInstance(RootAccessor.class))
.useDisallowCustomOgnlMap(Boolean.TRUE.toString());
assertThrows(OgnlException.class, () -> ognlUtil.getValue(vulnerableExpr, ognlUtil.createDefaultContext(null), null));
@@ -27,19 +27,18 @@ import com.opensymphony.xwork2.conversion.impl.ConversionData;
import com.opensymphony.xwork2.conversion.impl.XWorkConverter;
import com.opensymphony.xwork2.inject.ContainerBuilder;
import com.opensymphony.xwork2.ognl.accessor.CompoundRootAccessor;
import com.opensymphony.xwork2.ognl.accessor.RootAccessor;
import com.opensymphony.xwork2.test.StubConfigurationProvider;
import com.opensymphony.xwork2.test.TestBean2;
import com.opensymphony.xwork2.util.Bar;
import com.opensymphony.xwork2.util.BarJunior;
import com.opensymphony.xwork2.util.Cat;
import com.opensymphony.xwork2.util.CompoundRoot;
import com.opensymphony.xwork2.util.Dog;
import com.opensymphony.xwork2.util.Foo;
import com.opensymphony.xwork2.util.ValueStackFactory;
import com.opensymphony.xwork2.util.location.LocatableProperties;
import com.opensymphony.xwork2.util.reflection.ReflectionContextState;
import ognl.OgnlException;
import ognl.PropertyAccessor;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.core.LogEvent;
@@ -98,7 +97,7 @@ public class OgnlValueStackTest extends XWorkTestCase {
private OgnlValueStack createValueStack(boolean allowStaticFieldAccess) {
OgnlValueStack stack = new OgnlValueStack(
container.getInstance(XWorkConverter.class),
(CompoundRootAccessor) container.getInstance(PropertyAccessor.class, CompoundRoot.class.getName()),
(CompoundRootAccessor) container.getInstance(RootAccessor.class),
container.getInstance(TextProvider.class, "system"), allowStaticFieldAccess);
container.inject(stack);
return stack;
@@ -1078,7 +1077,7 @@ public class OgnlValueStackTest extends XWorkTestCase {
OgnlValueStack stack2 = new OgnlValueStack(vs,
container.getInstance(XWorkConverter.class),
(CompoundRootAccessor) container.getInstance(PropertyAccessor.class, CompoundRoot.class.getName()), true);
(CompoundRootAccessor) container.getInstance(RootAccessor.class), true);
container.inject(stack2);
assertEquals(vs.getRoot(), stack2.getRoot());
@@ -1122,9 +1121,18 @@ public class OgnlValueStackTest extends XWorkTestCase {
assertEquals("Hello World", vs.findValue("claus", String.class));
assertEquals("Hello World", vs.findValue("top", String.class));
assertNull(vs.findValue("unknown", String.class));
}
public void testExprFallbackToContext() {
vs.getContext().put("santa", "Hello Santa");
assertEquals("Hello Santa", vs.findValue("santa", String.class));
assertNull(vs.findValue("unknown", String.class));
}
public void testExprFallbackToContext_disabled() {
vs.setShouldFallbackToContext("false");
vs.getContext().put("santa", "Hello Santa");
assertNull(vs.findValue("santa", String.class));
}
public void testWarnAboutInvalidProperties() {
@@ -16,7 +16,7 @@
* specific language governing permissions and limitations
* under the License.
*/
package com.opensymphony.xwork2.interceptor;
package org.apache.struts2.interceptor.parameter;
import com.opensymphony.xwork2.Action;
import com.opensymphony.xwork2.ActionContext;
@@ -32,16 +32,19 @@ import com.opensymphony.xwork2.config.providers.MockConfigurationProvider;
import com.opensymphony.xwork2.config.providers.StrutsDefaultConfigurationProvider;
import com.opensymphony.xwork2.config.providers.XmlConfigurationProvider;
import com.opensymphony.xwork2.conversion.impl.XWorkConverter;
import com.opensymphony.xwork2.interceptor.ValidationAware;
import com.opensymphony.xwork2.mock.MockActionInvocation;
import com.opensymphony.xwork2.ognl.OgnlValueStack;
import com.opensymphony.xwork2.ognl.OgnlValueStackFactory;
import com.opensymphony.xwork2.ognl.accessor.CompoundRootAccessor;
import com.opensymphony.xwork2.util.CompoundRoot;
import com.opensymphony.xwork2.ognl.accessor.RootAccessor;
import com.opensymphony.xwork2.util.ValueStack;
import com.opensymphony.xwork2.util.ValueStackFactory;
import com.opensymphony.xwork2.util.reflection.ReflectionContextState;
import ognl.OgnlContext;
import ognl.PropertyAccessor;
import org.apache.struts2.action.NoParameters;
import org.apache.struts2.action.ParameterNameAware;
import org.apache.struts2.action.ParameterValueAware;
import org.apache.struts2.config.StrutsXmlConfigurationProvider;
import org.apache.struts2.dispatcher.HttpParameters;
import org.junit.Assert;
@@ -75,11 +78,7 @@ public class ParametersInterceptorTest extends XWorkTestCase {
put("barKey", "barValue");
}
};
Object a = new ParameterNameAware() {
public boolean acceptableParameterName(String parameterName) {
return expected.containsKey(parameterName);
}
};
ParameterNameAware a = expected::containsKey;
final Map<String, Object> parameters = new HashMap<String, Object>() {
{
put("fooKey", "fooValue");
@@ -232,10 +231,10 @@ public class ParametersInterceptorTest extends XWorkTestCase {
public void testModelDrivenParameters() throws Exception {
Map<String, Object> params = new HashMap<>();
final String fooVal = "com.opensymphony.xwork2.interceptor.ParametersInterceptorTest.foo";
final String fooVal = "org.apache.struts2.interceptor.parameter.ParametersInterceptorTest.foo";
params.put("foo", fooVal);
final String nameVal = "com.opensymphony.xwork2.interceptor.ParametersInterceptorTest.name";
final String nameVal = "org.apache.struts2.interceptor.parameter.ParametersInterceptorTest.name";
params.put("name", nameVal);
params.put("count", "15");
@@ -961,7 +960,7 @@ public class ParametersInterceptorTest extends XWorkTestCase {
private ValueStack createStubValueStack(final Map<String, Object> actual) {
ValueStack stack = new OgnlValueStack(
container.getInstance(XWorkConverter.class),
(CompoundRootAccessor) container.getInstance(PropertyAccessor.class, CompoundRoot.class.getName()),
(CompoundRootAccessor) container.getInstance(RootAccessor.class),
container.getInstance(TextProvider.class, "system"), true) {
@Override
public void setValue(String expr, Object value) {
@@ -1,7 +1,7 @@
<tr>
<td class="tdLabel"><label for="cb" class="label">mylabel:</label></td>
<td class="tdInput">
<script type="text/javascript" nonce="r4nd0m">
<script nonce="r4nd0m">
function autoPopulate_cb(targetElement) {
targetElement.form.elements['foo'].value=targetElement.options[targetElement.selectedIndex].value;
}
@@ -1,7 +1,7 @@
<tr>
<td class="tdLabel"><label for="myFavouriteFruit" class="label">My Favourite Fruit:</label></td>
<td class="tdInput">
<script type="text/javascript">
<script>
function autoPopulate_myFavouriteFruit(targetElement) {
if (targetElement.options[targetElement.selectedIndex].value == '-1') {
return;
@@ -1,7 +1,7 @@
<tr>
<td class="tdLabel"><label for="myFavouriteFruit" class="label">My Favourite Fruit:</label></td>
<td class="tdInput">
<script type="text/javascript">
<script>
function autoPopulate_myFavouriteFruit(targetElement) {
if (targetElement.options[targetElement.selectedIndex].value == '-1') {
return;
@@ -1,7 +1,7 @@
<tr>
<td class="tdLabel"><label for="cb[&#39;&quot;.&quot;&#39;]=bc(){};//" class="label">mylabel:</label></td>
<td class="tdInput">
<script type="text/javascript">
<script>
function autoPopulate_cb__________bc_______(targetElement) {
targetElement.form.elements['foo'].value=targetElement.options[targetElement.selectedIndex].value;
}
@@ -8,7 +8,7 @@
<br/>
<select name="region" id="region" class="c2" style="s2">
</select>
<script type="text/javascript" nonce="r4nd0m">
<script nonce="r4nd0m">
var fooGroup = new Array(2 + 0);
for (var i = 0; i < (2 + 0); i++) {
fooGroup[i] = [];
@@ -8,7 +8,7 @@
<br/>
<select name="region" id="region">
</select>
<script type="text/javascript">
<script>
var fooGroup = new Array(2 + 0);
for (var i = 0; i < (2 + 0); i++) {
fooGroup[i] = [];
@@ -8,7 +8,7 @@
<br/>
<select name="region" id="region">
</select>
<script type="text/javascript">
<script>
var foo_barGroup = new Array(2 + 0);
for (var i = 0; i < (2 + 0); i++) {
foo_barGroup[i] = [];
@@ -8,7 +8,7 @@
<br/>
<select name="region" id="region" class="c2" style="s2">
</select>
<script type="text/javascript">
<script>
var fooGroup = new Array(2 + 0);
for (var i = 0; i < (2 + 0); i++) {
fooGroup[i] = [];
@@ -8,7 +8,7 @@
</select>
<br />
<select name="region" disabled="disabled" id="region" title="someTitle" select-name="secondName"></select>
<script type="text/javascript">
<script>
var someIdGroup = newArray(2 + 0);
for (var i = 0; i < (2 + 0); i++) {
someIdGroup[i] = [];
@@ -6,7 +6,7 @@
<br/>
<select name="region" id="region" onclick="testMe()" ondblclick="testMe()" onmousedown="testMe()" onmouseup="testMe()" onmouseover="testMe()" onmousemove="testMe()" onmouseout="testMe()" onfocus="testMe()" onblur="testMe()" onkeypress="testMe()" onkeydown="testMe()" onselect="testMe()" onchange="testMe()">
</select>
<script type="text/javascript" >
<script>
var fooGroup = new Array(2 + 0);
for (var i = 0; i < (2 + 0); i++) {
fooGroup[i] = [];
@@ -1,8 +1,8 @@
<script type="text/javascript" src="/static/xhtml/validation.js"></script>
<script src="/static/xhtml/validation.js"></script>
<form id="testAction" name="myForm" onsubmit="submitMe(); return validateForm_testAction();" action="/testAction.action" method="post" enctype="myEncType" title="mytitle" accept-charset="UTF-8" onreset="clearErrorMessages(this);clearErrorLabels(this);">
<table class="wwFormTable"> <tr>
<td class="tdLabel"></td>
<td class="tdInput"> <script type="text/javascript" src="/static/optiontransferselect.js"></script>
<td class="tdInput"> <script src="/static/optiontransferselect.js"></script>
<table>
<tr><td>
<select name="myUpDownSelectTag" size="5" id="testAction_myUpDownSelectTag" multiple="multiple">
@@ -21,7 +21,7 @@
</form>
<script type="text/javascript">
<script>
var containingForm = document.getElementById("testAction");
StrutsUtils.addEventListener(containingForm, "submit",
function(evt) {
@@ -3,7 +3,7 @@
</table>
</form>
<script type="text/javascript">
<script>
StrutsUtils.addOnLoad(function() {
var element = document.getElementById("felement");
if(element) {
@@ -1,8 +1,8 @@
<script type="text/javascript" src="/static/xhtml/validation.js"></script>
<script src="/static/xhtml/validation.js"></script>
<form id="doubleValidationAction" name="myForm" onsubmit="submitMe(); return validateForm_doubleValidationAction();" action="/doubleValidationAction.action" method="post" enctype="myEncType" title="mytitle" accept-charset="UTF-8" onreset="clearErrorMessages(this);clearErrorLabels(this);">
<table class="wwFormTable"> <tr>
<td class="tdLabel"></td>
<td class="tdInput"> <script type="text/javascript" src="/static/optiontransferselect.js"></script>
<td class="tdInput"> <script src="/static/optiontransferselect.js"></script>
<table>
<tr><td>
<select name="myUpDownSelectTag" size="5" id="doubleValidationAction_myUpDownSelectTag" multiple="multiple">
@@ -21,7 +21,7 @@
</form>
<script type="text/javascript">
<script>
var containingForm = document.getElementById("doubleValidationAction");
StrutsUtils.addEventListener(containingForm, "submit",
function(evt) {
@@ -30,7 +30,7 @@
}, true);
</script>
<script type="text/javascript">
<script>
function validateForm_doubleValidationAction() {
var getFieldValue = function(field) {
@@ -1,8 +1,8 @@
<script type="text/javascript" src="/static/xhtml/validation.js"></script>
<script src="/static/xhtml/validation.js"></script>
<form id="doubleValidationAction" name="myForm" onsubmit="submitMe(); return validateForm_doubleValidationAction();" action="/doubleValidationAction.action" method="post" enctype="myEncType" title="mytitle" accept-charset="UTF-8" onreset="clearErrorMessages(this);clearErrorLabels(this);">
<table class="wwFormTable"> <tr>
<td class="tdLabel"></td>
<td class="tdInput"> <script type="text/javascript" src="/static/optiontransferselect.js"></script>
<td class="tdInput"> <script src="/static/optiontransferselect.js"></script>
<table>
<tr><td>
<select name="myUpDownSelectTag" size="5" id="doubleValidationAction_myUpDownSelectTag" multiple="multiple">
@@ -21,7 +21,7 @@
</form>
<script type="text/javascript">
<script>
var containingForm = document.getElementById("doubleValidationAction");
StrutsUtils.addEventListener(containingForm, "submit",
function(evt) {
@@ -30,7 +30,7 @@
}, true);
</script>
<script type="text/javascript">
<script>
function validateForm_doubleValidationAction() {
var getFieldValue = function(field) {
var type = field.type ? field.type : field[0].type;
@@ -1,8 +1,8 @@
<script type="text/javascript" src="/static/xhtml/validation.js"></script>
<script src="/static/xhtml/validation.js"></script>
<form id="doubleValidationAction" name="myForm" onsubmit="submitMe(); return validateForm_doubleValidationAction();" action="/doubleValidationAction.action" method="post" enctype="myEncType" title="mytitle" accept-charset="UTF-8" onreset="clearErrorMessages(this);clearErrorLabels(this);">
<table class="wwFormTable"> <tr>
<td class="tdLabel"></td>
<td class="tdInput"> <script type="text/javascript" src="/static/optiontransferselect.js"></script>
<td class="tdInput"> <script src="/static/optiontransferselect.js"></script>
<table>
<tr><td>
<select name="myUpDownSelectTag" size="5" id="doubleValidationAction_myUpDownSelectTag" multiple="multiple">
@@ -21,7 +21,7 @@
</form>
<script type="text/javascript">
<script>
var containingForm = document.getElementById("doubleValidationAction");
StrutsUtils.addEventListener(containingForm, "submit",
function(evt) {
@@ -30,7 +30,7 @@
}, true);
</script>
<script type="text/javascript">
<script>
function validateForm_doubleValidationAction() {
var getFieldValue = function(field) {
var type = field.type ? field.type : field[0].type;
@@ -1,7 +1,7 @@
<form id="myAction" name="myForm" onsubmit="submitMe()" action="myAction" method="post" enctype="myEncType" title="mytitle">
<table class="wwFormTable"> <tr>
<td class="tdLabel"></td>
<td class="tdInput"> <script type="text/javascript" src="/static/optiontransferselect.js"></script>
<td class="tdInput"> <script src="/static/optiontransferselect.js"></script>
<table>
<tr><td>
<select name="myUpDownSelectTag" size="5" id="myAction_myUpDownSelectTag" multiple="multiple">
@@ -20,9 +20,9 @@
</form>
<script type="text/javascript">
<script>
var containingForm = document.getElementById("myAction");
StrutsUtils.addEventListener(containingForm, "submit",
StrutsUtils.addEventListener(containingForm, "submit",
function(evt) {
var updownselectObj = document.getElementById("myAction_myUpDownSelectTag");
selectAllOptionsExceptSome(updownselectObj, "key", "");
@@ -1,2 +1,2 @@
<link rel="stylesheet" type="text/css" href="/static/xhtml/styles.css" nonce="r4nd0m"/>
<script type="text/javascript" src="/static/utils.js" nonce="r4nd0m"></script>
<script src="/static/utils.js" nonce="r4nd0m"></script>
@@ -1,7 +1,7 @@
<tr>
<td class="tdLabel"></td>
<td class="tdInput">
<script type="text/javascript" src="/static/inputtransferselect.js" nonce="r4nd0m"></script>
<script src="/static/inputtransferselect.js" nonce="r4nd0m"></script>
<table>
<tr><td><input type="text" name="collection_input" id="collection_input"/></td>
<td class="tdTransferSelect"><inputtype="button"value="-&gt;"onclick="addOption(document.getElementById('collection_input'),document.getElementById('collection'))"/>
@@ -1,7 +1,7 @@
<tr>
<td class="tdLabel"></td>
<td class="tdInput">
<script type="text/javascript" src="/static/inputtransferselect.js"></script>
<script src="/static/inputtransferselect.js"></script>
<table>
<tr>
<td><input type="text" name="collection_input" id="collection_input" collection-name="inputName" /></td>
@@ -1,7 +1,7 @@
<tr>
<td class="tdLabel"></td>
<tr>
<td class="tdLabel"></td>
<td class="tdInput">
<script type="text/javascript" src="/static/optiontransferselect.js" nonce="r4nd0m"></script>
<script src="/static/optiontransferselect.js" nonce="r4nd0m"></script>
<table>
<tr>
<td>
@@ -24,15 +24,15 @@
<input type="button" class="buttonCssClass" style="buttonCssStyle"
value="All Left" onclick="moveAllOptions(document.getElementById('doubleId'), document.getElementById('id'), false, 'Double Header Key', '');" /><br/><br/>
<input type="button" class="buttonCssClass" style="buttonCssStyle"
value="All Right" onclick="moveAllOptions(document.getElementById('id'), document.getElementById('doubleId'), false, 'Header Key', '');" /><br/><br/>
value="All Right" onclick="moveAllOptions(document.getElementById('id'), document.getElementById('doubleId'), false, 'Header Key', '');" /><br/><br/>
<input type="button" class="buttonCssClass" style="buttonCssStyle"
value="Select All" onclick="selectAllOptionsExceptSome(document.getElementById('id'), 'key', 'Header Key');selectAllOptionsExceptSome(document.getElementById('doubleId'), 'key', 'Double Header Key');" /><br/><br/>
</td>
</td>
<td>
<label for="rightTitle">Title Right</label><br/>
<select
<select
name="list2"
size="20"
size="20"
multiple="multiple"
id="doubleId"
class="c2"
@@ -49,5 +49,5 @@
</td>
</tr>
</table>
</td>
</td>
</tr>
@@ -1,7 +1,7 @@
<tr>
<tr>
<td class="tdLabel"></td>
<td class="tdInput">
<script type="text/javascript" src="/static/optiontransferselect.js"></script>
<script src="/static/optiontransferselect.js"></script>
<table>
<tr>
<td>
@@ -25,14 +25,14 @@
<input type="button" class="buttonCssClass" style="buttonCssStyle"
value="All Left" onclick="moveAllOptions(document.getElementById('doubleId'), document.getElementById('id'), false, 'Double Header Key', '');" /><br/><br/>
<input type="button" class="buttonCssClass" style="buttonCssStyle"
value="All Right" onclick="moveAllOptions(document.getElementById('id'), document.getElementById('doubleId'), false, 'Header Key', '');" /><br/><br/>
value="All Right" onclick="moveAllOptions(document.getElementById('id'), document.getElementById('doubleId'), false, 'Header Key', '');" /><br/><br/>
<input type="button" class="buttonCssClass" style="buttonCssStyle"
value="Select All" onclick="selectAllOptionsExceptSome(document.getElementById('id'), 'key', 'Header Key');selectAllOptionsExceptSome(document.getElementById('doubleId'), 'key', 'Double Header Key');" /><br/><br/>
</td> <td>
<label for="rightTitle">Title Right</label><br/>
<select
<select
name="list2"
size="20"
size="20"
multiple="multiple"
id="doubleId"
>
@@ -1,7 +1,7 @@
<tr>
<tr>
<td class="tdLabel"></td>
<td class="tdInput">
<script type="text/javascript" src="/static/optiontransferselect.js"></script>
<script src="/static/optiontransferselect.js"></script>
<table>
<tr>
<td>
@@ -24,15 +24,15 @@
<input type="button" class="buttonCssClass" style="buttonCssStyle"
value="All Left" onclick="moveAllOptions(document.getElementById('doubleId'), document.getElementById('id'), false, '');" /><br/><br/>
<input type="button" class="buttonCssClass" style="buttonCssStyle"
value="All Right" onclick="moveAllOptions(document.getElementById('id'), document.getElementById('doubleId'), false, '');" /><br/><br/>
value="All Right" onclick="moveAllOptions(document.getElementById('id'), document.getElementById('doubleId'), false, '');" /><br/><br/>
<input type="button" class="buttonCssClass" style="buttonCssStyle"
value="Select All" onclick="selectAllOptions(document.getElementById('id'));selectAllOptions(document.getElementById('doubleId'));" /><br/><br/>
</td>
<td>
<label for="rightTitle">Title Right</label><br/>
<select
<select
name="list2"
size="20"
size="20"
multiple="multiple"
id="doubleId"
>
@@ -1,7 +1,7 @@
<tr>
<tr>
<td class="tdLabel"></td>
<td class="tdInput">
<script type="text/javascript" src="/static/optiontransferselect.js"></script>
<script src="/static/optiontransferselect.js"></script>
<table>
<tr>
<td>
@@ -25,15 +25,15 @@
<input type="button" class="buttonCssClass" style="buttonCssStyle"
value="All Left" onclick="moveAllOptions(document.getElementById('doubleId'), document.getElementById('id'), false, '');" /><br/><br/>
<input type="button" class="buttonCssClass" style="buttonCssStyle"
value="All Right" onclick="moveAllOptions(document.getElementById('id'), document.getElementById('doubleId'), false, 'Header Key', '');" /><br/><br/>
value="All Right" onclick="moveAllOptions(document.getElementById('id'), document.getElementById('doubleId'), false, 'Header Key', '');" /><br/><br/>
<input type="button" class="buttonCssClass" style="buttonCssStyle"
value="Select All" onclick="selectAllOptionsExceptSome(document.getElementById('id'), 'key', 'Header Key');selectAllOptions(document.getElementById('doubleId'));" /><br/><br/>
</td>
<td>
<label for="rightTitle">Title Right</label><br/>
<select
<select
name="list2"
size="20"
size="20"
multiple="multiple"
id="doubleId"
>
@@ -45,7 +45,7 @@
<input type="hidden" id="__multiselect_doubleId" name="__multiselect_list2" value=""/>
<input type="button" onclick="moveOptionDown(document.getElementById('doubleId'), 'key', '');" value="v" />
<input type="button" onclick="moveOptionUp(document.getElementById('doubleId'), 'key', '');" value="^" />
</td>
</tr>
</table>
@@ -1,7 +1,7 @@
<tr>
<td class="tdLabel"></td>
<tr>
<td class="tdLabel"></td>
<td class="tdInput">
<script type="text/javascript" src="/static/optiontransferselect.js"></script>
<script src="/static/optiontransferselect.js"></script>
<table>
<tr>
<td>
@@ -24,15 +24,15 @@
<input type="button" class="buttonCssClass" style="buttonCssStyle"
value="All Left" onclick="moveAllOptions(document.getElementById('doubleId'), document.getElementById('id'), false, 'Double Header Key', '');" /><br/><br/>
<input type="button" class="buttonCssClass" style="buttonCssStyle"
value="All Right" onclick="moveAllOptions(document.getElementById('id'), document.getElementById('doubleId'), false, 'Header Key', '');" /><br/><br/>
value="All Right" onclick="moveAllOptions(document.getElementById('id'), document.getElementById('doubleId'), false, 'Header Key', '');" /><br/><br/>
<input type="button" class="buttonCssClass" style="buttonCssStyle"
value="Select All" onclick="selectAllOptionsExceptSome(document.getElementById('id'), 'key', 'Header Key');selectAllOptionsExceptSome(document.getElementById('doubleId'), 'key', 'Double Header Key');" /><br/><br/>
</td>
<td>
<label for="rightTitle">Title Right</label><br/>
<select
<select
name="list2"
size="20"
size="20"
multiple="multiple"
id="doubleId"
>
@@ -1,7 +1,7 @@
<tr>
<tr>
<td class="tdLabel"></td>
<td class="tdInput">
<script type="text/javascript" src="/static/optiontransferselect.js"></script>
<script src="/static/optiontransferselect.js"></script>
<table>
<tr>
<td>
@@ -25,15 +25,15 @@
<input type="button" class="buttonCssClass" style="buttonCssStyle"
value="All Left" onclick="moveAllOptions(document.getElementById('doubleId'), document.getElementById('id'), false, 'Double Header Key', '');" /><br/><br/>
<input type="button" class="buttonCssClass" style="buttonCssStyle"
value="All Right" onclick="moveAllOptions(document.getElementById('id'), document.getElementById('doubleId'), false, 'Header Key', '');" /><br/><br/>
value="All Right" onclick="moveAllOptions(document.getElementById('id'), document.getElementById('doubleId'), false, 'Header Key', '');" /><br/><br/>
<input type="button" class="buttonCssClass" style="buttonCssStyle"
value="Select All" onclick="selectAllOptionsExceptSome(document.getElementById('id'), 'key', 'Header Key');selectAllOptionsExceptSome(document.getElementById('doubleId'), 'key', 'Double Header Key');" /><br/><br/>
</td>
<td>
<label for="rightTitle">Title Right</label><br/>
<select
<select
name="list2"
size="20"
size="20"
multiple="multiple"
id="doubleId"
>
@@ -1,7 +1,7 @@
<tr>
<tr>
<td class="tdLabel"></td>
<td class="tdInput">
<script type="text/javascript" src="/static/optiontransferselect.js"></script>
<script src="/static/optiontransferselect.js"></script>
<table>
<tr>
<td>
@@ -22,12 +22,12 @@
value="Left" onclick="moveSelectedOptions(document.getElementById('doubleId'), document.getElementById('id'), false, 'Double Header Key', '');alert('Moving Left');" /><br/><br/>
<input type="button" class="buttonCssClass" style="buttonCssStyle"
value="Right" onclick="moveSelectedOptions(document.getElementById('id'), document.getElementById('doubleId'), false, 'Header Key', '');alert('Moving Right');" /><br/><br/>
</td>
</td>
<td>
<label for="rightTitle">Title Right</label><br/>
<select
<select
name="list2"
size="20"
size="20"
multiple="multiple"
id="doubleId"
>
@@ -1,7 +1,7 @@
<tr>
<td class="tdLabel"></td>
<td class="tdInput">
<script type="text/javascript" src="/static/optiontransferselect.js"></script>
<script src="/static/optiontransferselect.js"></script>
<table>
<tr>
<td>
@@ -15,7 +15,7 @@
</table>
</form>
<!--javascript that is needed for tooltips-->
<script type="text/javascript" src="/static/domTT.js"></script>
<script src="/static/domTT.js"></script>
<link rel="stylesheet" type="text/css" href="/static/domTT.css"/>
@@ -15,5 +15,5 @@
</table>
</form>
<!--javascript that is needed for tooltips-->
<script type="text/javascript" src="/static/domTT.js"></script>
<script src="/static/domTT.js"></script>
<link rel="stylesheet" type="text/css" href="/static/domTT.css"/>
@@ -16,6 +16,6 @@
</form>
<!--javascript that is needed for tooltips-->
<script type="text/javascript" src="/static/domTT.js"></script>
<script src="/static/domTT.js"></script>
<link rel="stylesheet" type="text/css" href="/static/domTT.css"/>
@@ -1,5 +1,5 @@
<tr> <td class="tdLabel"></td> <td class="tdInput">
<script type="text/javascript" src="/static/optiontransferselect.js"></script>
<script src="/static/optiontransferselect.js"></script>
<table>
<tr><td>
<select name="myName" size="5" id="myId" multiple="multiple">
@@ -1,9 +1,9 @@
<tr> <td class="tdLabel"></td> <td class="tdInput">
<script type="text/javascript" src="/static/optiontransferselect.js"></script>
<script src="/static/optiontransferselect.js"></script>
<table>
<tr><td>
<select name="myName" size="5" id="myId" multiple="multiple">
<option value=""></option>
<option value=""></option>
<option value="england">England</option>
<option value="america" selected="selected">America</option>
<option value="australia">Australia</option>
@@ -12,9 +12,9 @@
<input type="hidden" id="__multiselect_myId" name="__multiselect_myName" value=""/>
</td></tr>
<tr><td>
&nbsp;<input type="button" value="Move Up" onclick="moveOptionUp(document.getElementById('myId'), 'key', '');" />&nbsp;
&nbsp;<input type="button" value="v" onclick="moveOptionDown(document.getElementById('myId'), 'key', '');" />&nbsp;
&nbsp;<input type="button" value="*" onclick="selectAllOptions(document.getElementById('myId'), 'key', '');" />&nbsp;
&nbsp;<input type="button" value="Move Up" onclick="moveOptionUp(document.getElementById('myId'), 'key', '');" />&nbsp;
&nbsp;<input type="button" value="v" onclick="moveOptionDown(document.getElementById('myId'), 'key', '');" />&nbsp;
&nbsp;<input type="button" value="*" onclick="selectAllOptions(document.getElementById('myId'), 'key', '');" />&nbsp;
</td></tr>
</table>
</td> </tr>
@@ -1,5 +1,5 @@
<tr> <td class="tdLabel"></td> <td class="tdInput">
<script type="text/javascript" src="/static/optiontransferselect.js"></script>
<script src="/static/optiontransferselect.js"></script>
<table>
<tr><td>
<select name="myName" size="5" id="myId" multiple="multiple">
@@ -1,5 +1,5 @@
<tr> <td class="tdLabel"></td> <td class="tdInput">
<script type="text/javascript" src="/static/optiontransferselect.js" nonce="r4nd0m"></script>
<script src="/static/optiontransferselect.js" nonce="r4nd0m"></script>
<table>
<tr><td>
<select name="myName" size="5" id="myId" multiple="multiple">
@@ -1,5 +1,5 @@
<tr> <td class="tdLabel"></td> <td class="tdInput">
<script type="text/javascript" src="/static/optiontransferselect.js"></script>
<script src="/static/optiontransferselect.js"></script>
<table>
<tr><td>
<select name="myName" size="5" id="myId" multiple="multiple">
@@ -1,5 +1,5 @@
<tr> <td class="tdLabel"></td> <td class="tdInput">
<script type="text/javascript" src="/static/optiontransferselect.js"></script>
<script src="/static/optiontransferselect.js"></script>
<table>
<tr><td>
<select name="myName" size="5" id="myId" multiple="multiple">
@@ -1,5 +1,5 @@
<tr> <td class="tdLabel"></td> <td class="tdInput">
<script type="text/javascript" src="/static/optiontransferselect.js"></script>
<script src="/static/optiontransferselect.js"></script>
<table>
<tr><td>
<select name="myName" size="5" id="myId" multiple="multiple">
@@ -1,5 +1,5 @@
<tr> <td class="tdLabel"></td> <td class="tdInput">
<script type="text/javascript" src="/static/optiontransferselect.js"></script>
<script src="/static/optiontransferselect.js"></script>
<table>
<tr><td>
<select name="myName" size="5" id="myId" multiple="multiple">
@@ -1,5 +1,5 @@
<tr> <td class="tdLabel"></td> <td class="tdInput">
<script type="text/javascript" src="/static/optiontransferselect.js"></script>
<script src="/static/optiontransferselect.js"></script>
<table>
<tr><td>
<select name="myName" size="5" id="myId" multiple="multiple">
@@ -1,5 +1,5 @@
<tr> <td class="tdLabel"></td> <td class="tdInput">
<script type="text/javascript" src="/static/optiontransferselect.js"></script>
<script src="/static/optiontransferselect.js"></script>
<table>
<tr><td>
<select name="myName" size="5" id="myId" multiple="multiple">
@@ -1,5 +1,5 @@
<tr> <td class="tdLabel"></td> <td class="tdInput">
<script type="text/javascript" src="/static/optiontransferselect.js"></script>
<script src="/static/optiontransferselect.js"></script>
<table>
<tr><td>
<select name="myName" size="5" id="myId" multiple="multiple">
@@ -24,14 +24,23 @@ import com.opensymphony.xwork2.FileManagerFactory;
import com.opensymphony.xwork2.ObjectFactory;
import com.opensymphony.xwork2.config.Configuration;
import com.opensymphony.xwork2.config.ConfigurationException;
import com.opensymphony.xwork2.config.entities.*;
import com.opensymphony.xwork2.config.entities.ActionConfig;
import com.opensymphony.xwork2.config.entities.ExceptionMappingConfig;
import com.opensymphony.xwork2.config.entities.InterceptorMapping;
import com.opensymphony.xwork2.config.entities.PackageConfig;
import com.opensymphony.xwork2.config.entities.ResultConfig;
import com.opensymphony.xwork2.inject.Container;
import com.opensymphony.xwork2.inject.Inject;
import com.opensymphony.xwork2.util.AnnotationUtils;
import com.opensymphony.xwork2.util.TextParseUtil;
import com.opensymphony.xwork2.util.WildcardHelper;
import com.opensymphony.xwork2.util.classloader.ReloadingClassLoader;
import com.opensymphony.xwork2.util.finder.*;
import com.opensymphony.xwork2.util.finder.ClassFinder;
import com.opensymphony.xwork2.util.finder.ClassFinderFactory;
import com.opensymphony.xwork2.util.finder.ClassLoaderInterface;
import com.opensymphony.xwork2.util.finder.ClassLoaderInterfaceDelegate;
import com.opensymphony.xwork2.util.finder.Test;
import com.opensymphony.xwork2.util.finder.UrlSet;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
@@ -39,26 +48,50 @@ import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.struts2.StrutsConstants;
import org.apache.struts2.StrutsException;
import org.apache.struts2.convention.annotation.*;
import org.apache.struts2.convention.annotation.Action;
import org.apache.struts2.convention.annotation.Actions;
import org.apache.struts2.convention.annotation.AllowedMethods;
import org.apache.struts2.convention.annotation.DefaultInterceptorRef;
import org.apache.struts2.convention.annotation.ExceptionMapping;
import org.apache.struts2.convention.annotation.ExceptionMappings;
import org.apache.struts2.convention.annotation.Namespace;
import org.apache.struts2.convention.annotation.Namespaces;
import org.apache.struts2.convention.annotation.ParentPackage;
import java.io.IOException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.net.URL;
import java.util.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;
/**
* <p>
* This class implements the ActionConfigBuilder interface.
* </p>
*/
public class PackageBasedActionConfigBuilder implements ActionConfigBuilder {
private static final Logger LOG = LogManager.getLogger(PackageBasedActionConfigBuilder.class);
private static final String DEFAULT_ACTION_SUFFIX = "Action";
private static final String DEFAULT_METHOD = "execute";
private static final boolean EXTRACT_BASE_INTERFACES = true;
/**
* Pattern to match the whole path with sub-path as on JDK9+ getClassLoader().getResources("")
* can return also a sub-path like "!/META-INF/versions/..."
*/
private static final String EXCLUDE_ALL_JARS_PATTERN = ".*?\\.jar(!/|/)?(.*)?";
private static final String DEFAULT_SPLIT_PATTERN = "\\s*,\\s*";
private final Configuration configuration;
private final ActionNameBuilder actionNameBuilder;
private final ResultMapBuilder resultMapBuilder;
@@ -66,6 +99,9 @@ public class PackageBasedActionConfigBuilder implements ActionConfigBuilder {
private final ObjectFactory objectFactory;
private final String defaultParentPackage;
private final boolean redirectToSlash;
private final Set<String> loadedFileUrls = new HashSet<>();
private final boolean enableSmiInheritance;
private String[] actionPackages;
private String[] excludePackages;
private String[] packageLocators;
@@ -73,20 +109,17 @@ public class PackageBasedActionConfigBuilder implements ActionConfigBuilder {
private String packageLocatorsBasePackage;
private boolean disableActionScanning = false;
private boolean disablePackageLocatorsScanning = false;
private Set<String> actionSuffix = Collections.singleton("Action");
private Set<String> actionSuffix = Collections.singleton(DEFAULT_ACTION_SUFFIX);
private boolean checkImplementsAction = true;
private boolean mapAllMatches = false;
private Set<String> loadedFileUrls = new HashSet<>();
private boolean devMode;
private ReloadingClassLoader reloadingClassLoader;
private boolean reload;
private Set<String> fileProtocols;
private Set<String> fileProtocols = Collections.emptySet();
private boolean alwaysMapExecute;
private boolean excludeParentClassLoader;
private boolean slashesInActionNames;
private boolean enableSmiInheritance;
private static final String DEFAULT_METHOD = "execute";
private boolean eagerLoading = false;
private FileManager fileManager;
@@ -95,16 +128,17 @@ public class PackageBasedActionConfigBuilder implements ActionConfigBuilder {
/**
* Constructs actions based on a list of packages.
*
* @param configuration The XWork configuration that the new package configs and action configs
* are added to.
* @param container Xwork Container
* @param objectFactory The ObjectFactory used to create the actions and such.
* @param redirectToSlash A boolean parameter that controls whether or not this will create an
* action for indexes. If this is set to true, index actions are not created because
* the unknown handler will redirect from /foo to /foo/. The only action that is created
* is to the empty action in the namespace (e.g. the namespace /foo and the action "").
* @param enableSmiInheritance A boolean parameter which determines if a newly created package config inherits the SMI value of its parent package config
* @param defaultParentPackage The default parent package for all the configuration.
* @param configuration The XWork configuration that the new package configs and action configs
* are added to.
* @param container Xwork Container
* @param objectFactory The ObjectFactory used to create the actions and such.
* @param redirectToSlash A boolean parameter that controls whether or not this will create an
* action for indexes. If this is set to true, index actions are not created because
* the unknown handler will redirect from /foo to /foo/. The only action that is created
* is to the empty action in the namespace (e.g. the namespace /foo and the action "").
* @param enableSmiInheritance A boolean parameter, which determines if a newly created package config inherits
* the SMI value of its parent package config
* @param defaultParentPackage The default parent package for all the configuration.
*/
@Inject
public PackageBasedActionConfigBuilder(Configuration configuration, Container container, ObjectFactory objectFactory,
@@ -135,7 +169,7 @@ public class PackageBasedActionConfigBuilder implements ActionConfigBuilder {
/**
* @param reload Reload configuration when classes change. Defaults to "false" and should not be used
* in production.
* in production.
*/
@Inject(ConventionConstants.CONVENTION_CLASSES_RELOAD)
public void setReload(String reload) {
@@ -157,8 +191,8 @@ public class PackageBasedActionConfigBuilder implements ActionConfigBuilder {
}
/**
* @param alwaysMapExecute If this constant is true, and there is an "execute" method(not annotated), a mapping will be added
* pointing to it, even if there are other mapping in the class
* @param alwaysMapExecute If this constant is true, and there is an "execute" method(not annotated), a mapping will be added
* pointing to it, even if there are other mapping in the class
*/
@Inject(ConventionConstants.CONVENTION_ACTION_ALWAYS_MAP_EXECUTE)
public void setAlwaysMapExecute(String alwaysMapExecute) {
@@ -167,6 +201,7 @@ public class PackageBasedActionConfigBuilder implements ActionConfigBuilder {
/**
* File URLs whose protocol are in these list will be processed as jars containing classes
*
* @param fileProtocols Comma separated list of file protocols that will be considered as jar files and scanned
*/
@Inject(ConventionConstants.CONVENTION_ACTION_FILE_PROTOCOLS)
@@ -190,7 +225,7 @@ public class PackageBasedActionConfigBuilder implements ActionConfigBuilder {
@Inject(value = ConventionConstants.CONVENTION_ACTION_INCLUDE_JARS, required = false)
public void setIncludeJars(String includeJars) {
if (StringUtils.isNotEmpty(includeJars)) {
this.includeJars = includeJars.split("\\s*[,]\\s*");
this.includeJars = includeJars.split(DEFAULT_SPLIT_PATTERN);
}
}
@@ -209,13 +244,13 @@ public class PackageBasedActionConfigBuilder implements ActionConfigBuilder {
@Inject(value = ConventionConstants.CONVENTION_ACTION_PACKAGES, required = false)
public void setActionPackages(String actionPackages) {
if (StringUtils.isNotBlank(actionPackages)) {
this.actionPackages = actionPackages.split("\\s*[,]\\s*");
this.actionPackages = actionPackages.split(DEFAULT_SPLIT_PATTERN);
}
}
/**
* @param checkImplementsAction (Optional) Map classes that implement com.opensymphony.xwork2.Action
* as actions
* as actions
*/
@Inject(value = ConventionConstants.CONVENTION_ACTION_CHECK_IMPLEMENTS_ACTION, required = false)
public void setCheckImplementsAction(String checkImplementsAction) {
@@ -240,7 +275,7 @@ public class PackageBasedActionConfigBuilder implements ActionConfigBuilder {
@Inject(value = ConventionConstants.CONVENTION_EXCLUDE_PACKAGES, required = false)
public void setExcludePackages(String excludePackages) {
if (StringUtils.isNotBlank(excludePackages)) {
this.excludePackages = excludePackages.split("\\s*[,]\\s*");
this.excludePackages = excludePackages.split(DEFAULT_SPLIT_PATTERN);
}
}
@@ -249,7 +284,7 @@ public class PackageBasedActionConfigBuilder implements ActionConfigBuilder {
*/
@Inject(value = ConventionConstants.CONVENTION_PACKAGE_LOCATORS, required = false)
public void setPackageLocators(String packageLocators) {
this.packageLocators = packageLocators.split("\\s*[,]\\s*");
this.packageLocators = packageLocators.split(DEFAULT_SPLIT_PATTERN);
}
/**
@@ -273,7 +308,7 @@ public class PackageBasedActionConfigBuilder implements ActionConfigBuilder {
/**
* @param eagerLoading (Optional) If set, found action classes will be instantiated by the ObjectFactory to accelerate future use
* setting it up can clash with Spring managed beans
* setting it up can clash with Spring managed beans
*/
@Inject(value = ConventionConstants.CONVENTION_ACTION_EAGER_LOADING, required = false)
public void setEagerLoading(String eagerLoading) {
@@ -293,7 +328,7 @@ public class PackageBasedActionConfigBuilder implements ActionConfigBuilder {
protected void initReloadClassLoader() {
//when the configuration is reloaded, a new classloader will be setup
if (isReloadEnabled() && reloadingClassLoader == null)
reloadingClassLoader = new ReloadingClassLoader(getClassLoader());
reloadingClassLoader = new ReloadingClassLoader(getClassLoader());
}
protected ClassLoader getClassLoader() {
@@ -324,17 +359,17 @@ public class PackageBasedActionConfigBuilder implements ActionConfigBuilder {
if (LOG.isTraceEnabled()) {
LOG.trace("Loading action configurations");
if (actionPackages != null) {
LOG.trace("Actions being loaded from action packages: {}", actionPackages);
LOG.trace("Actions being loaded from action packages: {}", (Object[]) actionPackages);
}
if (packageLocators != null) {
LOG.trace("Actions being loaded using package locator's: {}", packageLocators);
LOG.trace("Actions being loaded using package locator's: {}", (Object[]) packageLocators);
}
if (excludePackages != null) {
LOG.trace("Excluding actions from packages: {}", excludePackages);
LOG.trace("Excluding actions from packages: {}", (Object[]) excludePackages);
}
}
Set<Class> classes = findActions();
Set<Class<?>> classes = findActions();
buildConfiguration(classes);
}
}
@@ -342,8 +377,7 @@ public class PackageBasedActionConfigBuilder implements ActionConfigBuilder {
protected ClassLoaderInterface getClassLoaderInterface() {
if (isReloadEnabled()) {
return new ClassLoaderInterfaceDelegate(this.reloadingClassLoader);
}
else {
} else {
/*
if there is a ClassLoaderInterface in the context, use it, otherwise
default to the default ClassLoaderInterface (a wrapper around the current
@@ -365,9 +399,8 @@ public class PackageBasedActionConfigBuilder implements ActionConfigBuilder {
return devMode && reload;
}
@SuppressWarnings("unchecked")
protected Set<Class> findActions() {
Set<Class> classes = new HashSet<>();
protected Set<Class<?>> findActions() {
Set<Class<?>> classes = new HashSet<>();
try {
if (actionPackages != null || (packageLocators != null && !disablePackageLocatorsScanning)) {
@@ -438,30 +471,26 @@ public class PackageBasedActionConfigBuilder implements ActionConfigBuilder {
}
//try to find classes dirs inside war files
urlSet = urlSet.includeClassesUrl(classLoaderInterface, new UrlSet.FileProtocolNormalizer() {
public URL normalizeToFileProtocol(URL url) {
return fileManager.normalizeToFileProtocol(url);
}
});
urlSet = urlSet.includeClassesUrl(classLoaderInterface, url -> fileManager.normalizeToFileProtocol(url));
urlSet = urlSet.excludeJavaExtDirs()
.excludeJavaEndorsedDirs()
.excludeUserExtensionsDir();
.excludeJavaEndorsedDirs()
.excludeUserExtensionsDir();
try {
urlSet = urlSet.excludeJavaHome();
urlSet = urlSet.excludeJavaHome();
} catch (NullPointerException e) {
// This happens in GAE since the sandbox contains no java.home directory
LOG.warn("Could not exclude JAVA_HOME, is this a sandbox jvm?");
// This happens in GAE since the sandbox contains no java.home directory
LOG.warn("Could not exclude JAVA_HOME, is this a sandbox jvm?");
}
urlSet = urlSet.excludePaths(System.getProperty("sun.boot.class.path", ""));
urlSet = urlSet.exclude(".*/JavaVM.framework/.*");
if (includeJars == null) {
urlSet = urlSet.exclude(".*?\\.jar(!/|/)?");
LOG.debug("\"{}\" is not defined, excluding all JAR files!", ConventionConstants.CONVENTION_ACTION_INCLUDE_JARS);
urlSet = urlSet.exclude(EXCLUDE_ALL_JARS_PATTERN);
} else {
if(LOG.isDebugEnabled()) {
if (LOG.isDebugEnabled()) {
LOG.debug("jar urls regexes were specified: {}", Arrays.asList(includeJars));
}
List<URL> rawIncludedUrls = urlSet.getUrls();
@@ -509,7 +538,7 @@ public class PackageBasedActionConfigBuilder implements ActionConfigBuilder {
*
* @param className the name of the class to test
* @return true if the specified class should be included in the
* package-based action scan
* package-based action scan
*/
protected boolean includeClassNameInActionScan(String className) {
String classPackageName = StringUtils.substringBeforeLast(className, ".");
@@ -523,15 +552,15 @@ public class PackageBasedActionConfigBuilder implements ActionConfigBuilder {
* @return false if class package is on the {@link #excludePackages} list
*/
protected boolean checkExcludePackages(String classPackageName) {
if(excludePackages != null && excludePackages.length > 0) {
if (excludePackages != null && excludePackages.length > 0) {
WildcardHelper wildcardHelper = new WildcardHelper();
//we really don't care about the results, just the boolean
Map<String, String> matchMap = new HashMap<>();
for(String packageExclude : excludePackages) {
for (String packageExclude : excludePackages) {
int[] packagePattern = wildcardHelper.compilePattern(packageExclude);
if(wildcardHelper.match(matchMap, classPackageName, packagePattern)) {
if (wildcardHelper.match(matchMap, classPackageName, packagePattern)) {
return false;
}
}
@@ -564,9 +593,9 @@ public class PackageBasedActionConfigBuilder implements ActionConfigBuilder {
* @return true if class package is on the {@link #packageLocators} list
*/
protected boolean checkPackageLocators(String classPackageName) {
if (packageLocators != null && !disablePackageLocatorsScanning && classPackageName.length() > 0
if (packageLocators != null && !disablePackageLocatorsScanning && !classPackageName.isEmpty()
&& (packageLocatorsBasePackage == null || classPackageName
.startsWith(packageLocatorsBasePackage))) {
.startsWith(packageLocatorsBasePackage))) {
for (String packageLocator : packageLocators) {
String[] splitted = classPackageName.split("\\.");
@@ -586,14 +615,10 @@ public class PackageBasedActionConfigBuilder implements ActionConfigBuilder {
* is to return the result of {@link #includeClassNameInActionScan(String)}.
*
* @return a {@link Test} object that returns true if the specified class
* name should be included in the package scan
* name should be included in the package scan
*/
protected Test<String> getClassPackageTest() {
return new Test<String>() {
public boolean test(String className) {
return includeClassNameInActionScan(className);
}
};
return this::includeClassNameInActionScan;
}
/**
@@ -604,7 +629,7 @@ public class PackageBasedActionConfigBuilder implements ActionConfigBuilder {
* super-classes of the specified class.
*
* @return a {@link Test} object that returns true if the specified class
* should be included in the package scan
* should be included in the package scan
*/
protected Test<ClassFinder.ClassInfo> getActionClassTest() {
return new Test<ClassFinder.ClassInfo>() {
@@ -638,8 +663,7 @@ public class PackageBasedActionConfigBuilder implements ActionConfigBuilder {
};
}
@SuppressWarnings("unchecked")
protected void buildConfiguration(Set<Class> classes) {
protected void buildConfiguration(Set<Class<?>> classes) {
Map<String, PackageConfig.Builder> packageConfigs = new HashMap<>();
for (Class<?> actionClass : classes) {
@@ -754,7 +778,7 @@ public class PackageBasedActionConfigBuilder implements ActionConfigBuilder {
private Set<String> getAllowedMethods(Class<?> actionClass) {
List<AllowedMethods> annotations = AnnotationUtils.findAnnotations(actionClass, AllowedMethods.class);
if (annotations == null || annotations.isEmpty()) {
if (annotations.isEmpty()) {
return Collections.emptySet();
} else {
Set<String> methods = new HashSet<>();
@@ -767,6 +791,7 @@ public class PackageBasedActionConfigBuilder implements ActionConfigBuilder {
/**
* Interfaces, enums, annotations, and abstract classes cannot be instantiated.
*
* @param actionClass class to check
* @return returns true if the class cannot be instantiated or should be ignored
*/
@@ -885,7 +910,7 @@ public class PackageBasedActionConfigBuilder implements ActionConfigBuilder {
} else {
Action ann = method.getAnnotation(Action.class);
if (ann != null) {
map.put(method.getName(), Arrays.asList(ann));
map.put(method.getName(), Collections.singletonList(ann));
}
}
}
@@ -894,7 +919,8 @@ public class PackageBasedActionConfigBuilder implements ActionConfigBuilder {
}
/**
* Builds a list of actions from an @Actions annotation, and check that they are not all empty
* Builds a list of actions from an @Actions annotation, and check that they are not all empty
*
* @param actionsAnnotation Actions annotation
* @return a list of Actions
*/
@@ -926,12 +952,12 @@ public class PackageBasedActionConfigBuilder implements ActionConfigBuilder {
*/
protected void createActionConfig(PackageConfig.Builder pkgCfg, Class<?> actionClass, String actionName,
String actionMethod, Action annotation, Set<String> allowedMethods) {
String className = actionClass.getName();
String className = actionClass.getName();
if (annotation != null) {
actionName = annotation.value().equals(Action.DEFAULT_VALUE) ? actionName : annotation.value();
actionName = StringUtils.contains(actionName, "/") && !slashesInActionNames ? StringUtils.substringAfterLast(actionName, "/") : actionName;
if(!Action.DEFAULT_VALUE.equals(annotation.className())){
className = annotation.className();
if (!Action.DEFAULT_VALUE.equals(annotation.className())) {
className = annotation.className();
}
}
@@ -988,8 +1014,10 @@ public class PackageBasedActionConfigBuilder implements ActionConfigBuilder {
//watch class file
if (isReloadEnabled()) {
URL classFile = actionClass.getResource(actionClass.getSimpleName() + ".class");
fileManager.monitorFile(classFile);
loadedFileUrls.add(classFile.toString());
if (classFile != null) {
fileManager.monitorFile(classFile);
loadedFileUrls.add(classFile.toString());
}
}
}
@@ -998,7 +1026,7 @@ public class PackageBasedActionConfigBuilder implements ActionConfigBuilder {
for (ExceptionMapping exceptionMapping : exceptions) {
LOG.trace("Mapping exception [{}] to result [{}] for action [{}]", exceptionMapping.exception(),
exceptionMapping.result(), actionName);
exceptionMapping.result(), actionName);
ExceptionMappingConfig.Builder builder = new ExceptionMappingConfig.Builder(null, exceptionMapping
.exception(), exceptionMapping.result());
builder.addParams(StringTools.createParameterMap(exceptionMapping.params()));
@@ -1009,8 +1037,8 @@ public class PackageBasedActionConfigBuilder implements ActionConfigBuilder {
}
protected PackageConfig.Builder getPackageConfig(final Map<String, PackageConfig.Builder> packageConfigs,
String actionNamespace, final String actionPackage, final Class<?> actionClass,
Action action) {
String actionNamespace, final String actionPackage, final Class<?> actionClass,
Action action) {
if (action != null && !action.value().equals(Action.DEFAULT_VALUE)) {
LOG.trace("Using non-default action namespace from the Action annotation of [{}]", action.value());
String actionName = action.value();
@@ -1067,7 +1095,7 @@ public class PackageBasedActionConfigBuilder implements ActionConfigBuilder {
/**
* Determine all the index handling actions and results based on this logic:
*
* <p>
* 1. Loop over all the namespaces such as /foo and see if it has an action named index
* 2. If an action doesn't exists in the parent namespace of the same name, create an action
* in the parent namespace of the same name as the namespace that points to the index
@@ -19,6 +19,7 @@
package org.apache.struts2.views.velocity;
import com.opensymphony.xwork2.util.ValueStack;
import org.apache.struts2.util.ValueStackProvider;
import org.apache.velocity.VelocityContext;
import java.util.ArrayList;
@@ -26,7 +27,7 @@ import java.util.Arrays;
import java.util.List;
import java.util.function.Function;
public class StrutsVelocityContext extends VelocityContext {
public class StrutsVelocityContext extends VelocityContext implements ValueStackProvider {
private final ValueStack stack;
private final List<VelocityContext> chainedContexts;
@@ -77,10 +78,10 @@ public class StrutsVelocityContext extends VelocityContext {
}
protected List<Function<String, Object>> contextGetterList() {
return Arrays.asList(this::superGet, this::chainedContextGet, this::stackGet);
return Arrays.asList(this::superInternalGet, this::chainedContextGet, this::stackGet);
}
protected Object superGet(String key) {
protected Object superInternalGet(String key) {
return super.internalGet(key);
}
@@ -96,11 +97,16 @@ public class StrutsVelocityContext extends VelocityContext {
return null;
}
for (VelocityContext chainedContext : chainedContexts) {
Object val = chainedContext.internalGet(key);
Object val = chainedContext.get(key);
if (val != null) {
return val;
}
}
return null;
}
@Override
public ValueStack getValueStack() {
return stack;
}
}
@@ -28,7 +28,12 @@ import jakarta.servlet.http.HttpServletResponse;
import org.apache.struts2.ServletActionContext;
import org.apache.struts2.components.Component;
import org.apache.struts2.util.ValueStackProvider;
import org.apache.struts2.views.util.ContextUtil;
import org.apache.velocity.context.AbstractContext;
import org.apache.velocity.context.Context;
import org.apache.velocity.context.InternalContextAdapter;
import org.apache.velocity.context.InternalWrapperContext;
import org.apache.velocity.exception.MethodInvocationException;
import org.apache.velocity.exception.ParseErrorException;
import org.apache.velocity.exception.ResourceNotFoundException;
@@ -57,8 +62,11 @@ public abstract class AbstractDirective extends Directive {
protected abstract Component getBean(ValueStack stack, HttpServletRequest req, HttpServletResponse res);
public boolean render(InternalContextAdapter ctx, Writer writer, Node node) throws IOException, ResourceNotFoundException, ParseErrorException, MethodInvocationException {
// get the bean
ValueStack stack = (ValueStack) ctx.get("stack");
ValueStack stack = extractValueStack(ctx);
if (stack == null) {
// Fallback to assuming the ValueStack was put into the Velocity context (as is by default)
stack = (ValueStack) ctx.get(ContextUtil.STACK);
}
HttpServletRequest req = (HttpServletRequest) stack.getContext().get(ServletActionContext.HTTP_REQUEST);
HttpServletResponse res = (HttpServletResponse) stack.getContext().get(ServletActionContext.HTTP_RESPONSE);
Component bean = getBean(stack, req, res);
@@ -79,6 +87,27 @@ public abstract class AbstractDirective extends Directive {
return true;
}
private ValueStack extractValueStack(Context context) {
do {
if (context instanceof ValueStackProvider) {
return ((ValueStackProvider) context).getValueStack();
}
context = extractContext(context);
} while (context != null);
return null;
}
private Context extractContext(Context context) {
if (context instanceof InternalWrapperContext) {
return ((InternalWrapperContext) context).getInternalUserContext();
}
if (context instanceof AbstractContext) {
return ((AbstractContext) context).getChainedContext();
}
return null;
}
/**
* <p>
* Create a Map of properties that the user has passed in. For example:
@@ -54,7 +54,7 @@ public class StrutsVelocityContextTest {
@Test
public void getChainedValue() {
when(chainedContext.internalGet("foo")).thenReturn("bar");
when(chainedContext.get("foo")).thenReturn("bar");
assertEquals("bar", strutsVelocityContext.internalGet("foo"));
}
@@ -75,7 +75,7 @@ public class StrutsVelocityContextTest {
when(stack.findValue("foo")).thenReturn("qux");
assertEquals("qux", strutsVelocityContext.internalGet("foo"));
when(chainedContext.internalGet("foo")).thenReturn("baz");
when(chainedContext.get("foo")).thenReturn("baz");
assertEquals("baz", strutsVelocityContext.internalGet("foo"));
strutsVelocityContext.put("foo", "bar");
+1 -1
View File
@@ -980,7 +980,7 @@
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-compress</artifactId>
<version>1.23.0</version>
<version>1.24.0</version>
</dependency>
<dependency>