diff --git a/core/src/main/java/org/apache/struts2/DefaultActionInvocation.java b/core/src/main/java/org/apache/struts2/DefaultActionInvocation.java index 6885dd50d1..98ee34c919 100644 --- a/core/src/main/java/org/apache/struts2/DefaultActionInvocation.java +++ b/core/src/main/java/org/apache/struts2/DefaultActionInvocation.java @@ -41,6 +41,7 @@ import java.util.ArrayList; import java.util.Iterator; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.concurrent.Callable; @@ -258,17 +259,11 @@ public String invoke() throws Exception { if (interceptors.hasNext()) { final InterceptorMapping interceptorMapping = interceptors.next(); Interceptor interceptor = interceptorMapping.getInterceptor(); - if (interceptor instanceof WithLazyParams) { - Map params = interceptorMapping.getParams(); - - proxy.getConfig().getInterceptors().stream() - .filter(im -> im.getName().equals(interceptorMapping.getName())) - .findFirst() - .ifPresent(im -> params.putAll(im.getParams())); - - interceptor = lazyParamInjector.injectParams(interceptor, params, invocationContext); - } - if (interceptor instanceof ConditionalInterceptor conditionalInterceptor) { + if (interceptor instanceof WithLazyParams.InvocationScoped invocationScopedInterceptor) { + resultCode = executeWithLazyParams(interceptorMapping, interceptor, invocationScopedInterceptor); + } else if (interceptor instanceof WithLazyParams) { + resultCode = executeWithLazyParams(interceptorMapping, interceptor); + } else if (interceptor instanceof ConditionalInterceptor conditionalInterceptor) { resultCode = executeConditional(conditionalInterceptor); } else { LOG.debug("Executing normal interceptor: {}", interceptorMapping.getName()); @@ -312,6 +307,56 @@ public String invoke() throws Exception { return resultCode; } + protected String executeWithLazyParams(InterceptorMapping interceptorMapping, + Interceptor interceptor) throws Exception { + lazyParamInjector.injectParams( + interceptor, + new LinkedHashMap<>(interceptorMapping.getParams()), + invocationContext + ); + + return executeLazyParamsInterceptor(interceptorMapping, interceptor); + } + + protected

String executeWithLazyParams(InterceptorMapping interceptorMapping, + Interceptor interceptor, + WithLazyParams.InvocationScoped

withLazyParamsInterceptor) throws Exception { + P lazyParams = lazyParamInjector.injectParams( + withLazyParamsInterceptor, + new LinkedHashMap<>(interceptorMapping.getParams()), + invocationContext + ); + + if (interceptor instanceof ConditionalInterceptor conditionalInterceptor) { + if (conditionalInterceptor.shouldIntercept(this)) { + LOG.debug("Executing conditional interceptor: {}", conditionalInterceptor.getClass().getSimpleName()); + return withLazyParamsInterceptor.intercept(this, lazyParams); + } else { + LOG.debug("Interceptor: {} is disabled, skipping to next", conditionalInterceptor.getClass().getSimpleName()); + return this.invoke(); + } + } + + LOG.debug("Executing normal interceptor: {}", interceptorMapping.getName()); + return withLazyParamsInterceptor.intercept(this, lazyParams); + } + + protected String executeLazyParamsInterceptor(InterceptorMapping interceptorMapping, + Interceptor interceptor) throws Exception { + if (interceptor instanceof ConditionalInterceptor conditionalInterceptor) { + if (conditionalInterceptor.shouldIntercept(this)) { + LOG.debug("Executing conditional interceptor: {}", conditionalInterceptor.getClass().getSimpleName()); + return interceptor.intercept(this); + } else { + LOG.debug("Interceptor: {} is disabled, skipping to next", conditionalInterceptor.getClass().getSimpleName()); + return this.invoke(); + } + } + + LOG.debug("Executing normal interceptor: {}", interceptorMapping.getName()); + return interceptor.intercept(this); + } + protected String executeConditional(ConditionalInterceptor conditionalInterceptor) throws Exception { if (conditionalInterceptor.shouldIntercept(this)) { LOG.debug("Executing conditional interceptor: {}", conditionalInterceptor.getClass().getSimpleName()); diff --git a/core/src/main/java/org/apache/struts2/factory/DefaultInterceptorFactory.java b/core/src/main/java/org/apache/struts2/factory/DefaultInterceptorFactory.java index cd5bc09bd2..9a279308ce 100644 --- a/core/src/main/java/org/apache/struts2/factory/DefaultInterceptorFactory.java +++ b/core/src/main/java/org/apache/struts2/factory/DefaultInterceptorFactory.java @@ -68,10 +68,16 @@ public Interceptor buildInterceptor(InterceptorConfig interceptorConfig, Map filterFactoryTimeParams(Map params) { + Map eagerParams = new HashMap<>(); + for (Map.Entry entry : params.entrySet()) { + if (!containsLazyParamExpression(entry.getValue())) { + eagerParams.put(entry.getKey(), entry.getValue()); + } + } + return eagerParams; + } + + private boolean containsLazyParamExpression(String value) { + return value != null && value.contains("${") && value.contains("}"); + } + } diff --git a/core/src/main/java/org/apache/struts2/interceptor/AbstractFileUploadInterceptor.java b/core/src/main/java/org/apache/struts2/interceptor/AbstractFileUploadInterceptor.java index c7cbb65c8d..23e699a6de 100644 --- a/core/src/main/java/org/apache/struts2/interceptor/AbstractFileUploadInterceptor.java +++ b/core/src/main/java/org/apache/struts2/interceptor/AbstractFileUploadInterceptor.java @@ -82,7 +82,7 @@ public void setContainer(Container container) { * @param allowedExtensions A comma-delimited list of extensions */ public void setAllowedExtensions(String allowedExtensions) { - allowedExtensionsSet = TextParseUtil.commaDelimitedStringToSet(allowedExtensions); + allowedExtensionsSet = parseCommaDelimitedSet(allowedExtensions); } /** @@ -91,7 +91,7 @@ public void setAllowedExtensions(String allowedExtensions) { * @param allowedTypes A comma-delimited list of types */ public void setAllowedTypes(String allowedTypes) { - allowedTypesSet = TextParseUtil.commaDelimitedStringToSet(allowedTypes); + allowedTypesSet = parseCommaDelimitedSet(allowedTypes); } /** @@ -114,6 +114,15 @@ public void setMaximumSize(Long maximumSize) { * @return true if the proposed file is acceptable by contentType and size. */ protected boolean acceptFile(Object action, UploadedFile file, String originalFilename, String contentType, String inputName) { + return acceptFile(action, file, originalFilename, contentType, inputName, createUploadValidationPolicy()); + } + + protected boolean acceptFile(Object action, + UploadedFile file, + String originalFilename, + String contentType, + String inputName, + UploadValidationPolicy validationPolicy) { Set errorMessages = new HashSet<>(); ValidationAware validation = null; @@ -131,21 +140,21 @@ protected boolean acceptFile(Object action, UploadedFile file, String originalFi return false; } - if (maximumSize != null && maximumSize < file.length()) { + if (validationPolicy.maximumSize != null && validationPolicy.maximumSize < file.length()) { String errMsg = getTextMessage(action, STRUTS_MESSAGES_ERROR_FILE_TOO_LARGE_KEY, new String[]{ - inputName, originalFilename, file.getName(), "" + file.length(), getMaximumSizeStr(action) + inputName, originalFilename, file.getName(), "" + file.length(), getMaximumSizeStr(action, validationPolicy.maximumSize) }); errorMessages.add(errMsg); LOG.warn(errMsg); } - if ((!allowedTypesSet.isEmpty()) && (!containsItem(allowedTypesSet, contentType))) { + if ((!validationPolicy.allowedTypesSet.isEmpty()) && (!containsItem(validationPolicy.allowedTypesSet, contentType))) { String errMsg = getTextMessage(action, STRUTS_MESSAGES_ERROR_CONTENT_TYPE_NOT_ALLOWED_KEY, new String[]{ inputName, originalFilename, file.getName(), contentType }); errorMessages.add(errMsg); LOG.warn(errMsg); } - if ((!allowedExtensionsSet.isEmpty()) && (!hasAllowedExtension(allowedExtensionsSet, originalFilename))) { + if ((!validationPolicy.allowedExtensionsSet.isEmpty()) && (!hasAllowedExtension(validationPolicy.allowedExtensionsSet, originalFilename))) { String errMsg = getTextMessage(action, STRUTS_MESSAGES_ERROR_FILE_EXTENSION_NOT_ALLOWED_KEY, new String[]{ inputName, originalFilename, file.getName(), contentType }); @@ -161,10 +170,21 @@ protected boolean acceptFile(Object action, UploadedFile file, String originalFi return errorMessages.isEmpty(); } - private String getMaximumSizeStr(Object action) { + private String getMaximumSizeStr(Object action, Long maximumSize) { return NumberFormat.getNumberInstance(getLocaleProvider(action).getLocale()).format(maximumSize); } + protected UploadValidationPolicy createUploadValidationPolicy() { + return new UploadValidationPolicy(maximumSize, allowedTypesSet, allowedExtensionsSet); + } + + private Set parseCommaDelimitedSet(String value) { + if (value == null || value.isBlank()) { + return Collections.emptySet(); + } + return TextParseUtil.commaDelimitedStringToSet(value); + } + /** * @param extensionCollection - Collection of extensions (all lowercase). * @param filename - filename to check. @@ -249,4 +269,32 @@ protected void applyValidation(Object action, MultiPartRequestWrapper multiWrapp } } + protected static class UploadValidationPolicy { + private Long maximumSize; + private Set allowedTypesSet = Collections.emptySet(); + private Set allowedExtensionsSet = Collections.emptySet(); + + private UploadValidationPolicy(Long maximumSize, Set allowedTypesSet, Set allowedExtensionsSet) { + this.maximumSize = maximumSize; + this.allowedTypesSet = allowedTypesSet; + this.allowedExtensionsSet = allowedExtensionsSet; + } + + public void setMaximumSize(Long maximumSize) { + this.maximumSize = maximumSize; + } + + public void setAllowedTypes(String allowedTypes) { + this.allowedTypesSet = allowedTypes == null || allowedTypes.isBlank() + ? Collections.emptySet() + : TextParseUtil.commaDelimitedStringToSet(allowedTypes); + } + + public void setAllowedExtensions(String allowedExtensions) { + this.allowedExtensionsSet = allowedExtensions == null || allowedExtensions.isBlank() + ? Collections.emptySet() + : TextParseUtil.commaDelimitedStringToSet(allowedExtensions); + } + } + } diff --git a/core/src/main/java/org/apache/struts2/interceptor/ActionFileUploadInterceptor.java b/core/src/main/java/org/apache/struts2/interceptor/ActionFileUploadInterceptor.java index 79d020a345..0c39621909 100644 --- a/core/src/main/java/org/apache/struts2/interceptor/ActionFileUploadInterceptor.java +++ b/core/src/main/java/org/apache/struts2/interceptor/ActionFileUploadInterceptor.java @@ -202,12 +202,23 @@ * @see UploadedFilesAware * @see AbstractFileUploadInterceptor */ -public class ActionFileUploadInterceptor extends AbstractFileUploadInterceptor implements WithLazyParams { +public class ActionFileUploadInterceptor extends AbstractFileUploadInterceptor + implements WithLazyParams.InvocationScoped { protected static final Logger LOG = LogManager.getLogger(ActionFileUploadInterceptor.class); @Override public String intercept(ActionInvocation invocation) throws Exception { + return WithLazyParams.InvocationScoped.super.intercept(invocation); + } + + @Override + public UploadValidationPolicy newLazyParams() { + return createUploadValidationPolicy(); + } + + @Override + public String intercept(ActionInvocation invocation, UploadValidationPolicy validationPolicy) throws Exception { HttpServletRequest request = invocation.getInvocationContext().getServletRequest(); MultiPartRequestWrapper multiWrapper = request instanceof HttpServletRequestWrapper wrapper ? findMultipartRequestWrapper(wrapper) @@ -245,7 +256,7 @@ public String intercept(ActionInvocation invocation) throws Exception { } } else { for (UploadedFile uploadedFile : uploadedFiles) { - if (acceptFile(action, uploadedFile, uploadedFile.getOriginalName(), uploadedFile.getContentType(), inputName)) { + if (acceptFile(action, uploadedFile, uploadedFile.getOriginalName(), uploadedFile.getContentType(), inputName, validationPolicy)) { acceptedFiles.add(uploadedFile); } } @@ -285,4 +296,3 @@ protected MultiPartRequestWrapper findMultipartRequestWrapper(HttpServletRequest } } - diff --git a/core/src/main/java/org/apache/struts2/interceptor/WithLazyParams.java b/core/src/main/java/org/apache/struts2/interceptor/WithLazyParams.java index cdfb3a8adb..13b8d4ddd6 100644 --- a/core/src/main/java/org/apache/struts2/interceptor/WithLazyParams.java +++ b/core/src/main/java/org/apache/struts2/interceptor/WithLazyParams.java @@ -18,20 +18,19 @@ */ package org.apache.struts2.interceptor; +import org.apache.struts2.ActionInvocation; import org.apache.struts2.ActionContext; import org.apache.struts2.inject.Inject; import org.apache.struts2.ognl.OgnlUtil; +import org.apache.struts2.ognl.ThreadAllowlist; import org.apache.struts2.util.TextParseUtil; import org.apache.struts2.util.TextParser; import org.apache.struts2.util.ValueStack; -import org.apache.struts2.util.reflection.ReflectionProvider; import java.util.Map; /** * Interceptors marked with this interface support dynamic parameter evaluation at action invocation time. - * Parameters are set during interceptor creation (factory time), then re-evaluated during each action - * invocation to resolve expressions like ${someValue}. *

* This enables both: *