struts2 file upload loosing parameters

Problem solved !

From the updated documentation, now the problem can be solved by using the new JakartaStreamMultiPartRequest :

As from Struts version 2.3.18 a new implementation of MultiPartRequest
was added – JakartaStreamMultiPartRequest. It can be used to handle
large files, see WW-3025 for more details, but you can simple set

<constant name="struts.multipart.parser" value="jakarta-stream" />

in struts.xml to start using it.

From the linked JIRA body :

When any size limits exceed, immediately a
FileUploadBase.SizeLimitExceededException or
FileUploadBase.FileSizeLimitExceededException is thrown and parsing of
the multipart request terminates without providing request parameters
for further processing.

This basically makes it impossible for any web application to handle
size limit exceeded cases gracefully.

My proposal is that request parsing should always complete to deliver
the request parameters. Size limit exceeded cases/exceptions might be
collected for later retrieval, FileSizeLimitExeededException should be
mapped to the FileItem to allow some validation on the FileItem on
application level. This would allow to mark upload input fields as
erronous if the uploaded file was too big.

Actually I made a patch for that (see attachment). With this patch,
commons-fileupload always completes request parsing in case of size
limit exceedings and only after complete parsing will throw an
exception if one was detected.

and Chris Cranford’s comment:

I am working on a new multipart parser for Struts2 I am calling
JakartaStreamMultiPartRequest.

This multi-part parser behaves identical to the existing Jakarta
multi-part parser except that it uses the Commons FileUpload Streaming
API and rather than delegating maximum request size check to the File
Upload API, it’s done internally to avoid the existing problem of the
Upload API breaking the loop iteration and parameters being lost.

Awesome, thanks guys 🙂


Old answer

I guess it is due to the different behavior of

  • a single file (or more files) that is exceeding its maximum defined size, and then can be redirected back at the end of a normal process with the INPUT result, and
  • the violation of the maximum size of the entire Request, that will (probably?) break any other element parsing, because it is a security mechanism, not a feature like the file size check;

When the files are parsed first (it should depend on their order in the page), if a file breaks the limit of the multipart request size, the other fields (the form fields) won’t be read and hence not returned back with the INPUT result.

Struts2 uses the Jakarta implementation for the MultiPartRequestWrapper:

struts.multipart.parser – This property should be set to a class that extends MultiPartRequest. Currently, the framework ships with the Jakarta FileUpload implementation.

You can find the source code on Struts2 official site or here (faster to google); this is what is called when posting a multipart form:

 public void parse(HttpServletRequest request, String saveDir) throws IOException {
        try {
            setLocale(request);
            processUpload(request, saveDir);
        } catch (FileUploadBase.SizeLimitExceededException e) {
            if (LOG.isWarnEnabled()) {
                LOG.warn("Request exceeded size limit!", e);
            }
            String errorMessage = buildErrorMessage(e, new Object[]{e.getPermittedSize(), e.getActualSize()});
            if (!errors.contains(errorMessage)) {
                errors.add(errorMessage);
            }
        } catch (Exception e) {
            if (LOG.isWarnEnabled()) {
                LOG.warn("Unable to parse request", e);
            }
            String errorMessage = buildErrorMessage(e, new Object[]{});
            if (!errors.contains(errorMessage)) {
                errors.add(errorMessage);
            }
        }
    }

then, this is where it cycles the multipart Items, both files and form fields:

   private void processUpload(HttpServletRequest request, String saveDir) throws FileUploadException, UnsupportedEncodingException {
        for (FileItem item : parseRequest(request, saveDir)) {
            if (LOG.isDebugEnabled()) {
                LOG.debug("Found item " + item.getFieldName());
            }
            if (item.isFormField()) {
                processNormalFormField(item, request.getCharacterEncoding());
            } else {
                processFileField(item);
            }
        }
    }

that will end, in the FileUploadBase, in this implementation for each item:

 FileItemStreamImpl(String pName, String pFieldName,
                    String pContentType, boolean pFormField,
                    long pContentLength) throws IOException {
                name = pName;
                fieldName = pFieldName;
                contentType = pContentType;
                formField = pFormField;
                final ItemInputStream itemStream = multi.newInputStream();
                InputStream istream = itemStream;
                if (fileSizeMax != -1) {
                    if (pContentLength != -1
                            &&  pContentLength > fileSizeMax) {
                        FileSizeLimitExceededException e =
                            new FileSizeLimitExceededException(
                                format("The field %s exceeds its maximum permitted size of %s bytes.",
                                       fieldName, fileSizeMax),
                                pContentLength, fileSizeMax);
                        e.setFileName(pName);
                        e.setFieldName(pFieldName);
                        throw new FileUploadIOException(e);
                    }
                    istream = new LimitedInputStream(istream, fileSizeMax) {
                        @Override
                        protected void raiseError(long pSizeMax, long pCount)
                                throws IOException {
                            itemStream.close(true);
                            FileSizeLimitExceededException e =
                                new FileSizeLimitExceededException(
                                    format("The field %s exceeds its maximum permitted size of %s bytes.",
                                           fieldName, pSizeMax),
                                    pCount, pSizeMax);
                            e.setFieldName(fieldName);
                            e.setFileName(name);
                            throw new FileUploadIOException(e);
                        }
                    };
                }
                stream = istream;
            }

as you can see, it handles pretty differently the file size cap and the request size cap;

I’ve looked at the source for fun but you could really confirm (or correct) this assumptions, trying to debug the MultiPartRequestWrapper to see if what happens inside is what I think is going on… good luck and have fun.

Leave a Comment