Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

struts2 file upload loosing parameters

Using Struts 2.3.15.1

Implementing file upload in struts2. This is something I've done a number of times, however, I'm trying to include some sanity checks (i.e. max file size primarily). I have the fileUpload interceptor in place as the last interceptor in my stack (i.e. struts.xml). My stack includes a few in-house interceptors as well as the validationWorkflowStack. I've set the following property in my struts.properties file:

struts.multipart.maxSize = 2000000

In addition to the file upload, I'm passing a few other params in my form. Form is defined as:

<s:form action="addResource" method="post" enctype="multipart/form-data"> 
  <s:hidden name="rfqId" value='%{rfq.id}' />
  <s:file name="uploadFile" id="uploadFile" label="File" size="40" value=""/>
  ....
</s:form>

As I'm sure we all know, the validationWorkflowStack includes the params interceptor, which sets the request params onto the action. Here's the issue, when the file being uploaded exceeds the maxSize, there are no params for the params interceptor to set. I've stepped through is and there's nothing in the actionContext. This is not good, because I need those params to handle the INPUT error that will result.

Am I missing something?

like image 909
fmpdmb Avatar asked Aug 06 '13 16:08

fmpdmb


2 Answers

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.

like image 115
Andrea Ligios Avatar answered Oct 25 '22 12:10

Andrea Ligios


Here's how I've worked around this issue. I wouldn't call this a solution.

enter image description here

like image 1
fmpdmb Avatar answered Oct 25 '22 11:10

fmpdmb