VYPR
Critical severityNVD Advisory· Published Dec 11, 2024· Updated Jan 3, 2025

Apache Struts: Mixing setters for uploaded files and normal fields can allow bypass file upload checks

CVE-2024-53677

Description

File upload logic in Apache Struts is flawed. An attacker can manipulate file upload params to enable paths traversal and under some circumstances this can lead to uploading a malicious file which can be used to perform Remote Code Execution.

This issue affects Apache Struts: from 2.0.0 before 6.4.0.

Users are recommended to upgrade to version 6.4.0 at least and migrate to the new file upload mechanism https://struts.apache.org/core-developers/file-upload . If you are not using an old file upload logic based on FileuploadInterceptor your application is safe.

You can find more details in  https://cwiki.apache.org/confluence/display/WW/S2-067

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

Apache Struts 2 before 6.4.0 has a file upload path traversal flaw that allows remote code execution via a malicious file upload.

Vulnerability

Overview The vulnerability lies in the file upload logic of Apache Struts 2, specifically in the FileUploadInterceptor class [1][2]. An attacker can manipulate file upload parameters to perform path traversal, bypassing intended directory restrictions and placing a malicious file (e.g., a web shell) into a web-accessible directory [2][3]. This is possible because the framework does not properly sanitize or validate the file path components derived from the upload request [3].

Exploitation

Mechanism To exploit this, an attacker must be able to send a crafted multipart request to a Struts 2 application that uses the legacy file upload interceptor [2]. The attacker does not need prior authentication if the upload endpoint is publicly accessible. By tampering with parameters that control the destination path, the attacker can write an arbitrary file (such as a JSP file) to a location from which it can be accessed and executed by the web server [2]. This step does not require any additional privileges beyond the ability to send the HTTP request.

Impact

Successful exploitation allows the attacker to achieve remote code execution (RCE) on the server [2][3]. By uploading a web shell and accessing it via a browser, the attacker can execute arbitrary system commands, potentially leading to full compromise of the server and any connected systems. The impact is severe, as it can result in data theft, service disruption, or further lateral movement within the network.

Mitigation

Status Apache has released Struts 6.4.0, which deprecates the vulnerable FileUploadInterceptor and introduces a new, secure ActionFileUploadInterceptor [1][2]. Users are strongly advised to upgrade to at least 6.4.0 and migrate their applications away from the old interceptor to the new mechanism [2]. Complete removal of the legacy code is scheduled for Struts 7.0.0 [2]. Users still relying on the old interceptor remain vulnerable, even after upgrading the framework version [2]. No workaround is provided other than migration.

AI Insight generated on May 20, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
org.apache.struts:struts2-coreMaven
< 6.4.06.4.0

Affected products

3

Patches

3
930fef7679d7

Merge pull request #1180 from henrikplate/fix-CVE-2024-53677

