mirror of
https://github.com/apache/struts.git
synced 2026-08-06 07:06:58 +00:00
Merge pull request #833 from apache/merge-master-to-7xx-2024-01-04
[Struts 7] Merge master to 7xx
This commit is contained in:
@@ -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}}"
|
||||
|
||||
@@ -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="#">×</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
|
||||
|
||||
@@ -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)
|
||||
|
||||
-3
@@ -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 {
|
||||
}
|
||||
|
||||
+3
-1
@@ -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;
|
||||
* <package name="csp-reports" namespace="/" extends="struts-default">
|
||||
* <action name="csp-reports" class="org.apache.struts2.action.DefaultCspReportAction">
|
||||
* <result type="httpheader">
|
||||
* <param name="statusCode">200</param>
|
||||
* <param name="status">204</param>
|
||||
* </result>
|
||||
* </action>
|
||||
* </package>
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
+3
-3
@@ -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>
|
||||
|
||||
+561
@@ -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>
|
||||
|
||||
@@ -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() {
|
||||
|
||||
+10
-11
@@ -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['"."']=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="->"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>
|
||||
<input type="button" value="Move Up" onclick="moveOptionUp(document.getElementById('myId'), 'key', '');" />
|
||||
<input type="button" value="v" onclick="moveOptionDown(document.getElementById('myId'), 'key', '');" />
|
||||
<input type="button" value="*" onclick="selectAllOptions(document.getElementById('myId'), 'key', '');" />
|
||||
<input type="button" value="Move Up" onclick="moveOptionUp(document.getElementById('myId'), 'key', '');" />
|
||||
<input type="button" value="v" onclick="moveOptionDown(document.getElementById('myId'), 'key', '');" />
|
||||
<input type="button" value="*" onclick="selectAllOptions(document.getElementById('myId'), 'key', '');" />
|
||||
</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">
|
||||
|
||||
+109
-81
@@ -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
|
||||
|
||||
+10
-4
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
+31
-2
@@ -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:
|
||||
|
||||
+2
-2
@@ -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");
|
||||
|
||||
Reference in New Issue
Block a user