This is an automated email from the ASF dual-hosted git repository.

lukaszlenart pushed a commit to branch WW-5273-servlet-upload
in repository https://gitbox.apache.org/repos/asf/struts.git

commit f92e3b945dd69d18034c6aa09ad6dcbec128f1fc
Author: Lukasz Lenart <lukaszlen...@apache.org>
AuthorDate: Thu Dec 29 10:36:45 2022 +0100

    WW-5273 Supports file upload using Servlet API 3.1
---
 apps/showcase/{README.txt => README.md}            |  11 +-
 apps/showcase/pom.xml                              |   4 -
 apps/showcase/src/main/webapp/WEB-INF/web.xml      |  12 +
 .../org/apache/struts2/dispatcher/Dispatcher.java  |   4 +-
 .../dispatcher/filter/FileUploadSupport.java       |  65 ++++++
 .../dispatcher/filter/StrutsExecuteFilter.java     |  17 +-
 .../filter/StrutsPrepareAndExecuteFilter.java      |   7 +-
 .../dispatcher/filter/StrutsPrepareFilter.java     |   1 +
 .../multipart/AbstractMultiPartRequest.java        |   6 +-
 .../multipart/JakartaMultiPartRequest.java         |   2 +
 .../multipart/JakartaStreamMultiPartRequest.java   |   2 +
 .../dispatcher/multipart/MultiPartRequest.java     |   8 +-
 .../multipart/ServletMultiPartRequest.java         | 246 +++++++++++++++++++++
 .../dispatcher/servlet/FileUploadServlet.java      |  74 +++++++
 .../struts2/interceptor/FileUploadInterceptor.java |  15 +-
 .../org/apache/struts2/default.properties          |   2 +-
 core/src/main/resources/struts-beans.xml           |   2 +
 ...rutsPrepareAndExecuteFilterIntegrationTest.java |  31 ++-
 .../dispatcher/TwoFilterIntegrationTest.java       |  55 +++--
 .../multipart/ServletMultiPartRequestTest.java     |  78 +++++++
 .../interceptor/FileUploadInterceptorTest.java     |  20 +-
 21 files changed, 597 insertions(+), 65 deletions(-)

diff --git a/apps/showcase/README.txt b/apps/showcase/README.md
similarity index 74%
rename from apps/showcase/README.txt
rename to apps/showcase/README.md
index 8e6cbb03c..a1b828042 100644
--- a/apps/showcase/README.txt
+++ b/apps/showcase/README.md
@@ -1,14 +1,11 @@
-README.txt - showcase
+# Showcase App
 
-Showcase is a collection of examples with code that you might be adopt and 
-adapt in your own applications. 
+Showcase is a collection of examples with code that you might be adopted and 
adapt in your own applications. 
 
-For more on getting started with Struts, see 
+For more on getting started with Struts, see 
https://struts.apache.org/getting-started/
 
-* http://cwiki.apache.org/WW/home.html
+## I18N
 
-I18N:
-=====
 Please note that this project was created with the assumption that it will be 
run
 in an environment where the default locale is set to English. This means that
 the default messages defined in package.properties are in English. If the 
default
diff --git a/apps/showcase/pom.xml b/apps/showcase/pom.xml
index cc6d51d5e..9329b7968 100644
--- a/apps/showcase/pom.xml
+++ b/apps/showcase/pom.xml
@@ -140,10 +140,6 @@
             <groupId>org.directwebremoting</groupId>
             <artifactId>dwr</artifactId>
         </dependency>
-        <dependency>
-            <groupId>commons-fileupload</groupId>
-            <artifactId>commons-fileupload</artifactId>
-        </dependency>
 
        <dependency>
            <groupId>junit</groupId>
diff --git a/apps/showcase/src/main/webapp/WEB-INF/web.xml 
b/apps/showcase/src/main/webapp/WEB-INF/web.xml
index 1bcfa4184..63059a08b 100644
--- a/apps/showcase/src/main/webapp/WEB-INF/web.xml
+++ b/apps/showcase/src/main/webapp/WEB-INF/web.xml
@@ -114,6 +114,12 @@
         <load-on-startup>1</load-on-startup>
     </servlet>
 
+    <servlet>
+        <servlet-name>fileUploadServlet</servlet-name>
+        
<servlet-class>org.apache.struts2.dispatcher.servlet.FileUploadServlet</servlet-class>
+        <load-on-startup>2</load-on-startup>
+    </servlet>
+
     <servlet>
         <servlet-name>strutsServlet</servlet-name>
         
<servlet-class>org.apache.struts2.dispatcher.servlet.StrutsServlet</servlet-class>
@@ -142,6 +148,12 @@
         <load-on-startup>4</load-on-startup>
     </servlet>
 
+    <servlet-mapping>
+        <servlet-name>fileUploadServlet</servlet-name>
+        <url-pattern>/fileupload/*</url-pattern>
+        <url-pattern>/tags/ui/*</url-pattern>
+    </servlet-mapping>
+
     <servlet-mapping>
         <servlet-name>dwr</servlet-name>
         <url-pattern>/dwr/*</url-pattern>
diff --git a/core/src/main/java/org/apache/struts2/dispatcher/Dispatcher.java 
b/core/src/main/java/org/apache/struts2/dispatcher/Dispatcher.java
index 43794c1c5..be1dba13c 100644
--- a/core/src/main/java/org/apache/struts2/dispatcher/Dispatcher.java
+++ b/core/src/main/java/org/apache/struts2/dispatcher/Dispatcher.java
@@ -941,7 +941,7 @@ public class Dispatcher {
      * @return false if disabled
      * @since 2.5.11
      */