https://github.com/apache/strutsLukasz LenartJan 7, 2025via ghsa
1 file changed · +12 9
  • core/src/main/java/org/apache/struts2/interceptor/FileUploadInterceptor.java+12 9 modified
    @@ -306,9 +306,9 @@ public String intercept(ActionInvocation invocation) throws Exception {
                             if (!acceptedFiles.isEmpty()) {
                                 Map<String, Object> params = ac.getParameters();
     
    -                            putCaseInsensitive(params, inputName, acceptedFiles.toArray(new File[acceptedFiles.size()]));
    -                            putCaseInsensitive(params, contentTypeName, acceptedContentTypes.toArray(new String[acceptedContentTypes.size()]));
    -                            putCaseInsensitive(params, fileNameName, acceptedFileNames.toArray(new String[acceptedFileNames.size()]));
    +                            params.put(inputName, acceptedFiles.toArray(new File[acceptedFiles.size()]));
    +                            putWithCheck(params, contentTypeName, acceptedContentTypes.toArray(new String[acceptedContentTypes.size()]));
    +                            putWithCheck(params, fileNameName, acceptedFileNames.toArray(new String[acceptedFileNames.size()]));
                             }
                         }
                     } else {
    @@ -328,19 +328,22 @@ public String intercept(ActionInvocation invocation) throws Exception {
         }
     
         /**
    -     * Removes existing parameters with identical name (case-insensitive) before adding the given parameter.
    -     * Minimal invasiv solution to fix CVE-2023-50164 for the 2.3.x branch.
    +     * Removes existing parameters that contain the given name (case-insensitive) before adding the parameter.
    +     * Minimal invasiv solution to fix CVE-2023-50164 and CVE-2024-53677 for the 2.3.x branch.
    +     * For example, malicious parameters "uploadFileName" (CVE-2023-50164) or "top.uploadFileName" (CVE-2024-53677)
    +     * would be removed from the map before adding the legitimate parameter "UploadFileName".
          * 
          * @param params - parameters to update.
    -     * @param key    - parameter name (compared case-insensitive with existing ones).
    +     * @param key    - parameter name (compared with existing ones).
          * @param value  - parameter value.
          */
    -    private void putCaseInsensitive(Map<String, Object> params, String key, Object value) {
    -        // Remove existing map entry if its key is equal to the given key (case-insensitive)
    +    private void putWithCheck(Map<String, Object> params, String key, Object value) {
    +        // Remove existing map entry if it contains the given key (a check for case- 
    +        // insensitive equality does not catch all OGNL expressions, cf. CVE-2024-53677).
             Iterator<Map.Entry<String, Object>> iterator = params.entrySet().iterator();
             while (iterator.hasNext()) {
                 Map.Entry<String, Object> entry = iterator.next();
    -            if (key.equalsIgnoreCase(entry.getKey())) {
    +            if (entry.getKey().toLowerCase().contains(key.toLowerCase())) {
                     iterator.remove();
                 }
             }
    
1ecfbae46543

WW-5386 Delete deprecated FileUploadInterceptor

https://github.com/apache/strutsKusal Kithul-GodageJul 26, 2024via ghsa
5 files changed · +2 943
  • core/src/main/java/org/apache/struts2/interceptor/FileUploadInterceptor.java+0 273 removed
    @@ -1,273 +0,0 @@
    -/*
    - * Licensed to the Apache Software Foundation (ASF) under one
    - * or more contributor license agreements.  See the NOTICE file
    - * distributed with this work for additional information
    - * regarding copyright ownership.  The ASF licenses this file
    - * to you under the Apache License, Version 2.0 (the
    - * "License"); you may not use this file except in compliance
    - * with the License.  You may obtain a copy of the License at
    - *
    - *  http://www.apache.org/licenses/LICENSE-2.0
    - *
    - * Unless required by applicable law or agreed to in writing,
    - * software distributed under the License is distributed on an
    - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
    - * KIND, either express or implied.  See the License for the
    - * specific language governing permissions and limitations
    - * under the License.
    - */
    -package org.apache.struts2.interceptor;
    -
    -import com.opensymphony.xwork2.ActionContext;
    -import com.opensymphony.xwork2.ActionInvocation;
    -import com.opensymphony.xwork2.ActionProxy;
    -import com.opensymphony.xwork2.interceptor.ValidationAware;
    -import jakarta.servlet.http.HttpServletRequest;
    -import org.apache.logging.log4j.LogManager;
    -import org.apache.logging.log4j.Logger;
    -import org.apache.struts2.action.UploadedFilesAware;
    -import org.apache.struts2.dispatcher.Parameter;
    -import org.apache.struts2.dispatcher.multipart.MultiPartRequestWrapper;
    -import org.apache.struts2.dispatcher.multipart.UploadedFile;
    -
    -import java.util.ArrayList;
    -import java.util.Enumeration;
    -import java.util.HashMap;
    -import java.util.List;
    -import java.util.Map;
    -
    -/**
    - * <!-- START SNIPPET: description -->
    - * <p>
    - * Interceptor that is based off of {@link MultiPartRequestWrapper}, which is automatically applied for any request that
    - * includes a file. It adds the following parameters, where [File Name] is the name given to the file uploaded by the
    - * HTML form:
    - * </p>
    - * <ul>
    - *
    - * <li>[File Name] : File - the actual File</li>
    - *
    - * <li>[File Name]ContentType : String - the content type of the file</li>
    - *
    - * <li>[File Name]FileName : String - the actual name of the file uploaded (not the HTML name)</li>
    - *
    - * </ul>
    - *
    - * <p>You can get access to these files by merely providing setters in your action that correspond to any of the three
    - * patterns above, such as setDocument(File document), setDocumentContentType(String contentType), etc.
    - * <br>See the example code section.
    - * </p>
    - *
    - * <p> This interceptor will add several field errors, assuming that the action implements {@link ValidationAware}.
    - * These error messages are based on several i18n values stored in struts-messages.properties, a default i18n file
    - * processed for all i18n requests. You can override the text of these messages by providing text for the following
    - * keys:
    - * </p>
    - *
    - * <ul>
    - *
    - * <li>struts.messages.error.uploading - a general error that occurs when the file could not be uploaded</li>
    - *
    - * <li>struts.messages.error.file.too.large - occurs when the uploaded file is too large</li>
    - *
    - * <li>struts.messages.error.content.type.not.allowed - occurs when the uploaded file does not match the expected
    - * content types specified</li>
    - *
    - * <li>struts.messages.error.file.extension.not.allowed - occurs when the uploaded file does not match the expected
    - * file extensions specified</li>
    - *
    - * </ul>
    - * <p>
    - * <!-- END SNIPPET: description -->
    - *
    - * <p><u>Interceptor parameters:</u></p>
    - * <p>
    - * <!-- START SNIPPET: parameters -->
    - *
    - * <ul>
    - *
    - * <li>maximumSize (optional) - the maximum size (in bytes) that the interceptor will allow a file reference to be set
    - * on the action. Note, this is <b>not</b> related to the various properties found in struts.properties.
    - * Default to approximately 2MB.</li>
    - *
    - * <li>allowedTypes (optional) - a comma separated list of content types (ie: text/html) that the interceptor will allow
    - * a file reference to be set on the action. If none is specified allow all types to be uploaded.</li>
    - *
    - * <li>allowedExtensions (optional) - a comma separated list of file extensions (ie: .html) that the interceptor will allow
    - * a file reference to be set on the action. If none is specified allow all extensions to be uploaded.</li>
    - * </ul>
    - * <p>
    - * <p>
    - * <!-- END SNIPPET: parameters -->
    - *
    - * <p><u>Extending the interceptor:</u></p>
    - * <p>
    - * <p>
    - * <p>
    - * <!-- START SNIPPET: extending -->
    - * <p>
    - * You can extend this interceptor and override the acceptFile method to provide more control over which files
    - * are supported and which are not.
    - * </p>
    - * <!-- END SNIPPET: extending -->
    - *
    - * <p><u>Example code:</u></p>
    - *
    - * <pre>
    - * <!-- START SNIPPET: example-configuration -->
    - * &lt;action name="doUpload" class="com.example.UploadAction"&gt;
    - *     &lt;interceptor-ref name="fileUpload"/&gt;
    - *     &lt;interceptor-ref name="basicStack"/&gt;
    - *     &lt;result name="success"&gt;good_result.jsp&lt;/result&gt;
    - * &lt;/action&gt;
    - * <!-- END SNIPPET: example-configuration -->
    - * </pre>
    - * <p>
    - * <!-- START SNIPPET: multipart-note -->
    - * <p>
    - * You must set the encoding to <code>multipart/form-data</code> in the form where the user selects the file to upload.
    - * </p>
    - * <!-- END SNIPPET: multipart-note -->
    - *
    - * <pre>
    - * <!-- START SNIPPET: example-form -->
    - *   &lt;s:form action="doUpload" method="post" enctype="multipart/form-data"&gt;
    - *       &lt;s:file name="upload" label="File"/&gt;
    - *       &lt;s:submit/&gt;
    - *   &lt;/s:form&gt;
    - * <!-- END SNIPPET: example-form -->
    - * </pre>
    - * <p>
    - * And then in your action code you'll have access to the File object if you provide setters according to the
    - * naming convention documented in the start.
    - * </p>
    - *
    - * <pre>
    - * <!-- START SNIPPET: example-action -->
    - *    package com.example;
    - *
    - *    import java.io.File;
    - *    import com.opensymphony.xwork2.ActionSupport;
    - *
    - *    public UploadAction extends ActionSupport {
    - *       private File file;
    - *       private String contentType;
    - *       private String filename;
    - *
    - *       public void setUpload(File file) {
    - *          this.file = file;
    - *       }
    - *
    - *       public void setUploadContentType(String contentType) {
    - *          this.contentType = contentType;
    - *       }
    - *
    - *       public void setUploadFileName(String filename) {
    - *          this.filename = filename;
    - *       }
    - *
    - *       public String execute() {
    - *          //...
    - *          return SUCCESS;
    - *       }
    - *  }
    - * <!-- END SNIPPET: example-action -->
    - * </pre>
    - *
    - * @deprecated since Struts 6.4.0, use {@link ActionFileUploadInterceptor} instead
    - */
    -@Deprecated
    -public class FileUploadInterceptor extends AbstractFileUploadInterceptor {
    -
    -    private static final long serialVersionUID = -4764627478894962478L;
    -
    -    protected static final Logger LOG = LogManager.getLogger(FileUploadInterceptor.class);
    -
    -    /* (non-Javadoc)
    -     * @see com.opensymphony.xwork2.interceptor.Interceptor#intercept(com.opensymphony.xwork2.ActionInvocation)
    -     */
    -
    -    public String intercept(ActionInvocation invocation) throws Exception {
    -        ActionContext ac = invocation.getInvocationContext();
    -
    -        HttpServletRequest request = ac.getServletRequest();
    -
    -        if (!(request instanceof MultiPartRequestWrapper multiWrapper)) {
    -            if (LOG.isDebugEnabled()) {
    -                ActionProxy proxy = invocation.getProxy();
    -                LOG.debug(getTextMessage(STRUTS_MESSAGES_BYPASS_REQUEST_KEY,
    -                        new String[]{proxy.getNamespace(), proxy.getActionName()})
    -                );
    -            }
    -
    -            return invocation.invoke();
    -        }
    -
    -        Object action = invocation.getAction();
    -        if (action instanceof UploadedFilesAware) {
    -            LOG.debug("Ignoring action: {} implementing: {} as it will be handled by: {}",
    -                    invocation.getProxy().getActionName(),
    -                    UploadedFilesAware.class.getSimpleName(),
    -                    ActionFileUploadInterceptor.class.getSimpleName()
    -            );
    -            return invocation.invoke();
    -        }
    -
    -        applyValidation(action, multiWrapper);
    -
    -        // bind allowed Files
    -        Enumeration<String> fileParameterNames = multiWrapper.getFileParameterNames();
    -        while (fileParameterNames != null && fileParameterNames.hasMoreElements()) {
    -            // get the value of this input tag
    -            String inputName = fileParameterNames.nextElement();
    -
    -            // get the content type
    -            String[] contentType = multiWrapper.getContentTypes(inputName);
    -
    -            if (isNonEmpty(contentType)) {
    -                // get the name of the file from the input tag
    -                String[] fileName = multiWrapper.getFileNames(inputName);
    -
    -                if (isNonEmpty(fileName)) {
    -                    // get a File object for the uploaded File
    -                    UploadedFile[] files = multiWrapper.getFiles(inputName);
    -                    if (files != null && files.length > 0) {
    -                        List<UploadedFile> acceptedFiles = new ArrayList<>(files.length);
    -                        List<String> acceptedContentTypes = new ArrayList<>(files.length);
    -                        List<String> acceptedFileNames = new ArrayList<>(files.length);
    -                        String contentTypeName = inputName + "ContentType";
    -                        String fileNameName = inputName + "FileName";
    -
    -                        for (int index = 0; index < files.length; index++) {
    -                            if (acceptFile(action, files[index], fileName[index], contentType[index], inputName)) {
    -                                acceptedFiles.add(files[index]);
    -                                acceptedContentTypes.add(contentType[index]);
    -                                acceptedFileNames.add(fileName[index]);
    -                            }
    -                        }
    -
    -                        if (!acceptedFiles.isEmpty()) {
    -                            Map<String, Parameter> newParams = new HashMap<>();
    -                            newParams.put(inputName, new Parameter.File(inputName, acceptedFiles.toArray(new UploadedFile[0])));
    -                            newParams.put(contentTypeName, new Parameter.File(contentTypeName, acceptedContentTypes.toArray(new String[0])));
    -                            newParams.put(fileNameName, new Parameter.File(fileNameName, acceptedFileNames.toArray(new String[0])));
    -                            ac.getParameters().appendAll(newParams);
    -                        }
    -                    }
    -                } else {
    -                    if (LOG.isWarnEnabled()) {
    -                        LOG.warn(getTextMessage(action, STRUTS_MESSAGES_INVALID_FILE_KEY, new String[]{inputName}));
    -                    }
    -                }
    -            } else {
    -                if (LOG.isWarnEnabled()) {
    -                    LOG.warn(getTextMessage(action, STRUTS_MESSAGES_INVALID_CONTENT_TYPE_KEY, new String[]{inputName}));
    -                }
    -            }
    -        }
    -
    -        // invoke action
    -        return invocation.invoke();
    -    }
    -
    -}
    
  • core/src/main/resources/struts-default.xml+0 9 modified
    @@ -57,7 +57,6 @@
                 <interceptor name="debugging" class="org.apache.struts2.interceptor.debugging.DebuggingInterceptor"/>
                 <interceptor name="execAndWait" class="org.apache.struts2.interceptor.ExecuteAndWaitInterceptor"/>
                 <interceptor name="exception" class="com.opensymphony.xwork2.interceptor.ExceptionMappingInterceptor"/>
    -            <interceptor name="fileUpload" class="org.apache.struts2.interceptor.FileUploadInterceptor"/>
                 <interceptor name="actionFileUpload" class="org.apache.struts2.interceptor.ActionFileUploadInterceptor"/>
                 <interceptor name="i18n" class="org.apache.struts2.interceptor.I18nInterceptor"/>
                 <interceptor name="logger" class="com.opensymphony.xwork2.interceptor.LoggingInterceptor"/>
    @@ -114,12 +113,6 @@
                     <interceptor-ref name="workflow"/>
                 </interceptor-stack>
     
    -            <!-- Sample file upload stack -->
    -            <interceptor-stack name="fileUploadStack">
    -                <interceptor-ref name="fileUpload"/>
    -                <interceptor-ref name="basicStack"/>
    -            </interceptor-stack>
    -
                 <!-- Action based file upload stack -->
                 <interceptor-stack name="actionFileUploadStack">
                     <interceptor-ref name="actionFileUpload"/>
    @@ -169,7 +162,6 @@
                     <interceptor-ref name="prepare"/>
                     <interceptor-ref name="chain"/>
                     <interceptor-ref name="modelDriven"/>
    -                <interceptor-ref name="fileUpload"/>
                     <interceptor-ref name="actionFileUpload"/>
                     <interceptor-ref name="staticParams"/>
                     <interceptor-ref name="actionMappingParams"/>
    @@ -208,7 +200,6 @@
                     <interceptor-ref name="chain"/>
                     <interceptor-ref name="scopedModelDriven"/>
                     <interceptor-ref name="modelDriven"/>
    -                <interceptor-ref name="fileUpload"/>
                     <interceptor-ref name="actionFileUpload"/>
                     <interceptor-ref name="checkbox"/>
                     <interceptor-ref name="datetime"/>
    
  • core/src/test/java/org/apache/struts2/interceptor/FileUploadInterceptorTest.java+0 659 removed
    @@ -1,659 +0,0 @@
    -/*
    - * Licensed to the Apache Software Foundation (ASF) under one
    - * or more contributor license agreements.  See the NOTICE file
    - * distributed with this work for additional information
    - * regarding copyright ownership.  The ASF licenses this file
    - * to you under the Apache License, Version 2.0 (the
    - * "License"); you may not use this file except in compliance
    - * with the License.  You may obtain a copy of the License at
    - *
    - *  http://www.apache.org/licenses/LICENSE-2.0
    - *
    - * Unless required by applicable law or agreed to in writing,
    - * software distributed under the License is distributed on an
    - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
    - * KIND, either express or implied.  See the License for the
    - * specific language governing permissions and limitations
    - * under the License.
    - */
    -package org.apache.struts2.interceptor;
    -
    -import com.opensymphony.xwork2.ActionContext;
    -import com.opensymphony.xwork2.ActionSupport;
    -import com.opensymphony.xwork2.DefaultLocaleProvider;
    -import com.opensymphony.xwork2.ValidationAwareSupport;
    -import com.opensymphony.xwork2.mock.MockActionInvocation;
    -import com.opensymphony.xwork2.mock.MockActionProxy;
    -import com.opensymphony.xwork2.util.ClassLoaderUtil;
    -import org.apache.commons.fileupload2.jakarta.servlet6.JakartaServletFileUpload;
    -import org.apache.struts2.StrutsInternalTestCase;
    -import org.apache.struts2.action.UploadedFilesAware;
    -import org.apache.struts2.dispatcher.HttpParameters;
    -import org.apache.struts2.dispatcher.multipart.JakartaMultiPartRequest;
    -import org.apache.struts2.dispatcher.multipart.MultiPartRequestWrapper;
    -import org.apache.struts2.dispatcher.multipart.StrutsUploadedFile;
    -import org.apache.struts2.dispatcher.multipart.UploadedFile;
    -import org.assertj.core.util.Files;
    -import org.springframework.mock.web.MockHttpServletRequest;
    -
    -import java.io.File;
    -import java.net.URI;
    -import java.net.URL;
    -import java.nio.charset.StandardCharsets;
    -import java.util.Collection;
    -import java.util.HashMap;
    -import java.util.List;
    -import java.util.Locale;
    -import java.util.Map;
    -
    -import static org.assertj.core.api.Assertions.assertThat;
    -
    -/**
    - * Test case for FileUploadInterceptor.
    - */
    -public class FileUploadInterceptorTest extends StrutsInternalTestCase {
    -
    -    private static final UploadedFile EMPTY_FILE = new UploadedFile() {
    -        @Override
    -        public Long length() {
    -            return 0L;
    -        }
    -
    -        @Override
    -        public String getName() {
    -            return "";
    -        }
    -
    -        @Override
    -        public boolean isFile() {
    -            return false;
    -        }
    -
    -        @Override
    -        public boolean delete() {
    -            return false;
    -        }
    -
    -        @Override
    -        public String getAbsolutePath() {
    -            return null;
    -        }
    -
    -        @Override
    -        public File getContent() {
    -            return Files.newTemporaryFile();
    -        }
    -
    -        @Override
    -        public String getOriginalName() {
    -            return null;
    -        }
    -
    -        @Override
    -        public String getContentType() {
    -            return null;
    -        }
    -    };
    -
    -    private FileUploadInterceptor interceptor;
    -    private File tempDir;
    -    private MockHttpServletRequest request;
    -
    -    public void testAcceptFileWithEmptyAllowedTypesAndExtensions() {
    -        // when allowed type is empty
    -        ValidationAwareSupport validation = new ValidationAwareSupport();
    -        boolean ok = interceptor.acceptFile(validation, EMPTY_FILE, "filename", "text/plain", "inputName");
    -
    -        assertTrue(ok);
    -        assertTrue(validation.getFieldErrors().isEmpty());
    -        assertFalse(validation.hasErrors());
    -    }
    -
    -    public void testAcceptFileWithoutEmptyTypes() {
    -        interceptor.setAllowedTypes("text/plain");
    -
    -        // when file is of allowed types
    -        ValidationAwareSupport validation = new ValidationAwareSupport();
    -        boolean ok = interceptor.acceptFile(validation, EMPTY_FILE, "filename.txt", "text/plain", "inputName");
    -
    -        assertTrue(ok);
    -        assertTrue(validation.getFieldErrors().isEmpty());
    -        assertFalse(validation.hasErrors());
    -
    -        // when file is not of allowed types
    -        validation = new ValidationAwareSupport();
    -        boolean notOk = interceptor.acceptFile(validation, EMPTY_FILE, "filename.html", "text/html", "inputName");
    -
    -        assertFalse(notOk);
    -        assertFalse(validation.getFieldErrors().isEmpty());
    -        assertTrue(validation.hasErrors());
    -    }
    -
    -
    -    public void testAcceptFileWithWildcardContent() {
    -        interceptor.setAllowedTypes("text/*");
    -
    -        ValidationAwareSupport validation = new ValidationAwareSupport();
    -        boolean ok = interceptor.acceptFile(validation, EMPTY_FILE, "filename.txt", "text/plain", "inputName");
    -
    -        assertTrue(ok);
    -        assertTrue(validation.getFieldErrors().isEmpty());
    -        assertFalse(validation.hasErrors());
    -
    -        interceptor.setAllowedTypes("text/h*");
    -        validation = new ValidationAwareSupport();
    -        boolean notOk = interceptor.acceptFile(validation, EMPTY_FILE, "filename.html", "text/plain", "inputName");
    -
    -        assertFalse(notOk);
    -        assertFalse(validation.getFieldErrors().isEmpty());
    -        assertTrue(validation.hasErrors());
    -    }
    -
    -    public void testAcceptFileWithoutEmptyExtensions() {
    -        interceptor.setAllowedExtensions(".txt");
    -
    -        // when file is of allowed extensions
    -        ValidationAwareSupport validation = new ValidationAwareSupport();
    -        boolean ok = interceptor.acceptFile(validation, EMPTY_FILE, "filename.txt", "text/plain", "inputName");
    -
    -        assertTrue(ok);
    -        assertTrue(validation.getFieldErrors().isEmpty());
    -        assertFalse(validation.hasErrors());
    -
    -        // when file is not of allowed extensions
    -        validation = new ValidationAwareSupport();
    -        boolean notOk = interceptor.acceptFile(validation, EMPTY_FILE, "filename.html", "text/html", "inputName");
    -
    -        assertFalse(notOk);
    -        assertFalse(validation.getFieldErrors().isEmpty());
    -        assertTrue(validation.hasErrors());
    -
    -        //test with multiple extensions
    -        interceptor.setAllowedExtensions(".txt,.lol");
    -        validation = new ValidationAwareSupport();
    -        ok = interceptor.acceptFile(validation, EMPTY_FILE, "filename.lol", "text/plain", "inputName");
    -
    -        assertTrue(ok);
    -        assertTrue(validation.getFieldErrors().isEmpty());
    -        assertFalse(validation.hasErrors());
    -    }
    -
    -    public void testAcceptFileWithNoFile() {
    -        interceptor.setAllowedTypes("text/plain");
    -
    -        // when file is not of allowed types
    -        ValidationAwareSupport validation = new ValidationAwareSupport();
    -        boolean notOk = interceptor.acceptFile(validation, null, "filename.html", "text/html", "inputName");
    -
    -        assertFalse(notOk);
    -        assertFalse(validation.getFieldErrors().isEmpty());
    -        assertTrue(validation.hasErrors());
    -        List<String> errors = validation.getFieldErrors().get("inputName");
    -        assertEquals(1, errors.size());
    -        String msg = errors.get(0);
    -        assertTrue(msg.startsWith("Error uploading:"));
    -        assertTrue(msg.indexOf("inputName") > 0);
    -    }
    -
    -    public void testAcceptFileWithMaxSize() throws Exception {
    -        interceptor.setMaximumSize(10L);
    -
    -        // when file is not of allowed types
    -        ValidationAwareSupport validation = new ValidationAwareSupport();
    -
    -        URL url = ClassLoaderUtil.getResource("log4j2.xml", FileUploadInterceptorTest.class);
    -        File file = new File(new URI(url.toString()));
    -        assertTrue("log4j2.xml should be in src/test folder", file.exists());
    -        UploadedFile uploadedFile = StrutsUploadedFile.Builder.create(file)
    -                .withContentType("text/html")
    -                .withOriginalName("filename")
    -                .build();
    -
    -        boolean notOk = interceptor.acceptFile(validation, uploadedFile, "filename", "text/html", "inputName");
    -
    -        assertFalse(notOk);
    -        assertFalse(validation.getFieldErrors().isEmpty());
    -        assertTrue(validation.hasErrors());
    -        List<String> errors = validation.getFieldErrors().get("inputName");
    -        assertEquals(1, errors.size());
    -        String msg = errors.get(0);
    -        // the error message should contain at least this test
    -        assertThat(msg).contains(
    -                "The file is too large to be uploaded",
    -                "inputName",
    -                "log4j2.xml",
    -                "allowed mx size is 10"
    -        );
    -    }
    -
    -    public void testNoMultipartRequest() throws Exception {
    -        MyFileupAction action = new MyFileupAction();
    -
    -        MockActionInvocation mai = new MockActionInvocation();
    -        mai.setAction(action);
    -        mai.setResultCode("NoMultipart");
    -        mai.setInvocationContext(ActionContext.getContext());
    -
    -        // if no multipart request it will bypass and execute it
    -        assertEquals("NoMultipart", interceptor.intercept(mai));
    -    }
    -
    -    public void testInvalidContentTypeMultipartRequest() throws Exception {
    -        request.setContentType("multipart/form-data"); // not a multipart contentype
    -        request.setMethod("post");
    -
    -        MyFileupAction action = container.inject(MyFileupAction.class);
    -        MockActionInvocation mai = new MockActionInvocation();
    -        mai.setAction(action);
    -        mai.setResultCode("success");
    -        mai.setInvocationContext(ActionContext.getContext());
    -
    -        ActionContext.getContext().withParameters(HttpParameters.create().build());
    -        ActionContext.getContext().withServletRequest(createMultipartRequestMaxSize(2000));
    -
    -        interceptor.intercept(mai);
    -
    -        assertTrue(action.hasErrors());
    -    }
    -
    -    public void testNoContentMultipartRequest() throws Exception {
    -        request.setCharacterEncoding(StandardCharsets.UTF_8.name());
    -        request.setMethod("post");
    -        request.addHeader("Content-type", "multipart/form-data");
    -        request.setContent(null); // there is no content
    -
    -        MyFileupAction action = container.inject(MyFileupAction.class);
    -        MockActionInvocation mai = new MockActionInvocation();
    -        mai.setAction(action);
    -        mai.setResultCode("success");
    -        mai.setInvocationContext(ActionContext.getContext());
    -
    -        ActionContext.getContext().withParameters(HttpParameters.create().build());
    -        ActionContext.getContext().withServletRequest(createMultipartRequestMaxSize(2000));
    -
    -        interceptor.intercept(mai);
    -
    -        assertTrue(action.hasErrors());
    -    }
    -
    -    public void testSuccessUploadOfATextFileMultipartRequest() throws Exception {
    -        request.setCharacterEncoding(StandardCharsets.UTF_8.name());
    -        request.setMethod("post");
    -        request.addHeader("Content-type", "multipart/form-data; boundary=---1234");
    -
    -        // inspired by the unit tests for jakarta commons fileupload
    -        String content = ("-----1234\r\n" +
    -                "Content-Disposition: form-data; name=\"file\"; filename=\"deleteme.txt\"\r\n" +
    -                "Content-Type: text/html\r\n" +
    -                "\r\n" +
    -                "Unit test of FileUploadInterceptor" +
    -                "\r\n" +
    -                "-----1234--\r\n");
    -        request.setContent(content.getBytes(StandardCharsets.US_ASCII));
    -
    -        MyFileupAction action = new MyFileupAction();
    -
    -        MockActionInvocation mai = new MockActionInvocation();
    -        mai.setAction(action);
    -        mai.setResultCode("success");
    -        mai.setInvocationContext(ActionContext.getContext());
    -        Map<String, Object> param = new HashMap<>();
    -        ActionContext.getContext().withParameters(HttpParameters.create(param).build());
    -        ActionContext.getContext().withServletRequest(createMultipartRequestMaxSize(2000));
    -
    -        interceptor.intercept(mai);
    -
    -        assertFalse(action.hasErrors());
    -
    -        HttpParameters parameters = mai.getInvocationContext().getParameters();
    -        assertEquals(3, parameters.keySet().size());
    -        UploadedFile[] files = (UploadedFile[]) parameters.get("file").getObject();
    -        String[] fileContentTypes = parameters.get("fileContentType").getMultipleValues();
    -        String[] fileRealFilenames = parameters.get("fileFileName").getMultipleValues();
    -
    -        assertNotNull(files);
    -        assertNotNull(fileContentTypes);
    -        assertNotNull(fileRealFilenames);
    -        assertEquals(1, files.length);
    -        assertEquals(1, fileContentTypes.length);
    -        assertEquals(1, fileRealFilenames.length);
    -        assertEquals("text/html", fileContentTypes[0]);
    -        assertNotNull("deleteme.txt", fileRealFilenames[0]);
    -    }
    -
    -    /**
    -     * tests whether with multiple files sent with the same name, the ones with forbiddenTypes (see
    -     * FileUploadInterceptor.setAllowedTypes(...) ) are sorted out.
    -     */
    -    public void testMultipleAccept() throws Exception {
    -        final String htmlContent = "<html><head></head><body>html content</body></html>";
    -        final String plainContent = "plain content";
    -        final String bondary = "simple boundary";
    -        final String endline = "\r\n";
    -
    -        request.setCharacterEncoding(StandardCharsets.UTF_8.name());
    -        request.setMethod("POST");
    -        request.addHeader("Content-type", "multipart/form-data; boundary=" + bondary);
    -        String content = encodeTextFile("test.html", "text/plain", plainContent) +
    -                encodeTextFile("test1.html", "text/html", htmlContent) +
    -                encodeTextFile("test2.html", "text/html", htmlContent) +
    -                endline +
    -                endline +
    -                endline +
    -                "--" +
    -                bondary +
    -                "--" +
    -                endline;
    -        request.setContent(content.getBytes());
    -
    -        assertTrue(JakartaServletFileUpload.isMultipartContent(request));
    -
    -        MyFileupAction action = new MyFileupAction();
    -        container.inject(action);
    -        MockActionInvocation mai = new MockActionInvocation();
    -        mai.setAction(action);
    -        mai.setResultCode("success");
    -        mai.setInvocationContext(ActionContext.getContext());
    -        Map<String, Object> param = new HashMap<>();
    -        ActionContext.getContext().withParameters(HttpParameters.create(param).build());
    -        ActionContext.getContext().withServletRequest(createMultipartRequestMaxSize(2000));
    -
    -        interceptor.setAllowedTypes("text/html");
    -        interceptor.intercept(mai);
    -
    -        HttpParameters parameters = mai.getInvocationContext().getParameters();
    -        assertEquals(3, parameters.keySet().size());
    -        UploadedFile[] files = (UploadedFile[]) parameters.get("file").getObject();
    -        String[] fileContentTypes = parameters.get("fileContentType").getMultipleValues();
    -        String[] fileRealFilenames = parameters.get("fileFileName").getMultipleValues();
    -
    -        assertNotNull(files);
    -        assertNotNull(fileContentTypes);
    -        assertNotNull(fileRealFilenames);
    -        assertEquals("files accepted ", 2, files.length);
    -        assertEquals(2, fileContentTypes.length);
    -        assertEquals(2, fileRealFilenames.length);
    -        assertEquals("text/html", fileContentTypes[0]);
    -        assertNotNull("test1.html", fileRealFilenames[0]);
    -    }
    -
    -    public void testUnacceptedNumberOfFiles() throws Exception {
    -        final String htmlContent = "<html><head></head><body>html content</body></html>";
    -        final String plainContent = "plain content";
    -        final String boundary = "simple boundary";
    -        final String endline = "\r\n";
    -
    -        request.setCharacterEncoding(StandardCharsets.UTF_8.name());
    -        request.setMethod("POST");
    -        request.addHeader("Content-type", "multipart/form-data; boundary=" + boundary);
    -        String content = encodeTextFile("test.html", "text/plain", plainContent) +
    -                encodeTextFile("test1.html", "text/html", htmlContent) +
    -                encodeTextFile("test2.html", "text/html", htmlContent) +
    -                encodeTextFile("test3.html", "text/html", htmlContent) +
    -                endline +
    -                "--" +
    -                boundary +
    -                "--" +
    -                endline;
    -        request.setContent(content.getBytes());
    -
    -        assertTrue(JakartaServletFileUpload.isMultipartContent(request));
    -
    -        MyFileupAction action = new MyFileupAction();
    -        container.inject(action);
    -        MockActionInvocation mai = new MockActionInvocation();
    -        mai.setAction(action);
    -        mai.setResultCode("success");
    -        mai.setInvocationContext(ActionContext.getContext());
    -        Map<String, Object> param = new HashMap<>();
    -        ActionContext.getContext()
    -                .withParameters(HttpParameters.create(param).build())
    -                .withServletRequest(createMultipartRequestMaxFiles());
    -
    -        interceptor.setAllowedTypes("text/html");
    -        interceptor.intercept(mai);
    -
    -        HttpParameters parameters = mai.getInvocationContext().getParameters();
    -        assertEquals(0, parameters.keySet().size());
    -        assertEquals(1, action.getActionErrors().size());
    -        assertEquals(
    -                "Request exceeded allowed number of files! Permitted number of files is: 3!",
    -                action.getActionErrors().iterator().next()
    -        );
    -    }
    -
    -    public void testMultipartRequestMaxFileSize() throws Exception {
    -        request.setCharacterEncoding(StandardCharsets.UTF_8.name());
    -        request.setMethod("post");
    -        request.addHeader("Content-type", "multipart/form-data; boundary=---1234");
    -
    -        // inspired by the unit tests for jakarta commons fileupload
    -        String content = ("-----1234\r\n" +
    -                "Content-Disposition: form-data; name=\"file\"; filename=\"deleteme.txt\"\r\n" +
    -                "Content-Type: text/html\r\n" +
    -                "\r\n" +
    -                "Unit test of FileUploadInterceptor" +
    -                "\r\n" +
    -                "-----1234--\r\n");
    -        request.setContent(content.getBytes(StandardCharsets.US_ASCII));
    -
    -        MyFileupAction action = container.inject(MyFileupAction.class);
    -
    -        MockActionInvocation mai = new MockActionInvocation();
    -        mai.setAction(action);
    -        mai.setResultCode("success");
    -        mai.setInvocationContext(ActionContext.getContext());
    -        Map<String, Object> param = new HashMap<>();
    -        ActionContext.getContext()
    -                .withParameters(HttpParameters.create(param).build())
    -                .withServletRequest(createMultipartRequestMaxFileSize());
    -
    -        interceptor.intercept(mai);
    -
    -        assertTrue(action.hasActionErrors());
    -
    -        Collection<String> errors = action.getActionErrors();
    -        assertEquals(1, errors.size());
    -        String msg = errors.iterator().next();
    -        // FIXME: the expected size is 40 - length of the string
    -        assertEquals(
    -                "File deleteme.txt assigned to file exceeded allowed size limit! Max size allowed is: 10 but file was: 11!",
    -                msg);
    -    }
    -
    -    public void testMultipartRequestMaxStringLength() throws Exception {
    -        request.setCharacterEncoding(StandardCharsets.UTF_8.name());
    -        request.setMethod("post");
    -        request.addHeader("Content-type", "multipart/form-data; boundary=---1234");
    -
    -        // inspired by the unit tests for jakarta commons fileupload
    -        String content = ("-----1234\r\n" +
    -                "Content-Disposition: form-data; name=\"file\"; filename=\"deleteme.txt\"\r\n" +
    -                "Content-Type: text/html\r\n" +
    -                "\r\n" +
    -                "Unit test of FileUploadInterceptor" +
    -                "\r\n" +
    -                "-----1234\r\n" +
    -                "Content-Disposition: form-data; name=\"normalFormField1\"\r\n" +
    -                "\r\n" +
    -                "it works" +
    -                "\r\n" +
    -                "-----1234\r\n" +
    -                "Content-Disposition: form-data; name=\"normalFormField2\"\r\n" +
    -                "\r\n" +
    -                "long string should not work" +
    -                "\r\n" +
    -                "-----1234--\r\n");
    -        request.setContent(content.getBytes(StandardCharsets.US_ASCII));
    -
    -        MyFileupAction action = container.inject(MyFileupAction.class);
    -
    -        MockActionInvocation mai = new MockActionInvocation();
    -        mai.setAction(action);
    -        mai.setResultCode("success");
    -        mai.setInvocationContext(ActionContext.getContext());
    -        Map<String, Object> param = new HashMap<>();
    -        ActionContext.getContext()
    -                .withParameters(HttpParameters.create(param).build())
    -                .withServletRequest(createMultipartRequestMaxStringLength());
    -
    -        interceptor.intercept(mai);
    -
    -        assertTrue(action.hasActionErrors());
    -
    -        Collection<String> errors = action.getActionErrors();
    -        assertEquals(1, errors.size());
    -        String msg = errors.iterator().next();
    -        assertEquals(
    -                "The request parameter \"normalFormField2\" was too long. Max length allowed is 20, but found 27!",
    -                msg);
    -    }
    -
    -    public void testMultipartRequestLocalizedError() throws Exception {
    -        request.setCharacterEncoding(StandardCharsets.UTF_8.name());
    -        request.setMethod("post");
    -        request.addHeader("Content-type", "multipart/form-data; boundary=---1234");
    -
    -        // inspired by the unit tests for jakarta commons fileupload
    -        String content = ("-----1234\r\n" +
    -                "Content-Disposition: form-data; name=\"file\"; filename=\"deleteme.txt\"\r\n" +
    -                "Content-Type: text/html\r\n" +
    -                "\r\n" +
    -                "Unit test of FileUploadInterceptor" +
    -                "\r\n" +
    -                "-----1234--\r\n");
    -        request.setContent(content.getBytes(StandardCharsets.US_ASCII));
    -
    -        MyFileupAction action = container.inject(MyFileupAction.class);
    -
    -        MockActionInvocation mai = new MockActionInvocation();
    -        mai.setAction(action);
    -        mai.setResultCode("success");
    -        mai.setInvocationContext(ActionContext.getContext());
    -        Map<String, Object> param = new HashMap<>();
    -        ActionContext.getContext()
    -                .withParameters(HttpParameters.create(param).build())
    -                .withLocale(Locale.GERMAN)
    -                .withServletRequest(createMultipartRequestMaxSize(10));
    -
    -        interceptor.intercept(mai);
    -
    -        assertTrue(action.hasActionErrors());
    -
    -        Collection<String> errors = action.getActionErrors();
    -        assertEquals(1, errors.size());
    -        String msg = errors.iterator().next();
    -        // the error message should contain at least this test
    -        assertTrue(msg.startsWith("Der Request übertraf die maximal erlaubte Größe"));
    -    }
    -
    -    public void testSkippingUploadedFileAware() throws Exception {
    -        request.setCharacterEncoding(StandardCharsets.UTF_8.name());
    -        request.setMethod("post");
    -        request.addHeader("Content-type", "multipart/form-data; boundary=---1234");
    -
    -        // inspired by the unit tests for jakarta commons fileupload
    -        String content = ("-----1234\r\n" +
    -                "Content-Disposition: form-data; name=\"file\"; filename=\"deleteme.txt\"\r\n" +
    -                "Content-Type: text/html\r\n" +
    -                "\r\n" +
    -                "Unit test of FileUploadInterceptor" +
    -                "\r\n" +
    -                "-----1234--\r\n");
    -        request.setContent(content.getBytes(StandardCharsets.US_ASCII));
    -
    -        MyFileUploadAction action = container.inject(MyFileUploadAction.class);
    -
    -        MockActionInvocation mai = new MockActionInvocation();
    -        mai.setInvocationContext(ActionContext.getContext());
    -        mai.setAction(action);
    -        MockActionProxy map = new MockActionProxy();
    -        map.setActionName("uploadedFiles");
    -        mai.setProxy(map);
    -        ActionContext.getContext()
    -                .withParameters(HttpParameters.create(new HashMap<String, Object>()).build())
    -                .withServletRequest(createMultipartRequestMaxSize(10));
    -
    -        interceptor.intercept(mai);
    -
    -        assertFalse(action.hasActionErrors());
    -    }
    -
    -    private String encodeTextFile(String filename, String contentType, String content) {
    -        return "\r\n" +
    -                "--" +
    -                "simple boundary" +
    -                "\r\n" +
    -                "Content-Disposition: form-data; name=\"" +
    -                "file" +
    -                "\"; filename=\"" +
    -                filename +
    -                "\r\n" +
    -                "Content-Type: " +
    -                contentType +
    -                "\r\n" +
    -                "\r\n" +
    -                content;
    -    }
    -
    -    private MultiPartRequestWrapper createMultipartRequestMaxFileSize() {
    -        return createMultipartRequest(-1, 10, -1, -1);
    -    }
    -
    -    private MultiPartRequestWrapper createMultipartRequestMaxFiles() {
    -        return createMultipartRequest(-1, -1, 3, -1);
    -    }
    -
    -    private MultiPartRequestWrapper createMultipartRequestMaxSize(int maxsize) {
    -        return createMultipartRequest(maxsize, -1, -1, -1);
    -    }
    -
    -    private MultiPartRequestWrapper createMultipartRequestMaxStringLength() {
    -        return createMultipartRequest(-1, -1, -1, 20);
    -    }
    -
    -    private MultiPartRequestWrapper createMultipartRequest(int maxsize, int maxfilesize, int maxfiles, int maxStringLength) {
    -        JakartaMultiPartRequest jak = new JakartaMultiPartRequest();
    -        jak.setMaxSize(String.valueOf(maxsize));
    -        jak.setMaxFileSize(String.valueOf(maxfilesize));
    -        jak.setMaxFiles(String.valueOf(maxfiles));
    -        jak.setMaxStringLength(String.valueOf(maxStringLength));
    -        jak.setDefaultEncoding(StandardCharsets.UTF_8.name());
    -        return new MultiPartRequestWrapper(jak, request, tempDir.getAbsolutePath(), new DefaultLocaleProvider());
    -    }
    -
    -    @Override
    -    protected void setUp() throws Exception {
    -        super.setUp();
    -
    -        request = new MockHttpServletRequest();
    -        interceptor = new FileUploadInterceptor();
    -        container.inject(interceptor);
    -        tempDir = File.createTempFile("struts", "fileupload");
    -        assertThat(tempDir.delete()).isTrue();
    -        assertThat(tempDir.mkdirs()).isTrue();
    -    }
    -
    -    @Override
    -    protected void tearDown() throws Exception {
    -        interceptor.destroy();
    -        super.tearDown();
    -    }
    -
    -    public static class MyFileupAction extends ActionSupport {
    -    }
    -
    -    public static class MyFileUploadAction extends ActionSupport implements UploadedFilesAware {
    -        private List<UploadedFile> uploadedFiles;
    -
    -        @Override
    -        public void withUploadedFiles(List<UploadedFile> uploadedFiles) {
    -            this.uploadedFiles = uploadedFiles;
    -        }
    -
    -        public List<UploadedFile> getUploadFiles() {
    -            return this.uploadedFiles;
    -        }
    -    }
    -
    -}
    
  • plugins/bean-validation/src/main/resources/struts-plugin.xml+1 1 modified
    @@ -47,7 +47,7 @@
                     <interceptor-ref name="chain"/>
                     <interceptor-ref name="scopedModelDriven"/>
                     <interceptor-ref name="modelDriven"/>
    -                <interceptor-ref name="fileUpload"/>
    +                <interceptor-ref name="actionFileUpload"/>
                     <interceptor-ref name="checkbox"/>
                     <interceptor-ref name="datetime"/>
                     <interceptor-ref name="multiselect"/>
    
  • plugins/rest/src/main/resources/struts-plugin.xml+1 1 modified
    @@ -86,7 +86,7 @@
                     <interceptor-ref name="modelDriven">
                         <param name="refreshModelBeforeResult">true</param>
                     </interceptor-ref>
    -                <interceptor-ref name="fileUpload"/>
    +                <interceptor-ref name="actionFileUpload"/>
                     <interceptor-ref name="checkbox"/>
                     <interceptor-ref name="staticParams"/>
                     <interceptor-ref name="params"/>
    
3ef9ade8902a

WW-5371 Document how to use the new file upload logic

https://github.com/apache/strutsLukasz LenartDec 12, 2023via ghsa
4 files changed · +45 90
  • core/src/main/java/org/apache/struts2/action/UploadedFilesAware.java+1 1 modified
    @@ -33,7 +33,7 @@ public interface UploadedFilesAware {
          * Notifies action about the multiple uploaded files, when a single file is uploaded
          * the list will have just one element
          *
    -     * @param uploadedFiles a list of {@link UploadedFile}.
    +     * @param uploadedFiles a list of {@link UploadedFile}, cannot be null. It can be empty.
          */
         void withUploadedFiles(List<UploadedFile> uploadedFiles);
     
    
  • core/src/main/java/org/apache/struts2/dispatcher/multipart/JakartaStreamMultiPartRequest.java+5 8 modified
    @@ -44,6 +44,7 @@
     import java.util.List;
     import java.util.Map;
     import java.util.UUID;
    +import java.util.stream.Collectors;
     
     /**
      * Multi-part form data request adapter for Jakarta Commons FileUpload package that
    @@ -109,16 +110,12 @@ public UploadedFile[] getFile(String fieldName) {
                 return null;
             }
     
    -        List<UploadedFile> files = new ArrayList<>(infos.size());
    -        for (FileInfo fileInfo : infos) {
    -            UploadedFile file = StrutsUploadedFile.Builder.create(fileInfo.getFile())
    +        return infos.stream().map(fileInfo ->
    +            StrutsUploadedFile.Builder.create(fileInfo.getFile())
                     .withContentType(fileInfo.contentType)
                     .withOriginalName(fileInfo.originalName)
    -                .build();
    -            files.add(file);
    -        }
    -
    -        return files.toArray(new UploadedFile[0]);
    +                .build()
    +        ).toArray(UploadedFile[]::new);
         }
     
         /* (non-Javadoc)
    
  • core/src/main/java/org/apache/struts2/interceptor/ActionFileUploadInterceptor.java+36 81 modified
    @@ -33,141 +33,96 @@
     import java.util.List;
     
     /**
    - * <!-- START SNIPPET: description -->
      * <p>
      * Interceptor that is based off of {@link MultiPartRequestWrapper}, which is automatically applied for any request that
    - * includes a file. It adds the following parameters, where [File Name] is the name given to the file uploaded by the
    - * HTML form:
    + * includes a file when the support for multi-part request is enabled,
    + * see <a href="https://struts.apache.org/core-developers/file-upload.html#disabling-file-upload-support">Disabling file upload</a>.
      * </p>
    - * <ul>
    - *
    - * <li>[File Name] : File - the actual File</li>
    - *
    - * <li>[File Name]ContentType : String - the content type of the file</li>
      *
    - * <li>[File Name]FileName : String - the actual name of the file uploaded (not the HTML name)</li>
    - *
    - * </ul>
    - *
    - * <p>You can get access to these files by merely providing setters in your action that correspond to any of the three
    - * patterns above, such as setDocument(File document), setDocumentContentType(String contentType), etc.
    - * <br>See the example code section.
    + * <p>
    + * You can get access to these files by implementing {@link UploadedFilesAware} interface. The interceptor will then
    + * call {@link UploadedFilesAware#withUploadedFiles(List)} when there are files which were accepted during the upload process.
      * </p>
      *
    - * <p> This interceptor will add several field errors, assuming that the action implements {@link ValidationAware}.
    + * <p>
    + * This interceptor will add several field errors, assuming that the action implements {@link ValidationAware}.
      * These error messages are based on several i18n values stored in struts-messages.properties, a default i18n file
      * processed for all i18n requests. You can override the text of these messages by providing text for the following
      * keys:
      * </p>
      *
      * <ul>
    - *
      * <li>struts.messages.error.uploading - a general error that occurs when the file could not be uploaded</li>
    - *
      * <li>struts.messages.error.file.too.large - occurs when the uploaded file is too large</li>
    - *
      * <li>struts.messages.error.content.type.not.allowed - occurs when the uploaded file does not match the expected
      * content types specified</li>
    - *
      * <li>struts.messages.error.file.extension.not.allowed - occurs when the uploaded file does not match the expected
      * file extensions specified</li>
    - *
      * </ul>
    - * <p>
    - * <!-- END SNIPPET: description -->
    - *
    - * <p><u>Interceptor parameters:</u></p>
    - * <p>
    - * <!-- START SNIPPET: parameters -->
      *
    + * <p>Interceptor parameters:</p>
      * <ul>
    - *
      * <li>maximumSize (optional) - the maximum size (in bytes) that the interceptor will allow a file reference to be set
      * on the action. Note, this is <b>not</b> related to the various properties found in struts.properties.
      * Default to approximately 2MB.</li>
    - *
      * <li>allowedTypes (optional) - a comma separated list of content types (ie: text/html) that the interceptor will allow
      * a file reference to be set on the action. If none is specified allow all types to be uploaded.</li>
    - *
      * <li>allowedExtensions (optional) - a comma separated list of file extensions (ie: .html) that the interceptor will allow
      * a file reference to be set on the action. If none is specified allow all extensions to be uploaded.</li>
      * </ul>
    - * <p>
    - * <p>
    - * <!-- END SNIPPET: parameters -->
      *
    - * <p><u>Extending the interceptor:</u></p>
    - * <p>
    - * <p>
    - * <p>
    - * <!-- START SNIPPET: extending -->
    - * <p>
    - * You can extend this interceptor and override the acceptFile method to provide more control over which files
    - * are supported and which are not.
    - * </p>
    - * <!-- END SNIPPET: extending -->
    - *
    - * <p><u>Example code:</u></p>
    + * <p>Example code:</p>
      *
      * <pre>
    - * <!-- START SNIPPET: example-configuration -->
      * &lt;action name="doUpload" class="com.example.UploadAction"&gt;
    - *     &lt;interceptor-ref name="fileUpload"/&gt;
    + *     &lt;interceptor-ref name="actionFileUpload"/&gt;
      *     &lt;interceptor-ref name="basicStack"/&gt;
      *     &lt;result name="success"&gt;good_result.jsp&lt;/result&gt;
      * &lt;/action&gt;
    - * <!-- END SNIPPET: example-configuration -->
      * </pre>
      * <p>
    - * <!-- START SNIPPET: multipart-note -->
      * <p>
      * You must set the encoding to <code>multipart/form-data</code> in the form where the user selects the file to upload.
      * </p>
    - * <!-- END SNIPPET: multipart-note -->
    - *
      * <pre>
    - * <!-- START SNIPPET: example-form -->
      *   &lt;s:form action="doUpload" method="post" enctype="multipart/form-data"&gt;
      *       &lt;s:file name="upload" label="File"/&gt;
      *       &lt;s:submit/&gt;
      *   &lt;/s:form&gt;
    - * <!-- END SNIPPET: example-form -->
      * </pre>
      * <p>
      * And then in your action code you'll have access to the File object if you provide setters according to the
      * naming convention documented in the start.
      * </p>
      *
      * <pre>
    - * <!-- START SNIPPET: example-action -->
    - *    package com.example;
    - *
    - *    import java.io.File;
    - *    import com.opensymphony.xwork2.ActionSupport;
    - *
    - *    public UploadAction extends ActionSupport {
    - *       private File file;
    - *       private String contentType;
    - *       private String filename;
    - *
    - *       public void setUpload(File file) {
    - *          this.file = file;
    - *       }
    - *
    - *       public void setUploadContentType(String contentType) {
    - *          this.contentType = contentType;
    - *       }
    - *
    - *       public void setUploadFileName(String filename) {
    - *          this.filename = filename;
    - *       }
    - *
    - *       public String execute() {
    - *          //...
    - *          return SUCCESS;
    - *       }
    + *  package com.example;
    + *
    + *  import java.io.File;
    + *  import com.opensymphony.xwork2.ActionSupport;
    + *  import org.apache.struts2.action.UploadedFilesAware;
    + *
    + *  public UploadAction extends ActionSupport implements UploadedFilesAware {
    + *    private UploadedFile uploadedFile;
    + *    private String contentType;
    + *    private String fileName;
    + *    private String originalName;
    + *
    + *    &#064;Override
    + *    public void withUploadedFiles(List<UploadedFile> uploadedFiles) {
    + *        if (!uploadedFiles.isEmpty() > 0) {
    + *            this.uploadedFile = uploadedFiles.get(0);
    + *            this.fileName = uploadedFile.getName();
    + *            this.contentType = uploadedFile.getContentType();
    + *            this.originalName = uploadedFile.getOriginalName();
    + *        }
    + *    }
    + *
    + *    public String execute() {
    + *       //...
    + *       return SUCCESS;
    + *    }
      *  }
    - * <!-- END SNIPPET: example-action -->
      * </pre>
      */
     public class ActionFileUploadInterceptor extends AbstractFileUploadInterceptor {
    
  • core/src/main/java/org/apache/struts2/interceptor/FileUploadInterceptor.java+3 0 modified
    @@ -172,7 +172,10 @@
      *  }
      * <!-- END SNIPPET: example-action -->
      * </pre>
    + *
    + * @deprecated since Struts 6.4.0, use {@link ActionFileUploadInterceptor} instead
      */
    +@Deprecated
     public class FileUploadInterceptor extends AbstractFileUploadInterceptor {
     
         private static final long serialVersionUID = -4764627478894962478L;
    

Vulnerability mechanics

Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

9

News mentions

0

No linked articles in our index yet.