-
Notifications
You must be signed in to change notification settings - Fork 815
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[WW-5388] Uses the latest JakartaEE FileUpload Servlet 6 package #861
[WW-5388] Uses the latest JakartaEE FileUpload Servlet 6 package #861
Conversation
Also refactors the Jakarta based parsers as they have a lot in common
core/src/main/java/org/apache/struts2/dispatcher/multipart/AbstractMultiPartRequest.java
Outdated
Show resolved
Hide resolved
ad2f820
to
1f9e0cd
Compare
LOG.debug("No file has been uploaded for the field: {}", sanitizeNewlines(item.getFieldName())); | ||
return; | ||
} | ||
LOG.debug("Using charset: {} or default: {}", charset, defaultEncoding); |
Check notice
Code scanning / SonarCloud
Logging should not be vulnerable to injection attacks
// if the requestSizePermitted check failed based on the | ||
// complete content-size of the request. | ||
else { | ||
LOG.debug("Using charset: {} or default: {}", charset, defaultEncoding); |
Check notice
Code scanning / SonarCloud
Logging should not be vulnerable to injection attacks
4601311
to
0eead10
Compare
0eead10
to
3294ed0
Compare
/cc: @burtonrhodes |
/cc: @deki |
@lukaszlenart Looks good. The current version of this PR works well in my initial testing (dev) environment. |
errorMessage = buildErrorMessage(e, new Object[]{ | ||
ex.getContentType() | ||
}); | ||
for (DiskFileItem item : servletFileUpload.parseRequest(request)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@lukaszlenart Quick thought on the maxFiles setting: I still think it makes more sense that maxFiles would apply to only file fields and not all form fields in the request. However it appears that this logic is actually baked into the JakartaServletFileUpload class itself when calling servletFileUpload.parseRequest()
. Wonder why they coded it this way??
Anyway, if we ever wanted to change this, you could set the maxFiles to unlimited in the servletFileUpload object, and then perform the files count check in the if (item.isFormField()...
logic below. Just a thought.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In thinking about it more, that might be a security issue, as the serletFileUpload object would upload everything first (something that the setting is trying to prevent), only to perform the check after the processing is done. However, the maxSize / maxFileSize might offset that security risk??
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I assume so, this can be changed with a proper implementation of Streaming API which can then calculate the max size of uploaded files internally in Struts ... hm or maybe even right now, let me think this through 🤔
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@burtonrhodes introduced a new constant maxSizeOfFiles
which controls how many files you can upload based on their size - this works with JakartaStreamMultiPartRequest
only.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah this is by design (see this comment), none of the other options mitigate against this as the risk is processing a very large amount of request parts, not necessarily large ones. Though we can probably raise our default limit from 256 to 1000 (used in latest Tomcat).
9c4c860
to
51bf8de
Compare
9f2e912
to
2155d17
Compare
@@ -27,14 +27,14 @@ | |||
* The {@link org.apache.struts2.interceptor.ActionFileUploadInterceptor} will use the interface | |||
* to notify action about the multiple uploaded files. | |||
*/ | |||
public interface UploadedFilesAware { | |||
public interface UploadedFilesAware<T> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure I understand the benefit of this generic type given this interface is coupled with the ActionFileUploadInterceptor
. In what situation would an action implement UploadedFilesAware<NotFile>
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a preparation to support InputStream
and allow to stream files directly into actions, yet I need to thinks this through as this change became huge :\
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh okay - yeah I just don't know if it makes sense for a marker interface to have a generic type - might just need 2 marker interfaces instead
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Reverted
@@ -142,18 +142,25 @@ public final class StrutsConstants { | |||
/** The maximum size of a multipart request (file upload) */ | |||
public static final String STRUTS_MULTIPART_MAXSIZE = "struts.multipart.maxSize"; | |||
|
|||
/** The maximum size of all uploaded files. | |||
Used only with {@link org.apache.struts2.dispatcher.multipart.JakartaStreamMultiPartRequest} */ | |||
public static final String STRUTS_MULTIPART_MAXSIZE_OF_FILES = "struts.multipart.maxSizeOfFiles"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Still not very consistent - shouldn't it be MAX_SIZE_OF_FILES
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IDEA doesn't complain about MAXSIZE
that's why I left it, but you are right :)
errorMessage = buildErrorMessage(e, new Object[]{ | ||
ex.getContentType() | ||
}); | ||
for (DiskFileItem item : servletFileUpload.parseRequest(request)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah this is by design (see this comment), none of the other options mitigate against this as the risk is processing a very large amount of request parts, not necessarily large ones. Though we can probably raise our default limit from 256 to 1000 (used in latest Tomcat).
LOG.warn("Failed to delete '{}' due to security exception above.", file.getName(), se); | ||
} | ||
} | ||
Long currentFilesSize = actualSizeOfUploadedFiles(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems expensive to compute from scratch every time a file field is encountered, especially when the application might not have even configured a maxSizeOfFiles
limit!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Tbh I'm not convinced this option adds much value in the first place - I think maxSize
serves this need well enough. I'm trying to think in what situation would an application need to configure maxSizeOfFiles
instead of or in addition to maxSize
. They want to accepts lots of big form fields but want to limit the amount of data written to disk? I guess it's possible
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems expensive to compute from scratch every time a file field is encountered, especially when the application might not have even configured a
maxSizeOfFiles
limit!
Fixed!
5ca2764
to
61ba63b
Compare
61ba63b
to
e2215c8
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Lot of refactoring here so tricky to follow all the changes, but looks good at a glance
LOG.warn("Failed to delete '{}' due to security exception above.", file.getName(), se); | ||
} | ||
} | ||
Long currentFilesSize = maxSizeOfFiles != null ? actualSizeOfUploadedFiles() : null; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can just inline this right?
if (maxSizeOfFiles != null && actualSizeOfUploadedFiles() + file.length() >= maxSizeOfFiles) {
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not really as it is used inside the if
clause in exceedsMaxSizeOfFiles
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missed that, all good :)
Just FYI, I implemented this PR on a small subset of my server cluster and have been receiving the following ambiguous errors in some of the upload requests. I can't duplicate it in any testing environment, but curious if you might have a clue as to why Struts is creating an actionError with no value in the error, instead it prints "{0}". For this implementation, I am not using the stream parser Error printed by the code below
Code that quickly checks for any file upload errors
A redacted version of the action
|
@kusalk sorry for that, I know it's a big change yet I wanted to fix all the issues I have discovered around Jakarta file upload at once. Also all the changes related to |
@burtonrhodes I tried to play with the Showcase app and right now there is a problem uploading large files - I get blank page, yet the app is using the new Action Upload Interceptor |
94cec3b
to
18e8c94
Compare
18e8c94
to
d024ccd
Compare
Quality Gate passedThe SonarCloud Quality Gate passed, but some issues were introduced. 1 New issue |
@burtonrhodes did you have a chance to re-test the latest changes? Everything works fine when testing with the Showcase app yet it uses the new action upload interceptor, but still the same MultiPartParser is used behind the scene. I even added a dedicated integration test to cover max files constraint. |
@lukaszlenart yes, all looks good on my end. |
LGTM 💯 |
*/ | ||
public String[] getParameterValues(String name) { | ||
return parameters.getOrDefault(name, Collections.emptyList()) | ||
.toArray(String[]::new); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This change broke query parameters in multipart requests. Their values are being lost. The old implementation returned null if the parameter didn't exist, but it now returns an empty array. MultiPartRequestWrapper::getParameterValues
relies on the fact that it returns null, though, so it's not working correctly anymore.
This is the old implementation:
public String[] getParameterValues(String name) {
List<String> v = params.get(name);
if (v != null && !v.isEmpty()) {
return v.toArray(new String[0]);
}
return null;
}
And this is the MultiPartRequestWrapper
method that doesn't work correctly after the change:
public String[] getParameterValues(String name) {
return ((multi == null) || (multi.getParameterValues(name) == null)) ? super.getParameterValues(name) : multi.getParameterValues(name);
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Closes WW-5388