-    protected boolean isMultipartSupportEnabled(HttpServletRequest request) {
+    public boolean isMultipartSupportEnabled(HttpServletRequest request) {
         return multipartSupportEnabled;
     }
 
@@ -952,7 +952,7 @@ public class Dispatcher {
      * @return true if it is a multipart request
      * @since 2.5.11
      */
-    protected boolean isMultipartRequest(HttpServletRequest request) {
+    public boolean isMultipartRequest(HttpServletRequest request) {
         String httpMethod = request.getMethod();
         String contentType = request.getContentType();
 
diff --git 
a/core/src/main/java/org/apache/struts2/dispatcher/filter/FileUploadSupport.java
 
b/core/src/main/java/org/apache/struts2/dispatcher/filter/FileUploadSupport.java
new file mode 100644
index 000000000..5c245ace2
--- /dev/null
+++ 
b/core/src/main/java/org/apache/struts2/dispatcher/filter/FileUploadSupport.java
@@ -0,0 +1,65 @@
+/*
+ * 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.dispatcher.filter;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.apache.struts2.dispatcher.Dispatcher;
+import org.apache.struts2.dispatcher.multipart.MultiPartRequest;
+import org.apache.struts2.dispatcher.multipart.MultiPartRequestWrapper;
+import org.apache.struts2.dispatcher.multipart.ServletMultiPartRequest;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+
+/**
+ * Detects if request is a multipart request with file upload, it works
+ * only when combined with {@link StrutsExecuteFilter} or {@link 
StrutsPrepareAndExecuteFilter}
+ * and {@link org.apache.struts2.dispatcher.servlet.FileUploadServlet}
+ */
+interface FileUploadSupport {
+
+    Logger LOG = LogManager.getLogger(FileUploadSupport.class);
+
+    default boolean isFileUploadRequest(HttpServletRequest request) throws 
ServletException {
+        Dispatcher dispatcher = 
Dispatcher.getInstance(request.getServletContext());
+
+        if (dispatcher == null) {
+            throw new ServletException("Dispatcher is not initialised!");
+        }
+
+        // TODO: remove this once the old implementations of MultiPartRequest 
have been removed
+        boolean isMultipartRequest = request instanceof 
MultiPartRequestWrapper;
+        MultiPartRequest multiPartRequest = 
dispatcher.getContainer().getInstance(MultiPartRequest.class);
+        boolean isServletMultipartRequest = multiPartRequest instanceof 
ServletMultiPartRequest;
+
+        if (isMultipartRequest && isServletMultipartRequest) {
+            LOG.debug("Using the new Servlet API 3.1 based file upload 
support");
+            if (dispatcher.isMultipartSupportEnabled(request) && 
dispatcher.isMultipartRequest(request)) {
+                LOG.debug("The file upload request is going to be handled by 
servlet");
+                return true;
+            }
+        } else {
+            LOG.debug("Skipping processing request as other implementation of: 
{} is used: {}", MultiPartRequest.class, multiPartRequest.getClass());
+            LOG.warn("Avoid using this Servlet with other implementations of: 
{} as it was designed to only work with Servlet API 3.1 based implementation", 
MultiPartRequest.class);
+        }
+        return false;
+    }
+
+}
diff --git 
a/core/src/main/java/org/apache/struts2/dispatcher/filter/StrutsExecuteFilter.java
 
b/core/src/main/java/org/apache/struts2/dispatcher/filter/StrutsExecuteFilter.java
index 0e479c449..c07617897 100644
--- 
a/core/src/main/java/org/apache/struts2/dispatcher/filter/StrutsExecuteFilter.java
+++ 
b/core/src/main/java/org/apache/struts2/dispatcher/filter/StrutsExecuteFilter.java
@@ -20,12 +20,17 @@ package org.apache.struts2.dispatcher.filter;
 
 import org.apache.struts2.StrutsStatics;
 import org.apache.struts2.dispatcher.Dispatcher;
-import org.apache.struts2.dispatcher.mapper.ActionMapping;
 import org.apache.struts2.dispatcher.ExecuteOperations;
 import org.apache.struts2.dispatcher.InitOperations;
 import org.apache.struts2.dispatcher.PrepareOperations;
+import org.apache.struts2.dispatcher.mapper.ActionMapping;
 
-import javax.servlet.*;
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 import java.io.IOException;
@@ -34,7 +39,7 @@ import java.io.IOException;
  * Executes the discovered request information.  This filter requires the 
{@link StrutsPrepareFilter} to have already
  * been executed in the current chain.
  */
-public class StrutsExecuteFilter implements StrutsStatics, Filter {
+public class StrutsExecuteFilter implements FileUploadSupport, StrutsStatics, 
Filter {
     protected PrepareOperations prepare;
     protected ExecuteOperations execute;
 
@@ -53,7 +58,6 @@ public class StrutsExecuteFilter implements StrutsStatics, 
Filter {
             prepare = new PrepareOperations(dispatcher);
             execute = new ExecuteOperations(dispatcher);
         }
-
     }
 
     public void doFilter(ServletRequest req, ServletResponse res, FilterChain 
chain) throws IOException, ServletException {
@@ -61,6 +65,11 @@ public class StrutsExecuteFilter implements StrutsStatics, 
Filter {
         HttpServletRequest request = (HttpServletRequest) req;
         HttpServletResponse response = (HttpServletResponse) res;
 
+        if (isFileUploadRequest(request)) {
+            chain.doFilter(request, response);
+            return;
+        }
+
         if (excludeUrl(request)) {
             chain.doFilter(request, response);
             return;
diff --git 
a/core/src/main/java/org/apache/struts2/dispatcher/filter/StrutsPrepareAndExecuteFilter.java
 
b/core/src/main/java/org/apache/struts2/dispatcher/filter/StrutsPrepareAndExecuteFilter.java
index 54ee6883d..0e9c62d67 100644
--- 
a/core/src/main/java/org/apache/struts2/dispatcher/filter/StrutsPrepareAndExecuteFilter.java
+++ 
b/core/src/main/java/org/apache/struts2/dispatcher/filter/StrutsPrepareAndExecuteFilter.java
@@ -44,7 +44,7 @@ import java.util.regex.Pattern;
  * Handles both the preparation and execution phases of the Struts dispatching 
process.  This filter is better to use
  * when you don't have another filter that needs access to action context 
information, such as Sitemesh.
  */
-public class StrutsPrepareAndExecuteFilter implements StrutsStatics, Filter {
+public class StrutsPrepareAndExecuteFilter implements FileUploadSupport, 
StrutsStatics, Filter {
 
     private static final Logger LOG = 
LogManager.getLogger(StrutsPrepareAndExecuteFilter.class);
 
@@ -117,6 +117,11 @@ public class StrutsPrepareAndExecuteFilter implements 
StrutsStatics, Filter {
         HttpServletRequest request = (HttpServletRequest) req;
         HttpServletResponse response = (HttpServletResponse) res;
 
+        if (isFileUploadRequest(request)) {
+            chain.doFilter(request, response);
+            return;
+        }
+
         try {
             String uri = RequestUtils.getUri(request);
             if (excludedPatterns != null && prepare.isUrlExcluded(request, 
excludedPatterns)) {
diff --git 
a/core/src/main/java/org/apache/struts2/dispatcher/filter/StrutsPrepareFilter.java
 
b/core/src/main/java/org/apache/struts2/dispatcher/filter/StrutsPrepareFilter.java
index b35d6cb10..5dac8b2dc 100644
--- 
a/core/src/main/java/org/apache/struts2/dispatcher/filter/StrutsPrepareFilter.java
+++ 
b/core/src/main/java/org/apache/struts2/dispatcher/filter/StrutsPrepareFilter.java
@@ -29,6 +29,7 @@ import javax.servlet.FilterConfig;
 import javax.servlet.ServletException;
 import javax.servlet.ServletRequest;
 import javax.servlet.ServletResponse;
+import javax.servlet.annotation.MultipartConfig;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 import java.io.IOException;
diff --git 
a/core/src/main/java/org/apache/struts2/dispatcher/multipart/AbstractMultiPartRequest.java
 
b/core/src/main/java/org/apache/struts2/dispatcher/multipart/AbstractMultiPartRequest.java
index 700364047..9984d2dc9 100644
--- 
a/core/src/main/java/org/apache/struts2/dispatcher/multipart/AbstractMultiPartRequest.java
+++ 
b/core/src/main/java/org/apache/struts2/dispatcher/multipart/AbstractMultiPartRequest.java
@@ -44,7 +44,7 @@ public abstract class AbstractMultiPartRequest implements 
MultiPartRequest {
     public static final int BUFFER_SIZE = 10240;
 
     /**
-     * Internal list of raised errors to be passed to the the Struts2 
framework.
+     * Internal list of raised errors to be passed to the Struts2 framework.
      */
     protected List<LocalizedMessage> errors = new ArrayList<>();
 
@@ -134,9 +134,9 @@ public abstract class AbstractMultiPartRequest implements 
MultiPartRequest {
         int forwardSlash = fileName.lastIndexOf('/');
         int backwardSlash = fileName.lastIndexOf('\\');
         if (forwardSlash != -1 && forwardSlash > backwardSlash) {
-            fileName = fileName.substring(forwardSlash + 1, fileName.length());
+            fileName = fileName.substring(forwardSlash + 1);
         } else {
-            fileName = fileName.substring(backwardSlash + 1, 
fileName.length());
+            fileName = fileName.substring(backwardSlash + 1);
         }
         return fileName;
     }
diff --git 
a/core/src/main/java/org/apache/struts2/dispatcher/multipart/JakartaMultiPartRequest.java
 
b/core/src/main/java/org/apache/struts2/dispatcher/multipart/JakartaMultiPartRequest.java
index c629ec043..1a3a37e22 100644
--- 
a/core/src/main/java/org/apache/struts2/dispatcher/multipart/JakartaMultiPartRequest.java
+++ 
b/core/src/main/java/org/apache/struts2/dispatcher/multipart/JakartaMultiPartRequest.java
@@ -39,7 +39,9 @@ import java.util.*;
 
 /**
  * Multipart form data request adapter for Jakarta Commons Fileupload package.
+ * @deprecated since Struts 6.2.0 - please use {@link ServletMultiPartRequest} 
instead
  */
+@Deprecated
 public class JakartaMultiPartRequest extends AbstractMultiPartRequest {
 
     static final Logger LOG = 
LogManager.getLogger(JakartaMultiPartRequest.class);
diff --git 
a/core/src/main/java/org/apache/struts2/dispatcher/multipart/JakartaStreamMultiPartRequest.java
 
b/core/src/main/java/org/apache/struts2/dispatcher/multipart/JakartaStreamMultiPartRequest.java
index 2d016f56b..2b069a6a9 100644
--- 
a/core/src/main/java/org/apache/struts2/dispatcher/multipart/JakartaStreamMultiPartRequest.java
+++ 
b/core/src/main/java/org/apache/struts2/dispatcher/multipart/JakartaStreamMultiPartRequest.java
@@ -40,7 +40,9 @@ import java.util.*;
  *
  * @author Chris Cranford
  * @since 2.3.18
+ * @deprecated since Struts 6.2.0, please use {@link ServletMultiPartRequest} 
instead
  */
+@Deprecated
 public class JakartaStreamMultiPartRequest extends AbstractMultiPartRequest {
 
     static final Logger LOG = 
LogManager.getLogger(JakartaStreamMultiPartRequest.class);
diff --git 
a/core/src/main/java/org/apache/struts2/dispatcher/multipart/MultiPartRequest.java
 
b/core/src/main/java/org/apache/struts2/dispatcher/multipart/MultiPartRequest.java
index 445dcd9a2..f21a01e51 100644
--- 
a/core/src/main/java/org/apache/struts2/dispatcher/multipart/MultiPartRequest.java
+++ 
b/core/src/main/java/org/apache/struts2/dispatcher/multipart/MultiPartRequest.java
@@ -18,11 +18,9 @@
  */
 package org.apache.struts2.dispatcher.multipart;
 
-import javax.servlet.http.HttpServletRequest;
-
 import org.apache.struts2.dispatcher.LocalizedMessage;
 
-import java.io.File;
+import javax.servlet.http.HttpServletRequest;
 import java.io.IOException;
 import java.util.Enumeration;
 import java.util.List;
@@ -33,7 +31,7 @@ import java.util.List;
 public interface MultiPartRequest {
 
     void parse(HttpServletRequest request, String saveDir) throws IOException;
-    
+
     /**
      * Returns an enumeration of the parameter names for uploaded files
      *
@@ -48,7 +46,7 @@ public interface MultiPartRequest {
      *
      * @param fieldName input field name
      * @return an array of content encoding for the specified input field name 
or <tt>null</tt> if
-     *         no content type was specified.
+     * no content type was specified.
      */
     String[] getContentType(String fieldName);
 
diff --git 
a/core/src/main/java/org/apache/struts2/dispatcher/multipart/ServletMultiPartRequest.java
 
b/core/src/main/java/org/apache/struts2/dispatcher/multipart/ServletMultiPartRequest.java
new file mode 100644
index 000000000..0ec967564
--- /dev/null
+++ 
b/core/src/main/java/org/apache/struts2/dispatcher/multipart/ServletMultiPartRequest.java
@@ -0,0 +1,246 @@
+/*
+ * 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.dispatcher.multipart;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.apache.struts2.dispatcher.LocalizedMessage;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.Part;
+import java.io.File;
+import java.io.IOException;
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+
+/**
+ * Pure Servlet API 3.1 based implementation
+ */
+public class ServletMultiPartRequest extends AbstractMultiPartRequest {
+
+    private static final Logger LOG = 
LogManager.getLogger(ServletMultiPartRequest.class);
+
+    private Map<String, List<FileData>> uploadedFiles = new HashMap<>();
+    private Map<String, List<String>> parameters = new HashMap<>();
+
+    @Override
+    public void parse(HttpServletRequest request, String saveDir) throws 
IOException {
+        try {
+            if (isSizeLimitExceeded(request)) {
+                applySizeLimitExceededError(request);
+                return;
+            }
+            parseParts(request, saveDir);
+        } catch (ServletException e) {
+            LOG.warn("Error occurred during parsing of multi part request", e);
+            LocalizedMessage errorMessage = buildErrorMessage(e, new 
Object[]{e.getMessage()});
+            if (!errors.contains(errorMessage)) {
+                errors.add(errorMessage);
+            }
+        }
+    }
+
+    private void parseParts(HttpServletRequest request, String saveDir) throws 
IOException, ServletException {
+        Collection<Part> parts = request.getParts();
+        if (parts.isEmpty()) {
+            LocalizedMessage error = buildErrorMessage(new IOException(), new 
Object[]{"No boundary defined!"});
+            if (!errors.contains(error)) {
+                errors.add(error);
+            }
+            return;
+        }
+        for (Part part : parts) {
+            if (part.getSubmittedFileName() == null) { // normal field
+                LOG.debug("Ignoring a normal form field: {}", part.getName());
+            } else { // file upload
+                LOG.debug("Storing file: {} in save dir: {}", 
part.getSubmittedFileName(), saveDir);
+                parseFile(part, saveDir);
+            }
+        }
+    }
+
+    private boolean isSizeLimitExceeded(HttpServletRequest request) {
+        if (request.getContentLength() > -1) {
+            return maxSizeProvided && request.getContentLength() > maxSize;
+        } else {
+            LOG.debug("Request Content Length is: {} which means the size 
overflows 2 GB!", request.getContentLength());
+            return true;
+        }
+    }
+
+    private void applySizeLimitExceededError(HttpServletRequest request) {
+        String exceptionMessage = "Request size: " + 
request.getContentLength() + " exceeded maximum size limit: " + maxSize;
+        SizeLimitExceededException exception = new 
SizeLimitExceededException(exceptionMessage);
+        LocalizedMessage message = buildErrorMessage(exception, new 
Object[]{request.getContentLength(), maxSize});
+        if (!errors.contains(message)) {
+            errors.add(message);
+        }
+    }
+
+    private void parseFile(Part part, String saveDir) throws IOException {
+        File file = extractFile(part, saveDir);
+        List<FileData> data = uploadedFiles.get(part.getName());
+        if (data == null) {
+            data = new ArrayList<>();
+        }
+        data.add(new FileData(file, part.getContentType(), 
part.getSubmittedFileName()));
+        uploadedFiles.put(part.getName(), data);
+    }
+
+    private File extractFile(Part part, String saveDir) throws IOException {
+        String name = part.getSubmittedFileName()
+            .substring(part.getSubmittedFileName().lastIndexOf('/') + 1)
+            .substring(part.getSubmittedFileName().lastIndexOf('\\') + 1);
+
+        String prefix = name;
+        String suffix = "";
+
+        if (name.contains(".")) {
+            prefix = name.substring(0, name.lastIndexOf('.'));
+            suffix = name.substring(name.lastIndexOf('.'));
+        }
+
+        if (prefix.length() < 3) {
+            prefix = UUID.randomUUID().toString();
+        }
+
+        File tempFile = File.createTempFile(prefix + "_", suffix, new 
File(saveDir));
+        LOG.debug("Stored file: {} as temporary file: {}", 
part.getSubmittedFileName(), tempFile.getName());
+        return tempFile;
+    }
+
+    @Override
+    public Enumeration<String> getFileParameterNames() {
+        return Collections.enumeration(uploadedFiles.keySet());
+    }
+
+    @Override
+    public String[] getContentType(String fieldName) {
+        List<FileData> fileData = uploadedFiles.get(fieldName);
+        if (fileData == null) {
+            LOG.debug("No file data for: {}", fieldName);
+            return null;
+        }
+        return 
fileData.stream().map(FileData::getContentType).toArray(String[]::new);
+    }
+
+    @Override
+    public UploadedFile[] getFile(String fieldName) {
+        List<FileData> fileData = uploadedFiles.get(fieldName);
+        if (fileData == null) {
+            LOG.debug("No file data for: {}", fieldName);
+            return null;
+        }
+
+        return fileData.stream().map(data -> new 
StrutsUploadedFile(data.getFile())).toArray(StrutsUploadedFile[]::new);
+    }
+
+    @Override
+    public String[] getFileNames(String fieldName) {
+        List<FileData> fileData = uploadedFiles.get(fieldName);
+        if (fileData == null) {
+            LOG.debug("No file data for: {}", fieldName);
+            return null;
+        }
+
+        return 
fileData.stream().map(FileData::getOriginalName).toArray(String[]::new);
+    }
+
+    @Override
+    public String[] getFilesystemName(String fieldName) {
+        List<FileData> fileData = uploadedFiles.get(fieldName);
+        if (fileData == null) {
+            LOG.debug("No file data for: {}", fieldName);
+            return null;
+        }
+
+        return fileData.stream().map(data -> 
data.getFile().getName()).toArray(String[]::new);
+    }
+
+    @Override
+    public String getParameter(String name) {
+        List<String> params = parameters.get(name);
+        if (params != null && params.size() > 0) {
+            return params.get(0);
+        }
+        LOG.debug("Ignoring parameter: {}", name);
+        return null;
+    }
+
+    @Override
+    public Enumeration<String> getParameterNames() {
+        return Collections.enumeration(parameters.keySet());
+    }
+
+    @Override
+    public String[] getParameterValues(String name) {
+        List<String> v = parameters.get(name);
+        if (v != null && v.size() > 0) {
+            return v.toArray(new String[0]);
+        }
+
+        LOG.debug("Ignoring values for parameter: {}", name);
+        return null;
+    }
+
+    @Override
+    public void cleanUp() {
+        uploadedFiles = new HashMap<>();
+        parameters = new HashMap<>();
+    }
+
+    public static class FileData implements Serializable {
+
+        private final File file;
+        private final String contentType;
+        private final String originalName;
+
+        public FileData(File file, String contentType, String originalName) {
+            this.file = file;
+            this.contentType = contentType;
+            this.originalName = originalName;
+        }
+
+        public File getFile() {
+            return file;
+        }
+
+        public String getContentType() {
+            return contentType;
+        }
+
+        public String getOriginalName() {
+            return originalName;
+        }
+    }
+
+    public static class SizeLimitExceededException extends Exception {
+        public SizeLimitExceededException(String message) {
+            super(message);
+        }
+    }
+}
diff --git 
a/core/src/main/java/org/apache/struts2/dispatcher/servlet/FileUploadServlet.java
 
b/core/src/main/java/org/apache/struts2/dispatcher/servlet/FileUploadServlet.java
new file mode 100644
index 000000000..ca420b557
--- /dev/null
+++ 
b/core/src/main/java/org/apache/struts2/dispatcher/servlet/FileUploadServlet.java
@@ -0,0 +1,74 @@
+/*
+ * 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.dispatcher.servlet;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.apache.struts2.RequestUtils;
+import org.apache.struts2.dispatcher.Dispatcher;
+import org.apache.struts2.dispatcher.ExecuteOperations;
+import org.apache.struts2.dispatcher.PrepareOperations;
+import org.apache.struts2.dispatcher.mapper.ActionMapping;
+
+import javax.servlet.ServletException;
+import javax.servlet.annotation.MultipartConfig;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+@MultipartConfig
+public class FileUploadServlet extends HttpServlet {
+
+    private static final Logger LOG = 
LogManager.getLogger(FileUploadServlet.class);
+
+    @Override
+    public void service(HttpServletRequest request, HttpServletResponse 
response) throws IOException, ServletException {
+        Dispatcher dispatcher = 
Dispatcher.getInstance(request.getServletContext());
+        if (dispatcher == null) {
+            throw new ServletException("No Dispatchers instance, servlet used 
out of the Struts filters!");
+        }
+
+        if (!dispatcher.isMultipartSupportEnabled(request)) {
+            throw new ServletException("Support for file upload is disabled!");
+        }
+
+        PrepareOperations prepare = new PrepareOperations(dispatcher);
+        ExecuteOperations execute = new ExecuteOperations(dispatcher);
+
+        String uri = RequestUtils.getUri(request);
+
+        if (dispatcher.isMultipartRequest(request)) {
+            prepare.setEncodingAndLocale(request, response);
+            prepare.createActionContext(request, response);
+            prepare.assignDispatcherToThread();
+            HttpServletRequest wrappedRequest = prepare.wrapRequest(request);
+            ActionMapping mapping = prepare.findActionMapping(wrappedRequest, 
response, true);
+            if (mapping == null) {
+                throw new ServletException(String.format("Cannot find mapping 
for %s, passing to other filters", uri));
+            } else {
+                LOG.trace("Found mapping {} for {}", mapping, uri);
+                execute.executeAction(wrappedRequest, response, mapping);
+            }
+        } else {
+            LOG.debug("Not a file upload request, ignoring uri: {}", uri);
+        }
+    }
+
+}
diff --git 
a/core/src/main/java/org/apache/struts2/interceptor/FileUploadInterceptor.java 
b/core/src/main/java/org/apache/struts2/interceptor/FileUploadInterceptor.java
index bb77ea093..482ab15b3 100644
--- 
a/core/src/main/java/org/apache/struts2/interceptor/FileUploadInterceptor.java
+++ 
b/core/src/main/java/org/apache/struts2/interceptor/FileUploadInterceptor.java
@@ -266,10 +266,10 @@ public class FileUploadInterceptor extends 
AbstractInterceptor {
         }
 
         // bind allowed Files
-        Enumeration fileParameterNames = multiWrapper.getFileParameterNames();
+        Enumeration<String> fileParameterNames = 
multiWrapper.getFileParameterNames();
         while (fileParameterNames != null && 
fileParameterNames.hasMoreElements()) {
             // get the value of this input tag
-            String inputName = (String) fileParameterNames.nextElement();
+            String inputName = fileParameterNames.nextElement();
 
             // get the content type
             String[] contentType = multiWrapper.getContentTypes(inputName);
@@ -298,9 +298,9 @@ public class FileUploadInterceptor extends 
AbstractInterceptor {
 
                         if (!acceptedFiles.isEmpty()) {
                             Map<String, Parameter> newParams = new HashMap<>();
-                            newParams.put(inputName, new 
Parameter.File(inputName, acceptedFiles.toArray(new 
UploadedFile[acceptedFiles.size()])));
-                            newParams.put(contentTypeName, new 
Parameter.File(contentTypeName, acceptedContentTypes.toArray(new 
String[acceptedContentTypes.size()])));
-                            newParams.put(fileNameName, new 
Parameter.File(fileNameName, acceptedFileNames.toArray(new 
String[acceptedFileNames.size()])));
+                            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);
                         }
                     }
@@ -430,9 +430,10 @@ public class FileUploadInterceptor extends 
AbstractInterceptor {
 
     private boolean isNonEmpty(Object[] objArray) {
         boolean result = false;
-        for (int index = 0; index < objArray.length && !result; index++) {
-            if (objArray[index] != null) {
+        for (Object o : objArray) {
+            if (o != null) {
                 result = true;
+                break;
             }
         }
         return result;
diff --git a/core/src/main/resources/org/apache/struts2/default.properties 
b/core/src/main/resources/org/apache/struts2/default.properties
index 5847db3be..2b53d6438 100644
--- a/core/src/main/resources/org/apache/struts2/default.properties
+++ b/core/src/main/resources/org/apache/struts2/default.properties
@@ -64,7 +64,7 @@ struts.objectFactory.spring.enableAopSupport = false
 # struts.multipart.parser=cos
 # struts.multipart.parser=pell
 # struts.multipart.parser=jakarta-stream
-struts.multipart.parser=jakarta
+struts.multipart.parser=servlet
 ### Uses javax.servlet.context.tempdir by default
 struts.multipart.saveDir=
 struts.multipart.maxSize=2097152
diff --git a/core/src/main/resources/struts-beans.xml 
b/core/src/main/resources/struts-beans.xml
index eac1ff8be..87a517667 100644
--- a/core/src/main/resources/struts-beans.xml
+++ b/core/src/main/resources/struts-beans.xml
@@ -88,6 +88,8 @@
           
class="org.apache.struts2.dispatcher.multipart.JakartaMultiPartRequest" 
scope="prototype"/>
     <bean type="org.apache.struts2.dispatcher.multipart.MultiPartRequest" 
name="jakarta-stream"
           
class="org.apache.struts2.dispatcher.multipart.JakartaStreamMultiPartRequest" 
scope="prototype"/>
+    <bean type="org.apache.struts2.dispatcher.multipart.MultiPartRequest" 
name="servlet"
+          
class="org.apache.struts2.dispatcher.multipart.ServletMultiPartRequest" 
scope="prototype"/>
 
     <bean type="org.apache.struts2.views.TagLibraryModelProvider" name="s"
           class="org.apache.struts2.views.DefaultTagLibrary"/>
diff --git 
a/core/src/test/java/org/apache/struts2/dispatcher/StrutsPrepareAndExecuteFilterIntegrationTest.java
 
b/core/src/test/java/org/apache/struts2/dispatcher/StrutsPrepareAndExecuteFilterIntegrationTest.java
index a5419cd8b..f7212f22d 100644
--- 
a/core/src/test/java/org/apache/struts2/dispatcher/StrutsPrepareAndExecuteFilterIntegrationTest.java
+++ 
b/core/src/test/java/org/apache/struts2/dispatcher/StrutsPrepareAndExecuteFilterIntegrationTest.java
@@ -26,6 +26,7 @@ import org.springframework.mock.web.MockFilterChain;
 import org.springframework.mock.web.MockFilterConfig;
 import org.springframework.mock.web.MockHttpServletRequest;
 import org.springframework.mock.web.MockHttpServletResponse;
+import org.springframework.mock.web.MockServletContext;
 
 import javax.servlet.ServletException;
 import javax.servlet.ServletRequest;
@@ -42,9 +43,10 @@ import java.util.ArrayList;
 public class StrutsPrepareAndExecuteFilterIntegrationTest extends TestCase {
 
     public void test404() throws ServletException, IOException {
-        MockHttpServletRequest request = new MockHttpServletRequest();
+        MockServletContext context = new MockServletContext();
+        MockHttpServletRequest request = new MockHttpServletRequest(context);
         MockHttpServletResponse response = new MockHttpServletResponse();
-        MockFilterConfig filterConfig = new MockFilterConfig();
+        MockFilterConfig filterConfig = new MockFilterConfig(context);
         MockFilterChain filterChain = new MockFilterChain() {
             @Override
             public void doFilter(ServletRequest req, ServletResponse res) {
@@ -62,9 +64,10 @@ public class StrutsPrepareAndExecuteFilterIntegrationTest 
extends TestCase {
     }
 
     public void test200() throws ServletException, IOException {
-        MockHttpServletRequest request = new MockHttpServletRequest();
+        MockServletContext context = new MockServletContext();
+        MockHttpServletRequest request = new MockHttpServletRequest(context);
         MockHttpServletResponse response = new MockHttpServletResponse();
-        MockFilterConfig filterConfig = new MockFilterConfig();
+        MockFilterConfig filterConfig = new MockFilterConfig(context);
         MockFilterChain filterChain = new MockFilterChain() {
             @Override
             public void doFilter(ServletRequest req, ServletResponse res) {
@@ -82,9 +85,10 @@ public class StrutsPrepareAndExecuteFilterIntegrationTest 
extends TestCase {
     }
 
     public void testActionMappingLookup() throws ServletException, IOException 
{
-        MockHttpServletRequest request = new MockHttpServletRequest();
+        MockServletContext context = new MockServletContext();
+        MockHttpServletRequest request = new MockHttpServletRequest(context);
         MockHttpServletResponse response = new MockHttpServletResponse();
-        MockFilterConfig filterConfig = new MockFilterConfig();
+        MockFilterConfig filterConfig = new MockFilterConfig(context);
         MockFilterChain filterChain = new MockFilterChain() {
             @Override
             public void doFilter(ServletRequest req, ServletResponse res) {
@@ -116,9 +120,10 @@ public class StrutsPrepareAndExecuteFilterIntegrationTest 
extends TestCase {
     }
 
     public void testUriPatternExclusion() throws ServletException, IOException 
{
-        MockHttpServletRequest request = new MockHttpServletRequest();
+        MockServletContext context = new MockServletContext();
+        MockHttpServletRequest request = new MockHttpServletRequest(context);
         MockHttpServletResponse response = new MockHttpServletResponse();
-        MockFilterConfig filterConfig = new MockFilterConfig();
+        MockFilterConfig filterConfig = new MockFilterConfig(context);
         MockFilterChain filterChain = new MockFilterChain() {
             @Override
             public void doFilter(ServletRequest req, ServletResponse res) {
@@ -142,9 +147,10 @@ public class StrutsPrepareAndExecuteFilterIntegrationTest 
extends TestCase {
     }
 
     public void testStaticFallthrough() throws ServletException, IOException {
-        MockHttpServletRequest request = new MockHttpServletRequest();
+        MockServletContext context = new MockServletContext();
+        MockHttpServletRequest request = new MockHttpServletRequest(context);
         MockHttpServletResponse response = new MockHttpServletResponse();
-        MockFilterConfig filterConfig = new MockFilterConfig();
+        MockFilterConfig filterConfig = new MockFilterConfig(context);
         MockFilterChain filterChain = new MockFilterChain() {
             @Override
             public void doFilter(ServletRequest req, ServletResponse res) {
@@ -169,9 +175,10 @@ public class StrutsPrepareAndExecuteFilterIntegrationTest 
extends TestCase {
     }
 
     public void testStaticExecute() throws ServletException, IOException {
-        MockHttpServletRequest request = new MockHttpServletRequest();
+        MockServletContext context = new MockServletContext();
+        MockHttpServletRequest request = new MockHttpServletRequest(context);
         MockHttpServletResponse response = new MockHttpServletResponse();
-        MockFilterConfig filterConfig = new MockFilterConfig();
+        MockFilterConfig filterConfig = new MockFilterConfig(context);
         MockFilterChain filterChain = new MockFilterChain() {
             @Override
             public void doFilter(ServletRequest req, ServletResponse res) {
diff --git 
a/core/src/test/java/org/apache/struts2/dispatcher/TwoFilterIntegrationTest.java
 
b/core/src/test/java/org/apache/struts2/dispatcher/TwoFilterIntegrationTest.java
index edc88ba17..7075c7b68 100644
--- 
a/core/src/test/java/org/apache/struts2/dispatcher/TwoFilterIntegrationTest.java
+++ 
b/core/src/test/java/org/apache/struts2/dispatcher/TwoFilterIntegrationTest.java
@@ -20,44 +20,60 @@ package org.apache.struts2.dispatcher;
 
 import com.opensymphony.xwork2.ActionContext;
 import junit.framework.TestCase;
-import org.apache.struts2.dispatcher.Dispatcher;
-import org.apache.struts2.dispatcher.PrepareOperations;
 import org.apache.struts2.dispatcher.filter.StrutsExecuteFilter;
 import org.apache.struts2.dispatcher.filter.StrutsPrepareFilter;
-import org.springframework.mock.web.*;
+import org.springframework.mock.web.MockFilterChain;
+import org.springframework.mock.web.MockFilterConfig;
+import org.springframework.mock.web.MockHttpServletRequest;
+import org.springframework.mock.web.MockHttpServletResponse;
+import org.springframework.mock.web.MockServletContext;
 
-import javax.servlet.*;
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
 import java.io.IOException;
-import java.util.LinkedList;
 import java.util.Arrays;
+import java.util.LinkedList;
 
 /**
  * Integration tests for the filter
  */
 public class TwoFilterIntegrationTest extends TestCase {
-    StrutsExecuteFilter filterExecute;
-    StrutsPrepareFilter filterPrepare;
-    Filter failFilter;
+
+    private StrutsExecuteFilter filterExecute;
+    private StrutsPrepareFilter filterPrepare;
+    private Filter failFilter;
     private Filter stringFilter;
 
     public void setUp() {
         filterPrepare = new StrutsPrepareFilter();
         filterExecute = new StrutsExecuteFilter();
         failFilter = new Filter() {
-            public void init(FilterConfig filterConfig) throws 
ServletException {}
+            public void init(FilterConfig filterConfig) throws 
ServletException {
+            }
+
             public void doFilter(ServletRequest request, ServletResponse 
response, FilterChain chain) throws IOException, ServletException {
                 fail("Should never get here");
             }
-            public void destroy() {}
+
+            public void destroy() {
+            }
         };
         stringFilter = new Filter() {
-            public void init(FilterConfig filterConfig) throws 
ServletException {}
+            public void init(FilterConfig filterConfig) throws 
ServletException {
+            }
+
             public void doFilter(ServletRequest request, ServletResponse 
response, FilterChain chain) throws IOException, ServletException {
                 response.getWriter().write("content");
                 assertNotNull(ActionContext.getContext());
                 assertNotNull(Dispatcher.getInstance());
             }
-            public void destroy() {}
+
+            public void destroy() {
+            }
         };
     }
 
@@ -86,7 +102,9 @@ public class TwoFilterIntegrationTest extends TestCase {
 
     public void testFilterInMiddle() throws ServletException, IOException {
         Filter middle = new Filter() {
-            public void init(FilterConfig filterConfig) throws 
ServletException {}
+            public void init(FilterConfig filterConfig) throws 
ServletException {
+            }
+
             public void doFilter(ServletRequest request, ServletResponse 
response, FilterChain chain) throws IOException, ServletException {
                 assertNotNull(ActionContext.getContext());
                 assertNotNull(Dispatcher.getInstance());
@@ -94,7 +112,9 @@ public class TwoFilterIntegrationTest extends TestCase {
                 chain.doFilter(request, response);
                 assertEquals("hello", 
ActionContext.getContext().getActionInvocation().getProxy().getActionName());
             }
-            public void destroy() {}
+
+            public void destroy() {
+            }
         };
         MockHttpServletResponse response = run("/hello.action", filterPrepare, 
middle, filterExecute, failFilter);
         assertEquals(200, response.getStatus());
@@ -102,9 +122,12 @@ public class TwoFilterIntegrationTest extends TestCase {
 
     private MockHttpServletResponse run(String uri, final Filter... filters) 
throws ServletException, IOException {
         final LinkedList<Filter> filterList = new 
LinkedList<>(Arrays.asList(filters));
-        MockHttpServletRequest request = new MockHttpServletRequest();
+
+        MockServletContext context = new MockServletContext();
+        MockHttpServletRequest request = new MockHttpServletRequest(context);
         MockHttpServletResponse response = new MockHttpServletResponse();
-        MockFilterConfig filterConfig = new MockFilterConfig();
+        MockFilterConfig filterConfig = new MockFilterConfig(context);
+
         MockFilterChain filterChain = new MockFilterChain() {
             @Override
             public void doFilter(ServletRequest req, ServletResponse res) {
diff --git 
a/core/src/test/java/org/apache/struts2/dispatcher/multipart/ServletMultiPartRequestTest.java
 
b/core/src/test/java/org/apache/struts2/dispatcher/multipart/ServletMultiPartRequestTest.java
new file mode 100644
index 000000000..b036477b2
--- /dev/null
+++ 
b/core/src/test/java/org/apache/struts2/dispatcher/multipart/ServletMultiPartRequestTest.java
@@ -0,0 +1,78 @@
+/*
+ * 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.dispatcher.multipart;
+
+import org.apache.struts2.dispatcher.LocalizedMessage;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mockito;
+import org.springframework.mock.web.DelegatingServletInputStream;
+
+import javax.servlet.http.HttpServletRequest;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class ServletMultiPartRequestTest {
+
+    private ServletMultiPartRequest multiPart;
+    private Path tempDir;
+
+    @Before
+    public void initialize() {
+        multiPart = new ServletMultiPartRequest();
+        tempDir = Paths.get("target", "multi-part-test");
+    }
+
+    /**
+     * Number of bytes in files greater than 2GB overflow the {@code int} 
primitive.
+     * The {@link HttpServletRequest#getContentLength()} returns {@literal -1}
+     * when the header is not present, or the size is greater than {@link 
Integer#MAX_VALUE}.
+     */
+    @Test
+    public void unknownContentLength() throws IOException {
+        HttpServletRequest request = mock(HttpServletRequest.class);
+        when(request.getContentType()).thenReturn("multipart/form-data; 
charset=utf-8; boundary=__X_BOUNDARY__");
+        when(request.getMethod()).thenReturn("POST");
+        when(request.getContentLength()).thenReturn(-1);
+
+        String entity = "\r\n--__X_BOUNDARY__\r\n" +
+            "Content-Disposition: form-data; name=\"upload\"; 
filename=\"test.csv\"\r\n" +
+            "Content-Type: text/csv\r\n\r\n1,2\r\n\r\n" +
+            "--__X_BOUNDARY__\r\n" +
+            "Content-Disposition: form-data; name=\"upload2\"; 
filename=\"test2.csv\"\r\n" +
+            "Content-Type: text/csv\r\n\r\n3,4\r\n\r\n" +
+            "--__X_BOUNDARY__--\r\n";
+        when(request.getInputStream()).thenReturn(new 
DelegatingServletInputStream(new 
ByteArrayInputStream(entity.getBytes(StandardCharsets.UTF_8))));
+
+        multiPart.setMaxSize("4");
+
+        multiPart.parse(request, tempDir.toString());
+        LocalizedMessage next = multiPart.getErrors().iterator().next();
+
+        assertEquals(next.getTextKey(), 
"struts.messages.upload.error.SizeLimitExceededException");
+    }
+}
diff --git 
a/core/src/test/java/org/apache/struts2/interceptor/FileUploadInterceptorTest.java
 
b/core/src/test/java/org/apache/struts2/interceptor/FileUploadInterceptorTest.java
index b6a41010e..366db033e 100644
--- 
a/core/src/test/java/org/apache/struts2/interceptor/FileUploadInterceptorTest.java
+++ 
b/core/src/test/java/org/apache/struts2/interceptor/FileUploadInterceptorTest.java
@@ -29,11 +29,13 @@ import org.apache.struts2.ServletActionContext;
 import org.apache.struts2.StrutsInternalTestCase;
 import org.apache.struts2.TestAction;
 import org.apache.struts2.dispatcher.HttpParameters;
-import org.apache.struts2.dispatcher.multipart.JakartaMultiPartRequest;
+import org.apache.struts2.dispatcher.multipart.ServletMultiPartRequest;
 import org.apache.struts2.dispatcher.multipart.StrutsUploadedFile;
 import org.apache.struts2.dispatcher.multipart.MultiPartRequestWrapper;
 import org.apache.struts2.dispatcher.multipart.UploadedFile;
+import org.springframework.http.MediaType;
 import org.springframework.mock.web.MockHttpServletRequest;
+import org.springframework.mock.web.MockPart;
 
 import javax.servlet.http.HttpServletRequest;
 import java.io.File;
@@ -225,7 +227,7 @@ public class FileUploadInterceptorTest extends 
StrutsInternalTestCase {
     public void testInvalidContentTypeMultipartRequest() throws Exception {
         MockHttpServletRequest req = new MockHttpServletRequest();
 
-        req.setContentType("multipart/form-data"); // not a multipart 
contentype
+        req.setContentType("multipart/form-data"); // not a multipart content 
type
         req.setMethod("post");
 
         MyFileupAction action = container.inject(MyFileupAction.class);
@@ -279,6 +281,9 @@ public class FileUploadInterceptorTest extends 
StrutsInternalTestCase {
                 "\r\n" +
                 "-----1234--\r\n");
         req.setContent(content.getBytes("US-ASCII"));
+        MockPart part = new MockPart("file", "deleteme.txt", "Unit test of 
FileUploadInterceptor".getBytes());
+        part.getHeaders().setContentType(MediaType.TEXT_HTML);
+        req.addPart(part);
 
         MyFileupAction action = new MyFileupAction();
 
@@ -338,6 +343,15 @@ public class FileUploadInterceptorTest extends 
StrutsInternalTestCase {
         content.append("--");
         content.append(endline);
         req.setContent(content.toString().getBytes());
+        MockPart part1 = new MockPart("file", "test.html", 
plainContent.getBytes());
+        part1.getHeaders().setContentType(MediaType.TEXT_PLAIN);
+        req.addPart(part1);
+        MockPart part2 = new MockPart("file", "test1.html", 
htmlContent.getBytes());
+        part2.getHeaders().setContentType(MediaType.TEXT_HTML);
+        req.addPart(part2);
+        MockPart part3 = new MockPart("file", "test2.html", 
htmlContent.getBytes());
+        part3.getHeaders().setContentType(MediaType.TEXT_HTML);
+        req.addPart(part3);
 
         assertTrue(ServletFileUpload.isMultipartContent(req));
 
@@ -430,7 +444,7 @@ public class FileUploadInterceptorTest extends 
StrutsInternalTestCase {
     }
 
     private MultiPartRequestWrapper createMultipartRequest(HttpServletRequest 
req, int maxsize) throws IOException {
-        JakartaMultiPartRequest jak = new JakartaMultiPartRequest();
+        ServletMultiPartRequest jak = new ServletMultiPartRequest();
         jak.setMaxSize(String.valueOf(maxsize));
         return new MultiPartRequestWrapper(jak, req, 
tempDir.getAbsolutePath(), new DefaultLocaleProvider());
     }

Reply via email to