Merge pull request #850 from apache/merge-master-to-7xx-2024-01-20

[Struts-7] Merge master to 7xx 2024-01-20
This commit is contained in:
Lukasz Lenart
2024-01-20 09:39:51 +01:00
committed by GitHub
48 changed files with 1141 additions and 902 deletions
+1 -1
View File
@@ -58,7 +58,7 @@ jobs:
publish_results: true
- name: "Upload artifact"
uses: actions/upload-artifact@c7d193f32edcb7bfad88892161225aeda64e9392 # 4.0.0
uses: actions/upload-artifact@1eb3cb2b3e0f29609092a73eb033bb759a334595 # 4.1.0
with:
name: SARIF file
path: results.sarif
Vendored
+11 -12
View File
@@ -125,18 +125,17 @@ pipeline {
steps {
sh './mvnw -B package -DskipTests'
sshPublisher(publishers: [
sshPublisherDesc(
configName: 'Nightlies',
transfers: [
sshTransfer(
remoteDirectory: '/struts/snapshot',
removePrefix: 'assembly/target/assembly/out',
sourceFiles: 'assembly/target/assembly/out/struts-*.zip',
cleanRemote: true
)
],
verbose: true
)
sshPublisherDesc(
configName: 'Nightlies',
transfers: [
sshTransfer(
remoteDirectory: '/struts/snapshot',
removePrefix: 'assembly/target/assembly/out',
sourceFiles: 'assembly/target/assembly/out/struts-*.zip'
)
],
verbose: true
)
])
}
}
+2
View File
@@ -41,6 +41,8 @@
<include>ognl:ognl</include>
<include>org.apache.commons:commons-fileupload2-jakarta</include>
<include>org.apache.commons:commons-io</include>
<include>com.github.ben-manes.caffeine:caffeine</include>
<include>org.javassist:javassist</include>
</includes>
</dependencySet>
</dependencySets>
@@ -51,10 +51,6 @@ public abstract class XWorkJUnit4TestCase {
@After
public void tearDown() throws Exception {
XWorkTestCaseHelper.tearDown(configurationManager);
configurationManager = null;
configuration = null;
container = null;
actionProxyFactory = null;
}
protected void loadConfigurationProviders(ConfigurationProvider... providers) {
@@ -64,10 +64,6 @@ public abstract class XWorkTestCase extends TestCase {
@Override
protected void tearDown() throws Exception {
XWorkTestCaseHelper.tearDown(configurationManager);
configurationManager = null;
configuration = null;
container = null;
actionProxyFactory = null;
}
protected void loadConfigurationProviders(ConfigurationProvider... providers) {
@@ -77,6 +73,16 @@ public abstract class XWorkTestCase extends TestCase {
actionProxyFactory = container.getInstance(ActionProxyFactory.class);
}
protected void loadButSet(Map<String, ?> properties) {
loadConfigurationProviders(new StubConfigurationProvider() {
@Override
public void register(ContainerBuilder builder,
LocatableProperties props) throws ConfigurationException {
properties.forEach((k, v) -> props.setProperty(k, String.valueOf(v)));
}
});
}
protected <T> void loadButAdd(final Class<T> type, final T impl) {
loadButAdd(type, Container.DEFAULT_NAME, impl);
}
@@ -90,6 +90,7 @@ import com.opensymphony.xwork2.ognl.OgnlValueStackFactory;
import com.opensymphony.xwork2.ognl.SecurityMemberAccess;
import com.opensymphony.xwork2.ognl.accessor.CompoundRootAccessor;
import com.opensymphony.xwork2.ognl.accessor.RootAccessor;
import com.opensymphony.xwork2.ognl.accessor.XWorkMethodAccessor;
import com.opensymphony.xwork2.util.OgnlTextParser;
import com.opensymphony.xwork2.util.PatternMatcher;
import com.opensymphony.xwork2.util.StrutsLocalizedTextProvider;
@@ -100,6 +101,7 @@ 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.MethodAccessor;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
@@ -323,12 +325,8 @@ public class DefaultConfiguration implements Configuration {
}
protected ActionContext setContext(Container cont) {
ActionContext context = ActionContext.getContext();
if (context == null) {
ValueStack vs = cont.getInstance(ValueStackFactory.class).createValueStack();
context = ActionContext.of(vs.getContext()).bind();
}
return context;
ValueStack vs = cont.getInstance(ValueStackFactory.class).createValueStack();
return ActionContext.of(vs.getContext()).bind();
}
protected Container createBootstrapContainer(List<ContainerProvider> providers) {
@@ -389,6 +387,7 @@ public class DefaultConfiguration implements Configuration {
.factory(ObjectTypeDeterminer.class, DefaultObjectTypeDeterminer.class, Scope.SINGLETON)
.factory(RootAccessor.class, CompoundRootAccessor.class, Scope.SINGLETON)
.factory(MethodAccessor.class, XWorkMethodAccessor.class, Scope.SINGLETON)
.factory(ExpressionCacheFactory.class, DefaultOgnlExpressionCacheFactory.class, Scope.SINGLETON)
.factory(BeanInfoCacheFactory.class, DefaultOgnlBeanInfoCacheFactory.class, Scope.SINGLETON)
@@ -47,7 +47,6 @@ import com.opensymphony.xwork2.ognl.accessor.XWorkEnumerationAccessor;
import com.opensymphony.xwork2.ognl.accessor.XWorkIteratorPropertyAccessor;
import com.opensymphony.xwork2.ognl.accessor.XWorkListPropertyAccessor;
import com.opensymphony.xwork2.ognl.accessor.XWorkMapPropertyAccessor;
import com.opensymphony.xwork2.ognl.accessor.XWorkMethodAccessor;
import com.opensymphony.xwork2.security.AcceptedPatternsChecker;
import com.opensymphony.xwork2.security.DefaultAcceptedPatternsChecker;
import com.opensymphony.xwork2.security.DefaultExcludedPatternsChecker;
@@ -66,7 +65,6 @@ import com.opensymphony.xwork2.validator.DefaultValidatorFactory;
import com.opensymphony.xwork2.validator.DefaultValidatorFileParser;
import com.opensymphony.xwork2.validator.ValidatorFactory;
import com.opensymphony.xwork2.validator.ValidatorFileParser;
import ognl.MethodAccessor;
import ognl.PropertyAccessor;
import org.apache.struts2.dispatcher.HttpParameters;
import org.apache.struts2.dispatcher.Parameter;
@@ -142,8 +140,6 @@ public class StrutsDefaultConfigurationProvider implements ConfigurationProvider
.factory(PropertyAccessor.class, HttpParameters.class.getName(), HttpParametersPropertyAccessor.class, Scope.SINGLETON)
.factory(PropertyAccessor.class, Parameter.class.getName(), ParameterPropertyAccessor.class, Scope.SINGLETON)
.factory(MethodAccessor.class, Object.class.getName(), XWorkMethodAccessor.class, Scope.SINGLETON)
.factory(NullHandler.class, Object.class.getName(), InstantiatingNullHandler.class, Scope.SINGLETON)
.factory(ActionValidatorManager.class, AnnotationActionValidatorManager.class, Scope.SINGLETON)
.factory(ActionValidatorManager.class, "no-annotations", DefaultActionValidatorManager.class, Scope.SINGLETON)
@@ -31,6 +31,8 @@ import ognl.MethodAccessor;
import ognl.OgnlRuntime;
import ognl.PropertyAccessor;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.struts2.StrutsConstants;
import java.util.Set;
@@ -40,6 +42,8 @@ import java.util.Set;
*/
public class OgnlValueStackFactory implements ValueStackFactory {
private static final Logger LOG = LogManager.getLogger(OgnlValueStackFactory.class);
protected XWorkConverter xworkConverter;
protected RootAccessor compoundRootAccessor;
protected TextProvider textProvider;
@@ -57,6 +61,11 @@ public class OgnlValueStackFactory implements ValueStackFactory {
OgnlRuntime.setMethodAccessor(CompoundRoot.class, compoundRootAccessor);
}
@Inject
protected void setMethodAccessor(MethodAccessor methodAccessor) {
OgnlRuntime.setMethodAccessor(Object.class, methodAccessor);
}
@Inject("system")
protected void setTextProvider(TextProvider textProvider) {
this.textProvider = textProvider;
@@ -79,26 +88,76 @@ public class OgnlValueStackFactory implements ValueStackFactory {
return newStack.getActionContext().withContainer(container).withValueStack(newStack).getValueStack();
}
/**
* {@link PropertyAccessor}'s, {@link MethodAccessor}'s and {@link NullHandler}'s are registered on a per-class
* basis by defining a bean adhering to the corresponding interface with a name corresponding to the class it is
* intended to handle.
* <p>
* The only exception is the {@link MethodAccessor} for the {@link Object} type which has its own extension point.
*
* @see #setMethodAccessor(MethodAccessor)
* @see #registerAdditionalMethodAccessors()
*/
@Inject
protected void setContainer(Container container) throws ClassNotFoundException {
this.container = container;
registerPropertyAccessors();
registerNullHandlers();
registerAdditionalMethodAccessors();
}
/**
* Note that the default {@link MethodAccessor} for handling {@link Object} methods is registered in
* {@link #setMethodAccessor} and can be configured using the extension point
* {@link StrutsConstants#STRUTS_METHOD_ACCESSOR}.
*/
protected void registerAdditionalMethodAccessors() {
Set<String> names = container.getInstanceNames(MethodAccessor.class);
for (String name : names) {
Class<?> cls;
try {
cls = Class.forName(name);
if (cls.equals(Object.class)) {
// The Object method accessor can only be configured using the struts.methodAccessor extension point
continue;
}
if (cls.equals(CompoundRoot.class)) {
// TODO: This bean is deprecated, please remove this if statement when removing the struts-beans.xml entry
continue;
}
} catch (ClassNotFoundException e) {
// Since this interface is also used as an extension point for the Object MethodAccessor, we expect
// there to be beans with names that don't correspond to classes. We can safely ignore these.
continue;
}
MethodAccessor methodAccessor = container.getInstance(MethodAccessor.class, name);
OgnlRuntime.setMethodAccessor(cls, methodAccessor);
LOG.debug("Registered custom OGNL MethodAccessor [{}] for class [{}]", methodAccessor.getClass().getName(), cls.getName());
}
}
protected void registerNullHandlers() throws ClassNotFoundException {
Set<String> names = container.getInstanceNames(NullHandler.class);
for (String name : names) {
Class<?> cls = Class.forName(name);
NullHandler nullHandler = container.getInstance(NullHandler.class, name);
OgnlRuntime.setNullHandler(cls, new OgnlNullHandlerWrapper(nullHandler));
LOG.debug("Registered custom OGNL NullHandler [{}] for class [{}]", nullHandler.getClass().getName(), cls.getName());
}
}
protected void registerPropertyAccessors() throws ClassNotFoundException {
Set<String> names = container.getInstanceNames(PropertyAccessor.class);
for (String name : names) {
Class<?> cls = Class.forName(name);
OgnlRuntime.setPropertyAccessor(cls, container.getInstance(PropertyAccessor.class, name));
if (cls.equals(CompoundRoot.class)) {
// TODO: This bean is deprecated, please remove this if statement when removing the struts-beans.xml entry
continue;
}
PropertyAccessor propertyAccessor = container.getInstance(PropertyAccessor.class, name);
OgnlRuntime.setPropertyAccessor(cls, propertyAccessor);
LOG.debug("Registered custom OGNL PropertyAccessor [{}] for class [{}]", propertyAccessor.getClass().getName(), cls.getName());
}
names = container.getInstanceNames(MethodAccessor.class);
for (String name : names) {
Class<?> cls = Class.forName(name);
OgnlRuntime.setMethodAccessor(cls, container.getInstance(MethodAccessor.class, name));
}
names = container.getInstanceNames(NullHandler.class);
for (String name : names) {
Class<?> cls = Class.forName(name);
OgnlRuntime.setNullHandler(cls, new OgnlNullHandlerWrapper(container.getInstance(NullHandler.class, name)));
}
this.container = container;
}
/**
@@ -32,37 +32,37 @@ public interface AcceptedPatternsChecker {
* @param value to check
* @return object containing result of matched pattern and pattern itself
*/
public IsAccepted isAccepted(String value);
IsAccepted isAccepted(String value);
/**
* Sets excluded patterns during runtime
*
* @param commaDelimitedPatterns comma delimited string with patterns
*/
public void setAcceptedPatterns(String commaDelimitedPatterns);
void setAcceptedPatterns(String commaDelimitedPatterns);
/**
* Set excluded patterns during runtime
*
* @param patterns array of additional excluded patterns
*/
public void setAcceptedPatterns(String[] patterns);
void setAcceptedPatterns(String[] patterns);
/**
* Sets excluded patterns during runtime
*
* @param patterns set of additional patterns
*/
public void setAcceptedPatterns(Set<String> patterns);
void setAcceptedPatterns(Set<String> patterns);
/**
* Allow access list of all defined excluded patterns
*
* @return set of excluded patterns
*/
public Set<Pattern> getAcceptedPatterns();
Set<Pattern> getAcceptedPatterns();
public final static class IsAccepted {
final class IsAccepted {
private final boolean accepted;
private final String acceptedPattern;
@@ -32,37 +32,37 @@ public interface ExcludedPatternsChecker {
* @param value to check
* @return object containing result of matched pattern and pattern itself
*/
public IsExcluded isExcluded(String value);
IsExcluded isExcluded(String value);
/**
* Sets excluded patterns during runtime
*
* @param commaDelimitedPatterns comma delimited string with patterns
*/
public void setExcludedPatterns(String commaDelimitedPatterns);
void setExcludedPatterns(String commaDelimitedPatterns);
/**
* Sets excluded patterns during runtime
*
* @param patterns array of additional excluded patterns
*/
public void setExcludedPatterns(String[] patterns);
void setExcludedPatterns(String[] patterns);
/**
* Sets excluded patterns during runtime
*
* @param patterns set of additional patterns
*/
public void setExcludedPatterns(Set<String> patterns);
void setExcludedPatterns(Set<String> patterns);
/**
* Allow access list of all defined excluded patterns
*
* @return set of excluded patterns
*/
public Set<Pattern> getExcludedPatterns();
Set<Pattern> getExcludedPatterns();
public final static class IsExcluded {
final class IsExcluded {
private final boolean excluded;
private final String excludedPattern;
@@ -223,6 +223,9 @@ public final class StrutsConstants {
/** Extension point for the Struts CompoundRootAccessor */
public static final String STRUTS_COMPOUND_ROOT_ACCESSOR = "struts.compoundRootAccessor";
/** Extension point for the Struts MethodAccessor */
public static final String STRUTS_METHOD_ACCESSOR = "struts.methodAccessor";
/** The name of the xwork converter implementation */
public static final String STRUTS_XWORKCONVERTER = "struts.xworkConverter";
@@ -66,10 +66,6 @@ public class Radio extends ListUIBean {
return TEMPLATE;
}
public void evaluateExtraParams() {
super.evaluateExtraParams();
}
/**
* Radio tag requires lazy evaluation as list of tags is dynamically generated using <s:iterator/>
*
@@ -80,8 +76,4 @@ public class Radio extends ListUIBean {
return true;
}
protected Class<?> getValueClassType() {
return String.class;
}
}
@@ -61,6 +61,7 @@ import com.opensymphony.xwork2.util.location.LocatableProperties;
import com.opensymphony.xwork2.util.reflection.ReflectionContextFactory;
import com.opensymphony.xwork2.util.reflection.ReflectionProvider;
import com.opensymphony.xwork2.validator.ActionValidatorManager;
import ognl.MethodAccessor;
import org.apache.struts2.StrutsConstants;
import org.apache.struts2.components.UrlRenderer;
import org.apache.struts2.components.date.DateFormatter;
@@ -389,6 +390,7 @@ 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(MethodAccessor.class, StrutsConstants.STRUTS_METHOD_ACCESSOR, builder, props);
alias(XWorkConverter.class, StrutsConstants.STRUTS_XWORKCONVERTER, builder, props);
alias(CollectionConverter.class, StrutsConstants.STRUTS_CONVERTER_COLLECTION, builder, props);
@@ -151,11 +151,16 @@ public class ApplicationMap extends AbstractMap<String, Object> implements Seria
* @param key the attribute to remove.
* @return the entry that was just removed.
*/
public Object remove(final String key) {
@Override
public Object remove(Object key) {
if (key == null) {
return null;
}
entries = null;
Object value = get(key);
context.removeAttribute(key);
context.removeAttribute(key.toString());
return value;
}
@@ -23,25 +23,25 @@ import com.opensymphony.xwork2.inject.Container;
/**
* Simple class to hold Container instance per thread to minimise number of attempts
* to read configuration and build each time a new configuration.
*
* <p>
* As ContainerHolder operates just per thread (which means per request) there is no need
* to check if configuration changed during the same request. If changed between requests,
* first call to store Container in ContainerHolder will be with the new configuration.
*/
class ContainerHolder {
private static ThreadLocal<Container> instance = new ThreadLocal<>();
private static final ThreadLocal<Container> instance = new ThreadLocal<>();
public static void store(Container instance) {
ContainerHolder.instance.set(instance);
public static void store(Container newInstance) {
instance.set(newInstance);
}
public static Container get() {
return ContainerHolder.instance.get();
return instance.get();
}
public static void clear() {
ContainerHolder.instance.remove();
instance.remove();
}
}
@@ -64,6 +64,7 @@ import org.apache.struts2.config.StrutsBeanSelectionProvider;
import org.apache.struts2.config.StrutsJavaConfiguration;
import org.apache.struts2.config.StrutsJavaConfigurationProvider;
import org.apache.struts2.config.StrutsXmlConfigurationProvider;
import org.apache.struts2.dispatcher.mapper.ActionMapper;
import org.apache.struts2.dispatcher.mapper.ActionMapping;
import org.apache.struts2.dispatcher.multipart.MultiPartRequest;
import org.apache.struts2.dispatcher.multipart.MultiPartRequestWrapper;
@@ -119,6 +120,12 @@ public class Dispatcher {
*/
private static final List<DispatcherListener> dispatcherListeners = new CopyOnWriteArrayList<>();
/**
* This field exists so {@link #getContainer()} can determine whether to (re-)inject this instance in the case of
* a {@link ConfigurationManager} reload.
*/
private Container injectedContainer;
/**
* Store state of StrutsConstants.STRUTS_DEVMODE setting.
*/
@@ -144,11 +151,6 @@ public class Dispatcher {
*/
private String multipartSaveDir;
/**
* Stores the value of {@link StrutsConstants#STRUTS_MULTIPART_PARSER} setting
*/
private String multipartHandlerName;
/**
* Stores the value of {@link StrutsConstants#STRUTS_MULTIPART_ENABLED}
*/
@@ -192,6 +194,11 @@ public class Dispatcher {
* Store ConfigurationManager instance, set on init.
*/
protected ConfigurationManager configurationManager;
private ObjectFactory objectFactory;
private ActionProxyFactory actionProxyFactory;
private LocaleProviderFactory localeProviderFactory;
private StaticContentLoader staticContentLoader;
private ActionMapper actionMapper;
/**
* Provide the dispatcher instance for the current thread.
@@ -211,6 +218,13 @@ public class Dispatcher {
Dispatcher.instance.set(instance);
}
/**
* Removes the dispatcher instance for this thread.
*/
public static void clearInstance() {
Dispatcher.instance.remove();
}
/**
* Add a dispatcher lifecycle listener.
*
@@ -306,9 +320,12 @@ public class Dispatcher {
multipartSaveDir = val;
}
@Inject(StrutsConstants.STRUTS_MULTIPART_PARSER)
/**
* @deprecated since 6.4.0, no replacement.
*/
@Deprecated(since = "6.4.9", forRemoval = true)
public void setMultipartHandler(String val) {
multipartHandlerName = val;
// no-op
}
@Inject(value = StrutsConstants.STRUTS_MULTIPART_ENABLED, required = false)
@@ -326,11 +343,21 @@ public class Dispatcher {
this.valueStackFactory = valueStackFactory;
}
public ValueStackFactory getValueStackFactory() {
return valueStackFactory;
}
@Inject(StrutsConstants.STRUTS_HANDLE_EXCEPTION)
public void setHandleException(String handleException) {
this.handleException = Boolean.parseBoolean(handleException);
}
@Inject(StrutsConstants.STRUTS_DISPATCHER_PARAMETERSWORKAROUND)
public void setDispatchersParametersWorkaround(String dispatchersParametersWorkaround) {
this.paramsWorkaroundEnabled = Boolean.parseBoolean(dispatchersParametersWorkaround)
|| (servletContext != null && StringUtils.contains(servletContext.getServerInfo(), "WebLogic"));
}
public boolean isHandleException() {
return handleException;
}
@@ -340,12 +367,48 @@ public class Dispatcher {
this.errorHandler = errorHandler;
}
@Inject
public void setObjectFactory(ObjectFactory objectFactory) {
this.objectFactory = objectFactory;
}
@Inject
public void setActionProxyFactory(ActionProxyFactory actionProxyFactory) {
this.actionProxyFactory = actionProxyFactory;
}
public ActionProxyFactory getActionProxyFactory() {
return actionProxyFactory;
}
@Inject
public void setLocaleProviderFactory(LocaleProviderFactory localeProviderFactory) {
this.localeProviderFactory = localeProviderFactory;
}
@Inject
public void setStaticContentLoader(StaticContentLoader staticContentLoader) {
this.staticContentLoader = staticContentLoader;
}
public StaticContentLoader getStaticContentLoader() {
return staticContentLoader;
}
@Inject
public void setActionMapper(ActionMapper actionMapper) {
this.actionMapper = actionMapper;
}
public ActionMapper getActionMapper() {
return actionMapper;
}
/**
* Releases all instances bound to this dispatcher instance.
*/
public void cleanup() {
// clean up ObjectFactory
ObjectFactory objectFactory = getContainer().getInstance(ObjectFactory.class);
if (objectFactory == null) {
LOG.warn("Object Factory is null, something is seriously wrong, no clean up will be performed");
}
@@ -359,7 +422,7 @@ public class Dispatcher {
}
// clean up Dispatcher itself for this thread
instance.set(null);
instance.remove();
servletContext.setAttribute(StrutsStatics.SERVLET_DISPATCHER, null);
// clean up DispatcherListeners
@@ -532,21 +595,6 @@ public class Dispatcher {
loadConfigPaths("struts-deferred.xml");
}
private Container init_PreloadConfiguration() {
return getContainer();
}
private void init_CheckWebLogicWorkaround(Container container) {
// test whether param-access workaround needs to be enabled
if (servletContext != null && StringUtils.contains(servletContext.getServerInfo(), "WebLogic")) {
LOG.info("WebLogic server detected. Enabling Struts parameter access work-around.");
paramsWorkaroundEnabled = true;
} else {
paramsWorkaroundEnabled = "true".equals(container.getInstance(String.class,
StrutsConstants.STRUTS_DISPATCHER_PARAMETERSWORKAROUND));
}
}
/**
* Load configurations, including both XML and zero-configuration strategies,
* and update optional settings, including whether to reload configurations and resource files.
@@ -567,9 +615,7 @@ public class Dispatcher {
init_AliasStandardObjects(); // [7]
init_DeferredXmlConfigurations();
Container container = init_PreloadConfiguration();
container.inject(this);
init_CheckWebLogicWorkaround(container);
getContainer(); // Inject this instance
if (!dispatcherListeners.isEmpty()) {
for (DispatcherListener l : dispatcherListeners) {
@@ -689,7 +735,6 @@ public class Dispatcher {
}
protected ActionProxy createActionProxy(String namespace, String name, String method, Map<String, Object> extraContext) {
ActionProxyFactory actionProxyFactory = getContainer().getInstance(ActionProxyFactory.class);
return actionProxyFactory.createActionProxy(namespace, name, method, extraContext, true, false);
}
@@ -865,6 +910,7 @@ public class Dispatcher {
* @param response The response
*/
public void prepare(HttpServletRequest request, HttpServletResponse response) {
getContainer(); // Init ContainerHolder and reinject this instance IF ConfigurationManager was reloaded
String encoding = null;
if (defaultEncoding != null) {
encoding = defaultEncoding;
@@ -937,15 +983,12 @@ public class Dispatcher {
}
if (isMultipartSupportEnabled(request) && isMultipartRequest(request)) {
MultiPartRequest multiPartRequest = getMultiPartRequest();
LocaleProviderFactory localeProviderFactory = getContainer().getInstance(LocaleProviderFactory.class);
request = new MultiPartRequestWrapper(
multiPartRequest,
request,
getSaveDir(),
localeProviderFactory.createLocaleProvider(),
disableRequestAttributeValueStackLookup
getMultiPartRequest(),
request,
getSaveDir(),
localeProviderFactory.createLocaleProvider(),
disableRequestAttributeValueStackLookup
);
} else {
request = new StrutsRequestWrapper(request, disableRequestAttributeValueStackLookup);
@@ -988,18 +1031,7 @@ public class Dispatcher {
* @return a multi part request object
*/
protected MultiPartRequest getMultiPartRequest() {
MultiPartRequest mpr = null;
//check for alternate implementations of MultiPartRequest
Set<String> multiNames = getContainer().getInstanceNames(MultiPartRequest.class);
for (String multiName : multiNames) {
if (multiName.equals(multipartHandlerName)) {
mpr = getContainer().getInstance(MultiPartRequest.class, multiName);
}
}
if (mpr == null) {
mpr = getContainer().getInstance(MultiPartRequest.class);
}
return mpr;
return getContainer().getInstance(MultiPartRequest.class);
}
/**
@@ -1063,27 +1095,26 @@ public class Dispatcher {
}
/**
* Expose the dependency injection container.
* Exposes a thread-cached reference of the dependency injection container. If the container is found to have
* changed since the last time it was cached, this Dispatcher instance is re-injected to ensure no stale
* configuration/dependencies persist.
* <p>
* A non-cached reference can be obtained by calling {@link #getConfigurationManager()}.
*
* @return Our dependency injection container
*/
public Container getContainer() {
if (ContainerHolder.get() != null) {
return ContainerHolder.get();
}
ConfigurationManager mgr = getConfigurationManager();
if (mgr == null) {
throw new IllegalStateException("The configuration manager shouldn't be null");
} else {
Configuration config = mgr.getConfiguration();
if (config == null) {
throw new IllegalStateException("Unable to load configuration");
} else {
Container container = config.getContainer();
ContainerHolder.store(container);
return container;
if (ContainerHolder.get() == null) {
try {
ContainerHolder.store(getConfigurationManager().getConfiguration().getContainer());
} catch (NullPointerException e) {
throw new IllegalStateException("ConfigurationManager and/or Configuration should not be null", e);
}
}
if (injectedContainer != ContainerHolder.get()) {
injectedContainer = ContainerHolder.get();
injectedContainer.inject(this);
}
return ContainerHolder.get();
}
}
@@ -18,8 +18,8 @@
*/
package org.apache.struts2.dispatcher;
import org.apache.struts2.dispatcher.mapper.ActionMapping;
import org.apache.struts2.RequestUtils;
import org.apache.struts2.dispatcher.mapper.ActionMapping;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
@@ -54,7 +54,7 @@ public class ExecuteOperations {
resourcePath = request.getPathInfo();
}
StaticContentLoader staticResourceLoader = dispatcher.getContainer().getInstance(StaticContentLoader.class);
StaticContentLoader staticResourceLoader = dispatcher.getStaticContentLoader();
if (staticResourceLoader.canHandle(resourcePath)) {
staticResourceLoader.findStaticResource(resourcePath, request, response);
// The framework did its job here
@@ -57,7 +57,7 @@ public class InitOperations {
* @return the static content loader
*/
public StaticContentLoader initStaticContentLoader(HostConfig filterConfig, Dispatcher dispatcher) {
StaticContentLoader loader = dispatcher.getContainer().getInstance(StaticContentLoader.class);
StaticContentLoader loader = dispatcher.getStaticContentLoader();
loader.setHostConfig(filterConfig);
return loader;
}
@@ -21,7 +21,6 @@ package org.apache.struts2.dispatcher;
import com.opensymphony.xwork2.config.ConfigurationManager;
import jakarta.servlet.ServletContext;
import java.util.HashMap;
import java.util.Map;
public class MockDispatcher extends Dispatcher {
@@ -18,12 +18,12 @@
*/
package org.apache.struts2.dispatcher;
import java.util.Objects;
import org.apache.commons.text.StringEscapeUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.util.Objects;
public interface Parameter {
String getName();
@@ -58,7 +58,7 @@ public interface Parameter {
@Override
public String getValue() {
String[] values = toStringArray();
return (values != null && values.length > 0) ? values[0] : null;
return values.length > 0 ? values[0] : null;
}
private String[] toStringArray() {
@@ -124,7 +124,7 @@ public interface Parameter {
class Empty implements Parameter {
private String name;
private final String name;
public Empty(String name) {
this.name = name;
@@ -20,13 +20,11 @@ package org.apache.struts2.dispatcher;
import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.util.ValueStack;
import com.opensymphony.xwork2.util.ValueStackFactory;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.struts2.RequestUtils;
import org.apache.struts2.ServletActionContext;
import org.apache.struts2.StrutsException;
import org.apache.struts2.dispatcher.mapper.ActionMapper;
import org.apache.struts2.dispatcher.mapper.ActionMapping;
import jakarta.servlet.ServletException;
@@ -78,7 +76,7 @@ public class PrepareOperations {
dispatcher.cleanUpRequest(request);
} finally {
ActionContext.clear();
Dispatcher.setInstance(null);
Dispatcher.clearInstance();
devModeOverride.remove();
}
});
@@ -101,7 +99,7 @@ public class PrepareOperations {
} else {
ctx = ServletActionContext.getActionContext(request); //checks if we are probably in an async
if (ctx == null) {
ValueStack stack = dispatcher.getContainer().getInstance(ValueStackFactory.class).createValueStack();
ValueStack stack = dispatcher.getValueStackFactory().createValueStack();
stack.getContext().putAll(dispatcher.createContextMap(request, response, null));
ctx = ActionContext.of(stack.getContext()).bind();
}
@@ -188,7 +186,7 @@ public class PrepareOperations {
Object mappingAttr = request.getAttribute(STRUTS_ACTION_MAPPING_KEY);
if (mappingAttr == null || forceLookup) {
try {
mapping = dispatcher.getContainer().getInstance(ActionMapper.class).getMapping(request, dispatcher.getConfigurationManager());
mapping = dispatcher.getActionMapper().getMapping(request, dispatcher.getConfigurationManager());
if (mapping != null) {
request.setAttribute(STRUTS_ACTION_MAPPING_KEY, mapping);
} else {
@@ -125,11 +125,16 @@ public class RequestMap extends AbstractMap<String, Object> implements Serializa
* @param key the name of the attribute to remove.
* @return the value that was removed or <tt>null</tt> if the value was not found (and hence, not removed).
*/
public Object remove(final String key) {
@Override
public Object remove(final Object key) {
if (key == null) {
return null;
}
entries = null;
Object value = get(key);
request.removeAttribute(key);
request.removeAttribute(key.toString());
return value;
}
@@ -76,12 +76,12 @@ public class ActionMappingParametersInterceptor extends ParametersInterceptor {
/**
* Get the parameter map from ActionMapping associated with the provided ActionContext.
*
* @param ac The action context
* @param actionContext The action context
* @return the parameters from the action mapping in the context. If none found, returns an empty map.
*/
@Override
protected HttpParameters retrieveParameters(ActionContext ac) {
ActionMapping mapping = ac.getActionMapping();
protected HttpParameters retrieveParameters(ActionContext actionContext) {
ActionMapping mapping = actionContext.getActionMapping();
if (mapping != null) {
return HttpParameters.create(mapping.getParams()).buildNoNestedWrapping();
} else {
@@ -43,7 +43,8 @@ public final class CspInterceptor extends AbstractInterceptor {
private static final Logger LOG = LogManager.getLogger(CspInterceptor.class);
private Boolean enforcingMode;
private boolean prependServletContext = true;
private boolean enforcingMode;
private String reportUri;
@Override
@@ -60,18 +61,23 @@ public final class CspInterceptor extends AbstractInterceptor {
}
private void applySettings(ActionInvocation invocation, CspSettings cspSettings) {
if (enforcingMode != null) {
LOG.trace("Applying: {} to enforcingMode", enforcingMode);
cspSettings.setEnforcingMode(enforcingMode);
}
if (reportUri != null) {
LOG.trace("Applying: {} to reportUri", reportUri);
cspSettings.setReportUri(reportUri);
}
HttpServletRequest request = invocation.getInvocationContext().getServletRequest();
HttpServletResponse response = invocation.getInvocationContext().getServletResponse();
LOG.trace("Applying: {} to enforcingMode", enforcingMode);
cspSettings.setEnforcingMode(enforcingMode);
if (reportUri != null) {
LOG.trace("Applying: {} to reportUri", reportUri);
String finalReportUri = reportUri;
if (prependServletContext && (request.getContextPath() != null) && (!request.getContextPath().isEmpty())) {
finalReportUri = request.getContextPath() + finalReportUri;
}
cspSettings.setReportUri(finalReportUri);
}
invocation.addPreResultListener((actionInvocation, resultCode) -> {
LOG.trace("Applying CSP header: {} to the request", cspSettings);
cspSettings.addCspHeaders(request, response);
@@ -99,8 +105,23 @@ public final class CspInterceptor extends AbstractInterceptor {
}
}
public void setEnforcingMode(String value) {
this.enforcingMode = Boolean.parseBoolean(value);
/**
* Enables enforcing mode, by default all exceptions are only reported
*
* @param enforcingMode {@code true} to enable enforcing mode, {@code false} to keep reporting mode.
*/
public void setEnforcingMode(boolean enforcingMode) {
this.enforcingMode = enforcingMode;
}
/**
* Sets whether to prepend the servlet context path to the {@link #reportUri}.
*
* @param prependServletContext {@code true} to prepend the location with the servlet context path,
* {@code false} otherwise.
*/
public void setPrependServletContext(boolean prependServletContext) {
this.prependServletContext = prependServletContext;
}
}
@@ -43,7 +43,6 @@ 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;
@@ -51,6 +50,8 @@ import java.util.Set;
import java.util.TreeMap;
import java.util.regex.Pattern;
import static java.util.Collections.unmodifiableSet;
import static java.util.stream.Collectors.joining;
import static org.apache.commons.lang3.StringUtils.normalizeSpace;
/**
@@ -132,138 +133,157 @@ public class ParametersInterceptor extends MethodFilterInterceptor {
@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);
}
}
if (action instanceof NoParameters) {
return invocation.invoke();
}
ActionContext actionContext = invocation.getInvocationContext();
HttpParameters parameters = retrieveParameters(actionContext);
if (parameters == null) {
return invocation.invoke();
}
if (LOG.isDebugEnabled()) {
LOG.debug("Setting params {}", normalizeSpace(getParameterLogMap(parameters)));
}
Map<String, Object> contextMap = actionContext.getContextMap();
batchApplyReflectionContextState(contextMap, true);
try {
setParameters(action, actionContext.getValueStack(), parameters);
} finally {
batchApplyReflectionContextState(contextMap, false);
}
return invocation.invoke();
}
/**
* Gets the parameter map to apply from wherever appropriate
*
* @param ac The action context
* @param actionContext The action context
* @return The parameter map to apply
*/
protected HttpParameters retrieveParameters(ActionContext ac) {
return ac.getParameters();
protected HttpParameters retrieveParameters(ActionContext actionContext) {
return actionContext.getParameters();
}
/**
* Adds the parameters into context's ParameterMap
* <p>
* In this class this is a no-op, since the parameters were fetched from the same location. In subclasses both this
* and {@link #retrieveParameters} should be overridden.
*
* @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) {
}
/**
* @deprecated since 6.4.0, use {@link #applyParameters}
*/
@Deprecated
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<>();
}
applyParameters(action, stack, parameters);
}
for (Map.Entry<String, Parameter> entry : params.entrySet()) {
String parameterName = entry.getKey();
boolean isAcceptableParameter = isAcceptableParameter(parameterName, action);
isAcceptableParameter &= isAcceptableParameterValue(entry.getValue(), action);
protected void applyParameters(final Object action, ValueStack stack, HttpParameters parameters) {
Map<String, Parameter> acceptableParameters = toAcceptableParameters(parameters, action);
if (isAcceptableParameter) {
acceptableParameters.put(parameterName, entry.getValue());
}
}
ValueStack newStack = toNewStack(stack);
batchApplyReflectionContextState(newStack.getContext(), true);
applyMemberAccessProperties(newStack);
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);
applyParametersOnStack(newStack, acceptableParameters, action);
//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) {
if (newStack instanceof ClearableValueStack) {
stack.getActionContext().withConversionErrors(newStack.getActionContext().getConversionErrors());
}
addParametersToContext(ActionContext.getContext(), acceptableParameters);
}
protected void batchApplyReflectionContextState(Map<String, Object> context, boolean value) {
ReflectionContextState.setCreatingNullObjects(context, value);
ReflectionContextState.setDenyMethodExecution(context, value);
ReflectionContextState.setReportingConversionErrors(context, value);
}
protected ValueStack toNewStack(ValueStack stack) {
ValueStack newStack = valueStackFactory.createValueStack(stack);
if (newStack instanceof ClearableValueStack) {
((ClearableValueStack) newStack).clearContextValues();
newStack.getActionContext().withLocale(stack.getActionContext().getLocale()).withValueStack(stack);
}
return newStack;
}
protected void applyMemberAccessProperties(ValueStack stack) {
if (!(stack instanceof MemberAccessValueStack)) {
return;
}
((MemberAccessValueStack) stack).useAcceptProperties(acceptedPatterns.getAcceptedPatterns());
((MemberAccessValueStack) stack).useExcludeProperties(excludedPatterns.getExcludedPatterns());
}
protected Map<String, Parameter> toAcceptableParameters(HttpParameters parameters, Object action) {
HttpParameters newParams = initNewHttpParameters(parameters);
Map<String, Parameter> acceptableParameters = initParameterMap();
for (Map.Entry<String, Parameter> entry : newParams.entrySet()) {
String parameterName = entry.getKey();
Parameter parameterValue = entry.getValue();
if (isAcceptableParameter(parameterName, action) && isAcceptableParameterValue(parameterValue, action)) {
acceptableParameters.put(parameterName, parameterValue);
}
}
return acceptableParameters;
}
protected Map<String, Parameter> initParameterMap() {
if (ordered) {
return new TreeMap<>(getOrderedComparator());
} else {
return new TreeMap<>();
}
}
protected HttpParameters initNewHttpParameters(HttpParameters parameters) {
if (ordered) {
return HttpParameters.create().withComparator(getOrderedComparator()).withParent(parameters).build();
} else {
return HttpParameters.create().withParent(parameters).build();
}
}
protected void applyParametersOnStack(ValueStack stack, Map<String, Parameter> parameters, Object action) {
for (Map.Entry<String, Parameter> entry : parameters.entrySet()) {
try {
stack.setParameter(entry.getKey(), entry.getValue().getObject());
} catch (RuntimeException e) {
if (devMode) {
notifyDeveloperParameterException(action, entry.getKey(), e.getMessage());
}
}
}
}
protected void notifyDeveloperParameterException(Object action, String property, String message) {
String developerNotification = "Unexpected Exception caught setting '" + property + "' on '" + action.getClass() + ": " + message;
String logMsg = "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}
);
logMsg = tp.getText("devmode.notification", "Developer Notification:\n{0}", new String[]{logMsg});
}
LOG.error(developerNotification);
LOG.error(logMsg);
if (action instanceof ValidationAware) {
// see https://issues.apache.org/jira/browse/WW-4066
Collection<String> messages = ((ValidationAware) action).getActionMessages();
ValidationAware validationAware = (ValidationAware) action;
Collection<String> messages = validationAware.getActionMessages();
messages.add(message);
((ValidationAware) action).setActionMessages(messages);
validationAware.setActionMessages(messages);
}
}
@@ -275,8 +295,11 @@ public class ParametersInterceptor extends MethodFilterInterceptor {
* @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));
return acceptableName(name) && isAcceptableParameterNameAware(name, action);
}
protected boolean isAcceptableParameterNameAware(String name, Object action) {
return !(action instanceof ParameterNameAware) || ((ParameterNameAware) action).acceptableParameterName(name);
}
/**
@@ -287,13 +310,11 @@ public class ParametersInterceptor extends MethodFilterInterceptor {
* @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;
return isAcceptableParameterValueAware(param, action) && acceptableValue(param.getName(), param.getValue());
}
protected boolean isAcceptableParameterValueAware(Parameter param, Object action) {
return !(action instanceof ParameterValueAware) || ((ParameterValueAware) action).acceptableParameterValue(param.getValue());
}
/**
@@ -311,16 +332,16 @@ public class ParametersInterceptor extends MethodFilterInterceptor {
if (parameters == null) {
return "NONE";
}
return parameters.entrySet().stream()
.map(entry -> String.format("%s => %s ", entry.getKey(), entry.getValue().getValue()))
.collect(joining());
}
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();
/**
* @deprecated since 6.4.0, use {@link #isAcceptableName}
*/
protected boolean acceptableName(String name) {
return isAcceptableName(name);
}
/**
@@ -332,24 +353,30 @@ public class ParametersInterceptor extends MethodFilterInterceptor {
* @param name - Name to check
* @return true if accepted
*/
protected boolean acceptableName(String name) {
protected boolean isAcceptableName(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
if (devMode && accepted) {
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 {
if (!dmiEnabled) {
return false;
}
return DMI_IGNORED_PATTERN.matcher(name).matches();
}
/**
* @deprecated since 6.4.0, use {@link #isAcceptableValue}
*/
protected boolean acceptableValue(String name, String value) {
return isAcceptableValue(name, value);
}
/**
@@ -362,8 +389,8 @@ public class ParametersInterceptor extends MethodFilterInterceptor {
* @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)));
protected boolean isAcceptableValue(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) {
@@ -378,7 +405,7 @@ public class ParametersInterceptor extends MethodFilterInterceptor {
protected boolean isWithinLengthLimit(String name) {
boolean matchLength = name.length() <= paramNameMaxLength;
if (!matchLength) {
if (devMode) { // warn only when in devMode
if (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",
@@ -392,22 +419,23 @@ public class ParametersInterceptor extends MethodFilterInterceptor {
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());
if (!result.isAccepted()) {
if (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;
}
return false;
return true;
}
protected boolean isExcluded(String paramName) {
ExcludedPatternsChecker.IsExcluded result = excludedPatterns.isExcluded(paramName);
if (result.isExcluded()) {
if (devMode) { // warn only when in devMode
if (devMode) {
LOG.warn("Parameter [{}] matches excluded pattern [{}]! See Accepted / Excluded patterns at\n" +
"https://struts.apache.org/security/#accepted--excluded-patterns",
paramName, result.getExcludedPattern());
@@ -460,11 +488,11 @@ public class ParametersInterceptor extends MethodFilterInterceptor {
}
private boolean hasParamValuesToExclude() {
return excludedValuePatterns != null && excludedValuePatterns.size() > 0;
return excludedValuePatterns != null && !excludedValuePatterns.isEmpty();
}
private boolean hasParamValuesToAccept() {
return acceptedValuePatterns != null && acceptedValuePatterns.size() > 0;
return acceptedValuePatterns != null && !acceptedValuePatterns.isEmpty();
}
/**
@@ -530,7 +558,7 @@ public class ParametersInterceptor extends MethodFilterInterceptor {
acceptedValuePatterns.add(Pattern.compile(pattern, Pattern.CASE_INSENSITIVE));
}
} finally {
acceptedValuePatterns = Collections.unmodifiableSet(acceptedValuePatterns);
acceptedValuePatterns = unmodifiableSet(acceptedValuePatterns);
}
}
@@ -555,7 +583,7 @@ public class ParametersInterceptor extends MethodFilterInterceptor {
excludedValuePatterns.add(Pattern.compile(pattern, Pattern.CASE_INSENSITIVE));
}
} finally {
excludedValuePatterns = Collections.unmodifiableSet(excludedValuePatterns);
excludedValuePatterns = unmodifiableSet(excludedValuePatterns);
}
}
}
@@ -28,19 +28,17 @@ import org.apache.struts2.dispatcher.DispatcherErrorHandler;
import jakarta.servlet.ServletContext;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.util.HashMap;
import java.util.Map;
import static java.util.Collections.emptyMap;
/**
* Generic test setup methods to be used with any unit testing framework.
* Generic test setup methods to be used with any unit testing framework.
*/
public class StrutsTestCaseHelper {
public static Dispatcher initDispatcher(ServletContext ctx, Map<String,String> params) {
if (params == null) {
params = new HashMap<>();
}
Dispatcher du = new DispatcherWrapper(ctx, params);
public static Dispatcher initDispatcher(ServletContext ctx, Map<String, String> params) {
Dispatcher du = new DispatcherWrapper(ctx, params != null ? params : emptyMap());
du.init();
Dispatcher.setInstance(du);
@@ -52,8 +50,16 @@ public class StrutsTestCaseHelper {
return du;
}
public static void tearDown() throws Exception {
Dispatcher.setInstance(null);
public static void tearDown(Dispatcher dispatcher) {
if (dispatcher != null && dispatcher.getConfigurationManager() != null) {
dispatcher.cleanup();
}
tearDown();
}
public static void tearDown() {
(new Dispatcher(null, null)).cleanUpAfterInit(); // Clear ContainerHolder
Dispatcher.clearInstance();
ActionContext.clear();
}
+5
View File
@@ -208,6 +208,11 @@
<bean type="ognl.PropertyAccessor" name="org.apache.struts2.dispatcher.Parameter"
class="com.opensymphony.xwork2.ognl.accessor.ParameterPropertyAccessor"/>
<bean type="ognl.MethodAccessor" name="struts"
class="com.opensymphony.xwork2.ognl.accessor.XWorkMethodAccessor"/>
<!-- DEPRECATED since 6.4.0 - Retained for backwards compatibility with custom beans
that may expect it to be present. Inject the DEFAULT bean of 'ognl.MethodAccessor' above instead. -->
<bean type="ognl.MethodAccessor" name="java.lang.Object"
class="com.opensymphony.xwork2.ognl.accessor.XWorkMethodAccessor"/>
@@ -25,6 +25,9 @@
<#if parameters.href??>
href="${parameters.href?no_esc}"<#rt/>
</#if>
<#if parameters.disabled!false>
disabled="disabled"<#rt/>
</#if>
<#if parameters.tabindex??>
tabindex="${parameters.tabindex}"<#rt/>
</#if>
@@ -90,43 +90,27 @@ public class OgnlValueStackTest extends XWorkTestCase {
@Override
public void setUp() throws Exception {
super.setUp();
refreshContainerFields();
}
protected void refreshContainerFields() {
ognlUtil = container.getInstance(OgnlUtil.class);
vs = createValueStack(true);
}
private OgnlValueStack createValueStack(boolean allowStaticFieldAccess) {
OgnlValueStack stack = new OgnlValueStack(
container.getInstance(XWorkConverter.class),
(CompoundRootAccessor) container.getInstance(RootAccessor.class),
container.getInstance(TextProvider.class, "system"), allowStaticFieldAccess);
container.inject(stack);
return stack;
vs = (OgnlValueStack) container.getInstance(ValueStackFactory.class).createValueStack();
}
/**
* @return current OgnlValueStackFactory instance from current container
*/
private OgnlValueStackFactory getValueStackFactory() {
return (OgnlValueStackFactory) container.getInstance(ValueStackFactory.class);
}
/**
* Reloads container and gets a new OgnlValueStackFactory with specified new configuration.
* Reloads container and sets a new OgnlValueStackFactory with specified new configuration.
* Intended for testing OgnlValueStack instance(s) that are minimally configured.
* This should help ensure no underlying configuration/injection side-effects are responsible
* for the behaviour of fundamental access control flags).
*
* @param allowStaticField new allowStaticField configuration
* @return a new OgnlValueStackFactory with specified new configuration
*/
private OgnlValueStackFactory reloadValueStackFactory(boolean allowStaticField) {
try {
reloadTestContainerConfiguration(allowStaticField);
} catch (Exception ex) {
fail("Unable to reload container configuration and configure ognlValueStackFactory - exception: " + ex);
}
return getValueStackFactory();
private void reloadContainer(boolean allowStaticField) {
Map<String, String> properties = new HashMap<>();
properties.put(StrutsConstants.STRUTS_ALLOW_STATIC_FIELD_ACCESS, Boolean.toString(allowStaticField));
loadButSet(properties);
refreshContainerFields();
}
public void testExpOverridesCanStackExpUp() throws Exception {
@@ -1159,31 +1143,29 @@ public class OgnlValueStackTest extends XWorkTestCase {
* when a default configuration is used.
*/
public void testOgnlValueStackFromOgnlValueStackFactoryDefaultConfig() throws IllegalAccessException {
OgnlValueStackFactory ognlValueStackFactory = getValueStackFactory();
OgnlValueStack ognlValueStack = (OgnlValueStack) ognlValueStackFactory.createValueStack();
Object accessedValue;
assertTrue("OgnlValueStackFactory staticFieldAccess (default flags) not true?",
reflectField(ognlValueStack.securityMemberAccess, "allowStaticFieldAccess"));
reflectField(vs.securityMemberAccess, "allowStaticFieldAccess"));
// An OgnlValueStack created from the above OgnlValueStackFactory should allow public field access,
// but prevent non-public field access. It should also deny static method access.
accessedValue = ognlValueStack.findValue("@com.opensymphony.xwork2.ognl.OgnlValueStackTest@staticInteger100Method()");
accessedValue = vs.findValue("@com.opensymphony.xwork2.ognl.OgnlValueStackTest@staticInteger100Method()");
assertNull("able to access static method (result not null) ?", accessedValue);
accessedValue = ognlValueStack.findValue("@com.opensymphony.xwork2.ognl.OgnlValueStackTest@STATIC_FINAL_PUBLIC_ATTRIBUTE");
accessedValue = vs.findValue("@com.opensymphony.xwork2.ognl.OgnlValueStackTest@STATIC_FINAL_PUBLIC_ATTRIBUTE");
assertEquals("accessed static final public field value not equal to actual?", accessedValue, STATIC_FINAL_PUBLIC_ATTRIBUTE);
accessedValue = ognlValueStack.findValue("@com.opensymphony.xwork2.ognl.OgnlValueStackTest@STATIC_PUBLIC_ATTRIBUTE");
accessedValue = vs.findValue("@com.opensymphony.xwork2.ognl.OgnlValueStackTest@STATIC_PUBLIC_ATTRIBUTE");
assertEquals("accessed static public field value not equal to actual?", accessedValue, STATIC_PUBLIC_ATTRIBUTE);
accessedValue = ognlValueStack.findValue("@com.opensymphony.xwork2.ognl.OgnlValueStackTest@STATIC_FINAL_PACKAGE_ATTRIBUTE");
accessedValue = vs.findValue("@com.opensymphony.xwork2.ognl.OgnlValueStackTest@STATIC_FINAL_PACKAGE_ATTRIBUTE");
assertNull("accessed final package field (result not null) ?", accessedValue);
accessedValue = ognlValueStack.findValue("@com.opensymphony.xwork2.ognl.OgnlValueStackTest@STATIC_PACKAGE_ATTRIBUTE");
accessedValue = vs.findValue("@com.opensymphony.xwork2.ognl.OgnlValueStackTest@STATIC_PACKAGE_ATTRIBUTE");
assertNull("accessed package field (result not null) ?", accessedValue);
accessedValue = ognlValueStack.findValue("@com.opensymphony.xwork2.ognl.OgnlValueStackTest@STATIC_FINAL_PROTECTED_ATTRIBUTE");
accessedValue = vs.findValue("@com.opensymphony.xwork2.ognl.OgnlValueStackTest@STATIC_FINAL_PROTECTED_ATTRIBUTE");
assertNull("accessed final protected field (result not null) ?", accessedValue);
accessedValue = ognlValueStack.findValue("@com.opensymphony.xwork2.ognl.OgnlValueStackTest@STATIC_PROTECTED_ATTRIBUTE");
accessedValue = vs.findValue("@com.opensymphony.xwork2.ognl.OgnlValueStackTest@STATIC_PROTECTED_ATTRIBUTE");
assertNull("accessed protected field (result not null) ?", accessedValue);
accessedValue = ognlValueStack.findValue("@com.opensymphony.xwork2.ognl.OgnlValueStackTest@STATIC_FINAL_PRIVATE_ATTRIBUTE");
accessedValue = vs.findValue("@com.opensymphony.xwork2.ognl.OgnlValueStackTest@STATIC_FINAL_PRIVATE_ATTRIBUTE");
assertNull("accessed final private field (result not null) ?", accessedValue);
accessedValue = ognlValueStack.findValue("@com.opensymphony.xwork2.ognl.OgnlValueStackTest@STATIC_PRIVATE_ATTRIBUTE");
accessedValue = vs.findValue("@com.opensymphony.xwork2.ognl.OgnlValueStackTest@STATIC_PRIVATE_ATTRIBUTE");
assertNull("accessed private field (result not null) ?", accessedValue);
}
@@ -1192,31 +1174,30 @@ public class OgnlValueStackTest extends XWorkTestCase {
* when static access flag is set to false.
*/
public void testOgnlValueStackFromOgnlValueStackFactoryNoStaticAccess() throws IllegalAccessException {
OgnlValueStackFactory ognlValueStackFactory = reloadValueStackFactory(false);
OgnlValueStack ognlValueStack = (OgnlValueStack) ognlValueStackFactory.createValueStack();
reloadContainer(false);
Object accessedValue;
assertFalse("OgnlValueStackFactory staticFieldAccess (set false) not false?",
reflectField(ognlValueStack.securityMemberAccess, "allowStaticFieldAccess"));
reflectField(vs.securityMemberAccess, "allowStaticFieldAccess"));
// An OgnlValueStack created from the above OgnlValueStackFactory should prevent public field access,
// and prevent non-public field access. It should also deny static method access.
accessedValue = ognlValueStack.findValue("@com.opensymphony.xwork2.ognl.OgnlValueStackTest@staticInteger100Method()");
accessedValue = vs.findValue("@com.opensymphony.xwork2.ognl.OgnlValueStackTest@staticInteger100Method()");
assertNull("able to access static method (result not null) ?", accessedValue);
accessedValue = ognlValueStack.findValue("@com.opensymphony.xwork2.ognl.OgnlValueStackTest@STATIC_FINAL_PUBLIC_ATTRIBUTE");
accessedValue = vs.findValue("@com.opensymphony.xwork2.ognl.OgnlValueStackTest@STATIC_FINAL_PUBLIC_ATTRIBUTE");
assertNull("able to access static final public field (result not null) ?", accessedValue);
accessedValue = ognlValueStack.findValue("@com.opensymphony.xwork2.ognl.OgnlValueStackTest@STATIC_PUBLIC_ATTRIBUTE");
accessedValue = vs.findValue("@com.opensymphony.xwork2.ognl.OgnlValueStackTest@STATIC_PUBLIC_ATTRIBUTE");
assertNull("able to access static public field (result not null) ?", accessedValue);
accessedValue = ognlValueStack.findValue("@com.opensymphony.xwork2.ognl.OgnlValueStackTest@STATIC_FINAL_PACKAGE_ATTRIBUTE");
accessedValue = vs.findValue("@com.opensymphony.xwork2.ognl.OgnlValueStackTest@STATIC_FINAL_PACKAGE_ATTRIBUTE");
assertNull("accessed final package field (result not null) ?", accessedValue);
accessedValue = ognlValueStack.findValue("@com.opensymphony.xwork2.ognl.OgnlValueStackTest@STATIC_PACKAGE_ATTRIBUTE");
accessedValue = vs.findValue("@com.opensymphony.xwork2.ognl.OgnlValueStackTest@STATIC_PACKAGE_ATTRIBUTE");
assertNull("accessed package field (result not null) ?", accessedValue);
accessedValue = ognlValueStack.findValue("@com.opensymphony.xwork2.ognl.OgnlValueStackTest@STATIC_FINAL_PROTECTED_ATTRIBUTE");
accessedValue = vs.findValue("@com.opensymphony.xwork2.ognl.OgnlValueStackTest@STATIC_FINAL_PROTECTED_ATTRIBUTE");
assertNull("accessed final protected field (result not null) ?", accessedValue);
accessedValue = ognlValueStack.findValue("@com.opensymphony.xwork2.ognl.OgnlValueStackTest@STATIC_PROTECTED_ATTRIBUTE");
accessedValue = vs.findValue("@com.opensymphony.xwork2.ognl.OgnlValueStackTest@STATIC_PROTECTED_ATTRIBUTE");
assertNull("accessed protected field (result not null) ?", accessedValue);
accessedValue = ognlValueStack.findValue("@com.opensymphony.xwork2.ognl.OgnlValueStackTest@STATIC_FINAL_PRIVATE_ATTRIBUTE");
accessedValue = vs.findValue("@com.opensymphony.xwork2.ognl.OgnlValueStackTest@STATIC_FINAL_PRIVATE_ATTRIBUTE");
assertNull("accessed final private field (result not null) ?", accessedValue);
accessedValue = ognlValueStack.findValue("@com.opensymphony.xwork2.ognl.OgnlValueStackTest@STATIC_PRIVATE_ATTRIBUTE");
accessedValue = vs.findValue("@com.opensymphony.xwork2.ognl.OgnlValueStackTest@STATIC_PRIVATE_ATTRIBUTE");
assertNull("accessed private field (result not null) ?", accessedValue);
}
@@ -1225,45 +1206,33 @@ public class OgnlValueStackTest extends XWorkTestCase {
* when static access flag is set to true.
*/
public void testOgnlValueStackFromOgnlValueStackFactoryAllStaticAccess() throws IllegalAccessException {
OgnlValueStackFactory ognlValueStackFactory = reloadValueStackFactory(true);
OgnlValueStack ognlValueStack = (OgnlValueStack) ognlValueStackFactory.createValueStack();
reloadContainer(true);
Object accessedValue;
assertTrue("OgnlValueStackFactory staticFieldAccess (set true) not true?",
reflectField(ognlValueStack.securityMemberAccess, "allowStaticFieldAccess"));
reflectField(vs.securityMemberAccess, "allowStaticFieldAccess"));
// An OgnlValueStack created from the above OgnlValueStackFactory should allow public field access,
// but prevent non-public field access. It should also allow static method access.
accessedValue = ognlValueStack.findValue("@com.opensymphony.xwork2.ognl.OgnlValueStackTest@staticInteger100Method()");
accessedValue = vs.findValue("@com.opensymphony.xwork2.ognl.OgnlValueStackTest@staticInteger100Method()");
assertNull("able to access static method (result non-null)!!!", accessedValue);
accessedValue = ognlValueStack.findValue("@com.opensymphony.xwork2.ognl.OgnlValueStackTest@STATIC_FINAL_PUBLIC_ATTRIBUTE");
accessedValue = vs.findValue("@com.opensymphony.xwork2.ognl.OgnlValueStackTest@STATIC_FINAL_PUBLIC_ATTRIBUTE");
assertEquals("accessed static final public field value not equal to actual?", accessedValue, STATIC_FINAL_PUBLIC_ATTRIBUTE);
accessedValue = ognlValueStack.findValue("@com.opensymphony.xwork2.ognl.OgnlValueStackTest@STATIC_PUBLIC_ATTRIBUTE");
accessedValue = vs.findValue("@com.opensymphony.xwork2.ognl.OgnlValueStackTest@STATIC_PUBLIC_ATTRIBUTE");
assertEquals("accessed static public field value not equal to actual?", accessedValue, STATIC_PUBLIC_ATTRIBUTE);
accessedValue = ognlValueStack.findValue("@com.opensymphony.xwork2.ognl.OgnlValueStackTest@STATIC_FINAL_PACKAGE_ATTRIBUTE");
accessedValue = vs.findValue("@com.opensymphony.xwork2.ognl.OgnlValueStackTest@STATIC_FINAL_PACKAGE_ATTRIBUTE");
assertNull("accessed final package field (result not null) ?", accessedValue);
accessedValue = ognlValueStack.findValue("@com.opensymphony.xwork2.ognl.OgnlValueStackTest@STATIC_PACKAGE_ATTRIBUTE");
accessedValue = vs.findValue("@com.opensymphony.xwork2.ognl.OgnlValueStackTest@STATIC_PACKAGE_ATTRIBUTE");
assertNull("accessed package field (result not null) ?", accessedValue);
accessedValue = ognlValueStack.findValue("@com.opensymphony.xwork2.ognl.OgnlValueStackTest@STATIC_FINAL_PROTECTED_ATTRIBUTE");
accessedValue = vs.findValue("@com.opensymphony.xwork2.ognl.OgnlValueStackTest@STATIC_FINAL_PROTECTED_ATTRIBUTE");
assertNull("accessed final protected field (result not null) ?", accessedValue);
accessedValue = ognlValueStack.findValue("@com.opensymphony.xwork2.ognl.OgnlValueStackTest@STATIC_PROTECTED_ATTRIBUTE");
accessedValue = vs.findValue("@com.opensymphony.xwork2.ognl.OgnlValueStackTest@STATIC_PROTECTED_ATTRIBUTE");
assertNull("accessed protected field (result not null) ?", accessedValue);
accessedValue = ognlValueStack.findValue("@com.opensymphony.xwork2.ognl.OgnlValueStackTest@STATIC_FINAL_PRIVATE_ATTRIBUTE");
accessedValue = vs.findValue("@com.opensymphony.xwork2.ognl.OgnlValueStackTest@STATIC_FINAL_PRIVATE_ATTRIBUTE");
assertNull("accessed final private field (result not null) ?", accessedValue);
accessedValue = ognlValueStack.findValue("@com.opensymphony.xwork2.ognl.OgnlValueStackTest@STATIC_PRIVATE_ATTRIBUTE");
accessedValue = vs.findValue("@com.opensymphony.xwork2.ognl.OgnlValueStackTest@STATIC_PRIVATE_ATTRIBUTE");
assertNull("accessed private field (result not null) ?", accessedValue);
}
private void reloadTestContainerConfiguration(boolean allowStaticField) throws Exception {
loadConfigurationProviders(new StubConfigurationProvider() {
@Override
public void register(ContainerBuilder builder,
LocatableProperties props) throws ConfigurationException {
props.setProperty(StrutsConstants.STRUTS_ALLOW_STATIC_FIELD_ACCESS, String.valueOf(allowStaticField));
}
});
ognlUtil = container.getInstance(OgnlUtil.class);
}
static class BadJavaBean {
private int count;
private int count2;
@@ -18,12 +18,14 @@
*/
package org.apache.struts2;
import com.opensymphony.xwork2.ActionProxyFactory;
import com.opensymphony.xwork2.XWorkTestCase;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.struts2.dispatcher.Dispatcher;
import org.apache.struts2.dispatcher.PrepareOperations;
import org.apache.struts2.util.StrutsTestCaseHelper;
import org.apache.struts2.views.jsp.StrutsMockServletContext;
import java.util.HashMap;
import java.util.Map;
@@ -38,22 +40,22 @@ public abstract class StrutsInternalTestCase extends XWorkTestCase {
/**
* Sets up the configuration settings, XWork configuration, and
* message resources
*
*
* @throws java.lang.Exception
*/
@Override
protected void setUp() throws Exception {
super.setUp();
PrepareOperations.clearDevModeOverride(); // Clear DevMode override every time (consistent ThreadLocal state for tests).
initDispatcher(null);
}
protected Dispatcher initDispatcher(Map<String,String> params) {
servletContext = new StrutsMockServletContext();
dispatcher = StrutsTestCaseHelper.initDispatcher(servletContext, params);
configurationManager = dispatcher.getConfigurationManager();
configuration = configurationManager.getConfiguration();
container = configuration.getContainer();
actionProxyFactory = container.getInstance(ActionProxyFactory.class);
container.inject(dispatcher);
return dispatcher;
}
@@ -66,31 +68,25 @@ public abstract class StrutsInternalTestCase extends XWorkTestCase {
* @return instance of {@see Dispatcher}
*/
protected Dispatcher initDispatcherWithConfigs(String configs) {
Map<String, String> params = new HashMap<String, String>();
Map<String, String> params = new HashMap<>();
params.put("config", configs);
return initDispatcher(params);
}
@Override
protected void tearDown() throws Exception {
super.tearDown();
// maybe someone else already destroyed Dispatcher
if (dispatcher != null && dispatcher.getConfigurationManager() != null) {
dispatcher.cleanup();
dispatcher = null;
}
StrutsTestCaseHelper.tearDown();
StrutsTestCaseHelper.tearDown(dispatcher);
}
/**
* Compare if two objects are considered equal according to their fields as accessed
* Compare if two objects are considered equal according to their fields as accessed
* via reflection.
*
* Utilizes {@link EqualsBuilder#reflectionEquals(java.lang.Object, java.lang.Object, boolean)} to perform
*
* Utilizes {@link EqualsBuilder#reflectionEquals(java.lang.Object, java.lang.Object, boolean)} to perform
* the check, and compares transient fields as well. This may fail when run while a security manager is
* active, due to a need to user reflection.
*
*
*
*
* @param obj1 the first {@link Object} to compare against the other.
* @param obj2 the second {@link Object} to compare against the other.
* @return true if the objects are equal based on field comparisons by reflection, false otherwise.
@@ -0,0 +1,61 @@
/*
* 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;
import com.opensymphony.xwork2.ActionProxyFactory;
import com.opensymphony.xwork2.XWorkJUnit4TestCase;
import org.apache.struts2.dispatcher.Dispatcher;
import org.apache.struts2.util.StrutsTestCaseHelper;
import org.apache.struts2.views.jsp.StrutsMockServletContext;
import org.junit.After;
import org.junit.Before;
import java.util.Map;
public class StrutsJUnit4InternalTestCase extends XWorkJUnit4TestCase {
protected StrutsMockServletContext servletContext;
protected Dispatcher dispatcher;
@Override
@Before
public void setUp() throws Exception {
initDispatcher();
}
@Override
@After
public void tearDown() throws Exception {
StrutsTestCaseHelper.tearDown(dispatcher);
}
protected void initDispatcher() {
initDispatcher(null);
}
protected void initDispatcher(Map<String, String> params) {
StrutsTestCaseHelper.tearDown();
servletContext = new StrutsMockServletContext();
dispatcher = StrutsTestCaseHelper.initDispatcher(servletContext, params);
configurationManager = dispatcher.getConfigurationManager();
configuration = configurationManager.getConfiguration();
container = configuration.getContainer();
actionProxyFactory = container.getInstance(ActionProxyFactory.class);
}
}
@@ -53,6 +53,7 @@ public class TestAction extends ActionSupport {
private Long id;
private List<SomeEnum> enumList;
private List<Integer> intList;
private Boolean someBool;
private final Map<String, String> texts = new HashMap<>();
@@ -254,4 +255,12 @@ public class TestAction extends ActionSupport {
public void setIntList(List<Integer> intList) {
this.intList = intList;
}
public Boolean getSomeBool() {
return someBool;
}
public void setSomeBool(Boolean someBool) {
this.someBool = someBool;
}
}
@@ -36,7 +36,7 @@ public class SettingsTest extends StrutsInternalTestCase {
Settings settings = new DefaultSettings();
assertEquals("12345", settings.get(StrutsConstants.STRUTS_MULTIPART_MAXSIZE));
assertEquals("\temp", settings.get(StrutsConstants.STRUTS_MULTIPART_SAVEDIR));
assertEquals("\\temp", settings.get(StrutsConstants.STRUTS_MULTIPART_SAVEDIR));
assertEquals("test,org/apache/struts2/othertest", settings.get( StrutsConstants.STRUTS_CUSTOM_PROPERTIES));
assertEquals("testvalue", settings.get("testkey"));
@@ -18,27 +18,29 @@
*/
package org.apache.struts2.dispatcher;
import com.mockobjects.dynamic.C;
import com.mockobjects.dynamic.Mock;
import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.LocalizedTextProvider;
import com.opensymphony.xwork2.ObjectFactory;
import com.opensymphony.xwork2.StubValueStack;
import com.opensymphony.xwork2.config.Configuration;
import com.opensymphony.xwork2.config.ConfigurationException;
import com.opensymphony.xwork2.config.ConfigurationManager;
import com.opensymphony.xwork2.config.entities.InterceptorMapping;
import com.opensymphony.xwork2.config.entities.InterceptorStackConfig;
import com.opensymphony.xwork2.config.entities.PackageConfig;
import com.opensymphony.xwork2.inject.Container;
import com.opensymphony.xwork2.inject.ContainerBuilder;
import com.opensymphony.xwork2.interceptor.Interceptor;
import com.opensymphony.xwork2.mock.MockActionInvocation;
import com.opensymphony.xwork2.mock.MockActionProxy;
import com.opensymphony.xwork2.test.StubConfigurationProvider;
import com.opensymphony.xwork2.util.location.LocatableProperties;
import org.apache.struts2.ServletActionContext;
import org.apache.struts2.StrutsConstants;
import org.apache.struts2.StrutsInternalTestCase;
import org.apache.struts2.StrutsJUnit4InternalTestCase;
import org.apache.struts2.dispatcher.mapper.ActionMapping;
import org.apache.struts2.dispatcher.multipart.MultiPartRequestWrapper;
import org.apache.struts2.util.ObjectFactoryDestroyable;
import org.junit.Test;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.mock.web.MockHttpSession;
@@ -46,18 +48,36 @@ import org.springframework.mock.web.MockServletContext;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.util.Collections;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import static java.nio.charset.StandardCharsets.UTF_8;
import static java.util.Collections.emptyMap;
import static java.util.Collections.singletonMap;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
/**
* Test case for Dispatcher.
*/
public class DispatcherTest extends StrutsInternalTestCase {
public class DispatcherTest extends StrutsJUnit4InternalTestCase {
@Test
public void testDefaultResourceBundlePropertyLoaded() {
LocalizedTextProvider localizedTextProvider = container.getInstance(LocalizedTextProvider.class);
@@ -71,115 +91,107 @@ public class DispatcherTest extends StrutsInternalTestCase {
"Error uploading: some error messages");
}
@Test
public void testPrepareSetEncodingProperly() {
HttpServletRequest req = new MockHttpServletRequest();
HttpServletResponse res = new MockHttpServletResponse();
Dispatcher du = initDispatcher(new HashMap<String, String>() {{
put(StrutsConstants.STRUTS_I18N_ENCODING, "utf-8");
}});
du.prepare(req, res);
initDispatcher(singletonMap(StrutsConstants.STRUTS_I18N_ENCODING, UTF_8.name()));
dispatcher.prepare(req, res);
assertEquals(req.getCharacterEncoding(), "utf-8");
assertEquals(res.getCharacterEncoding(), "utf-8");
assertEquals(req.getCharacterEncoding(), UTF_8.name());
assertEquals(res.getCharacterEncoding(), UTF_8.name());
}
@Test
public void testEncodingForXMLHttpRequest() {
// given
MockHttpServletRequest req = new MockHttpServletRequest();
req.addHeader("X-Requested-With", "XMLHttpRequest");
req.setCharacterEncoding("UTF-8");
req.setCharacterEncoding(UTF_8.name());
HttpServletResponse res = new MockHttpServletResponse();
Dispatcher du = initDispatcher(new HashMap<String, String>() {{
put(StrutsConstants.STRUTS_I18N_ENCODING, "latin-2");
}});
initDispatcher(singletonMap(StrutsConstants.STRUTS_I18N_ENCODING, StandardCharsets.ISO_8859_1.name()));
// when
du.prepare(req, res);
dispatcher.prepare(req, res);
// then
assertEquals(req.getCharacterEncoding(), "UTF-8");
assertEquals(res.getCharacterEncoding(), "UTF-8");
assertEquals(req.getCharacterEncoding(), UTF_8.name());
assertEquals(res.getCharacterEncoding(), UTF_8.name());
}
@Test
public void testSetEncodingIfDiffer() {
// given
Mock mock = new Mock(HttpServletRequest.class);
mock.expectAndReturn("getCharacterEncoding", "utf-8");
mock.expectAndReturn("getHeader", "X-Requested-With", "");
mock.expectAndReturn("getCharacterEncoding", "utf-8");
HttpServletRequest req = (HttpServletRequest) mock.proxy();
HttpServletRequest req = mock(HttpServletRequest.class);
when(req.getCharacterEncoding()).thenReturn(UTF_8.name());
when(req.getHeader("X-Requested-With")).thenReturn("");
HttpServletResponse res = new MockHttpServletResponse();
Dispatcher du = initDispatcher(new HashMap<String, String>() {{
put(StrutsConstants.STRUTS_I18N_ENCODING, "utf-8");
}});
initDispatcher(singletonMap(StrutsConstants.STRUTS_I18N_ENCODING, UTF_8.name()));
// when
du.prepare(req, res);
dispatcher.prepare(req, res);
// then
assertEquals(req.getCharacterEncoding(), "utf-8");
assertEquals(res.getCharacterEncoding(), "utf-8");
mock.verify();
assertEquals(UTF_8.name(), req.getCharacterEncoding());
assertEquals(UTF_8.name(), res.getCharacterEncoding());
}
@Test
public void testPrepareSetEncodingPropertyWithMultipartRequest() {
MockHttpServletRequest req = new MockHttpServletRequest();
MockHttpServletResponse res = new MockHttpServletResponse();
req.setContentType("multipart/form-data");
Dispatcher du = initDispatcher(new HashMap<String, String>() {{
put(StrutsConstants.STRUTS_I18N_ENCODING, "utf-8");
}});
du.prepare(req, res);
initDispatcher(singletonMap(StrutsConstants.STRUTS_I18N_ENCODING, UTF_8.name()));
dispatcher.prepare(req, res);
assertEquals("utf-8", req.getCharacterEncoding());
assertEquals("utf-8", res.getCharacterEncoding());
assertEquals(UTF_8.name(), req.getCharacterEncoding());
assertEquals(UTF_8.name(), res.getCharacterEncoding());
}
@Test
public void testPrepareMultipartRequest() throws Exception {
MockHttpServletRequest req = new MockHttpServletRequest();
MockHttpServletResponse res = new MockHttpServletResponse();
req.setMethod("post");
req.setContentType("multipart/form-data; boundary=asdcvb345asd");
Dispatcher du = initDispatcher(Collections.emptyMap());
du.prepare(req, res);
HttpServletRequest wrapped = du.wrapRequest(req);
assertTrue(wrapped instanceof MultiPartRequestWrapper);
dispatcher.prepare(req, res);
assertTrue(dispatcher.wrapRequest(req) instanceof MultiPartRequestWrapper);
}
@Test
public void testPrepareMultipartRequestAllAllowedCharacters() throws Exception {
MockHttpServletRequest req = new MockHttpServletRequest();
MockHttpServletResponse res = new MockHttpServletResponse();
req.setMethod("post");
req.setContentType("multipart/form-data; boundary=01=23a.bC:D((e)d'z?p+o_r,e-");
Dispatcher du = initDispatcher(Collections.emptyMap());
du.prepare(req, res);
HttpServletRequest wrapped = du.wrapRequest(req);
assertTrue(wrapped instanceof MultiPartRequestWrapper);
dispatcher.prepare(req, res);
assertTrue(dispatcher.wrapRequest(req) instanceof MultiPartRequestWrapper);
}
@Test
public void testPrepareMultipartRequestIllegalCharacter() throws Exception {
MockHttpServletRequest req = new MockHttpServletRequest();
MockHttpServletResponse res = new MockHttpServletResponse();
req.setMethod("post");
req.setContentType("multipart/form-data; boundary=01=2;3a.bC:D((e)d'z?p+o_r,e-");
Dispatcher du = initDispatcher(Collections.emptyMap());
du.prepare(req, res);
HttpServletRequest wrapped = du.wrapRequest(req);
assertFalse(wrapped instanceof MultiPartRequestWrapper);
dispatcher.prepare(req, res);
assertFalse(dispatcher.wrapRequest(req) instanceof MultiPartRequestWrapper);
}
@Test
public void testDispatcherListener() {
final DispatcherListenerState state = new DispatcherListenerState();
@@ -198,39 +210,32 @@ public class DispatcherTest extends StrutsInternalTestCase {
assertFalse(state.isDestroyed);
assertFalse(state.isInitialized);
Dispatcher du = initDispatcher(new HashMap<>());
dispatcher.init();
assertTrue(state.isInitialized);
du.cleanup();
dispatcher.cleanup();
assertTrue(state.isDestroyed);
}
@Test
public void testConfigurationManager() {
Dispatcher du;
final InternalConfigurationManager configurationManager = new InternalConfigurationManager(Container.DEFAULT_NAME);
try {
du = new MockDispatcher(new MockServletContext(), new HashMap<>(), configurationManager);
du.init();
Dispatcher.setInstance(du);
configurationManager = spy(new ConfigurationManager(Container.DEFAULT_NAME));
dispatcher = spyDispatcherWithConfigurationManager(new Dispatcher(new MockServletContext(), emptyMap()), configurationManager);
assertFalse(configurationManager.destroyConfiguration);
dispatcher.init();
du.cleanup();
verify(configurationManager, never()).destroyConfiguration();
assertTrue(configurationManager.destroyConfiguration);
dispatcher.cleanup();
} finally {
Dispatcher.setInstance(null);
}
verify(configurationManager).destroyConfiguration();
}
@Test
public void testInitLoadsDefaultConfig() {
Dispatcher du = new Dispatcher(new MockServletContext(), new HashMap<>());
du.init();
Configuration config = du.getConfigurationManager().getConfiguration();
assertNotNull(config);
assertNotNull(configuration);
Set<String> expected = new HashSet<>();
expected.add("struts-default.xml");
expected.add("struts-beans.xml");
@@ -238,135 +243,109 @@ public class DispatcherTest extends StrutsInternalTestCase {
expected.add("struts-plugin.xml");
expected.add("struts.xml");
expected.add("struts-deferred.xml");
assertEquals(expected, config.getLoadedFileNames());
assertTrue(config.getPackageConfigs().size() > 0);
PackageConfig packageConfig = config.getPackageConfig("struts-default");
assertTrue(packageConfig.getInterceptorConfigs().size() > 0);
assertTrue(packageConfig.getResultTypeConfigs().size() > 0);
assertEquals(expected, configuration.getLoadedFileNames());
assertFalse(configuration.getPackageConfigs().isEmpty());
PackageConfig packageConfig = configuration.getPackageConfig("struts-default");
assertFalse(packageConfig.getInterceptorConfigs().isEmpty());
assertFalse(packageConfig.getResultTypeConfigs().isEmpty());
}
@Test
public void testObjectFactoryDestroy() {
InnerDestroyableObjectFactory destroyedObjectFactory = new InnerDestroyableObjectFactory();
dispatcher.setObjectFactory(destroyedObjectFactory);
ConfigurationManager cm = new ConfigurationManager(Container.DEFAULT_NAME);
Dispatcher du = new MockDispatcher(new MockServletContext(), new HashMap<>(), cm);
Mock mockConfiguration = new Mock(Configuration.class);
cm.setConfiguration((Configuration) mockConfiguration.proxy());
Mock mockContainer = new Mock(Container.class);
final InnerDestroyableObjectFactory destroyedObjectFactory = new InnerDestroyableObjectFactory();
destroyedObjectFactory.setContainer((Container) mockContainer.proxy());
mockContainer.expectAndReturn("getInstance", C.args(C.eq(ObjectFactory.class)), destroyedObjectFactory);
mockConfiguration.expectAndReturn("getContainer", mockContainer.proxy());
mockConfiguration.expect("destroy");
mockConfiguration.matchAndReturn("getPackageConfigs", new HashMap<String, PackageConfig>());
du.init();
assertFalse(destroyedObjectFactory.destroyed);
du.cleanup();
dispatcher.cleanup();
assertTrue(destroyedObjectFactory.destroyed);
mockConfiguration.verify();
mockContainer.verify();
}
@Test
public void testInterceptorDestroy() {
Mock mockInterceptor = new Mock(Interceptor.class);
mockInterceptor.matchAndReturn("hashCode", 0);
mockInterceptor.expect("destroy");
InterceptorMapping interceptorMapping = new InterceptorMapping("test", (Interceptor) mockInterceptor.proxy());
Interceptor mockedInterceptor = mock(Interceptor.class);
InterceptorMapping interceptorMapping = new InterceptorMapping("test", mockedInterceptor);
InterceptorStackConfig isc = new InterceptorStackConfig.Builder("test").addInterceptor(interceptorMapping).build();
PackageConfig packageConfig = new PackageConfig.Builder("test").addInterceptorStackConfig(isc).build();
Map<String, PackageConfig> packageConfigs = new HashMap<>();
packageConfigs.put("test", packageConfig);
configurationManager = spy(new ConfigurationManager(Container.DEFAULT_NAME));
dispatcher = spyDispatcherWithConfigurationManager(new Dispatcher(new MockServletContext(), emptyMap()), configurationManager);
Mock mockContainer = new Mock(Container.class);
mockContainer.matchAndReturn("getInstance", C.args(C.eq(ObjectFactory.class)), new ObjectFactory());
Mock mockConfiguration = new Mock(Configuration.class);
mockConfiguration.matchAndReturn("getPackageConfigs", packageConfigs);
mockConfiguration.matchAndReturn("getContainer", mockContainer.proxy());
mockConfiguration.expect("destroy");
ConfigurationManager configurationManager = new ConfigurationManager(Container.DEFAULT_NAME);
configurationManager.setConfiguration((Configuration) mockConfiguration.proxy());
Dispatcher dispatcher = new MockDispatcher(new MockServletContext(), new HashMap<>(), configurationManager);
dispatcher.init();
configuration = spy(configurationManager.getConfiguration());
configurationManager.setConfiguration(configuration);
when(configuration.getPackageConfigs()).thenReturn(singletonMap("test", packageConfig));
dispatcher.cleanup();
mockInterceptor.verify();
mockContainer.verify();
mockConfiguration.verify();
verify(mockedInterceptor).destroy();
verify(configuration).destroy();
}
@Test
public void testMultipartSupportEnabledByDefault() {
HttpServletRequest req = new MockHttpServletRequest();
HttpServletResponse res = new MockHttpServletResponse();
Dispatcher du = initDispatcher(Collections.emptyMap());
du.prepare(req, res);
dispatcher.prepare(req, res);
assertTrue(du.isMultipartSupportEnabled(req));
assertTrue(dispatcher.isMultipartSupportEnabled(req));
}
@Test
public void testIsMultipartRequest() {
MockHttpServletRequest req = new MockHttpServletRequest();
HttpServletResponse res = new MockHttpServletResponse();
req.setMethod("POST");
Dispatcher du = initDispatcher(Collections.emptyMap());
du.prepare(req, res);
dispatcher.prepare(req, res);
req.setContentType("multipart/form-data");
assertTrue(du.isMultipartRequest(req));
assertTrue(dispatcher.isMultipartRequest(req));
req.setContentType("multipart/form-data; boundary=---------------------------207103069210263");
assertTrue(du.isMultipartRequest(req));
assertTrue(dispatcher.isMultipartRequest(req));
req.setContentType("multipart/form-data; boundary=---------------------------207103069210263;charset=UTF-8");
assertTrue(du.isMultipartRequest(req));
assertTrue(dispatcher.isMultipartRequest(req));
req.setContentType("multipart/form-data; boundary=---------------------------207103069210263;charset=ISO-8859-1");
assertTrue(du.isMultipartRequest(req));
assertTrue(dispatcher.isMultipartRequest(req));
req.setContentType("multipart/form-data; boundary=---------------------------207103069210263;charset=Windows-1250");
assertTrue(du.isMultipartRequest(req));
assertTrue(dispatcher.isMultipartRequest(req));
req.setContentType("multipart/form-data; boundary=---------------------------207103069210263;charset=US-ASCII");
assertTrue(du.isMultipartRequest(req));
assertTrue(dispatcher.isMultipartRequest(req));
req.setContentType("multipart/form-data; boundary=---------------------------207103069210263;charset=UTF-16LE");
assertTrue(du.isMultipartRequest(req));
assertTrue(dispatcher.isMultipartRequest(req));
req.setContentType("multipart/form-data;boundary=---------------------------207103069210263;charset=UTF-16LE");
assertTrue(du.isMultipartRequest(req));
assertTrue(dispatcher.isMultipartRequest(req));
req.setContentType("multipart/form-data;boundary=---------------------------207103069210263; charset=UTF-16LE");
assertTrue(du.isMultipartRequest(req));
assertTrue(dispatcher.isMultipartRequest(req));
req.setContentType("multipart/form-data;boundary=---------------------------207103069210263 ;charset=UTF-16LE");
assertTrue(du.isMultipartRequest(req));
assertTrue(dispatcher.isMultipartRequest(req));
req.setContentType("multipart/form-data;boundary=---------------------------207103069210263 ; charset=UTF-16LE");
assertTrue(du.isMultipartRequest(req));
assertTrue(dispatcher.isMultipartRequest(req));
req.setContentType("multipart/form-data ;boundary=---------------------------207103069210263;charset=UTF-16LE");
assertTrue(du.isMultipartRequest(req));
assertTrue(dispatcher.isMultipartRequest(req));
req.setContentType("multipart/form-data ; boundary=---------------------------207103069210263;charset=UTF-16LE");
assertTrue(du.isMultipartRequest(req));
assertTrue(dispatcher.isMultipartRequest(req));
req.setContentType("Multipart/Form-Data ; boundary=---------------------------207103069210263;charset=UTF-16LE");
assertTrue(du.isMultipartRequest(req));
assertTrue(dispatcher.isMultipartRequest(req));
}
@Test
public void testServiceActionResumePreviousProxy() throws Exception {
Dispatcher du = initDispatcher(Collections.emptyMap());
MockActionInvocation mai = new MockActionInvocation();
ActionContext.getContext().withActionInvocation(mai);
@@ -381,17 +360,15 @@ public class DispatcherTest extends StrutsInternalTestCase {
assertFalse(actionProxy.isExecutedCalled());
du.setDevMode("false");
du.setHandleException("false");
du.serviceAction(req, null, new ActionMapping());
dispatcher.setDevMode("false");
dispatcher.setHandleException("false");
dispatcher.serviceAction(req, null, new ActionMapping());
assertTrue("should execute previous proxy", actionProxy.isExecutedCalled());
}
@Test
public void testServiceActionCreatesNewProxyIfDifferentMapping() throws Exception {
Dispatcher du = initDispatcher(Collections.emptyMap());
container.inject(du);
MockActionInvocation mai = new MockActionInvocation();
ActionContext.getContext().withActionInvocation(mai);
@@ -412,7 +389,7 @@ public class DispatcherTest extends StrutsInternalTestCase {
ActionMapping newActionMapping = new ActionMapping();
newActionMapping.setName("hello");
du.serviceAction(request, response, newActionMapping);
dispatcher.serviceAction(request, response, newActionMapping);
assertFalse(previousActionProxy.isExecutedCalled());
}
@@ -421,196 +398,201 @@ public class DispatcherTest extends StrutsInternalTestCase {
* Verify proper default (true) handleExceptionState for Dispatcher and that
* it properly reflects a manually configured change to false.
*/
@Test
public void testHandleException() {
Dispatcher du = initDispatcher(new HashMap<>());
assertTrue("Default Dispatcher handleException state not true ?", du.isHandleException());
assertTrue("Default Dispatcher handleException state not true ?", dispatcher.isHandleException());
Dispatcher du2 = initDispatcher(new HashMap<String, String>() {{
put(StrutsConstants.STRUTS_HANDLE_EXCEPTION, "false");
}});
assertFalse("Modified Dispatcher handleException state not false ?", du2.isHandleException());
initDispatcher(singletonMap(StrutsConstants.STRUTS_HANDLE_EXCEPTION, "false"));
assertFalse("Modified Dispatcher handleException state not false ?", dispatcher.isHandleException());
}
/**
* Verify proper default (false) devMode for Dispatcher and that
* it properly reflects a manually configured change to true.
*/
@Test
public void testDevMode() {
Dispatcher du = initDispatcher(new HashMap<>());
assertFalse("Default Dispatcher devMode state not false ?", du.isDevMode());
assertFalse("Default Dispatcher devMode state not false ?", dispatcher.isDevMode());
Dispatcher du2 = initDispatcher(new HashMap<String, String>() {{
put(StrutsConstants.STRUTS_DEVMODE, "true");
}});
assertTrue("Modified Dispatcher devMode state not true ?", du2.isDevMode());
initDispatcher(singletonMap(StrutsConstants.STRUTS_DEVMODE, "true"));
assertTrue("Modified Dispatcher devMode state not true ?", dispatcher.isDevMode());
}
@Test
public void testGetLocale_With_DefaultLocale_FromConfiguration() {
// Given
Mock mock = new Mock(HttpServletRequest.class);
HttpServletRequest request = mock(HttpServletRequest.class);
MockHttpSession mockHttpSession = new MockHttpSession();
mock.expectAndReturn("getCharacterEncoding", "utf-8"); // From Dispatcher prepare().
mock.expectAndReturn("getHeader", "X-Requested-With", ""); // From Dispatcher prepare().
mock.expectAndReturn("getParameterMap", new HashMap<String, Object>()); // From Dispatcher prepare().
mock.expectAndReturn("getSession", false, mockHttpSession); // From Dispatcher prepare().
mock.expectAndReturn("getSession", true, mockHttpSession); // From createTestContextMap().
HttpServletRequest request = (HttpServletRequest) mock.proxy();
when(request.getCharacterEncoding()).thenReturn(UTF_8.name());
when(request.getHeader("X-Requested-With")).thenReturn("");
when(request.getParameterMap()).thenReturn(emptyMap());
when(request.getSession(anyBoolean())).thenReturn(mockHttpSession);
HttpServletResponse response = new MockHttpServletResponse();
Dispatcher testDispatcher = initDispatcher(new HashMap<String, String>() {{
put(StrutsConstants.STRUTS_I18N_ENCODING, "utf-8");
// Not setting a Struts Locale here, so we should receive the default "de_DE" from the test configuration.
}});
// Not setting a Struts Locale here, so we should receive the default "de_DE" from the test configuration.
initDispatcher(singletonMap(StrutsConstants.STRUTS_I18N_ENCODING, UTF_8.name()));
// When
testDispatcher.prepare(request, response);
ActionContext context = ActionContext.of(createTestContextMap(testDispatcher, request, response));
dispatcher.prepare(request, response);
ActionContext context = ActionContext.of(createTestContextMap(dispatcher, request, response));
// Then
assertEquals(Locale.GERMANY, context.getLocale()); // Expect the Dispatcher defaultLocale value "de_DE" from the test configuration.
mock.verify();
}
@Test
public void testGetLocale_With_DefaultLocale_fr_CA() {
// Given
Mock mock = new Mock(HttpServletRequest.class);
HttpServletRequest request = mock(HttpServletRequest.class);
MockHttpSession mockHttpSession = new MockHttpSession();
mock.expectAndReturn("getCharacterEncoding", "utf-8"); // From Dispatcher prepare().
mock.expectAndReturn("getHeader", "X-Requested-With", ""); // From Dispatcher prepare().
mock.expectAndReturn("getParameterMap", new HashMap<String, Object>()); // From Dispatcher prepare().
mock.expectAndReturn("getSession", false, mockHttpSession); // From Dispatcher prepare().
mock.expectAndReturn("getSession", true, mockHttpSession); // From createTestContextMap().
HttpServletRequest request = (HttpServletRequest) mock.proxy();
when(request.getCharacterEncoding()).thenReturn(UTF_8.name());
when(request.getHeader("X-Requested-With")).thenReturn("");
when(request.getParameterMap()).thenReturn(emptyMap());
when(request.getSession(anyBoolean())).thenReturn(mockHttpSession);
HttpServletResponse response = new MockHttpServletResponse();
Dispatcher testDispatcher = initDispatcher(new HashMap<String, String>() {{
put(StrutsConstants.STRUTS_I18N_ENCODING, "utf-8");
initDispatcher(new HashMap<String, String>() {{
put(StrutsConstants.STRUTS_I18N_ENCODING, UTF_8.name());
put(StrutsConstants.STRUTS_LOCALE, Locale.CANADA_FRENCH.toString()); // Set the Dispatcher defaultLocale to fr_CA.
}});
// When
testDispatcher.prepare(request, response);
ActionContext context = ActionContext.of(createTestContextMap(testDispatcher, request, response));
dispatcher.prepare(request, response);
ActionContext context = ActionContext.of(createTestContextMap(dispatcher, request, response));
// Then
assertEquals(Locale.CANADA_FRENCH, context.getLocale()); // Expect the Dispatcher defaultLocale value.
mock.verify();
}
@Test
public void testGetLocale_With_BadDefaultLocale_RequestLocale_en_UK() {
// Given
Mock mock = new Mock(HttpServletRequest.class);
HttpServletRequest request = mock(HttpServletRequest.class);
MockHttpSession mockHttpSession = new MockHttpSession();
mock.expectAndReturn("getCharacterEncoding", "utf-8"); // From Dispatcher prepare().
mock.expectAndReturn("getHeader", "X-Requested-With", ""); // From Dispatcher prepare().
mock.expectAndReturn("getLocale", Locale.UK); // From Dispatcher prepare().
mock.expectAndReturn("getParameterMap", new HashMap<String, Object>()); // From Dispatcher prepare().
mock.expectAndReturn("getSession", false, mockHttpSession); // From Dispatcher prepare().
mock.expectAndReturn("getSession", true, mockHttpSession); // From createTestContextMap().
mock.expectAndReturn("getLocale", Locale.UK); // From createTestContextMap().
HttpServletRequest request = (HttpServletRequest) mock.proxy();
when(request.getCharacterEncoding()).thenReturn(UTF_8.name());
when(request.getHeader("X-Requested-With")).thenReturn("");
when(request.getParameterMap()).thenReturn(emptyMap());
when(request.getSession(anyBoolean())).thenReturn(mockHttpSession);
when(request.getLocale()).thenReturn(Locale.UK);
HttpServletResponse response = new MockHttpServletResponse();
Dispatcher testDispatcher = initDispatcher(new HashMap<String, String>() {{
put(StrutsConstants.STRUTS_I18N_ENCODING, "utf-8");
initDispatcher(new HashMap<String, String>() {{
put(StrutsConstants.STRUTS_I18N_ENCODING, UTF_8.name());
put(StrutsConstants.STRUTS_LOCALE, "This_is_not_a_valid_Locale_string"); // Set Dispatcher defaultLocale to an invalid value.
}});
// When
testDispatcher.prepare(request, response);
ActionContext context = ActionContext.of(createTestContextMap(testDispatcher, request, response));
dispatcher.prepare(request, response);
ActionContext context = ActionContext.of(createTestContextMap(dispatcher, request, response));
// Then
assertEquals(Locale.UK, context.getLocale()); // Expect the request set value from Mock.
mock.verify();
}
@Test
public void testGetLocale_With_BadDefaultLocale_And_RuntimeException() {
// Given
Mock mock = new Mock(HttpServletRequest.class);
HttpServletRequest request = mock(HttpServletRequest.class);
MockHttpSession mockHttpSession = new MockHttpSession();
mock.expectAndReturn("getCharacterEncoding", "utf-8"); // From Dispatcher prepare().
mock.expectAndReturn("getHeader", "X-Requested-With", ""); // From Dispatcher prepare().
mock.expectAndReturn("getLocale", Locale.UK); // From Dispatcher prepare().
mock.expectAndReturn("getParameterMap", new HashMap<String, Object>()); // From Dispatcher prepare().
mock.expectAndReturn("getSession", false, mockHttpSession); // From Dispatcher prepare().
mock.expectAndReturn("getSession", true, mockHttpSession); // From createTestContextMap().
mock.expectAndThrow("getLocale", new IllegalStateException("Test theoretical state preventing HTTP Request Locale access")); // From createTestContextMap().
HttpServletRequest request = (HttpServletRequest) mock.proxy();
when(request.getCharacterEncoding()).thenReturn(UTF_8.name());
when(request.getHeader("X-Requested-With")).thenReturn("");
when(request.getParameterMap()).thenReturn(emptyMap());
when(request.getSession(anyBoolean())).thenReturn(mockHttpSession);
when(request.getLocale()).thenReturn(Locale.UK);
HttpServletResponse response = new MockHttpServletResponse();
Dispatcher testDispatcher = initDispatcher(new HashMap<String, String>() {{
put(StrutsConstants.STRUTS_I18N_ENCODING, "utf-8");
initDispatcher(new HashMap<String, String>() {{
put(StrutsConstants.STRUTS_I18N_ENCODING, UTF_8.name());
put(StrutsConstants.STRUTS_LOCALE, "This_is_not_a_valid_Locale_string"); // Set the Dispatcher defaultLocale to an invalid value.
}});
// When
testDispatcher.prepare(request, response);
ActionContext context = ActionContext.of(createTestContextMap(testDispatcher, request, response));
dispatcher.prepare(request, response);
when(request.getLocale()).thenThrow(new IllegalStateException("Test theoretical state preventing HTTP Request Locale access"));
ActionContext context = ActionContext.of(createTestContextMap(dispatcher, request, response));
// Then
assertEquals(Locale.getDefault(), context.getLocale()); // Expect the system default value, when BOTH Dispatcher default Locale AND request access fail.
mock.verify();
}
@Test
public void testGetLocale_With_NullDefaultLocale() {
// Given
Mock mock = new Mock(HttpServletRequest.class);
HttpServletRequest request = mock(HttpServletRequest.class);
MockHttpSession mockHttpSession = new MockHttpSession();
mock.expectAndReturn("getCharacterEncoding", "utf-8"); // From Dispatcher prepare().
mock.expectAndReturn("getHeader", "X-Requested-With", ""); // From Dispatcher prepare().
mock.expectAndReturn("getLocale", Locale.CANADA_FRENCH); // From Dispatcher prepare().
mock.expectAndReturn("getParameterMap", new HashMap<String, Object>()); // From Dispatcher prepare().
mock.expectAndReturn("getSession", false, mockHttpSession); // From Dispatcher prepare().
mock.expectAndReturn("getSession", true, mockHttpSession); // From createTestContextMap().
mock.expectAndReturn("getLocale", Locale.CANADA_FRENCH); // From createTestContextMap().
HttpServletRequest request = (HttpServletRequest) mock.proxy();
when(request.getCharacterEncoding()).thenReturn(UTF_8.name());
when(request.getHeader("X-Requested-With")).thenReturn("");
when(request.getParameterMap()).thenReturn(emptyMap());
when(request.getSession(anyBoolean())).thenReturn(mockHttpSession);
when(request.getLocale()).thenReturn(Locale.CANADA_FRENCH);
HttpServletResponse response = new MockHttpServletResponse();
Dispatcher testDispatcher = initDispatcher(new HashMap<String, String>() {{
put(StrutsConstants.STRUTS_I18N_ENCODING, "utf-8");
// Attempting to set StrutsConstants.STRUTS_LOCALE to null here via parameters causes an NPE.
}});
// Attempting to set StrutsConstants.STRUTS_LOCALE to null here via parameters causes an NPE.
initDispatcher(singletonMap(StrutsConstants.STRUTS_I18N_ENCODING, UTF_8.name()));
testDispatcher.setDefaultLocale(null); // Force a null Struts default locale, otherwise we receive the default "de_DE" from the test configuration.
dispatcher.setDefaultLocale(null); // Force a null Struts default locale, otherwise we receive the default "de_DE" from the test configuration.
// When
testDispatcher.prepare(request, response);
ActionContext context = ActionContext.of(createTestContextMap(testDispatcher, request, response));
dispatcher.prepare(request, response);
ActionContext context = ActionContext.of(createTestContextMap(dispatcher, request, response));
// Then
assertEquals(Locale.CANADA_FRENCH, context.getLocale()); // Expect the request set value from Mock.
mock.verify();
}
@Test
public void testGetLocale_With_NullDefaultLocale_And_RuntimeException() {
// Given
Mock mock = new Mock(HttpServletRequest.class);
HttpServletRequest request = mock(HttpServletRequest.class);
MockHttpSession mockHttpSession = new MockHttpSession();
mock.expectAndReturn("getCharacterEncoding", "utf-8"); // From Dispatcher prepare().
mock.expectAndReturn("getHeader", "X-Requested-With", ""); // From Dispatcher prepare().
mock.expectAndReturn("getLocale", Locale.CANADA_FRENCH); // From Dispatcher prepare().
mock.expectAndReturn("getParameterMap", new HashMap<String, Object>()); // From Dispatcher prepare().
mock.expectAndReturn("getSession", false, mockHttpSession); // From Dispatcher prepare().
mock.expectAndReturn("getSession", true, mockHttpSession); // From createTestContextMap().
mock.expectAndThrow("getLocale", new IllegalStateException("Test some theoretical state preventing HTTP Request Locale access")); // From createTestContextMap().
HttpServletRequest request = (HttpServletRequest) mock.proxy();
when(request.getCharacterEncoding()).thenReturn(UTF_8.name());
when(request.getHeader("X-Requested-With")).thenReturn("");
when(request.getParameterMap()).thenReturn(emptyMap());
when(request.getSession(anyBoolean())).thenReturn(mockHttpSession);
when(request.getLocale()).thenReturn(Locale.CANADA_FRENCH);
HttpServletResponse response = new MockHttpServletResponse();
Dispatcher testDispatcher = initDispatcher(new HashMap<String, String>() {{
put(StrutsConstants.STRUTS_I18N_ENCODING, "utf-8");
// Attempting to set StrutsConstants.STRUTS_LOCALE to null via parameters causes an NPE.
}});
// Attempting to set StrutsConstants.STRUTS_LOCALE to null via parameters causes an NPE.
initDispatcher(singletonMap(StrutsConstants.STRUTS_I18N_ENCODING, UTF_8.name()));
testDispatcher.setDefaultLocale(null); // Force a null Struts default locale, otherwise we receive the default "de_DE" from the test configuration.
dispatcher.setDefaultLocale(null); // Force a null Struts default locale, otherwise we receive the default "de_DE" from the test configuration.
// When
testDispatcher.prepare(request, response);
ActionContext context = ActionContext.of(createTestContextMap(testDispatcher, request, response));
dispatcher.prepare(request, response);
when(request.getLocale()).thenThrow(new IllegalStateException("Test theoretical state preventing HTTP Request Locale access"));
ActionContext context = ActionContext.of(createTestContextMap(dispatcher, request, response));
// Then
assertEquals(Locale.getDefault(), context.getLocale()); // Expect the system default value when Mock request access fails.
mock.verify();
}
@Test
public void dispatcherReinjectedAfterReload() {
HttpServletRequest request = mock(HttpServletRequest.class);
HttpServletResponse response = mock(HttpServletResponse.class);
dispatcher.prepare(request, response);
assertEquals(Locale.GERMANY, dispatcher.getLocale(request));
configurationManager.addContainerProvider(new StubConfigurationProvider() {
@Override
public void register(ContainerBuilder builder,
LocatableProperties props) throws ConfigurationException {
props.setProperty(StrutsConstants.STRUTS_LOCALE, "fr_CA");
}
});
configurationManager.reload();
dispatcher.cleanUpRequest(request);
dispatcher.prepare(request, response);
assertEquals(Locale.CANADA_FRENCH, dispatcher.getLocale(request));
}
public static Dispatcher spyDispatcherWithConfigurationManager(Dispatcher dispatcher, ConfigurationManager configurationManager) {
Dispatcher spiedDispatcher = spy(dispatcher);
doReturn(configurationManager).when(spiedDispatcher).createConfigurationManager(any());
return spiedDispatcher;
}
/**
@@ -641,21 +623,6 @@ public class DispatcherTest extends StrutsInternalTestCase {
response);
}
static class InternalConfigurationManager extends ConfigurationManager {
public boolean destroyConfiguration = false;
public InternalConfigurationManager(String name) {
super(name);
}
@Override
public synchronized void destroyConfiguration() {
super.destroyConfiguration();
destroyConfiguration = true;
}
}
static class DispatcherListenerState {
public boolean isInitialized = false;
public boolean isDestroyed = false;
@@ -22,6 +22,7 @@ import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.mock.MockActionInvocation;
import org.apache.logging.log4j.util.Strings;
import org.apache.struts2.StrutsInternalTestCase;
import org.apache.struts2.TestAction;
import org.apache.struts2.action.CspSettingsAware;
import org.apache.struts2.dispatcher.SessionMap;
import org.apache.struts2.interceptor.csp.CspInterceptor;
@@ -45,7 +46,7 @@ public class CspInterceptorTest extends StrutsInternalTestCase {
public void test_whenRequestReceived_thenNonceIsSetInSession_andCspHeaderContainsIt() throws Exception {
String reportUri = "/barfoo";
String reporting = "false";
boolean reporting = false;
interceptor.setReportUri(reportUri);
interceptor.setEnforcingMode(reporting);
@@ -58,7 +59,7 @@ public class CspInterceptorTest extends StrutsInternalTestCase {
public void test_whenNonceAlreadySetInSession_andRequestReceived_thenNewNonceIsSet() throws Exception {
String reportUri = "https://www.google.com/";
String enforcingMode = "true";
boolean enforcingMode = true;
interceptor.setReportUri(reportUri);
interceptor.setEnforcingMode(enforcingMode);
session.setAttribute("nonce", "foo");
@@ -73,7 +74,7 @@ public class CspInterceptorTest extends StrutsInternalTestCase {
public void testEnforcingCspHeadersSet() throws Exception {
String reportUri = "/csp-reports";
String enforcingMode = "true";
boolean enforcingMode = true;
interceptor.setReportUri(reportUri);
interceptor.setEnforcingMode(enforcingMode);
session.setAttribute("nonce", "foo");
@@ -88,7 +89,7 @@ public class CspInterceptorTest extends StrutsInternalTestCase {
public void testReportingCspHeadersSet() throws Exception {
String reportUri = "/csp-reports";
String enforcingMode = "false";
boolean enforcingMode = false;
interceptor.setReportUri(reportUri);
interceptor.setEnforcingMode(enforcingMode);
session.setAttribute("nonce", "foo");
@@ -101,7 +102,7 @@ public class CspInterceptorTest extends StrutsInternalTestCase {
}
public void test_uriSetOnlyWhenSetIsCalled() throws Exception {
String enforcingMode = "false";
boolean enforcingMode = false;
interceptor.setEnforcingMode(enforcingMode);
interceptor.intercept(mai);
@@ -115,7 +116,7 @@ public class CspInterceptorTest extends StrutsInternalTestCase {
}
public void testCannotParseUri() {
String enforcingMode = "false";
boolean enforcingMode = false;
interceptor.setEnforcingMode(enforcingMode);
try {
@@ -127,7 +128,7 @@ public class CspInterceptorTest extends StrutsInternalTestCase {
}
public void testCannotParseRelativeUri() {
String enforcingMode = "false";
boolean enforcingMode = false;
interceptor.setEnforcingMode(enforcingMode);
try {
@@ -139,13 +140,41 @@ public class CspInterceptorTest extends StrutsInternalTestCase {
}
public void testCustomPreResultListener() throws Exception {
boolean enforcingMode = false;
mai.setAction(new CustomerCspAction("/report-uri"));
interceptor.setEnforcingMode("false");
interceptor.setEnforcingMode(enforcingMode);
interceptor.intercept(mai);
checkHeader("/report-uri", "false");
checkHeader("/report-uri", enforcingMode);
}
public void checkHeader(String reportUri, String enforcingMode) {
public void testPrependContext() throws Exception {
boolean enforcingMode = true;
mai.setAction(new TestAction());
request.setContextPath("/app");
interceptor.setEnforcingMode(enforcingMode);
interceptor.setReportUri("/report-uri");
interceptor.intercept(mai);
checkHeader("/app/report-uri", enforcingMode);
}
public void testNoPrependContext() throws Exception {
boolean enforcingMode = true;
mai.setAction(new TestAction());
request.setContextPath("/app");
interceptor.setEnforcingMode(enforcingMode);
interceptor.setReportUri("/report-uri");
interceptor.setPrependServletContext(false);
interceptor.intercept(mai);
checkHeader("/report-uri", enforcingMode);
}
public void checkHeader(String reportUri, boolean enforcingMode) {
String expectedCspHeader;
if (Strings.isEmpty(reportUri)) {
expectedCspHeader = String.format("%s '%s'; %s 'nonce-%s' '%s' %s %s; %s '%s'; ",
@@ -163,7 +192,7 @@ public class CspInterceptorTest extends StrutsInternalTestCase {
}
String header;
if (enforcingMode.equals("true")) {
if (enforcingMode) {
header = response.getHeader(CspSettings.CSP_ENFORCE_HEADER);
} else {
header = response.getHeader(CspSettings.CSP_REPORT_HEADER);
@@ -113,7 +113,7 @@ public class StrutsBackgroundProcessTest extends StrutsInternalTestCase {
executor.execute(bp);
}
Thread.sleep(500);
Thread.sleep(800);
for (BackgroundProcess bp : bps) {
assertTrue("Process is still active: " + bp, bp.isDone());
@@ -1052,7 +1052,7 @@ class ValidateAction implements ValidationAware {
}
public boolean hasActionMessages() {
return messages.size() > 0;
return !messages.isEmpty();
}
public boolean hasErrors() {
@@ -34,7 +34,6 @@ import org.apache.struts2.TestAction;
import org.apache.struts2.dispatcher.ApplicationMap;
import org.apache.struts2.dispatcher.Dispatcher;
import org.apache.struts2.dispatcher.HttpParameters;
import org.apache.struts2.dispatcher.MockDispatcher;
import org.apache.struts2.dispatcher.RequestMap;
import org.apache.struts2.dispatcher.SessionMap;
@@ -42,9 +41,10 @@ import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.jsp.JspWriter;
import java.io.File;
import java.io.StringWriter;
import java.util.HashMap;
import java.util.Map;
import static java.util.Collections.emptyMap;
/**
* Base class to extend for unit testing UI Tags.
*/
@@ -108,7 +108,7 @@ public abstract class AbstractTagTest extends StrutsInternalTestCase {
pageContext.setJspWriter(jspWriter);
mockContainer = new Mock(Container.class);
MockDispatcher du = new MockDispatcher(pageContext.getServletContext(), new HashMap<>(), configurationManager);
Dispatcher du = new Dispatcher(pageContext.getServletContext(), emptyMap());
du.init();
Dispatcher.setInstance(du);
session = new SessionMap(request);
@@ -276,6 +276,23 @@ public class AnchorTest extends AbstractUITagTest {
verifyResource("href-5.txt");
}
public void testSimpleDisabled() throws Exception {
createAction();
AnchorTag tag = createTag();
tag.setHref("a");
tag.setDisabled("true");
StrutsBodyContent body = new StrutsBodyContent(null);
body.print("should have disabled attribute");
tag.setBodyContent(body);
tag.doStartTag();
tag.doEndTag();
verifyResource("href-6.txt");
}
public void testInjectEscapeHtmlBodyFlag() throws Exception {
// given
initDispatcherWithConfigs("struts-default.xml, struts-escape-body.xml");
@@ -64,6 +64,37 @@ public class RadioTest extends AbstractUITagTest {
strutsBodyTagsAreReflectionEqual(tag, freshTag));
}
public void testMapWithBooleanAsKeyWithoutForceValue() throws Exception {
TestAction testAction = (TestAction) action;
Map<Boolean, String> map = new LinkedHashMap<>();
map.put(Boolean.TRUE, "male");
map.put(Boolean.FALSE, "female");
testAction.setMap(map);
testAction.setSomeBool(false);
RadioTag tag = new RadioTag();
tag.setPageContext(pageContext);
tag.setLabel("mylabel");
tag.setName("myname");
tag.setValue("someBool");
tag.setList("map");
tag.setTheme("simple");
tag.doStartTag();
tag.doEndTag();
verify(RadioTag.class.getResource("Radio-11.txt"));
// Basic sanity check of clearTagStateForTagPoolingServers() behaviour for Struts Tags after doEndTag().
RadioTag freshTag = new RadioTag();
freshTag.setPageContext(pageContext);
assertFalse("Tag state after doEndTag() under default tag clear state is equal to new Tag with pageContext/parent set. " +
"May indicate that clearTagStateForTagPoolingServers() calls are not working properly.",
strutsBodyTagsAreReflectionEqual(tag, freshTag));
}
public void testMapWithBooleanAsKey_clearTagStateSet() throws Exception {
TestAction testAction = (TestAction) action;
@@ -165,12 +196,13 @@ public class RadioTest extends AbstractUITagTest {
List<SomeEnum> enumList = new ArrayList<>(Arrays.asList(SomeEnum.values()));
testAction.setEnumList(enumList);
testAction.setStatus(SomeEnum.INIT);
RadioTag tag = new RadioTag();
tag.setTheme("simple");
tag.setPageContext(pageContext);
tag.setName("status");
tag.setValue("INIT");
tag.setValue("status");
tag.setList("enumList");
tag.doStartTag();
@@ -191,13 +223,14 @@ public class RadioTest extends AbstractUITagTest {
List<SomeEnum> enumList = new ArrayList<>(Arrays.asList(SomeEnum.values()));
testAction.setEnumList(enumList);
testAction.setStatus(SomeEnum.INIT);
RadioTag tag = new RadioTag();
tag.setPerformClearTagStateForTagPoolingServers(true); // Explicitly request tag state clearing.
tag.setTheme("simple");
tag.setPageContext(pageContext);
tag.setName("status");
tag.setValue("INIT");
tag.setValue("status");
tag.setList("enumList");
tag.doStartTag();
@@ -0,0 +1,4 @@
<input type="radio" name="myname" id="mynametrue" value="true"/>
<label for="mynametrue">male</label>
<input type="radio" name="myname" id="mynamefalse" checked="checked" value="false"/>
<label for="mynamefalse">female</label>
@@ -0,0 +1 @@
<a id="mylink" href="a" disabled="disabled">should have disabled attribute</a>
+1 -1
View File
@@ -24,7 +24,7 @@
struts.i18n.encoding=ISO-8859-1
struts.locale=de_DE
struts.multipart.saveDir=\temp
struts.multipart.saveDir=\\temp
struts.multipart.maxSize=12345
### Load custom property files (does not override struts.properties!)
@@ -203,9 +203,9 @@ public abstract class StrutsJUnit4TestCase<T> extends XWorkJUnit4TestCase {
* Sets up the configuration settings, XWork configuration, and
* message resources
*/
@Override
@Before
public void setUp() throws Exception {
super.setUp();
initServletMockObjects();
setupBeforeInitDispatcher();
initDispatcherParams();
@@ -239,14 +239,10 @@ public abstract class StrutsJUnit4TestCase<T> extends XWorkJUnit4TestCase {
return null;
}
@Override
@After
public void tearDown() throws Exception {
super.tearDown();
if (dispatcher != null && dispatcher.getConfigurationManager() != null) {
dispatcher.cleanup();
dispatcher = null;
}
StrutsTestCaseHelper.tearDown();
StrutsTestCaseHelper.tearDown(dispatcher);
}
}
@@ -169,8 +169,8 @@ public abstract class StrutsTestCase extends XWorkTestCase {
* Sets up the configuration settings, XWork configuration, and
* message resources
*/
@Override
protected void setUp() throws Exception {
super.setUp();
initServletMockObjects();
setupBeforeInitDispatcher();
dispatcher = initDispatcher(dispatcherInitParams);
@@ -200,14 +200,9 @@ public abstract class StrutsTestCase extends XWorkTestCase {
return du;
}
@Override
protected void tearDown() throws Exception {
super.tearDown();
// maybe someone else already destroyed Dispatcher
if (dispatcher != null && dispatcher.getConfigurationManager() != null) {
dispatcher.cleanup();
dispatcher = null;
}
StrutsTestCaseHelper.tearDown();
StrutsTestCaseHelper.tearDown(dispatcher);
}
}
@@ -1,199 +1,204 @@
/*
* 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.sitemesh;
import com.opensymphony.module.sitemesh.RequestConstants;
import com.opensymphony.sitemesh.Content;
import com.opensymphony.sitemesh.webapp.SiteMeshWebAppContext;
import com.opensymphony.sitemesh.webapp.decorator.BaseWebAppDecorator;
import com.opensymphony.xwork2.*;
import com.opensymphony.xwork2.interceptor.PreResultListener;
import com.opensymphony.xwork2.util.ValueStack;
import com.opensymphony.xwork2.util.ValueStackFactory;
import freemarker.template.Configuration;
import org.apache.struts2.ServletActionContext;
import org.apache.struts2.dispatcher.Dispatcher;
import jakarta.servlet.ServletContext;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Locale;
/**
* Adapts a SiteMesh 2 {@link com.opensymphony.module.sitemesh.Decorator} to a
* SiteMesh 3 {@link com.opensymphony.sitemesh.Decorator}.
*
* @since SiteMesh 3
*/
public abstract class OldDecorator2NewStrutsDecorator extends BaseWebAppDecorator implements RequestConstants {
protected com.opensymphony.module.sitemesh.Decorator oldDecorator;
private static String customEncoding;
public OldDecorator2NewStrutsDecorator(com.opensymphony.module.sitemesh.Decorator oldDecorator) {
this.oldDecorator = oldDecorator;
}
public OldDecorator2NewStrutsDecorator() {
oldDecorator = null;
}
/**
* Applies the decorator, using the relevent contexts
*
* @param content The content
* @param request The servlet request
* @param response The servlet response
* @param servletContext The servlet context
* @param ctx The action context for this request, populated with the server state
*/
protected abstract void render(Content content, HttpServletRequest request, HttpServletResponse response, ServletContext servletContext, ActionContext ctx) throws ServletException, IOException;
/**
* Applies the decorator, creating the relevent contexts and delegating to
* the extended applyDecorator().
*
* @param content The content
* @param request The servlet request
* @param response The servlet response
* @param servletContext The servlet context
* @param webAppContext The web app context
*/
protected void render(Content content, HttpServletRequest request, HttpServletResponse response, ServletContext servletContext, SiteMeshWebAppContext webAppContext) throws IOException, ServletException {
// see if the URI path (webapp) is set
if (oldDecorator.getURIPath() != null) {
// in a security conscious environment, the servlet container
// may return null for a given URL
if (servletContext.getContext(oldDecorator.getURIPath()) != null) {
servletContext = servletContext.getContext(oldDecorator.getURIPath());
}
}
ActionContext ctx = ServletActionContext.getActionContext(request);
if (ctx == null) {
// ok, one isn't associated with the request, so let's create one using the current Dispatcher
ValueStack vs = Dispatcher.getInstance().getContainer().getInstance(ValueStackFactory.class).createValueStack();
vs.getContext().putAll(Dispatcher.getInstance().createContextMap(request, response, null));
ctx = ActionContext.of(vs.getContext());
if (ctx.getActionInvocation() == null) {
// put in a dummy ActionSupport so basic functionality still works
ActionSupport action = new ActionSupport();
vs.push(action);
ctx.withActionInvocation(new DummyActionInvocation(action));
}
}
// delegate to the actual page decorator
render(content, request, response, servletContext, ctx);
}
/**
* Returns the locale used for the {@link freemarker.template.Configuration#getTemplate(String, java.util.Locale)} call. The base implementation
* simply returns the locale setting of the action (assuming the action implements {@link LocaleProvider}) or, if
* the action does not the configuration's locale is returned. Override this method to provide different behaviour,
*/
protected Locale getLocale(ActionInvocation invocation, Configuration configuration) {
if (invocation.getAction() instanceof LocaleProvider) {
return ((LocaleProvider) invocation.getAction()).getLocale();
} else {
return configuration.getLocale();
}
}
/**
* Gets the L18N encoding of the system. The default is UTF-8.
*/
protected String getEncoding() {
String encoding = customEncoding;
if (encoding == null) {
encoding = System.getProperty("file.encoding");
}
if (encoding == null) {
encoding = "UTF-8";
}
return encoding;
}
static class DummyActionInvocation implements ActionInvocation {
ActionSupport action;
public DummyActionInvocation(ActionSupport action) {
this.action = action;
}
public Object getAction() {
return action;
}
public boolean isExecuted() {
return false;
}
public ActionContext getInvocationContext() {
return null;
}
public ActionProxy getProxy() {
return null;
}
public Result getResult() throws Exception {
return null;
}
public String getResultCode() {
return null;
}
public void setResultCode(String resultCode) {
}
public ValueStack getStack() {
return null;
}
public void addPreResultListener(PreResultListener listener) {
}
public String invoke() throws Exception {
return null;
}
public String invokeActionOnly() throws Exception {
return null;
}
public void setActionEventListener(ActionEventListener listener) {
}
public void init(ActionProxy proxy) {
}
}
}
/*
* 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.sitemesh;
import com.opensymphony.module.sitemesh.RequestConstants;
import com.opensymphony.sitemesh.Content;
import com.opensymphony.sitemesh.webapp.SiteMeshWebAppContext;
import com.opensymphony.sitemesh.webapp.decorator.BaseWebAppDecorator;
import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.ActionEventListener;
import com.opensymphony.xwork2.ActionInvocation;
import com.opensymphony.xwork2.ActionProxy;
import com.opensymphony.xwork2.ActionSupport;
import com.opensymphony.xwork2.LocaleProvider;
import com.opensymphony.xwork2.Result;
import com.opensymphony.xwork2.interceptor.PreResultListener;
import com.opensymphony.xwork2.util.ValueStack;
import freemarker.template.Configuration;
import org.apache.struts2.ServletActionContext;
import org.apache.struts2.dispatcher.Dispatcher;
import jakarta.servlet.ServletContext;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Locale;
/**
* Adapts a SiteMesh 2 {@link com.opensymphony.module.sitemesh.Decorator} to a
* SiteMesh 3 {@link com.opensymphony.sitemesh.Decorator}.
*
* @since SiteMesh 3
*/
public abstract class OldDecorator2NewStrutsDecorator extends BaseWebAppDecorator implements RequestConstants {
protected com.opensymphony.module.sitemesh.Decorator oldDecorator;
private static String customEncoding;
public OldDecorator2NewStrutsDecorator(com.opensymphony.module.sitemesh.Decorator oldDecorator) {
this.oldDecorator = oldDecorator;
}
public OldDecorator2NewStrutsDecorator() {
oldDecorator = null;
}
/**
* Applies the decorator, using the relevent contexts
*
* @param content The content
* @param request The servlet request
* @param response The servlet response
* @param servletContext The servlet context
* @param ctx The action context for this request, populated with the server state
*/
protected abstract void render(Content content, HttpServletRequest request, HttpServletResponse response, ServletContext servletContext, ActionContext ctx) throws ServletException, IOException;
/**
* Applies the decorator, creating the relevent contexts and delegating to
* the extended applyDecorator().
*
* @param content The content
* @param request The servlet request
* @param response The servlet response
* @param servletContext The servlet context
* @param webAppContext The web app context
*/
protected void render(Content content, HttpServletRequest request, HttpServletResponse response, ServletContext servletContext, SiteMeshWebAppContext webAppContext) throws IOException, ServletException {
// see if the URI path (webapp) is set
if (oldDecorator.getURIPath() != null) {
// in a security conscious environment, the servlet container
// may return null for a given URL
if (servletContext.getContext(oldDecorator.getURIPath()) != null) {
servletContext = servletContext.getContext(oldDecorator.getURIPath());
}
}
ActionContext ctx = ServletActionContext.getActionContext(request);
if (ctx == null) {
// ok, one isn't associated with the request, so let's create one using the current Dispatcher
ValueStack vs = Dispatcher.getInstance().getValueStackFactory().createValueStack();
vs.getContext().putAll(Dispatcher.getInstance().createContextMap(request, response, null));
ctx = ActionContext.of(vs.getContext());
if (ctx.getActionInvocation() == null) {
// put in a dummy ActionSupport so basic functionality still works
ActionSupport action = new ActionSupport();
vs.push(action);
ctx.withActionInvocation(new DummyActionInvocation(action));
}
}
// delegate to the actual page decorator
render(content, request, response, servletContext, ctx);
}
/**
* Returns the locale used for the {@link freemarker.template.Configuration#getTemplate(String, java.util.Locale)} call. The base implementation
* simply returns the locale setting of the action (assuming the action implements {@link LocaleProvider}) or, if
* the action does not the configuration's locale is returned. Override this method to provide different behaviour,
*/
protected Locale getLocale(ActionInvocation invocation, Configuration configuration) {
if (invocation.getAction() instanceof LocaleProvider) {
return ((LocaleProvider) invocation.getAction()).getLocale();
} else {
return configuration.getLocale();
}
}
/**
* Gets the L18N encoding of the system. The default is UTF-8.
*/
protected String getEncoding() {
String encoding = customEncoding;
if (encoding == null) {
encoding = System.getProperty("file.encoding");
}
if (encoding == null) {
encoding = "UTF-8";
}
return encoding;
}
static class DummyActionInvocation implements ActionInvocation {
ActionSupport action;
public DummyActionInvocation(ActionSupport action) {
this.action = action;
}
public Object getAction() {
return action;
}
public boolean isExecuted() {
return false;
}
public ActionContext getInvocationContext() {
return null;
}
public ActionProxy getProxy() {
return null;
}
public Result getResult() throws Exception {
return null;
}
public String getResultCode() {
return null;
}
public void setResultCode(String resultCode) {
}
public ValueStack getStack() {
return null;
}
public void addPreResultListener(PreResultListener listener) {
}
public String invoke() throws Exception {
return null;
}
public String invokeActionOnly() throws Exception {
return null;
}
public void setActionEventListener(ActionEventListener listener) {
}
public void init(ActionProxy proxy) {
}
}
}
@@ -18,13 +18,13 @@
*/
package org.apache.struts2.testng;
import java.util.Map;
import org.apache.struts2.dispatcher.Dispatcher;
import org.apache.struts2.util.StrutsTestCaseHelper;
import org.springframework.mock.web.MockServletContext;
import org.testng.annotations.AfterTest;
import org.testng.annotations.BeforeTest;
import org.springframework.mock.web.MockServletContext;
import java.util.Map;
/**
* Base test class for TestNG unit tests. Provides common Struts variables
@@ -33,12 +33,12 @@ import org.springframework.mock.web.MockServletContext;
public class StrutsTestCase extends TestNGXWorkTestCase {
@BeforeTest
@Override
protected void setUp() throws Exception {
super.setUp();
initDispatcher(null);
}
protected Dispatcher initDispatcher(Map<String,String> params) {
protected Dispatcher initDispatcher(Map<String, String> params) {
Dispatcher du = StrutsTestCaseHelper.initDispatcher(new MockServletContext(), params);
configurationManager = du.getConfigurationManager();
configuration = configurationManager.getConfiguration();
@@ -56,8 +56,8 @@ public class StrutsTestCase extends TestNGXWorkTestCase {
}
@AfterTest
@Override
protected void tearDown() throws Exception {
super.tearDown();
StrutsTestCaseHelper.tearDown();
}
}
+4 -2
View File
@@ -107,6 +107,8 @@
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.build.outputTimestamp>2023-11-12T10:00:00Z</project.build.outputTimestamp>
<maven.compiler.release>17</maven.compiler.release>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<!-- dependency versions in alphanumeric order -->
<asm.version>9.6</asm.version>
@@ -379,7 +381,7 @@
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-release-plugin</artifactId>
<!-- See https://issues.apache.org/jira/browse/MRELEASE-1029 -->
<version>3.0.0-M1</version>
<version>3.0.1</version>
</plugin>
<plugin>
<artifactId>maven-jar-plugin</artifactId>
@@ -980,7 +982,7 @@
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-compress</artifactId>
<version>1.24.0</version>
<version>1.25.0</version>
</dependency>
<dependency>