This is an automated email from the ASF dual-hosted git repository. lukaszlenart pushed a commit to branch docs/struts-7.3.0-content-audit in repository https://gitbox.apache.org/repos/asf/struts-site.git
commit b8ff92560957d646cd28d8bea246c3bd49a926ac Author: Lukasz Lenart <[email protected]> AuthorDate: Mon Aug 3 07:29:43 2026 +0200 docs: document Struts 7.3.0 multipart and upload-policy changes WW-5474: struts.multipart.maxFiles now counts uploaded files only, and the new struts.multipart.maxParameterCount (default 256) limits normal fields. Both limits are fail-closed and enforced identically by the jakarta and jakarta-stream parsers. WW-5413: small uploads stay in memory; the temporary file is written lazily. Document the new UploadedFile.getInputStream() / isMissing() default methods. WW-5659: lazy ${...} interceptor params resolve into a per-invocation UploadPolicy; an unresolved expression now rejects the upload. Document the acceptFile(UploadPolicy, ...) signature change and the WithLazyParams break. Also correct the upload error-message keys: Struts 7.x uses the Commons FileUpload 2 exception names, Struts 6.x the 1.x ones. Co-Authored-By: Claude Opus 5 <[email protected]> --- .../action-file-upload-interceptor.md | 44 +++++++++- source/core-developers/action-file-upload.md | 94 ++++++++++++++++++---- source/core-developers/file-upload.md | 12 ++- 3 files changed, 131 insertions(+), 19 deletions(-) diff --git a/source/core-developers/action-file-upload-interceptor.md b/source/core-developers/action-file-upload-interceptor.md index 52e489e8b..bc4bc9257 100644 --- a/source/core-developers/action-file-upload-interceptor.md +++ b/source/core-developers/action-file-upload-interceptor.md @@ -33,6 +33,8 @@ You can override the text of these messages by providing text for the following types specified - `struts.messages.error.file.extension.not.allowed` - occurs when the uploaded file does not match the expected file extensions specified + - `struts.messages.error.upload.policy.unresolved` - occurs when a `${...}` parameter could not be resolved for the + current invocation, so the file is rejected (since Struts 7.3.0) ## Parameters @@ -61,10 +63,48 @@ enabling per-request dynamic validation. This is available when used with `WithL The expressions are evaluated against the ValueStack at the time of the upload, allowing your action to provide dynamic values based on the current request context. +Since Struts 7.3.0 the resolved values are held in a per-invocation `org.apache.struts2.interceptor.UploadPolicy` +instead of being written onto the shared interceptor instance, so two concurrent requests can no longer be validated +against each other's policy — see [WW-5659](https://issues.apache.org/jira/browse/WW-5659). + +Also since Struts 7.3.0, an expression that cannot be resolved makes the policy unusable and the upload is **rejected** +with the `struts.messages.error.upload.policy.unresolved` message, rather than silently validated against a +partially-resolved policy. A typo in a `${...}` parameter, or a parameter name with no matching property, now fails +closed instead of relaxing validation. The `disabled` parameter is exempt: unresolved it is simply `false`, which leaves +the interceptor running with the rest of the policy intact. +{:.alert .alert-warning} + ## Extending the Interceptor -You can extend this interceptor and override the acceptFile method to provide more control over which files are supported -and which are not. +You can extend this interceptor and override the `acceptFile` method to provide more control over which files are +supported and which are not. + +Since Struts 7.3.0 `acceptFile` takes the per-invocation policy as its first argument: + +```java +protected boolean acceptFile(UploadPolicy policy, Object action, UploadedFile file, + String originalFilename, String contentType, String inputName) +``` + +Read `policy.getMaximumSize()`, `policy.getAllowedTypes()` and `policy.getAllowedExtensions()` instead of the former +interceptor fields, which are configuration-time state only. Subclasses overriding the old five-argument signature must +be updated — the old method is gone, so the compiler will point them out. +{:.alert .alert-warning} + +The same release changed the `org.apache.struts2.interceptor.WithLazyParams` interface, which is now generic over an +`org.apache.struts2.interceptor.InterceptorParams` holder: + +```java +public interface WithLazyParams<P extends InterceptorParams> { + P newLazyParams(); + String intercept(ActionInvocation invocation, P lazyParams) throws Exception; +} +``` + +Third-party interceptors implementing `WithLazyParams` must supply a holder class (extend +`org.apache.struts2.interceptor.DisableParams` if the interceptor supports the `disabled` parameter) and move the +resolved values off the singleton into it. +{:.alert .alert-warning} ## Examples diff --git a/source/core-developers/action-file-upload.md b/source/core-developers/action-file-upload.md index 289b5090e..f19f958aa 100644 --- a/source/core-developers/action-file-upload.md +++ b/source/core-developers/action-file-upload.md @@ -157,6 +157,7 @@ struts.multipart.parser=jakarta struts.multipart.saveDir= # Filesystem location to save parsed request data struts.multipart.maxSize=2097152 # Max combined size of files per request struts.multipart.maxFiles=256 # Max number of files per request +struts.multipart.maxParameterCount=256 # Max number of normal fields per request (since Struts 7.3.0) struts.multipart.maxFileSize= # Max size per file per request struts.multipart.maxStringLength=4096 # Max length of a string parameter (a normal field) in a multipart request (since Struts 6.1.2.1) ``` @@ -166,9 +167,8 @@ further details on these options first. ### Files Number Limit -Since Struts 6.1.2 a new option was added, which uses Commons FileUpload feature to limit how many files can be -uploaded at once, in one request. This option requires to use Commons FileUpload ver. 1.5 at least and by default is set -to **256**. Please always set this to a finite value to prevent DoS attacks. +Since Struts 6.1.2 a new option was added to limit how many files can be uploaded at once, in one request. By default it +is set to **256**. Please always set this to a finite value to prevent DoS attacks. To change this value define a constant in `struts.xml` as follows: @@ -178,8 +178,31 @@ To change this value define a constant in `struts.xml` as follows: </struts> ``` -**Note**: This limit also affects number of normal fields in the request, there is an open bug in the Commons FileUpload -library to address this problem, see [FILEUPLOAD-351](https://issues.apache.org/jira/browse/FILEUPLOAD-351). +### Parameters Number Limit + +> Since Struts 7.3.0 + +`struts.multipart.maxFiles` now counts **uploaded files only**, and a companion option +`struts.multipart.maxParameterCount` (default **256**) limits the number of normal, non-file fields in a multipart +request. The two limits are independent: a request may carry up to `maxFiles` files **and** up to `maxParameterCount` +form fields. Both are enforced identically by the `jakarta` and the `jakarta-stream` parser. + +```xml +<struts> + <constant name="struts.multipart.maxFiles" value="500"/> + <constant name="struts.multipart.maxParameterCount" value="1000"/> +</struts> +``` + +Exceeding either limit is fail-closed: parsing is aborted, an upload error is recorded, and the action receives **no** +parameters and **no** files — never a partially populated request. + +**Behaviour change in Struts 7.3.0**: before 7.3.0, `struts.multipart.maxFiles` capped files *and* normal fields +together (`jakarta` parser) or counted distinct file field names (`jakarta-stream` parser), so a form with many normal +fields and few files could be rejected — see [WW-5474](https://issues.apache.org/jira/browse/WW-5474). Such requests now +pass, while a request with more than `maxParameterCount` normal fields is rejected with the new +`struts.messages.upload.error.FileUploadParameterCountLimitException` message. If you raised `maxFiles` only to +accommodate large forms, lower it back to a realistic file count and raise `struts.multipart.maxParameterCount` instead. {:.alert .alert-warning} ### File Size Limits @@ -274,16 +297,23 @@ or extends `com.opensymphony.xwork2.ActionSupport`. These error messages are bas 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: -| Error Key | Description | -|----------------------------------------------------------------|--------------------------------------------------------------------------------------------------------| -| `struts.messages.error.uploading` | A general error that occurs when the file could not be uploaded | -| `struts.messages.error.file.too.large` | Occurs when the uploaded file is too large as specified by maximumSize. | -| `struts.messages.error.content.type.not.allowed` | Occurs when the uploaded file does not match the expected content types specified | -| `struts.messages.error.file.extension.not.allowed` | Occurs when uploaded file has disallowed extension | -| `struts.messages.upload.error.SizeLimitExceededException` | Occurs when the upload request (as a whole) exceed configured **struts.multipart.maxSize** | -| `struts.messages.upload.error.FileSizeLimitExceededException` | Occurs when a file within the upload request exceeds configured **struts.multipart.maxFileSize** | -| `struts.messages.upload.error.FileCountLimitExceededException` | Occurs when the number of files in the upload request exceeds configured **struts.multipart.maxFiles** | -| `struts.messages.upload.error.<Exception class SimpleName>` | Occurs when any other exception took place during file upload process | +| Error Key | Description | +|-----------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------| +| `struts.messages.error.uploading` | A general error that occurs when the file could not be uploaded | +| `struts.messages.error.file.too.large` | Occurs when the uploaded file is too large as specified by maximumSize. | +| `struts.messages.error.content.type.not.allowed` | Occurs when the uploaded file does not match the expected content types specified | +| `struts.messages.error.file.extension.not.allowed` | Occurs when uploaded file has disallowed extension | +| `struts.messages.error.upload.policy.unresolved` | Occurs when a `${...}` interceptor parameter could not be resolved, see [Dynamic Parameter Evaluation](action-file-upload-interceptor#dynamic-parameter-evaluation) (since Struts 7.3.0) | +| `struts.messages.upload.error.FileUploadSizeException` | Occurs when the upload request (as a whole) exceeds configured **struts.multipart.maxSize** | +| `struts.messages.upload.error.FileUploadByteCountLimitException` | Occurs when a file within the upload request exceeds configured **struts.multipart.maxFileSize** | +| `struts.messages.upload.error.FileUploadFileCountLimitException` | Occurs when the number of files in the upload request exceeds configured **struts.multipart.maxFiles** | +| `struts.messages.upload.error.FileUploadParameterCountLimitException` | Occurs when the number of normal fields in the upload request exceeds configured **struts.multipart.maxParameterCount** (since Struts 7.3.0) | +| `struts.messages.upload.error.<Exception class SimpleName>` | Occurs when any other exception took place during file upload process | + +The exception-based keys above are the Commons FileUpload 2 names used since Struts 7.0.0. Struts 6.x uses the Commons +FileUpload 1.x names instead: `SizeLimitExceededException`, `FileSizeLimitExceededException` +and `FileCountLimitExceededException`. +{:.alert .alert-info} ### Temporary Directories @@ -294,6 +324,40 @@ to the directory where the uploaded files will be placed. If this property is no to `javax.servlet.context.tempdir`. Keep in mind that on some operating systems, like Solaris, `/tmp` is memory based and files stored in that directory would consume an amount of RAM approximately equal to the size of the uploaded file. +### In-Memory Uploads + +> Since Struts 7.3.0 + +Small uploads (below the parser's disk-spill threshold, around 8 KB) are kept in memory and are **no longer written to a +temporary file eagerly** — see [WW-5413](https://issues.apache.org/jira/browse/WW-5413). The temporary file is written +lazily, only when something asks for a `java.io.File`. Uploads rejected by size, content-type or extension checks +therefore never touch the filesystem at all. + +Two methods were added to `org.apache.struts2.dispatcher.multipart.UploadedFile` to support this. Both are `default` +methods, so existing third-party implementations keep compiling: + +| Method | Purpose | +|-----------------------------------|----------------------------------------------------------------------------------------------------| +| `InputStream getInputStream()` | Reads the uploaded content without forcing it to disk — the preferred way to consume an upload | +| `boolean isMissing()` | Reports a failed upload with no content, answered without materialising the content | + +```java +public void withUploadedFiles(List<UploadedFile> uploadedFiles) { + for (UploadedFile file : uploadedFiles) { + try (InputStream in = file.getInputStream()) { + // process the bytes; no temporary file is created for small uploads + } catch (IOException e) { + // handle + } + } +} +``` + +`getContent()` and `getAbsolutePath()` still return a `java.io.File` as before, so existing code — including actions +using the legacy `File`-typed property — keeps working unchanged; the first such call simply materialises the temporary +file at that point. Prefer `getInputStream()` in new code when you only need the bytes. +{:.alert .alert-info} + ### Alternate Libraries The `struts.multipart.parser` used by the fileUpload interceptor to handle HTTP POST requests, encoded using the diff --git a/source/core-developers/file-upload.md b/source/core-developers/file-upload.md index d7811940a..46500d5c2 100644 --- a/source/core-developers/file-upload.md +++ b/source/core-developers/file-upload.md @@ -261,6 +261,7 @@ struts.multipart.parser=jakarta struts.multipart.saveDir= # Filesystem location to save parsed request data struts.multipart.maxSize=2097152 # Max combined size of files per request struts.multipart.maxFiles=256 # Max number of files per request +struts.multipart.maxParameterCount=256 # Max number of normal fields per request (since Struts 7.3.0) struts.multipart.maxFileSize= # Max size per file per request struts.multipart.maxStringLength=4096 # Max length of a string parameter (a normal field) in a multipart request (since Struts 6.1.2.1) ``` @@ -282,8 +283,10 @@ To change this value define a constant in `struts.xml` as follows: </struts> ``` -**Note**: This limit also affects number of normal fields in the request, there is an open bug in the Commons FileUpload -library to address this problem, see [FILEUPLOAD-351](https://issues.apache.org/jira/browse/FILEUPLOAD-351). +**Note**: Up to Struts 7.2.x this limit also affects the number of normal fields in the request, see +[FILEUPLOAD-351](https://issues.apache.org/jira/browse/FILEUPLOAD-351). Since Struts 7.3.0 `struts.multipart.maxFiles` +counts uploaded files only, and normal fields are limited separately by `struts.multipart.maxParameterCount` +(default **256**) — see [Parameters Number Limit](action-file-upload#parameters-number-limit). {:.alert .alert-warning} ### File Size Limits @@ -388,6 +391,11 @@ messages by providing text for the following keys: | `struts.messages.upload.error.FileCountLimitExceededException` | Occurs when the number of files in the upload request exceeds configured **struts.multipart.maxFiles** | | `struts.messages.upload.error.<Exception class SimpleName>` | Occurs when any other exception took place during file upload process | +The exception-based keys above are the Commons FileUpload 1.x names used by Struts 6.x. Since Struts 7.0.0 the +Commons FileUpload 2 names apply instead — see +[Error Messages](action-file-upload#error-messages) on the Action File Upload page. +{:.alert .alert-info} + ### Temporary Directories All uploaded files are saved to a temporary directory by the framework before being passed in to an Action. Depending on
