diff --git a/.github/GH-ROBOTS.txt b/.github/GH-ROBOTS.txt
new file mode 100644
index 0000000000..e3329e55fb
--- /dev/null
+++ b/.github/GH-ROBOTS.txt
@@ -0,0 +1,19 @@
+# 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.
+
+# Keeps on creating FUD PRs in test code
+# Does not follow Apache disclosure policies
+User-agent: JLLeitschuh/security-research
+Disallow: *
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 0000000000..9ebcd0ebb1
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,27 @@
+# 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.
+
+version: 2
+updates:
+ - package-ecosystem: "maven"
+ directory: "/"
+ schedule:
+ interval: "weekly"
+ day: "friday"
+ - package-ecosystem: "github-actions"
+ directory: "/"
+ schedule:
+ interval: "weekly"
+ day: "friday"
diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml
new file mode 100644
index 0000000000..94c6d720d7
--- /dev/null
+++ b/.github/workflows/codeql-analysis.yml
@@ -0,0 +1,85 @@
+# 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.
+
+name: "CodeQL"
+
+on:
+ push:
+ branches: [ 1.x ]
+ pull_request:
+ # The branches below must be a subset of the branches above
+ branches: [ 1.x ]
+ schedule:
+ - cron: '33 9 * * 4'
+
+permissions:
+ contents: read
+
+jobs:
+ analyze:
+ name: Analyze
+ runs-on: ubuntu-latest
+ permissions:
+ actions: read
+ contents: read
+ security-events: write
+
+ strategy:
+ fail-fast: false
+ matrix:
+ language: [ 'java' ]
+ # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
+ # Learn more about CodeQL language support at https://git.io/codeql-language-support
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
+ with:
+ persist-credentials: false
+ - uses: actions/cache@d4323d4df104b026a6aa633fdb11d772146be0bf # v4.2.2
+ with:
+ path: ~/.m2/repository
+ key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
+ restore-keys: |
+ ${{ runner.os }}-maven-
+
+ # Initializes the CodeQL tools for scanning.
+ - name: Initialize CodeQL
+ uses: github/codeql-action/init@6bb031afdd8eb862ea3fc1848194185e076637e5 # 3.28.11
+ with:
+ languages: ${{ matrix.language }}
+ # If you wish to specify custom queries, you can do so here or in a config file.
+ # By default, queries listed here will override any specified in a config file.
+ # Prefix the list here with "+" to use these queries and those in the config file.
+ # queries: ./path/to/local/query, your-org/your-repo/queries@main
+
+ # Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
+ # If this step fails, then you should remove it and run the build manually (see below)
+ - name: Autobuild
+ uses: github/codeql-action/autobuild@6bb031afdd8eb862ea3fc1848194185e076637e5 # 3.28.11
+
+ # ℹ️ Command-line programs to run using the OS shell.
+ # 📚 https://git.io/JvXDl
+
+ # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
+ # and modify them (or add more) to build your code if your project
+ # uses a compiled language
+
+ #- run: |
+ # make bootstrap
+ # make release
+
+ - name: Perform CodeQL Analysis
+ uses: github/codeql-action/analyze@6bb031afdd8eb862ea3fc1848194185e076637e5 # 3.28.11
diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml
new file mode 100644
index 0000000000..4595e8d1fc
--- /dev/null
+++ b/.github/workflows/maven.yml
@@ -0,0 +1,56 @@
+# 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.
+
+name: Java CI
+
+on:
+ push:
+ branches: [ 1.x ]
+ pull_request:
+ branches: [ 1.x ]
+
+permissions:
+ contents: read
+
+jobs:
+ build:
+
+ runs-on: ubuntu-latest
+ continue-on-error: ${{ matrix.experimental }}
+ strategy:
+ matrix:
+ java: [ 8, 11, 17, 21, 24 ]
+ experimental: [false]
+ include:
+ - java: 25-ea
+ experimental: true
+
+ steps:
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
+ with:
+ persist-credentials: false
+ - uses: actions/cache@d4323d4df104b026a6aa633fdb11d772146be0bf # v4.2.2
+ with:
+ path: ~/.m2/repository
+ key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
+ restore-keys: |
+ ${{ runner.os }}-maven-
+ - name: Set up JDK ${{ matrix.java }}
+ uses: actions/setup-java@3a4f6e1af504cf6a31855fa899c6aa5355ba6c12 # v4.7.0
+ with:
+ distribution: 'temurin'
+ java-version: ${{ matrix.java }}
+ - name: Build with Maven
+ run: mvn --errors --show-version --batch-mode --no-transfer-progress
diff --git a/.gitignore b/.gitignore
index b7027a5513..a373709026 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,8 @@
+/bin/
/target/
/.settings/
/.classpath
/.project
-site-content/
\ No newline at end of file
+/.sdkmanrc
+site-content/
+/.checkstyle
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 34d890464d..1a5d6d1a72 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -6,7 +6,7 @@
(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
+ https://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,
@@ -25,7 +25,7 @@
| commons-build-plugin/trunk/src/main/resources/commons-xdoc-templates |
+======================================================================+
| |
- | 1) Re-generate using: mvn commons:contributing-md |
+ | 1) Re-generate using: mvn commons-build:contributing-md |
| |
| 2) Set the following properties in the component's pom: |
| - commons.jira.id (required, alphabetic, upper case) |
@@ -41,57 +41,76 @@
Contributing to Apache Commons FileUpload
======================
-You have found a bug or you have an idea for a cool new feature? Contributing code is a great way to give something back to
-the open source community. Before you dig right into the code there are a few guidelines that we need contributors to
-follow so that we can have a chance of keeping on top of things.
+Have you found a bug or have an idea for a cool new feature? Contributing code is a great way to give something back to the open-source community.
+Before you dig right into the code, we need contributors to follow a few guidelines to have a chance of keeping on top of things.
Getting Started
---------------
+ Make sure you have a [JIRA account](https://issues.apache.org/jira/).
-+ Make sure you have a [GitHub account](https://github.com/signup/free).
-+ If you're planning to implement a new feature it makes sense to discuss you're changes on the [dev list](https://commons.apache.org/mail-lists.html) first. This way you can make sure you're not wasting your time on something that isn't considered to be in Apache Commons FileUpload's scope.
-+ Submit a ticket for your issue, assuming one does not already exist.
++ Make sure you have a [GitHub account](https://github.com/signup/free). This is not essential, but makes providing patches much easier.
++ If you're planning to implement a new feature it makes sense to discuss your changes on the [dev list](https://commons.apache.org/mail-lists.html) first. This way you can make sure you're not wasting your time on something that isn't considered to be in Apache Commons FileUpload's scope.
++ Submit a [Jira Ticket][jira] for your issue, assuming one does not already exist.
+ Clearly describe the issue including steps to reproduce when it is a bug.
+ Make sure you fill in the earliest version that you know has the issue.
-+ Fork the repository on GitHub.
++ Find the corresponding [repository on GitHub](https://github.com/apache/?query=commons-),
+[fork](https://help.github.com/articles/fork-a-repo/) and check out your forked repository. If you don't have a GitHub account, you can still clone the Commons repository.
Making Changes
--------------
-+ Create a topic branch from where you want to base your work (this is usually the master/trunk branch).
++ Create a _topic branch_ for your isolated work.
+ * Usually you should base your branch from the `master` branch.
+ * A good topic branch name can be the JIRA bug ID plus a keyword, e.g. `FILEUPLOAD-123-InputStream`.
+ * If you have submitted multiple JIRA issues, try to maintain separate branches and pull requests.
+ Make commits of logical units.
+ * Make sure your commit messages are meaningful and in the proper format. Your commit message should contain the key of the JIRA issue.
+ * For example, `[FILEUPLOAD-123] Close input stream sooner`
+ Respect the original code style:
- + Only use spaces for indentation.
- + Create minimal diffs - disable on save actions like reformat source code or organize imports. If you feel the source code should be reformatted create a separate PR for this change.
- + Check for unnecessary whitespace with git diff --check before committing.
-+ Make sure your commit messages are in the proper format. Your commit message should contain the key of the JIRA issue.
-+ Make sure you have added the necessary tests for your changes.
-+ Run all the tests with `mvn clean verify` to assure nothing else was accidentally broken.
+ + Only use spaces for indentation; you can check for unnecessary whitespace with `git diff` before committing.
+ + Create minimal diffs - disable _On Save_ actions like _Reformat Source Code_ or _Organize Imports_. If you feel the source code should be reformatted create a separate PR for this change first.
++ Write unit tests that match behavioral changes, where the tests fail if the changes to the runtime are not applied. This may not always be possible but is a best-practice.
+Unit tests are typically in the `src/test/java` directory.
++ Run a successful build using the default [Maven](https://maven.apache.org/) goal with `mvn`; that's `mvn` on the command line by itself.
++ Write a pull request description that is detailed enough to understand what the pull request does, how, and why.
++ Each commit in the pull request should have a meaningful subject line and body. Note that commits might be squashed by a maintainer on merge.
+
Making Trivial Changes
----------------------
+The JIRA tickets are used to generate the changelog for the next release.
+
For changes of a trivial nature to comments and documentation, it is not always necessary to create a new ticket in JIRA.
-In this case, it is appropriate to start the first line of a commit with '(doc)' instead of a ticket number.
+In this case, it is appropriate to start the first line of a commit with '[doc]' or '[javadoc]' instead of a ticket number.
+
Submitting Changes
------------------
-+ Sign the [Contributor License Agreement][cla] if you haven't already.
++ Sign and submit the Apache [Contributor License Agreement][cla] if you haven't already.
+ * Note that small patches & typical bug fixes do not require a CLA as
+ clause 5 of the [Apache License](https://www.apache.org/licenses/LICENSE-2.0.html#contributions)
+ covers them.
+ Push your changes to a topic branch in your fork of the repository.
-+ Submit a pull request to the repository in the apache organization.
++ Submit a _Pull Request_ to the corresponding repository in the `apache` organization.
+ * Verify _Files Changed_ shows only your intended changes and does not
+ include additional files like `target/*.class`
+ Update your JIRA ticket and include a link to the pull request in the ticket.
+If you prefer to not use GitHub, then you can instead use
+`git format-patch` (or `svn diff`) and attach the patch file to the JIRA issue.
+
+
Additional Resources
--------------------
+ [Contributing patches](https://commons.apache.org/patches.html)
-+ [Apache Commons FileUpload JIRA project page](https://issues.apache.org/jira/browse/FILEUPLOAD)
++ [Apache Commons FileUpload JIRA project page][jira]
+ [Contributor License Agreement][cla]
+ [General GitHub documentation](https://help.github.com/)
-+ [GitHub pull request documentation](https://help.github.com/send-pull-requests/)
++ [GitHub pull request documentation](https://help.github.com/articles/creating-a-pull-request/)
+ [Apache Commons Twitter Account](https://twitter.com/ApacheCommons)
-+ #apachecommons IRC channel on freenode.org
[cla]:https://www.apache.org/licenses/#clas
+[jira]:https://issues.apache.org/jira/browse/FILEUPLOAD
diff --git a/NOTICE.txt b/NOTICE.txt
index eb6f2fc445..2cabc8ce24 100644
--- a/NOTICE.txt
+++ b/NOTICE.txt
@@ -1,5 +1,5 @@
Apache Commons FileUpload
-Copyright 2002-2018 The Apache Software Foundation
+Copyright 2002-2025 The Apache Software Foundation
This product includes software developed at
-The Apache Software Foundation (http://www.apache.org/).
+The Apache Software Foundation (https://www.apache.org/).
diff --git a/README.md b/README.md
index 0ef3cf6755..f4f0f63b14 100644
--- a/README.md
+++ b/README.md
@@ -6,7 +6,7 @@
(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
+ https://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,
@@ -25,7 +25,7 @@
| commons-build-plugin/trunk/src/main/resources/commons-xdoc-templates |
+======================================================================+
| |
- | 1) Re-generate using: mvn commons:readme-md |
+ | 1) Re-generate using: mvn commons-build:readme-md |
| |
| 2) Set the following properties in the component's pom: |
| - commons.componentid (required, alphabetic, lower case) |
@@ -43,8 +43,11 @@
Apache Commons FileUpload
===================
-[](https://travis-ci.org/apache/commons-fileupload)
-[](https://maven-badges.herokuapp.com/maven-central/commons-fileupload/commons-fileupload/)
+[](https://github.com/apache/commons-fileupload/actions/workflows/maven.yml)
+[](https://search.maven.org/artifact/commons-fileupload/commons-fileupload)
+[](https://javadoc.io/doc/commons-fileupload/commons-fileupload/1.6.0)
+[](https://github.com/apache/commons-fileupload/actions/workflows/codeql-analysis.yml)
+[](https://api.securityscorecards.dev/projects/github.com/apache/commons-fileupload)
The Apache Commons FileUpload component provides a simple yet flexible means of adding support for multipart
file upload functionality to servlets and web applications.
@@ -53,52 +56,65 @@ Documentation
-------------
More information can be found on the [Apache Commons FileUpload homepage](https://commons.apache.org/proper/commons-fileupload).
-The [Javadoc](https://commons.apache.org/proper/commons-fileupload/javadocs/api-release) can be browsed.
-Questions related to the usage of Apache Commons FileUpload should be posted to the [user mailing list][ml].
+The [Javadoc](https://commons.apache.org/proper/commons-fileupload/apidocs) can be browsed.
+Questions related to the usage of Apache Commons FileUpload should be posted to the [user mailing list](https://commons.apache.org/mail-lists.html).
-Where can I get the latest release?
------------------------------------
+Getting the latest release
+--------------------------
You can download source and binaries from our [download page](https://commons.apache.org/proper/commons-fileupload/download_fileupload.cgi).
-Alternatively you can pull it from the central Maven repositories:
+Alternatively, you can pull it from the central Maven repositories:
```xml
High level API for processing file uploads. This class handles multiple files per single HTML widget, sent using
- * This class represents a file or form item that was received within a
- * After retrieving an instance of this class from a {@link
* org.apache.commons.fileupload.FileUpload FileUpload} instance (see
@@ -36,45 +36,67 @@
* it into memory, which may come handy with large files.
*
* While this interface does not extend
- *
- * This method is not guaranteed to succeed if called more than once for
- * the same item. This allows a particular implementation to use, for
- * example, file renaming, where possible, rather than copying all of the
- * underlying data, thus gaining a significant performance benefit.
- *
- * @param file The
+ * This method is not guaranteed to succeed if called more than once for
+ * the same item. This allows a particular implementation to use, for
+ * example, file renaming, where possible, rather than copying all of the
+ * underlying data, thus gaining a significant performance benefit.
*
- * @return An {@link java.io.OutputStream OutputStream} that can be used
- * for storing the contensts of the file.
+ * @param file The {@code File} into which the uploaded item should
+ * be stored.
*
- * @throws IOException if an error occurs.
+ * @throws Exception if an error occurs.
*/
- OutputStream getOutputStream() throws IOException;
+ void write(File file) throws Exception;
}
diff --git a/src/main/java/org/apache/commons/fileupload/FileItemFactory.java b/src/main/java/org/apache/commons/fileupload/FileItemFactory.java
index f450a0437b..a6aa209a8a 100644
--- a/src/main/java/org/apache/commons/fileupload/FileItemFactory.java
+++ b/src/main/java/org/apache/commons/fileupload/FileItemFactory.java
@@ -29,8 +29,8 @@ public interface FileItemFactory {
*
* @param fieldName The name of the form field.
* @param contentType The content type of the form field.
- * @param isFormField This class provides support for accessing the headers for a file or form
- * item that was received within a
+ * Returns an {@code Iterator} of all the header names.
+ *
* Returns all the values of the specified item header as an
- *
* If the item did not include any headers of the specified name, this
- * method returns an empty
- * Returns an This interface provides access to a file or form item that was
- * received within a Instances of this class are created by accessing the
* iterator, returned by
@@ -40,7 +40,7 @@ public interface FileItemStream extends FileItemHeadersSupport {
* {@link java.util.Iterator#hasNext()} has been invoked on the
* iterator, which created the {@link FileItemStream}.
*/
- public static class ItemSkippedException extends IOException {
+ class ItemSkippedException extends IOException {
/**
* The exceptions serial version UID, which is being used
@@ -48,55 +48,62 @@ public static class ItemSkippedException extends IOException {
*/
private static final long serialVersionUID = -7280778431581963740L;
+ /**
+ * Constructs a new instance.
+ */
+ public ItemSkippedException() {
+ // empty
+ }
+
}
/**
- * Creates an {@link InputStream}, which allows to read the
- * items contents.
+ * Returns the content type passed by the browser or {@code null} if
+ * not defined.
*
- * @return The input stream, from which the items data may
- * be read.
- * @throws IllegalStateException The method was already invoked on
- * this item. It is not possible to recreate the data stream.
- * @throws IOException An I/O error occurred.
- * @see ItemSkippedException
+ * @return The content type passed by the browser or {@code null} if
+ * not defined.
*/
- InputStream openStream() throws IOException;
+ String getContentType();
/**
- * Returns the content type passed by the browser or High level API for processing file uploads. This class handles multiple files per single HTML widget, sent using
- * High level API for processing file uploads. This class handles multiple files per single HTML widget, sent using
- *
+ * This class handles multiple files per single HTML widget, sent using {@code multipart/mixed} encoding type, as specified by
+ * RFC 1867. Use {@link #parseRequest(RequestContext)} to acquire a list of
+ * {@link org.apache.commons.fileupload.FileItem}s associated with a given HTML widget.
+ * How the data for individual parts is stored is determined by the factory
- * used to create them; a given part may be in memory, on disk, or somewhere
- * else.
+ * How the data for individual parts is stored is determined by the factory used to create them; a given part may be in memory, on disk, or somewhere else.
+ * Utility method that determines whether the request contains multipart
- * content. NOTE:This method will be moved to the
- * Parses the If there are multiple headers of the same names, the name
- * will map to a comma-separated list containing the values.
- *
- * @param headerPart The Parses the If there are multiple headers of the same names, the name
- * will map to a comma-separated list containing the values.
- *
- * @param headerPart The Utility method that determines whether the request contains multipart
+ * content. NOTE:This method will be moved to the
+ * {@code ServletFileUpload} class after the FileUpload 1.1 release.
+ * Unfortunately, since this method is static, it is not possible to
+ * provide its replacement until this method is removed. Parses the {@code header-part} and returns as key/value
+ * pairs.
+ *
+ * If there are multiple headers of the same names, the name
+ * will map to a comma-separated list containing the values.
+ *
+ * @param headerPart The {@code header-part} of the current
+ * {@code encapsulation}.
+ *
+ * @return A {@code Map} containing the parsed HTTP request headers.
+ */
+ protected FileItemHeaders getParsedHeaders(final String headerPart) {
+ final int len = headerPart.length();
+ final FileItemHeadersImpl headers = newFileItemHeaders();
+ int start = 0;
+ for (;;) {
+ int end = parseEndOfLine(headerPart, start);
+ if (start == end) {
+ break;
+ }
+ final StringBuilder header = new StringBuilder(headerPart.substring(start, end));
+ start = end + 2;
+ while (start < len) {
+ int nonWs = start;
+ while (nonWs < len) {
+ final char c = headerPart.charAt(nonWs);
+ if (c != ' ' && c != '\t') {
+ break;
+ }
+ ++nonWs;
+ }
+ if (nonWs == start) {
+ break;
+ }
+ // Continuation line found
+ end = parseEndOfLine(headerPart, nonWs);
+ header.append(' ').append(headerPart, nonWs, end);
+ start = end + 2;
+ }
+ parseHeaderLine(headers, header.toString());
}
-
+ return headers;
}
/**
- * This exception is thrown, if a requests permitted size
- * is exceeded.
+ * Obtain the per part size limit for headers.
+ *
+ * @return The maximum size of the headers for a single part in bytes.
+ *
+ * @since 1.6.0
*/
- protected abstract static class SizeException extends FileUploadException {
-
- /**
- * Serial version UID, being used, if serialized.
- */
- private static final long serialVersionUID = -8776225574705254126L;
+ public int getPartHeaderSizeMax() {
+ return partHeaderSizeMax;
+ }
- /**
- * The actual size of the request.
- */
- private final long actual;
+ /**
+ * Returns the progress listener.
+ *
+ * @return The progress listener, if any, or null.
+ */
+ public ProgressListener getProgressListener() {
+ return listener;
+ }
- /**
- * The maximum permitted size of the request.
- */
- private final long permitted;
+ /**
+ * Returns the maximum allowed size of a complete request, as opposed
+ * to {@link #getFileSizeMax()}.
+ *
+ * @return The maximum allowed size, in bytes. The default value of
+ * -1 indicates, that there is no limit.
+ *
+ * @see #setSizeMax(long)
+ *
+ */
+ public long getSizeMax() {
+ return sizeMax;
+ }
- /**
- * Creates a new instance.
- *
- * @param message The detail message.
- * @param actual The actual number of bytes in the request.
- * @param permitted The requests size limit, in bytes.
- */
- protected SizeException(String message, long actual, long permitted) {
- super(message);
- this.actual = actual;
- this.permitted = permitted;
- }
+ /**
+ * Creates a new instance of {@link FileItemHeaders}.
+ * @return The new instance.
+ */
+ protected FileItemHeadersImpl newFileItemHeaders() {
+ return new FileItemHeadersImpl();
+ }
- /**
- * Retrieves the actual size of the request.
- *
- * @return The actual size of the request.
- * @since 1.3
- */
- public long getActualSize() {
- return actual;
+ /**
+ * Skips bytes until the end of the current line.
+ * @param headerPart The headers, which are being parsed.
+ * @param end Index of the last byte, which has yet been
+ * processed.
+ * @return Index of the \r\n sequence, which indicates
+ * end of line.
+ */
+ private int parseEndOfLine(final String headerPart, final int end) {
+ int index = end;
+ for (;;) {
+ final int offset = headerPart.indexOf(CR, index);
+ if (offset == -1 || offset + 1 >= headerPart.length()) {
+ throw new IllegalStateException(
+ "Expected headers to be terminated by an empty line.");
+ }
+ if (headerPart.charAt(offset + 1) == LF) {
+ return offset;
+ }
+ index = offset + 1;
}
+ }
- /**
- * Retrieves the permitted size of the request.
- *
- * @return The permitted size of the request.
- * @since 1.3
- */
- public long getPermittedSize() {
- return permitted;
+ /**
+ * Reads the next header line.
+ * @param headers String with all headers.
+ * @param header Map where to store the current header.
+ */
+ private void parseHeaderLine(final FileItemHeadersImpl headers, final String header) {
+ final int colonOffset = header.indexOf(':');
+ if (colonOffset == -1) {
+ // This header line is malformed, skip it.
+ return;
}
-
+ final String headerName = header.substring(0, colonOffset).trim();
+ final String headerValue = header.substring(colonOffset + 1).trim();
+ headers.addHeader(headerName, headerValue);
}
/**
- * Thrown to indicate that the request size is not specified. In other
- * words, it is thrown, if the content-length header is missing or
- * contains the value -1.
+ * Parses the {@code header-part} and returns as key/value
+ * pairs.
*
- * @deprecated 1.2 As of commons-fileupload 1.2, the presence of a
- * content-length header is no longer required.
+ * If there are multiple headers of the same names, the name
+ * will map to a comma-separated list containing the values.
+ *
+ * @param headerPart The {@code header-part} of the current
+ * {@code encapsulation}.
+ *
+ * @return A {@code Map} containing the parsed HTTP request headers.
+ * @deprecated 1.2.1 Use {@link #getParsedHeaders(String)}
*/
@Deprecated
- public static class UnknownSizeException
- extends FileUploadException {
-
- /**
- * The exceptions UID, for serializing an instance.
- */
- private static final long serialVersionUID = 7062279004812015273L;
-
- /**
- * Constructs a Low level API for processing file uploads.
+ * Low level API for processing file uploads.
*
- * This class can be used to process data streams conforming to MIME
- * 'multipart' format as defined in
- * RFC 1867. Arbitrarily
- * large amounts of data in the stream can be processed under constant
- * memory usage.
+ *
+ * This class can be used to process data streams conforming to MIME 'multipart' format as defined in RFC
+ * 1867. Arbitrarily large amounts of data in the stream can be processed under constant memory usage.
+ * The format of the stream is defined in the following way:
+ * The format of the stream is defined in the following way:
+ * Note that body-data can contain another mulipart entity. There
- * is limited support for single pass processing of such nested
- * streams. The nested stream is required to have a
- * boundary token of the same length as the parent stream (see {@link
- * #setBoundary(byte[])}).
+ *
+ * Note that body-data can contain another mulipart entity. There is limited support for single pass processing of such nested streams. The nested stream is
+ * required to have a boundary token of the same length as the parent stream (see {@link #setBoundary(byte[])}).
+ * Here is an example of usage of this class.
+ * Here is an example of usage of this class.
+ * Constructs a Note that the buffer must be at least big enough to contain the
- * boundary string, plus 4 characters for CR/LF and double dash, plus at
- * least one byte of data. Too small a buffer size setting will degrade
- * performance.
- *
- * @param input The
+ * Note that the buffer must be at least big enough to contain the boundary string, plus 4 characters for CR/LF and double dash, plus at least one byte of
+ * data. Too small a buffer size setting will degrade performance.
+ * Constructs a Note that the buffer must be at least big enough to contain the
- * boundary string, plus 4 characters for CR/LF and double dash, plus at
- * least one byte of data. Too small a buffer size setting will degrade
- * performance.
+ *
+ * Note that the buffer must be at least big enough to contain the boundary string, plus 4 characters for CR/LF and double dash, plus at least one byte of
+ * data. Too small a buffer size setting will degrade performance.
+ * Constructs a Constructs a
+ * Use this method to skip encapsulations you don't need or don't understand.
+ *
+ * Arbitrary large amounts of data can be processed by this method using a constant size buffer. (see
+ * {@link #MultipartStream(InputStream,byte[],int, MultipartStream.ProgressNotifier) constructor}).
+ * Changes the boundary token used for partitioning the stream.
- *
- * This method allows single pass processing of nested multipart
- * streams.
- *
- * The boundary token of the nested stream is Restoring the parent stream boundary token after processing of a
- * nested stream is left to the application.
- *
- * @param boundary The boundary to be used for parsing of the nested
- * stream.
+ * Reads a byte from the {@code buffer}, and refills it as
+ * necessary.
*
- * @throws IllegalBoundaryException if the Reads the Headers are returned verbatim to the input stream, including the
- * trailing TODO allow limiting maximum header size to
- * protect against abuse.
- *
- * @return The
+ * Headers are returned verbatim to the input stream, including the trailing {@code CRLF} marker. Parsing is left to the application.
+ * Reads Arbitrary large amounts of data can be processed by this
- * method using a constant size buffer. (see {@link
- * #MultipartStream(InputStream,byte[],int,
- * MultipartStream.ProgressNotifier) constructor}).
+ *
+ * This method allows single pass processing of nested multipart streams.
+ *
+ * The boundary token of the nested stream is {@code required} to be of the same length as the boundary token in parent stream.
+ *
+ * Restoring the parent stream boundary token after processing of a nested stream is left to the application.
+ * Reads Use this method to skip encapsulations you don't need or don't
- * understand.
- *
- * @return The amount of data discarded.
+ * @param partHeaderSizeMax The maximum size of the headers in bytes.
*
- * @throws MalformedStreamException if the stream ends unexpectedly.
- * @throws IOException if an i/o error occurs.
+ * @since 1.6.0
*/
- public int discardBodyData() throws MalformedStreamException, IOException {
- return readBodyData(null);
+ public void setPartHeaderSizeMax(final int partHeaderSizeMax) {
+ this.partHeaderSizeMax = partHeaderSizeMax;
}
/**
- * Finds the beginning of the first
- *
+ * RFC 5987 builds on RFC 2231, but has lesser scope like mandatory charset definition and
+ * no parameter continuation
+ *
+ * Eg 1. {@code us-ascii'en-us'This%20is%20%2A%2A%2Afun%2A%2A%2A} will be decoded to {@code This is ***fun***}
+ *
+ * Eg 2. {@code iso-8859-1'en'%A3%20rate} will be decoded to {@code £ rate}
+ *
+ * Eg 3. {@code UTF-8''%c2%a3%20and%20%e2%82%ac%20rates} will be decoded to {@code £ and € rates}
+ * Abstracts access to the request information needed for file uploads. This
@@ -35,13 +35,6 @@ public interface RequestContext {
*/
String getCharacterEncoding();
- /**
- * Retrieve the content type of the request.
- *
- * @return The content type of the request.
- */
- String getContentType();
-
/**
* Retrieve the content length of the request.
*
@@ -51,11 +44,17 @@ public interface RequestContext {
@Deprecated
int getContentLength();
+ /**
+ * Retrieve the content type of the request.
+ *
+ * @return The content type of the request.
+ */
+ String getContentType();
+
/**
* Retrieve the input stream for the request.
*
* @return The input stream for the request.
- *
* @throws IOException if a problem occurs.
*/
InputStream getInputStream() throws IOException;
diff --git a/src/main/java/org/apache/commons/fileupload/disk/DiskFileItem.java b/src/main/java/org/apache/commons/fileupload/disk/DiskFileItem.java
index e5e3bf9b02..12551761f6 100644
--- a/src/main/java/org/apache/commons/fileupload/disk/DiskFileItem.java
+++ b/src/main/java/org/apache/commons/fileupload/disk/DiskFileItem.java
@@ -16,16 +16,15 @@
*/
package org.apache.commons.fileupload.disk;
-import static java.lang.String.format;
-
import java.io.ByteArrayInputStream;
import java.io.File;
-import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
+import java.io.UncheckedIOException;
import java.io.UnsupportedEncodingException;
+import java.nio.file.Files;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicInteger;
@@ -36,42 +35,35 @@
import org.apache.commons.fileupload.ParameterParser;
import org.apache.commons.fileupload.util.Streams;
import org.apache.commons.io.FileUtils;
-import org.apache.commons.io.IOUtils;
+import org.apache.commons.io.function.Uncheck;
import org.apache.commons.io.output.DeferredFileOutputStream;
/**
- * The default implementation of the
- * {@link org.apache.commons.fileupload.FileItem FileItem} interface.
- *
- * After retrieving an instance of this class from a {@link
- * DiskFileItemFactory} instance (see
- * {@link org.apache.commons.fileupload.servlet.ServletFileUpload
- * #parseRequest(javax.servlet.http.HttpServletRequest)}), you may
- * either request all contents of file at once using {@link #get()} or
- * request an {@link java.io.InputStream InputStream} with
- * {@link #getInputStream()} and process the file without attempting to load
- * it into memory, which may come handy with large files.
+ * The default implementation of the {@link org.apache.commons.fileupload.FileItem FileItem} interface.
*
- * Temporary files, which are created for file items, should be
- * deleted later on. The best way to do this is using a
- * {@link org.apache.commons.io.FileCleaningTracker}, which you can set on the
- * {@link DiskFileItemFactory}. However, if you do use such a tracker,
- * then you must consider the following: Temporary files are automatically
- * deleted as soon as they are no longer needed. (More precisely, when the
- * corresponding instance of {@link java.io.File} is garbage collected.)
- * This is done by the so-called reaper thread, which is started and stopped
- * automatically by the {@link org.apache.commons.io.FileCleaningTracker} when
- * there are files to be tracked.
- * It might make sense to terminate that thread, for example, if
- * your web application ends. See the section on "Resource cleanup"
- * in the users guide of commons-fileupload.
+ * After retrieving an instance of this class from a {@link DiskFileItemFactory} instance (see {@link org.apache.commons.fileupload.servlet.ServletFileUpload
+ * #parseRequest(javax.servlet.http.HttpServletRequest)}), you may either request all contents of file at once using {@link #get()} or request an
+ * {@link java.io.InputStream InputStream} with {@link #getInputStream()} and process the file without attempting to load it into memory, which may come handy
+ * with large files.
+ *
+ * Temporary files, which are created for file items, should be deleted later on. The best way to do this is using a
+ * {@link org.apache.commons.io.FileCleaningTracker}, which you can set on the {@link DiskFileItemFactory}. However, if you do use such a tracker, then you must
+ * consider the following: Temporary files are automatically deleted as soon as they are no longer needed. (More precisely, when the corresponding instance of
+ * {@link java.io.File} is garbage collected.) This is done by the so-called reaper thread, which is started and stopped automatically by the
+ * {@link org.apache.commons.io.FileCleaningTracker} when there are files to be tracked. It might make sense to terminate that thread, for example, if your web
+ * application ends. See the section on "Resource cleanup" in the users guide of commons-fileupload.
+ *
- * This implementation first attempts to rename the uploaded item to the
- * specified destination file, if the item was originally written to disk.
- * Otherwise, the data will be copied to the specified file.
- *
- * This method is only guaranteed to work once, the first time it
- * is invoked for a particular item. This is because, in the event that the
- * method renames a temporary file, that file will no longer be available
- * to copy or rename again at a later time.
+ * Gets the default charset for use when no explicit charset parameter is provided by the sender.
*
- * @param file The
+ * TODO Consider making this method throw UnsupportedEncodingException.
+ *
+ * @return The contents of the file, as a string.
*/
@Override
- protected void finalize() {
- if (dfos == null || dfos.isInMemory()) {
- return;
+ public String getString() {
+ final byte[] rawData = get();
+ String charset = getCharSet();
+ if (charset == null) {
+ charset = defaultCharset;
}
- File outputFile = dfos.getFile();
-
- if (outputFile != null && outputFile.exists()) {
- outputFile.delete();
+ try {
+ return new String(rawData, charset);
+ } catch (final UnsupportedEncodingException e) {
+ return "";
}
}
/**
- * Creates and returns a {@link java.io.File File} representing a uniquely
- * named temporary file in the configured repository path. The lifetime of
- * the file is tied to the lifetime of the
- * Note: Subclasses that override this method must ensure that they return the
- * same File each time.
+ * Note: Subclasses that override this method must ensure that they return the same File each time.
*
* @return The {@link java.io.File File} to be used for temporary storage.
*/
@@ -566,79 +437,139 @@ protected File getTempFile() {
tempDir = new File(System.getProperty("java.io.tmpdir"));
}
- String tempFileName = format("upload_%s_%s.tmp", UID, getUniqueId());
+ final String tempFileName = String.format("upload_%s_%s.tmp", UID, getUniqueId());
tempFile = new File(tempDir, tempFileName);
}
return tempFile;
}
- // -------------------------------------------------------- Private methods
-
/**
- * Returns an identifier that is unique within the class loader used to
- * load this class, but does not have random-like appearance.
+ * Tests whether or not a {@code FileItem} instance represents a simple form field.
*
- * @return A String with the non-random looking instance identifier.
+ * @return {@code true} if the instance represents a simple form field; {@code false} if it represents an uploaded
+ * file.
+ *
+ * @see #setFormField(boolean)
*/
- private static String getUniqueId() {
- final int limit = 100000000;
- int current = COUNTER.getAndIncrement();
- String id = Integer.toString(current);
+ @Override
+ public boolean isFormField() {
+ return formField;
+ }
- // If you manage to get more than 100 million of ids, you'll
- // start getting ids longer than 8 characters.
- if (current < limit) {
- id = ("00000000" + id).substring(id.length());
+ /**
+ * Provides a hint as to whether or not the file contents will be read from memory.
+ *
+ * @return {@code true} if the file contents will be read from memory; {@code false} otherwise.
+ */
+ @Override
+ public boolean isInMemory() {
+ if (cachedContent != null) {
+ return true;
}
- return id;
+ return dfos.isInMemory();
}
/**
- * Returns a string representation of this object.
+ * Sets the default charset for use when no explicit charset parameter is provided by the sender.
*
- * @return a string representation of this object.
+ * @param charset the default charset
+ */
+ public void setDefaultCharset(final String charset) {
+ defaultCharset = charset;
+ }
+
+ /**
+ * Sets the field name used to reference this file item.
+ *
+ * @param fieldName The name of the form field.
+ * @see #getFieldName()
*/
@Override
- public String toString() {
- return format("name=%s, StoreLocation=%s, size=%s bytes, isFormField=%s, FieldName=%s",
- getName(), getStoreLocation(), Long.valueOf(getSize()),
- Boolean.valueOf(isFormField()), getFieldName());
+ public void setFieldName(final String fieldName) {
+ this.fieldName = fieldName;
}
/**
- * Returns the file item headers.
- * @return The file items headers.
+ * Sets whether or not a {@code FileItem} instance represents a simple form field.
+ *
+ * @param formField {@code true} if the instance represents a simple form
+ * field; {@code false} if it represents an uploaded file.
+ *
+ * @see #isFormField()
*/
@Override
- public FileItemHeaders getHeaders() {
- return headers;
+ public void setFormField(final boolean formField) {
+ this.formField = formField;
}
/**
* Sets the file item headers.
- * @param pHeaders The file items headers.
+ *
+ * @param headers The file items headers.
*/
@Override
- public void setHeaders(FileItemHeaders pHeaders) {
- headers = pHeaders;
+ public void setHeaders(final FileItemHeaders headers) {
+ this.headers = headers;
}
/**
- * Returns the default charset for use when no explicit charset
- * parameter is provided by the sender.
- * @return the default charset
+ * Returns a string representation of this object.
+ *
+ * @return a string representation of this object.
*/
- public String getDefaultCharset() {
- return defaultCharset;
+ @Override
+ public String toString() {
+ return String.format("name=%s, StoreLocation=%s, size=%s bytes, isFormField=%s, FieldName=%s",
+ getName(), getStoreLocation(), Long.valueOf(getSize()), Boolean.valueOf(isFormField()), getFieldName());
}
/**
- * Sets the default charset for use when no explicit charset
- * parameter is provided by the sender.
- * @param charset the default charset
+ * A convenience method to write an uploaded item to disk. The client code
+ * is not concerned with whether or not the item is stored in memory, or on
+ * disk in a temporary location. They just want to write the uploaded item
+ * to a file.
+ *
+ * This implementation first attempts to rename the uploaded item to the
+ * specified destination file, if the item was originally written to disk.
+ * Otherwise, the data will be copied to the specified file.
+ *
+ * This method is only guaranteed to work once, the first time it
+ * is invoked for a particular item. This is because, in the event that the
+ * method renames a temporary file, that file will no longer be available
+ * to copy or rename again at a later time.
+ *
- * NOTE: Files are created in the system default temp directory with
+ * NOTE: Files are created in the system default temp directory with
* predictable names. This means that a local attacker with write access to that
* directory can perform a TOUTOC attack to replace any uploaded file with a
* file of the attackers choice. The implications of this will depend on how the
@@ -47,7 +47,7 @@
* implementation in an environment with local, untrusted users,
* {@link #setRepository(File)} MUST be used to configure a repository location
* that is not publicly writable. In a Servlet container the location identified
- * by the ServletContext attribute
* Please see the FileUpload
- * User Guide
+ * User Guide
* for further details and examples of how to use this package.
*
* In the example above, the first file is loaded into memory as a
- *
* Please see the FileUpload
- * User Guide
+ * User Guide
* for further details and examples of how to use this package.
* High level API for processing file uploads. This class handles multiple files per single HTML widget, sent using
- * High level API for processing file uploads. This class handles multiple files per single HTML widget, sent using
- *
* Please see the FileUpload
- * User Guide
+ * User Guide
* for further details and examples of how to use this package.
*
* This method
- * simply performs
- * This method simply performs Secondly this happens when attempting to rely on a shared copy of
the Commons FileUpload jar file provided by your web container. The
diff --git a/src/site/resources/profile.cobertura b/src/site/resources/profile.cobertura
deleted file mode 100644
index f2074dfcf0..0000000000
--- a/src/site/resources/profile.cobertura
+++ /dev/null
@@ -1,2 +0,0 @@
-# This file is intentionally empty. It is only used, because its
-# presence activates the generation of a Coberturta report.
diff --git a/.travis.yml b/src/site/resources/profile.jacoco
similarity index 82%
rename from .travis.yml
rename to src/site/resources/profile.jacoco
index 2e8e6b9544..a12755f3ba 100644
--- a/.travis.yml
+++ b/src/site/resources/profile.jacoco
@@ -12,14 +12,6 @@
# 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.
-
-language: java
-sudo: false
-
-jdk:
- - openjdk7
- - oraclejdk8
- - oraclejdk9
-
-after_success:
- - mvn clean test jacoco:report coveralls:report -Ptravis-jacoco
\ No newline at end of file
+# -----------------------------------------------------------------------------
+#
+# Empty file used to automatically trigger JaCoCo profile from commons parent pom
diff --git a/src/site/site.xml b/src/site/site.xml
index 7c0b3ce130..9ca6cc5b5b 100644
--- a/src/site/site.xml
+++ b/src/site/site.xml
@@ -15,13 +15,13 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-
We recommend you use a mirror to download our release
- builds, but you must verify the integrity of
+ builds, but you must verify the integrity of
the downloaded files using signatures downloaded from our main
distribution directories. Recent releases (48 hours) may not yet
- be available from the mirrors.
+ be available from all the mirrors.
@@ -66,7 +81,7 @@ limitations under the License.
mirrors (at the end of the mirrors list) that should be
available.
- The KEYS
- link links to the code signing keys used to sign the product.
- The
+ The KEYS
+ file contains the public PGP keys used by Apache Commons developers
+ to sign releases.
You can also browse the Subversion repository. You can also browse the git repository.
- Commons FileUpload uses ASF JIRA for tracking issues.
- See the Commons FileUpload JIRA project page.
+ Apache Commons FileUpload uses ASF JIRA for tracking issues.
+ See the Apache Commons FileUpload JIRA project page.
- To use JIRA you may need to create an account
+ To use JIRA you may need to create an account
(if you have previously created/updated Commons issues using Bugzilla an account will have been automatically
- created and you can use the Forgot Password
+ created and you can use the Forgot Password
page to get a new password).
If you would like to report a bug, or raise an enhancement request with
- Commons FileUpload please do the following:
+ Apache Commons FileUpload please do the following:
+
Please also remember these points:
+
- For more information on subversion and creating patches see the
- Apache Contributors Guide.
+ For more information on creating patches see the
+ Apache Contributors Guide.
You may also find these links useful:
+
-
+ Build updates: Include NOTICE.txt in the jar file and distributions.
+
-
-
DiskFileItem
instead.
+ * @deprecated 1.1 Use {@code DiskFileItem} instead.
*/
@Deprecated
public class DefaultFileItem
extends DiskFileItem {
- // ----------------------------------------------------------- Constructors
-
/**
- * Constructs a new DefaultFileItem
instance.
+ * Constructs a new {@code DefaultFileItem} instance.
*
* @param fieldName The name of the form field.
* @param contentType The content type passed by the browser or
- * null
if not specified.
+ * {@code null} if not specified.
* @param isFormField Whether or not this item is a plain form field, as
* opposed to a file upload.
- * @param fileName The original filename in the user's filesystem, or
- * null
if not specified.
+ * @param fileName The original file name in the user's file system, or
+ * {@code null} if not specified.
* @param sizeThreshold The threshold, in bytes, below which items will be
* retained in memory and above which they will be
* stored as a file.
@@ -57,12 +56,12 @@ public class DefaultFileItem
* which files will be created, should the item size
* exceed the threshold.
*
- * @deprecated 1.1 Use DiskFileItem
instead.
+ * @deprecated 1.1 Use {@code DiskFileItem} instead.
*/
@Deprecated
- public DefaultFileItem(String fieldName, String contentType,
- boolean isFormField, String fileName, int sizeThreshold,
- File repository) {
+ public DefaultFileItem(final String fieldName, final String contentType,
+ final boolean isFormField, final String fileName, final int sizeThreshold,
+ final File repository) {
super(fieldName, contentType, isFormField, fileName, sizeThreshold,
repository);
}
diff --git a/src/main/java/org/apache/commons/fileupload/DefaultFileItemFactory.java b/src/main/java/org/apache/commons/fileupload/DefaultFileItemFactory.java
index 46bc7a37e1..0e336156ad 100644
--- a/src/main/java/org/apache/commons/fileupload/DefaultFileItemFactory.java
+++ b/src/main/java/org/apache/commons/fileupload/DefaultFileItemFactory.java
@@ -17,6 +17,7 @@
package org.apache.commons.fileupload;
import java.io.File;
+
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
/**
@@ -33,25 +34,22 @@
*
*
*
- * @deprecated 1.1 Use System.getProperty("java.io.tmpdir")
.DiskFileItemFactory
instead.
+ * @deprecated 1.1 Use {@code DiskFileItemFactory} instead.
*/
@Deprecated
public class DefaultFileItemFactory extends DiskFileItemFactory {
- // ----------------------------------------------------------- Constructors
-
/**
* Constructs an unconfigured instance of this class. The resulting factory
* may be configured by calling the appropriate setter methods.
*
- * @deprecated 1.1 Use DiskFileItemFactory
instead.
+ * @deprecated 1.1 Use {@code DiskFileItemFactory} instead.
*/
@Deprecated
public DefaultFileItemFactory() {
- super();
}
/**
@@ -64,15 +62,13 @@ public DefaultFileItemFactory() {
* which files will be created, should the item size
* exceed the threshold.
*
- * @deprecated 1.1 Use DiskFileItemFactory
instead.
+ * @deprecated 1.1 Use {@code DiskFileItemFactory} instead.
*/
@Deprecated
- public DefaultFileItemFactory(int sizeThreshold, File repository) {
+ public DefaultFileItemFactory(final int sizeThreshold, final File repository) {
super(sizeThreshold, repository);
}
- // --------------------------------------------------------- Public Methods
-
/**
* Create a new {@link org.apache.commons.fileupload.DefaultFileItem}
* instance from the supplied parameters and the local factory
@@ -80,22 +76,21 @@ public DefaultFileItemFactory(int sizeThreshold, File repository) {
*
* @param fieldName The name of the form field.
* @param contentType The content type of the form field.
- * @param isFormField true
if this is a plain form field;
- * false
otherwise.
+ * @param isFormField {@code true} if this is a plain form field;
+ * {@code false} otherwise.
* @param fileName The name of the uploaded file, if any, as supplied
* by the browser or other client.
*
* @return The newly created file item.
- *
- * @deprecated 1.1 Use DiskFileItemFactory
instead.
+ * @deprecated 1.1 Use {@code DiskFileItemFactory} instead.
*/
@Override
@Deprecated
public FileItem createItem(
- String fieldName,
- String contentType,
- boolean isFormField,
- String fileName
+ final String fieldName,
+ final String contentType,
+ final boolean isFormField,
+ final String fileName
) {
return new DefaultFileItem(fieldName, contentType,
isFormField, fileName, getSizeThreshold(), getRepository());
diff --git a/src/main/java/org/apache/commons/fileupload/DiskFileUpload.java b/src/main/java/org/apache/commons/fileupload/DiskFileUpload.java
index 3fad4f10fd..ba782b36a5 100644
--- a/src/main/java/org/apache/commons/fileupload/DiskFileUpload.java
+++ b/src/main/java/org/apache/commons/fileupload/DiskFileUpload.java
@@ -18,13 +18,14 @@
import java.io.File;
import java.util.List;
+
import javax.servlet.http.HttpServletRequest;
/**
* multipart/mixed
encoding type, as specified by
+ * {@code multipart/mixed} encoding type, as specified by
* RFC 1867. Use {@link
* #parseRequest(HttpServletRequest)} to acquire a list of {@link
* org.apache.commons.fileupload.FileItem}s associated with a given HTML
@@ -34,59 +35,48 @@
* depending on their size, and will be available as {@link
* org.apache.commons.fileupload.FileItem}s.ServletFileUpload
together with
- * DiskFileItemFactory
instead.
+ * @deprecated 1.1 Use {@code ServletFileUpload} together with
+ * {@code DiskFileItemFactory} instead.
*/
@Deprecated
public class DiskFileUpload
extends FileUploadBase {
- // ----------------------------------------------------------- Data members
-
/**
* The factory to use to create new form items.
*/
private DefaultFileItemFactory fileItemFactory;
- // ----------------------------------------------------------- Constructors
-
/**
* Constructs an instance of this class which uses the default factory to
- * create FileItem
instances.
+ * create {@code FileItem} instances.
*
* @see #DiskFileUpload(DefaultFileItemFactory fileItemFactory)
- *
- * @deprecated 1.1 Use FileUpload
instead.
+ * @deprecated 1.1 Use {@code FileUpload} instead.
*/
@Deprecated
public DiskFileUpload() {
- super();
this.fileItemFactory = new DefaultFileItemFactory();
}
/**
* Constructs an instance of this class which uses the supplied factory to
- * create FileItem
instances.
+ * create {@code FileItem} instances.
*
* @see #DiskFileUpload()
* @param fileItemFactory The file item factory to use.
- *
- * @deprecated 1.1 Use FileUpload
instead.
+ * @deprecated 1.1 Use {@code FileUpload} instead.
*/
@Deprecated
- public DiskFileUpload(DefaultFileItemFactory fileItemFactory) {
- super();
+ public DiskFileUpload(final DefaultFileItemFactory fileItemFactory) {
this.fileItemFactory = fileItemFactory;
}
- // ----------------------------------------------------- Property accessors
-
/**
* Returns the factory class used when creating file items.
*
* @return The factory class for new file items.
- *
- * @deprecated 1.1 Use FileUpload
instead.
+ * @deprecated 1.1 Use {@code FileUpload} instead.
*/
@Override
@Deprecated
@@ -95,18 +85,16 @@ public FileItemFactory getFileItemFactory() {
}
/**
- * Sets the factory class to use when creating file items. The factory must
- * be an instance of DefaultFileItemFactory
or a subclass
- * thereof, or else a ClassCastException
will be thrown.
- *
- * @param factory The factory class for new file items.
+ * Returns the location used to temporarily store files that are larger
+ * than the configured size threshold.
*
- * @deprecated 1.1 Use FileUpload
instead.
+ * @return The path to the temporary file location.
+ * @see #setRepositoryPath(String)
+ * @deprecated 1.1 Use {@code DiskFileItemFactory} instead.
*/
- @Override
@Deprecated
- public void setFileItemFactory(FileItemFactory factory) {
- this.fileItemFactory = (DefaultFileItemFactory) factory;
+ public String getRepositoryPath() {
+ return fileItemFactory.getRepository().getPath();
}
/**
@@ -114,10 +102,8 @@ public void setFileItemFactory(FileItemFactory factory) {
* disk.
*
* @return The size threshold, in bytes.
- *
* @see #setSizeThreshold(int)
- *
- * @deprecated 1.1 Use DiskFileItemFactory
instead.
+ * @deprecated 1.1 Use {@code DiskFileItemFactory} instead.
*/
@Deprecated
public int getSizeThreshold() {
@@ -125,32 +111,45 @@ public int getSizeThreshold() {
}
/**
- * Sets the size threshold beyond which files are written directly to disk.
+ * Processes an RFC 1867
+ * compliant {@code multipart/form-data} stream. If files are stored
+ * on disk, the path is given by {@code getRepository()}.
*
- * @param sizeThreshold The size threshold, in bytes.
+ * @param req The servlet request to be parsed. Must be non-null.
+ * @param sizeThreshold The max size in bytes to be stored in memory.
+ * @param sizeMax The maximum allowed upload size, in bytes.
+ * @param path The location where the files should be stored.
+ * @return A list of {@code FileItem} instances parsed from the
+ * request, in the order that they were transmitted.
*
- * @see #getSizeThreshold()
+ * @throws FileUploadException if there are problems reading/parsing
+ * the request or storing files.
*
- * @deprecated 1.1 Use DiskFileItemFactory
instead.
+ * @deprecated 1.1 Use {@code ServletFileUpload} instead.
*/
@Deprecated
- public void setSizeThreshold(int sizeThreshold) {
- fileItemFactory.setSizeThreshold(sizeThreshold);
+ public ListDiskFileItemFactory
instead.
+ * @param fileItemFactory The factory class for new file items.
+ * @deprecated 1.1 Use {@code FileUpload} instead.
*/
+ @Override
@Deprecated
- public String getRepositoryPath() {
- return fileItemFactory.getRepository().getPath();
+ public void setFileItemFactory(final FileItemFactory fileItemFactory) {
+ this.fileItemFactory = (DefaultFileItemFactory) fileItemFactory;
}
/**
@@ -158,45 +157,24 @@ public String getRepositoryPath() {
* than the configured size threshold.
*
* @param repositoryPath The path to the temporary file location.
- *
* @see #getRepositoryPath()
- *
- * @deprecated 1.1 Use DiskFileItemFactory
instead.
+ * @deprecated 1.1 Use {@code DiskFileItemFactory} instead.
*/
@Deprecated
- public void setRepositoryPath(String repositoryPath) {
+ public void setRepositoryPath(final String repositoryPath) {
fileItemFactory.setRepository(new File(repositoryPath));
}
- // --------------------------------------------------------- Public methods
-
/**
- * Processes an RFC 1867
- * compliant multipart/form-data
stream. If files are stored
- * on disk, the path is given by getRepository()
.
- *
- * @param req The servlet request to be parsed. Must be non-null.
- * @param sizeThreshold The max size in bytes to be stored in memory.
- * @param sizeMax The maximum allowed upload size, in bytes.
- * @param path The location where the files should be stored.
- *
- * @return A list of FileItem
instances parsed from the
- * request, in the order that they were transmitted.
- *
- * @throws FileUploadException if there are problems reading/parsing
- * the request or storing files.
+ * Sets the size threshold beyond which files are written directly to disk.
*
- * @deprecated 1.1 Use ServletFileUpload
instead.
+ * @param sizeThreshold The size threshold, in bytes.
+ * @see #getSizeThreshold()
+ * @deprecated 1.1 Use {@code DiskFileItemFactory} instead.
*/
@Deprecated
- public Listmultipart/form-data
POST request.
+ * {@code multipart/form-data} POST request.
*
* javax.activation.DataSource
per se (to avoid a seldom used
+ * {@code javax.activation.DataSource} per se (to avoid a seldom used
* dependency), several of the defined methods are specifically defined with
* the same signatures as methods in that interface. This allows an
* implementation of this interface to also implement
- * javax.activation.DataSource
with minimal additional work.
+ * {@code javax.activation.DataSource} with minimal additional work.
*
* @since 1.3 additionally implements FileItemHeadersSupport
*/
public interface FileItem extends FileItemHeadersSupport {
- // ------------------------------- Methods from javax.activation.DataSource
+ /**
+ * Deletes the underlying storage for a file item, including deleting any
+ * associated temporary disk file. Although this storage will be deleted
+ * automatically when the {@code FileItem} instance is garbage
+ * collected, this method can be used to ensure that this is done at an
+ * earlier time, thus preserving system resources.
+ */
+ void delete();
/**
- * Returns an {@link java.io.InputStream InputStream} that can be
- * used to retrieve the contents of the file.
- *
- * @return An {@link java.io.InputStream InputStream} that can be
- * used to retrieve the contents of the file.
+ * Returns the contents of the file item as an array of bytes.
*
- * @throws IOException if an error occurs.
+ * @return The contents of the file item as an array of bytes.
*/
- InputStream getInputStream() throws IOException;
+ byte[] get();
/**
- * Returns the content type passed by the browser or null
if
+ * Returns the content type passed by the browser or {@code null} if
* not defined.
*
- * @return The content type passed by the browser or null
if
+ * @return The content type passed by the browser or {@code null} if
* not defined.
*/
String getContentType();
/**
- * Returns the original filename in the client's filesystem, as provided by
+ * Returns the name of the field in the multipart form corresponding to
+ * this file item.
+ *
+ * @return The name of the form field.
+ */
+ String getFieldName();
+
+ /**
+ * Returns an {@link java.io.InputStream InputStream} that can be
+ * used to retrieve the contents of the file.
+ *
+ * @return An {@link java.io.InputStream InputStream} that can be
+ * used to retrieve the contents of the file.
+ *
+ * @throws IOException if an error occurs.
+ */
+ InputStream getInputStream() throws IOException;
+
+ /**
+ * Returns the original file name in the client's file system, as provided by
* the browser (or other client software). In most cases, this will be the
* base file name, without path information. However, some clients, such as
* the Opera browser, do include path information.
*
- * @return The original filename in the client's filesystem.
+ * @return The original file name in the client's file system.
* @throws InvalidFileNameException The file name contains a NUL character,
* which might be an indicator of a security attack. If you intend to
* use the file name anyways, catch the exception and use
@@ -82,16 +104,16 @@ public interface FileItem extends FileItemHeadersSupport {
*/
String getName();
- // ------------------------------------------------------- FileItem methods
-
/**
- * Provides a hint as to whether or not the file contents will be read
- * from memory.
+ * Returns an {@link java.io.OutputStream OutputStream} that can
+ * be used for storing the contents of the file.
*
- * @return true
if the file contents will be read from memory;
- * false
otherwise.
+ * @return An {@link java.io.OutputStream OutputStream} that can be used
+ * for storing the contents of the file.
+ *
+ * @throws IOException if an error occurs.
*/
- boolean isInMemory();
+ OutputStream getOutputStream() throws IOException;
/**
* Returns the size of the file item.
@@ -101,11 +123,13 @@ public interface FileItem extends FileItemHeadersSupport {
long getSize();
/**
- * Returns the contents of the file item as an array of bytes.
+ * Returns the contents of the file item as a String, using the default
+ * character encoding. This method uses {@link #get()} to retrieve the
+ * contents of the item.
*
- * @return The contents of the file item as an array of bytes.
+ * @return The contents of the item, as a string.
*/
- byte[] get();
+ String getString();
/**
* Returns the contents of the file item as a String, using the specified
@@ -113,57 +137,29 @@ public interface FileItem extends FileItemHeadersSupport {
* contents of the item.
*
* @param encoding The character encoding to use.
- *
* @return The contents of the item, as a string.
- *
* @throws UnsupportedEncodingException if the requested character
* encoding is not available.
*/
String getString(String encoding) throws UnsupportedEncodingException;
/**
- * Returns the contents of the file item as a String, using the default
- * character encoding. This method uses {@link #get()} to retrieve the
- * contents of the item.
- *
- * @return The contents of the item, as a string.
- */
- String getString();
-
- /**
- * A convenience method to write an uploaded item to disk. The client code
- * is not concerned with whether or not the item is stored in memory, or on
- * disk in a temporary location. They just want to write the uploaded item
- * to a file.
- * File
into which the uploaded item should
- * be stored.
+ * Determines whether or not a {@code FileItem} instance represents
+ * a simple form field.
*
- * @throws Exception if an error occurs.
+ * @return {@code true} if the instance represents a simple form
+ * field; {@code false} if it represents an uploaded file.
*/
- void write(File file) throws Exception;
-
- /**
- * Deletes the underlying storage for a file item, including deleting any
- * associated temporary disk file. Although this storage will be deleted
- * automatically when the FileItem
instance is garbage
- * collected, this method can be used to ensure that this is done at an
- * earlier time, thus preserving system resources.
- */
- void delete();
+ boolean isFormField();
/**
- * Returns the name of the field in the multipart form corresponding to
- * this file item.
+ * Provides a hint as to whether or not the file contents will be read
+ * from memory.
*
- * @return The name of the form field.
+ * @return {@code true} if the file contents will be read from memory;
+ * {@code false} otherwise.
*/
- String getFieldName();
+ boolean isInMemory();
/**
* Sets the field name used to reference this file item.
@@ -173,32 +169,30 @@ public interface FileItem extends FileItemHeadersSupport {
void setFieldName(String name);
/**
- * Determines whether or not a FileItem
instance represents
+ * Specifies whether or not a {@code FileItem} instance represents
* a simple form field.
*
- * @return true
if the instance represents a simple form
- * field; false
if it represents an uploaded file.
- */
- boolean isFormField();
-
- /**
- * Specifies whether or not a FileItem
instance represents
- * a simple form field.
- *
- * @param state true
if the instance represents a simple form
- * field; false
if it represents an uploaded file.
+ * @param state {@code true} if the instance represents a simple form
+ * field; {@code false} if it represents an uploaded file.
*/
void setFormField(boolean state);
/**
- * Returns an {@link java.io.OutputStream OutputStream} that can
- * be used for storing the contents of the file.
+ * A convenience method to write an uploaded item to disk. The client code
+ * is not concerned with whether or not the item is stored in memory, or on
+ * disk in a temporary location. They just want to write the uploaded item
+ * to a file.
+ * true
if this is a plain form field;
- * false
otherwise.
+ * @param isFormField {@code true} if this is a plain form field;
+ * {@code false} otherwise.
* @param fileName The name of the uploaded file, if any, as supplied
* by the browser or other client.
*
diff --git a/src/main/java/org/apache/commons/fileupload/FileItemHeaders.java b/src/main/java/org/apache/commons/fileupload/FileItemHeaders.java
index 3fbda6d8f6..a9b875442b 100644
--- a/src/main/java/org/apache/commons/fileupload/FileItemHeaders.java
+++ b/src/main/java/org/apache/commons/fileupload/FileItemHeaders.java
@@ -20,7 +20,7 @@
/**
* multipart/form-data
POST
+ * item that was received within a {@code multipart/form-data} POST
* request.String
.
+ * Returns the value of the specified part header as a {@code String}.
*
* If the part did not include a header of the specified name, this method
- * return null
. If there are multiple headers with the same
+ * return {@code null}. If there are multiple headers with the same
* name, this method returns the first header in the item. The header
* name is case insensitive.
*
- * @param name a String
specifying the header name
- * @return a String
containing the value of the requested
- * header, or null
if the item does not have a header
+ * @param name a {@code String} specifying the header name
+ * @return a {@code String} containing the value of the requested
+ * header, or {@code null} if the item does not have a header
* of that name
*/
String getHeader(String name);
+ /**
+ * Iterator
of String
objects.
+ * {@code Iterator} of {@code String} objects.
* Iterator
. The header name is
+ * method returns an empty {@code Iterator}. The header name is
* case insensitive.
* String
specifying the header name
- * @return an Iterator
containing the values of the
+ * @param name a {@code String} specifying the header name
+ * @return an {@code Iterator} containing the values of the
* requested header. If the item does not have any headers of
- * that name, return an empty Iterator
+ * that name, return an empty {@code Iterator}
*/
IteratorIterator
of all the header names.
- * Iterator
containing all of the names of
- * headers provided with this file item. If the item does not have
- * any headers return an empty Iterator
- */
- Iteratormultipart/form-data
POST request.
+ * received within a {@code multipart/form-data} POST request.
* The items contents are retrieved by calling {@link #openStream()}.null
if
- * not defined.
+ * Returns the name of the field in the multipart form corresponding to
+ * this file item.
*
- * @return The content type passed by the browser or null
if
- * not defined.
+ * @return The name of the form field.
*/
- String getContentType();
+ String getFieldName();
/**
- * Returns the original filename in the client's filesystem, as provided by
+ * Returns the original file name in the client's file system, as provided by
* the browser (or other client software). In most cases, this will be the
* base file name, without path information. However, some clients, such as
* the Opera browser, do include path information.
*
- * @return The original filename in the client's filesystem.
+ * @return The original file name in the client's file system.
*/
String getName();
/**
- * Returns the name of the field in the multipart form corresponding to
- * this file item.
+ * Determines whether or not a {@code FileItem} instance represents
+ * a simple form field.
*
- * @return The name of the form field.
+ * @return {@code true} if the instance represents a simple form
+ * field; {@code false} if it represents an uploaded file.
*/
- String getFieldName();
+ boolean isFormField();
/**
- * Determines whether or not a FileItem
instance represents
- * a simple form field.
+ * Creates an {@link InputStream}, which allows to read the
+ * items contents.
*
- * @return true
if the instance represents a simple form
- * field; false
if it represents an uploaded file.
+ * @return The input stream, from which the items data may
+ * be read.
+ * @throws IllegalStateException The method was already invoked on
+ * this item. It is not possible to recreate the data stream.
+ * @throws IOException An I/O error occurred.
+ * @see ItemSkippedException
*/
- boolean isFormField();
+ InputStream openStream() throws IOException;
}
diff --git a/src/main/java/org/apache/commons/fileupload/FileUpload.java b/src/main/java/org/apache/commons/fileupload/FileUpload.java
index 4a48a49b67..9277658582 100644
--- a/src/main/java/org/apache/commons/fileupload/FileUpload.java
+++ b/src/main/java/org/apache/commons/fileupload/FileUpload.java
@@ -20,7 +20,7 @@
* multipart/mixed
encoding type, as specified by
+ * {@code multipart/mixed} encoding type, as specified by
* RFC 1867. Use {@link
* #parseRequest(RequestContext)} to acquire a list
* of {@link org.apache.commons.fileupload.FileItem FileItems} associated
@@ -33,42 +33,34 @@
public class FileUpload
extends FileUploadBase {
- // ----------------------------------------------------------- Data members
-
/**
* The factory to use to create new form items.
*/
private FileItemFactory fileItemFactory;
- // ----------------------------------------------------------- Constructors
-
/**
- * Constructs an uninitialised instance of this class.
+ * Constructs an uninitialized instance of this class.
*
* A factory must be
- * configured, using setFileItemFactory()
, before attempting
+ * configured, using {@code setFileItemFactory()}, before attempting
* to parse requests.
*
* @see #FileUpload(FileItemFactory)
*/
public FileUpload() {
- super();
}
/**
* Constructs an instance of this class which uses the supplied factory to
- * create FileItem
instances.
+ * create {@code FileItem} instances.
*
* @see #FileUpload()
* @param fileItemFactory The factory to use for creating file items.
*/
- public FileUpload(FileItemFactory fileItemFactory) {
- super();
+ public FileUpload(final FileItemFactory fileItemFactory) {
this.fileItemFactory = fileItemFactory;
}
- // ----------------------------------------------------- Property accessors
-
/**
* Returns the factory class used when creating file items.
*
@@ -82,11 +74,11 @@ public FileItemFactory getFileItemFactory() {
/**
* Sets the factory class to use when creating file items.
*
- * @param factory The factory class for new file items.
+ * @param fileItemFactory The factory class for new file items.
*/
@Override
- public void setFileItemFactory(FileItemFactory factory) {
- this.fileItemFactory = factory;
+ public void setFileItemFactory(final FileItemFactory fileItemFactory) {
+ this.fileItemFactory = fileItemFactory;
}
}
diff --git a/src/main/java/org/apache/commons/fileupload/FileUploadBase.java b/src/main/java/org/apache/commons/fileupload/FileUploadBase.java
index aaad4d2d4e..9448ead00c 100644
--- a/src/main/java/org/apache/commons/fileupload/FileUploadBase.java
+++ b/src/main/java/org/apache/commons/fileupload/FileUploadBase.java
@@ -20,7 +20,7 @@
import java.io.IOException;
import java.io.InputStream;
-import java.io.UnsupportedEncodingException;
+import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
@@ -28,6 +28,7 @@
import java.util.Locale;
import java.util.Map;
import java.util.NoSuchElementException;
+import java.util.Objects;
import javax.servlet.http.HttpServletRequest;
@@ -41,666 +42,20 @@
import org.apache.commons.io.IOUtils;
/**
- * multipart/mixed
encoding type, as specified by
- * RFC 1867. Use {@link
- * #parseRequest(RequestContext)} to acquire a list of {@link
- * org.apache.commons.fileupload.FileItem}s associated with a given HTML
- * widget.ServletFileUpload
class after the FileUpload 1.1 release.
- * Unfortunately, since this method is static, it is not possible to
- * provide its replacement until this method is removed.true
if the request is multipart;
- * false
otherwise.
- */
- public static final boolean isMultipartContent(RequestContext ctx) {
- String contentType = ctx.getContentType();
- if (contentType == null) {
- return false;
- }
- if (contentType.toLowerCase(Locale.ENGLISH).startsWith(MULTIPART)) {
- return true;
- }
- return false;
- }
-
- /**
- * Utility method that determines whether the request contains multipart
- * content.
- *
- * @param req The servlet request to be evaluated. Must be non-null.
- *
- * @return true
if the request is multipart;
- * false
otherwise.
- *
- * @deprecated 1.1 Use the method on ServletFileUpload
instead.
- */
- @Deprecated
- public static boolean isMultipartContent(HttpServletRequest req) {
- return ServletFileUpload.isMultipartContent(req);
- }
-
- // ----------------------------------------------------- Manifest constants
-
- /**
- * HTTP content type header name.
- */
- public static final String CONTENT_TYPE = "Content-type";
-
- /**
- * HTTP content disposition header name.
- */
- public static final String CONTENT_DISPOSITION = "Content-disposition";
-
- /**
- * HTTP content length header name.
- */
- public static final String CONTENT_LENGTH = "Content-length";
-
- /**
- * Content-disposition value for form data.
- */
- public static final String FORM_DATA = "form-data";
-
- /**
- * Content-disposition value for file attachment.
- */
- public static final String ATTACHMENT = "attachment";
-
- /**
- * Part of HTTP content type header.
- */
- public static final String MULTIPART = "multipart/";
-
- /**
- * HTTP content type header for multipart forms.
- */
- public static final String MULTIPART_FORM_DATA = "multipart/form-data";
-
- /**
- * HTTP content type header for multiple uploads.
- */
- public static final String MULTIPART_MIXED = "multipart/mixed";
-
- /**
- * The maximum length of a single header line that will be parsed
- * (1024 bytes).
- * @deprecated This constant is no longer used. As of commons-fileupload
- * 1.2, the only applicable limit is the total size of a parts headers,
- * {@link MultipartStream#HEADER_PART_SIZE_MAX}.
- */
- @Deprecated
- public static final int MAX_HEADER_SIZE = 1024;
-
- // ----------------------------------------------------------- Data members
-
- /**
- * The maximum size permitted for the complete request, as opposed to
- * {@link #fileSizeMax}. A value of -1 indicates no maximum.
- */
- private long sizeMax = -1;
-
- /**
- * The maximum size permitted for a single uploaded file, as opposed
- * to {@link #sizeMax}. A value of -1 indicates no maximum.
- */
- private long fileSizeMax = -1;
-
- /**
- * The content encoding to use when reading part headers.
- */
- private String headerEncoding;
-
- /**
- * The progress listener.
- */
- private ProgressListener listener;
-
- // ----------------------------------------------------- Property accessors
-
- /**
- * Returns the factory class used when creating file items.
- *
- * @return The factory class for new file items.
- */
- public abstract FileItemFactory getFileItemFactory();
-
- /**
- * Sets the factory class to use when creating file items.
- *
- * @param factory The factory class for new file items.
- */
- public abstract void setFileItemFactory(FileItemFactory factory);
-
- /**
- * Returns the maximum allowed size of a complete request, as opposed
- * to {@link #getFileSizeMax()}.
- *
- * @return The maximum allowed size, in bytes. The default value of
- * -1 indicates, that there is no limit.
- *
- * @see #setSizeMax(long)
- *
- */
- public long getSizeMax() {
- return sizeMax;
- }
-
- /**
- * Sets the maximum allowed size of a complete request, as opposed
- * to {@link #setFileSizeMax(long)}.
- *
- * @param sizeMax The maximum allowed size, in bytes. The default value of
- * -1 indicates, that there is no limit.
- *
- * @see #getSizeMax()
- *
- */
- public void setSizeMax(long sizeMax) {
- this.sizeMax = sizeMax;
- }
-
- /**
- * Returns the maximum allowed size of a single uploaded file,
- * as opposed to {@link #getSizeMax()}.
- *
- * @see #setFileSizeMax(long)
- * @return Maximum size of a single uploaded file.
- */
- public long getFileSizeMax() {
- return fileSizeMax;
- }
-
- /**
- * Sets the maximum allowed size of a single uploaded file,
- * as opposed to {@link #getSizeMax()}.
- *
- * @see #getFileSizeMax()
- * @param fileSizeMax Maximum size of a single uploaded file.
- */
- public void setFileSizeMax(long fileSizeMax) {
- this.fileSizeMax = fileSizeMax;
- }
-
- /**
- * Retrieves the character encoding used when reading the headers of an
- * individual part. When not specified, or null
, the request
- * encoding is used. If that is also not specified, or null
,
- * the platform default encoding is used.
- *
- * @return The encoding used to read part headers.
- */
- public String getHeaderEncoding() {
- return headerEncoding;
- }
-
- /**
- * Specifies the character encoding to be used when reading the headers of
- * individual part. When not specified, or null
, the request
- * encoding is used. If that is also not specified, or null
,
- * the platform default encoding is used.
- *
- * @param encoding The encoding used to read part headers.
- */
- public void setHeaderEncoding(String encoding) {
- headerEncoding = encoding;
- }
-
- // --------------------------------------------------------- Public methods
-
- /**
- * Processes an RFC 1867
- * compliant multipart/form-data
stream.
- *
- * @param req The servlet request to be parsed.
- *
- * @return A list of FileItem
instances parsed from the
- * request, in the order that they were transmitted.
- *
- * @throws FileUploadException if there are problems reading/parsing
- * the request or storing files.
- *
- * @deprecated 1.1 Use {@link ServletFileUpload#parseRequest(HttpServletRequest)} instead.
- */
- @Deprecated
- public Listmultipart/form-data
stream.
- *
- * @param ctx The context for the request to be parsed.
- *
- * @return An iterator to instances of FileItemStream
- * parsed from the request, in the order that they were
- * transmitted.
- *
- * @throws FileUploadException if there are problems reading/parsing
- * the request or storing files.
- * @throws IOException An I/O error occurred. This may be a network
- * error while communicating with the client or a problem while
- * storing the uploaded content.
- */
- public FileItemIterator getItemIterator(RequestContext ctx)
- throws FileUploadException, IOException {
- try {
- return new FileItemIteratorImpl(ctx);
- } catch (FileUploadIOException e) {
- // unwrap encapsulated SizeException
- throw (FileUploadException) e.getCause();
- }
- }
-
- /**
- * Processes an RFC 1867
- * compliant multipart/form-data
stream.
- *
- * @param ctx The context for the request to be parsed.
- *
- * @return A list of FileItem
instances parsed from the
- * request, in the order that they were transmitted.
- *
- * @throws FileUploadException if there are problems reading/parsing
- * the request or storing files.
- */
- public Listmultipart/form-data
stream.
- *
- * @param ctx The context for the request to be parsed.
- *
- * @return A map of FileItem
instances parsed from the request.
- *
- * @throws FileUploadException if there are problems reading/parsing
- * the request or storing files.
- *
- * @since 1.3
- */
- public MapContent-type
header.
- *
- * @param contentType The value of the content type header from which to
- * extract the boundary value.
- *
- * @return The boundary, as a byte array.
- */
- protected byte[] getBoundary(String contentType) {
- ParameterParser parser = new ParameterParser();
- parser.setLowerCaseNames(true);
- // Parameter parser can handle null input
- MapContent-disposition
- * header.
- *
- * @param headers A Map
containing the HTTP request headers.
- *
- * @return The file name for the current encapsulation
.
- * @deprecated 1.2.1 Use {@link #getFileName(FileItemHeaders)}.
- */
- @Deprecated
- protected String getFileName(MapContent-disposition
- * header.
- *
- * @param headers The HTTP headers object.
- *
- * @return The file name for the current encapsulation
.
- */
- protected String getFileName(FileItemHeaders headers) {
- return getFileName(headers.getHeader(CONTENT_DISPOSITION));
- }
-
- /**
- * Returns the given content-disposition headers file name.
- * @param pContentDisposition The content-disposition headers value.
- * @return The file name
- */
- private String getFileName(String pContentDisposition) {
- String fileName = null;
- if (pContentDisposition != null) {
- String cdl = pContentDisposition.toLowerCase(Locale.ENGLISH);
- if (cdl.startsWith(FORM_DATA) || cdl.startsWith(ATTACHMENT)) {
- ParameterParser parser = new ParameterParser();
- parser.setLowerCaseNames(true);
- // Parameter parser can handle null input
- MapContent-disposition
- * header.
- *
- * @param headers A Map
containing the HTTP request headers.
- *
- * @return The field name for the current encapsulation
.
- */
- protected String getFieldName(FileItemHeaders headers) {
- return getFieldName(headers.getHeader(CONTENT_DISPOSITION));
- }
-
- /**
- * Returns the field name, which is given by the content-disposition
- * header.
- * @param pContentDisposition The content-dispositions header value.
- * @return The field jake
- */
- private String getFieldName(String pContentDisposition) {
- String fieldName = null;
- if (pContentDisposition != null
- && pContentDisposition.toLowerCase(Locale.ENGLISH).startsWith(FORM_DATA)) {
- ParameterParser parser = new ParameterParser();
- parser.setLowerCaseNames(true);
- // Parameter parser can handle null input
- MapContent-disposition
- * header.
- *
- * @param headers A Map
containing the HTTP request headers.
- *
- * @return The field name for the current encapsulation
.
- * @deprecated 1.2.1 Use {@link #getFieldName(FileItemHeaders)}.
- */
- @Deprecated
- protected String getFieldName(MapMap
containing the HTTP request
- * headers.
- * @param isFormField Whether or not this item is a form field, as
- * opposed to a file.
- *
- * @return A newly created FileItem
instance.
- *
- * @throws FileUploadException if an error occurs.
- * @deprecated 1.2 This method is no longer used in favour of
- * internally created instances of {@link FileItem}.
- */
- @Deprecated
- protected FileItem createItem(Mapheader-part
and returns as key/value
- * pairs.
- *
- * header-part
of the current
- * encapsulation
.
- *
- * @return A Map
containing the parsed HTTP request headers.
- */
- protected FileItemHeaders getParsedHeaders(String headerPart) {
- final int len = headerPart.length();
- FileItemHeadersImpl headers = newFileItemHeaders();
- int start = 0;
- for (;;) {
- int end = parseEndOfLine(headerPart, start);
- if (start == end) {
- break;
- }
- StringBuilder header = new StringBuilder(headerPart.substring(start, end));
- start = end + 2;
- while (start < len) {
- int nonWs = start;
- while (nonWs < len) {
- char c = headerPart.charAt(nonWs);
- if (c != ' ' && c != '\t') {
- break;
- }
- ++nonWs;
- }
- if (nonWs == start) {
- break;
- }
- // Continuation line found
- end = parseEndOfLine(headerPart, nonWs);
- header.append(" ").append(headerPart.substring(nonWs, end));
- start = end + 2;
- }
- parseHeaderLine(headers, header.toString());
- }
- return headers;
- }
-
- /**
- * Creates a new instance of {@link FileItemHeaders}.
- * @return The new instance.
- */
- protected FileItemHeadersImpl newFileItemHeaders() {
- return new FileItemHeadersImpl();
- }
-
- /**
- * header-part
and returns as key/value
- * pairs.
- *
- * header-part
of the current
- * encapsulation
.
- *
- * @return A Map
containing the parsed HTTP request headers.
- * @deprecated 1.2.1 Use {@link #getParsedHeaders(String)}
- */
- @Deprecated
- protected MapMap
containing the HTTP request headers.
- * @param name The name of the header to return.
- *
- * @return The value of specified header, or a comma-separated list if
- * there were multiple headers of that name.
- * @deprecated 1.2.1 Use {@link FileItemHeaders#getHeader(String)}.
- */
- @Deprecated
- protected final String getHeader(MapFileUploadIOException
with the
- * given cause.
+ * Constructs an {@code UnknownSizeException} with
+ * the specified detail message.
*
- * @param pCause The exceptions cause, if any, or null.
+ * @param message The detail message.
*/
- public FileUploadIOException(FileUploadException pCause) {
- // We're not doing super(pCause) cause of 1.3 compatibility.
- cause = pCause;
+ public UnknownSizeException(final String message) {
+ super(message);
}
- /**
- * Returns the exceptions cause.
- *
- * @return The exceptions cause, if any, or null.
- */
- @Override
- public Throwable getCause() {
- return cause;
+ }
+
+ /**
+ * Line feed.
+ */
+ private static final char LF = '\n';
+
+ /**
+ * Carriage return.
+ */
+ private static final char CR = '\r';
+
+ /**
+ * HTTP content type header name.
+ */
+ public static final String CONTENT_TYPE = "Content-type";
+
+ /**
+ * HTTP content disposition header name.
+ */
+ public static final String CONTENT_DISPOSITION = "Content-disposition";
+
+ /**
+ * HTTP content length header name.
+ */
+ public static final String CONTENT_LENGTH = "Content-length";
+
+ /**
+ * Content-disposition value for form data.
+ */
+ public static final String FORM_DATA = "form-data";
+
+ /**
+ * Content-disposition value for file attachment.
+ */
+ public static final String ATTACHMENT = "attachment";
+
+ /**
+ * Part of HTTP content type header.
+ */
+ public static final String MULTIPART = "multipart/";
+
+ /**
+ * HTTP content type header for multipart forms.
+ */
+ public static final String MULTIPART_FORM_DATA = "multipart/form-data";
+
+ /**
+ * HTTP content type header for multiple uploads.
+ */
+ public static final String MULTIPART_MIXED = "multipart/mixed";
+
+ /**
+ * HTTP content type header for multiple related data.
+ *
+ * @since 1.6.0
+ */
+ public static final String MULTIPART_RELATED = "multipart/related";
+
+ /**
+ * The maximum length of a single header line that will be parsed
+ * (1024 bytes).
+ * @deprecated This constant is no longer used. As of commons-fileupload
+ * 1.6, the applicable limit is the total size of a single part's headers,
+ * {@link #getPartHeaderSizeMax()} in bytes.
+ */
+ @Deprecated
+ public static final int MAX_HEADER_SIZE = 1024;
+
+ /**
+ * Default per part header size limit in bytes.
+ *
+ * @since 1.6.0
+ */
+ public static final int DEFAULT_PART_HEADER_SIZE_MAX = 512;
+
+
+ /**
+ * Utility method that determines whether the request contains multipart
+ * content.
+ *
+ * @param req The servlet request to be evaluated. Must be non-null.
+ * @return {@code true} if the request is multipart;
+ * {@code false} otherwise.
+ *
+ * @deprecated 1.1 Use the method on {@code ServletFileUpload} instead.
+ */
+ @Deprecated
+ public static boolean isMultipartContent(final HttpServletRequest req) {
+ return ServletFileUpload.isMultipartContent(req);
+ }
+
+ /**
+ * InvalidContentTypeException
with no
- * detail message.
- */
- public InvalidContentTypeException() {
- super();
- }
+ /**
+ * Gets the file name from the {@code Content-disposition}
+ * header.
+ *
+ * @param headers The HTTP headers object.
+ * @return The file name for the current {@code encapsulation}.
+ */
+ protected String getFileName(final FileItemHeaders headers) {
+ return getFileName(headers.getHeader(CONTENT_DISPOSITION));
+ }
- /**
- * Constructs an InvalidContentTypeException
with
- * the specified detail message.
- *
- * @param message The detail message.
- */
- public InvalidContentTypeException(String message) {
- super(message);
- }
+ /**
+ * Gets the file name from the {@code Content-disposition}
+ * header.
+ *
+ * @param headers A {@code Map} containing the HTTP request headers.
+ * @return The file name for the current {@code encapsulation}.
+ * @deprecated 1.2.1 Use {@link #getFileName(FileItemHeaders)}.
+ */
+ @Deprecated
+ protected String getFileName(final MapInvalidContentTypeException
with
- * the specified detail message and cause.
- *
- * @param msg The detail message.
- * @param cause the original cause
- *
- * @since 1.3.1
- */
- public InvalidContentTypeException(String msg, Throwable cause) {
- super(msg, cause);
+ /**
+ * Returns the given content-disposition headers file name.
+ * @param contentDisposition The content-disposition headers value.
+ * @return The file name
+ */
+ private String getFileName(final String contentDisposition) {
+ String fileName = null;
+ if (contentDisposition != null) {
+ final String cdl = contentDisposition.toLowerCase(Locale.ROOT);
+ if (cdl.startsWith(FORM_DATA) || cdl.startsWith(ATTACHMENT)) {
+ final ParameterParser parser = new ParameterParser();
+ parser.setLowerCaseNames(true);
+ // Parameter parser can handle null input
+ final MapUnknownSizeException
with no
- * detail message.
- */
- public UnknownSizeException() {
- super();
- }
-
- /**
- * Constructs an UnknownSizeException
with
- * the specified detail message.
- *
- * @param message The detail message.
- */
- public UnknownSizeException(String message) {
- super(message);
+ protected MapSizeExceededException
with
- * the specified detail message, and actual and permitted sizes.
- *
- * @param message The detail message.
- * @param actual The actual request size.
- * @param permitted The maximum permitted request size.
- */
- public SizeLimitExceededException(String message, long actual,
- long permitted) {
- super(message, actual, permitted);
+ public MapSizeExceededException
with
- * the specified detail message, and actual and permitted sizes.
- *
- * @param message The detail message.
- * @param actual The actual request size.
- * @param permitted The maximum permitted request size.
- */
- public FileSizeLimitExceededException(String message, long actual,
- long permitted) {
- super(message, actual, permitted);
- }
+ @Deprecated
+ public ListFileUploadException
without message.
+ * Constructs a new {@code FileUploadException} without message.
*/
public FileUploadException() {
- this(null, null);
+ // empty
}
/**
- * Constructs a new FileUploadException
with specified detail
+ * Constructs a new {@code FileUploadException} with specified detail
* message.
*
- * @param msg the error message.
+ * @param message the error message.
*/
- public FileUploadException(final String msg) {
- this(msg, null);
+ public FileUploadException(final String message) {
+ super(message);
}
/**
- * Creates a new FileUploadException
with the given
+ * Creates a new {@code FileUploadException} with the given
* detail message and cause.
*
- * @param msg The exceptions detail message.
+ * @param message The exceptions detail message.
* @param cause The exceptions cause.
*/
- public FileUploadException(String msg, Throwable cause) {
- super(msg);
- this.cause = cause;
- }
-
- /**
- * Prints this throwable and its backtrace to the specified print stream.
- *
- * @param stream PrintStream
to use for output
- */
- @Override
- public void printStackTrace(PrintStream stream) {
- super.printStackTrace(stream);
- if (cause != null) {
- stream.println("Caused by:");
- cause.printStackTrace(stream);
- }
- }
-
- /**
- * Prints this throwable and its backtrace to the specified
- * print writer.
- *
- * @param writer PrintWriter
to use for output
- */
- @Override
- public void printStackTrace(PrintWriter writer) {
- super.printStackTrace(writer);
- if (cause != null) {
- writer.println("Caused by:");
- cause.printStackTrace(writer);
- }
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public Throwable getCause() {
- return cause;
+ public FileUploadException(final String message, final Throwable cause) {
+ super(message, cause);
}
}
diff --git a/src/main/java/org/apache/commons/fileupload/InvalidFileNameException.java b/src/main/java/org/apache/commons/fileupload/InvalidFileNameException.java
index 8bdee38e5e..997e42633f 100644
--- a/src/main/java/org/apache/commons/fileupload/InvalidFileNameException.java
+++ b/src/main/java/org/apache/commons/fileupload/InvalidFileNameException.java
@@ -42,12 +42,12 @@ public class InvalidFileNameException extends RuntimeException {
/**
* Creates a new instance.
*
- * @param pName The file name causing the exception.
- * @param pMessage A human readable error message.
+ * @param name The file name causing the exception.
+ * @param message A human readable error message.
*/
- public InvalidFileNameException(String pName, String pMessage) {
- super(pMessage);
- name = pName;
+ public InvalidFileNameException(final String name, final String message) {
+ super(message);
+ this.name = name;
}
/**
diff --git a/src/main/java/org/apache/commons/fileupload/MultipartStream.java b/src/main/java/org/apache/commons/fileupload/MultipartStream.java
index 2c58e7e413..07389e0506 100644
--- a/src/main/java/org/apache/commons/fileupload/MultipartStream.java
+++ b/src/main/java/org/apache/commons/fileupload/MultipartStream.java
@@ -16,8 +16,6 @@
*/
package org.apache.commons.fileupload;
-import static java.lang.String.format;
-
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
@@ -25,194 +23,527 @@
import java.io.UnsupportedEncodingException;
import org.apache.commons.fileupload.FileUploadBase.FileUploadIOException;
+import org.apache.commons.fileupload.FileUploadBase.SizeLimitExceededException;
import org.apache.commons.fileupload.util.Closeable;
import org.apache.commons.fileupload.util.Streams;
/**
- *
+ *
- * multipart-body := preamble 1*encapsulation close-delimiter epilogue
+ *
- * encapsulation := delimiter body CRLF
- * delimiter := "--" boundary CRLF
- * close-delimiter := "--" boundary "--"
- * preamble := <ignore>
- * epilogue := <ignore>
- * body := header-part CRLF body-part
- * header-part := 1*header CRLF
- * header := header-name ":" header-value
- * header-name := <printable ascii characters except ":">
- * header-value := <any ascii characters except CR & LF>
- * body-data := <arbitrary data>
- * {@code
+ * multipart-body := preamble 1*encapsulation close-delimiter epilogue
+ * encapsulation := delimiter body CRLF
+ * delimiter := "--" boundary CRLF
+ * close-delimiter := "--" boundary "--"
+ * preamble := <ignore>
+ * epilogue := <ignore>
+ * body := header-part CRLF body-part
+ * header-part := 1*header CRLF
+ * header := header-name ":" header-value
+ * header-name := <printable ASCII characters except ":">
+ * header-value := <any ASCII characters except CR & LF>
+ * body-data := <arbitrary data>
+ * }
*
- *
+ *
- * try {
+ *
*/
public class MultipartStream {
/**
- * Internal class, which is used to invoke the
- * {@link ProgressListener}.
+ * Thrown upon attempt of setting an invalid boundary token.
*/
- public static class ProgressNotifier {
+ public static class IllegalBoundaryException extends IOException {
/**
- * The listener to invoke.
+ * The UID to use when serializing this instance.
*/
- private final ProgressListener listener;
+ private static final long serialVersionUID = -161533165102632918L;
/**
- * Number of expected bytes, if known, or -1.
+ * Constructs an {@code IllegalBoundaryException} with no
+ * detail message.
*/
- private final long contentLength;
+ public IllegalBoundaryException() {
+ }
/**
- * Number of bytes, which have been read so far.
+ * Constructs an {@code IllegalBoundaryException} with
+ * the specified detail message.
+ *
+ * @param message The detail message.
*/
- private long bytesRead;
+ public IllegalBoundaryException(final String message) {
+ super(message);
+ }
+
+ }
+
+ /**
+ * An {@link InputStream} for reading an items contents.
+ */
+ public class ItemInputStream extends InputStream implements Closeable {
/**
- * Number of items, which have been read so far.
+ * Offset when converting negative bytes to integers.
*/
- private int items;
+ private static final int BYTE_POSITIVE_OFFSET = 256;
/**
- * Creates a new instance with the given listener
- * and content length.
- *
- * @param pListener The listener to invoke.
- * @param pContentLength The expected content length.
+ * The number of bytes, which have been read so far.
+ */
+ private long total;
+
+ /**
+ * The number of bytes, which must be hold, because
+ * they might be a part of the boundary.
+ */
+ private int pad;
+
+ /**
+ * The current offset in the buffer.
*/
- ProgressNotifier(ProgressListener pListener, long pContentLength) {
- listener = pListener;
- contentLength = pContentLength;
+ private int pos;
+
+ /**
+ * Whether the stream is already closed.
+ */
+ private boolean closed;
+
+ /**
+ * Creates a new instance.
+ */
+ ItemInputStream() {
+ findSeparator();
}
/**
- * Called to indicate that bytes have been read.
+ * Returns the number of bytes, which are currently
+ * available, without blocking.
*
- * @param pBytes Number of bytes, which have been read.
+ * @throws IOException An I/O error occurs.
+ * @return Number of bytes in the buffer.
*/
- void noteBytesRead(int pBytes) {
- /* Indicates, that the given number of bytes have been read from
- * the input stream.
- */
- bytesRead += pBytes;
- notifyListener();
+ @Override
+ public int available() throws IOException {
+ if (pos == -1) {
+ return tail - head - pad;
+ }
+ return pos - head;
}
/**
- * Called to indicate, that a new file item has been detected.
+ * Closes the input stream.
+ *
+ * @throws IOException An I/O error occurred.
*/
- void noteItem() {
- ++items;
- notifyListener();
+ @Override
+ public void close() throws IOException {
+ close(false);
}
/**
- * Called for notifying the listener.
+ * Closes the input stream.
+ *
+ * @param closeUnderlying Whether to close the underlying stream (hard close)
+ * @throws IOException An I/O error occurred.
*/
- private void notifyListener() {
- if (listener != null) {
- listener.update(bytesRead, contentLength, items);
+ public void close(final boolean closeUnderlying) throws IOException {
+ if (closed) {
+ return;
+ }
+ if (closeUnderlying) {
+ closed = true;
+ input.close();
+ } else {
+ for (;;) {
+ int available = available();
+ if (available == 0) {
+ available = makeAvailable();
+ if (available == 0) {
+ break;
+ }
+ }
+ if (skip(available) != available) {
+ // TODO log or throw?
+ }
+ }
}
+ closed = true;
}
- }
-
- // ----------------------------------------------------- Manifest constants
+ /**
+ * Called for finding the separator.
+ */
+ private void findSeparator() {
+ pos = MultipartStream.this.findSeparator();
+ if (pos == -1) {
+ if (tail - head > keepRegion) {
+ pad = keepRegion;
+ } else {
+ pad = tail - head;
+ }
+ }
+ }
- /**
- * The Carriage Return ASCII character value.
- */
- public static final byte CR = 0x0D;
+ /**
+ * Returns the number of bytes, which have been read
+ * by the stream.
+ *
+ * @return Number of bytes, which have been read so far.
+ */
+ public long getBytesRead() {
+ return total;
+ }
- /**
- * The Line Feed ASCII character value.
- */
- public static final byte LF = 0x0A;
+ /**
+ * Returns, whether the stream is closed.
+ *
+ * @return True, if the stream is closed, otherwise false.
+ */
+ @Override
+ public boolean isClosed() {
+ return closed;
+ }
- /**
- * The dash (-) ASCII character value.
- */
- public static final byte DASH = 0x2D;
+ /**
+ * Attempts to read more data.
+ *
+ * @return Number of available bytes
+ * @throws IOException An I/O error occurred.
+ */
+ private int makeAvailable() throws IOException {
+ if (pos != -1) {
+ return 0;
+ }
- /**
- * The maximum length of {@code
+ * try {
* MultipartStream multipartStream = new MultipartStream(input, boundary);
* boolean nextPart = multipartStream.skipPreamble();
* OutputStream output;
- * while(nextPart) {
- * String header = multipartStream.readHeaders();
- * // process headers
- * // create some output stream
- * multipartStream.readBodyData(output);
- * nextPart = multipartStream.readBoundary();
+ * while (nextPart) {
+ * String header = multipartStream.readHeaders();
+ * // process headers
+ * // create some output stream
+ * multipartStream.readBodyData(output);
+ * nextPart = multipartStream.readBoundary();
* }
- * } catch(MultipartStream.MalformedStreamException e) {
+ * } catch (MultipartStream.MalformedStreamException e) {
* // the stream failed to follow required syntax
- * } catch(IOException e) {
+ * } catch (IOException e) {
* // a read or write error occurred
- * }
- *
+ * }
+ * }header-part
that will be
- * processed (10 kilobytes = 10240 bytes.).
- */
- public static final int HEADER_PART_SIZE_MAX = 10240;
+ // Move the data to the beginning of the buffer.
+ total += tail - head - pad;
+ System.arraycopy(buffer, tail - pad, buffer, 0, pad);
- /**
- * The default length of the buffer used for processing a request.
- */
- protected static final int DEFAULT_BUFSIZE = 4096;
+ // Refill buffer with new data.
+ head = 0;
+ tail = pad;
- /**
- * A byte sequence that marks the end of header-part
- * (CRLFCRLF
).
- */
- protected static final byte[] HEADER_SEPARATOR = {CR, LF, CR, LF};
+ for (;;) {
+ final int bytesRead = input.read(buffer, tail, bufSize - tail);
+ if (bytesRead == -1) {
+ // The last pad amount is left in the buffer.
+ // Boundary can't be in there so signal an error
+ // condition.
+ final String msg = "Stream ended unexpectedly";
+ throw new MalformedStreamException(msg);
+ }
+ if (notifier != null) {
+ notifier.noteBytesRead(bytesRead);
+ }
+ tail += bytesRead;
- /**
- * A byte sequence that that follows a delimiter that will be
- * followed by an encapsulation (CRLF
).
- */
- protected static final byte[] FIELD_SEPARATOR = {CR, LF};
+ findSeparator();
+ final int av = available();
- /**
- * A byte sequence that that follows a delimiter of the last
- * encapsulation in the stream (--
).
- */
- protected static final byte[] STREAM_TERMINATOR = {DASH, DASH};
+ if (av > 0 || pos != -1) {
+ return av;
+ }
+ }
+ }
- /**
- * A byte sequence that precedes a boundary (CRLF--
).
- */
- protected static final byte[] BOUNDARY_PREFIX = {CR, LF, DASH, DASH};
+ /**
+ * Returns the next byte in the stream.
+ *
+ * @return The next byte in the stream, as a non-negative
+ * integer, or -1 for EOF.
+ * @throws IOException An I/O error occurred.
+ */
+ @Override
+ public int read() throws IOException {
+ if (closed) {
+ throw new FileItemStream.ItemSkippedException();
+ }
+ if (available() == 0 && makeAvailable() == 0) {
+ return -1;
+ }
+ ++total;
+ final int b = buffer[head++];
+ if (b >= 0) {
+ return b;
+ }
+ return b + BYTE_POSITIVE_OFFSET;
+ }
- // ----------------------------------------------------------- Data members
+ /**
+ * Reads bytes into the given buffer.
+ *
+ * @param b The destination buffer, where to write to.
+ * @param off Offset of the first byte in the buffer.
+ * @param len Maximum number of bytes to read.
+ * @return Number of bytes, which have been actually read,
+ * or -1 for EOF.
+ * @throws IOException An I/O error occurred.
+ */
+ @Override
+ public int read(final byte[] b, final int off, final int len) throws IOException {
+ if (closed) {
+ throw new FileItemStream.ItemSkippedException();
+ }
+ if (len == 0) {
+ return 0;
+ }
+ int res = available();
+ if (res == 0) {
+ res = makeAvailable();
+ if (res == 0) {
+ return -1;
+ }
+ }
+ res = Math.min(res, len);
+ System.arraycopy(buffer, head, b, off, res);
+ head += res;
+ total += res;
+ return res;
+ }
- /**
+ /**
+ * Skips the given number of bytes.
+ *
+ * @param bytes Number of bytes to skip.
+ * @return The number of bytes, which have actually been
+ * skipped.
+ * @throws IOException An I/O error occurred.
+ */
+ @Override
+ public long skip(final long bytes) throws IOException {
+ if (closed) {
+ throw new FileItemStream.ItemSkippedException();
+ }
+ int av = available();
+ if (av == 0) {
+ av = makeAvailable();
+ if (av == 0) {
+ return 0;
+ }
+ }
+ final long res = Math.min(av, bytes);
+ head += res;
+ return res;
+ }
+
+ }
+
+ /**
+ * Thrown to indicate that the input stream fails to follow the
+ * required syntax.
+ */
+ public static class MalformedStreamException extends IOException {
+
+ /**
+ * The UID to use when serializing this instance.
+ */
+ private static final long serialVersionUID = 6466926458059796677L;
+
+ /**
+ * Constructs a {@code MalformedStreamException} with no
+ * detail message.
+ */
+ public MalformedStreamException() {
+ }
+
+ /**
+ * Constructs an {@code MalformedStreamException} with
+ * the specified detail message.
+ *
+ * @param message The detail message.
+ */
+ public MalformedStreamException(final String message) {
+ super(message);
+ }
+
+ }
+
+ /**
+ * Internal class, which is used to invoke the
+ * {@link ProgressListener}.
+ */
+ public static class ProgressNotifier {
+
+ /**
+ * The listener to invoke.
+ */
+ private final ProgressListener listener;
+
+ /**
+ * Number of expected bytes, if known, or -1.
+ */
+ private final long contentLength;
+
+ /**
+ * Number of bytes, which have been read so far.
+ */
+ private long bytesRead;
+
+ /**
+ * Number of items, which have been read so far.
+ */
+ private int items;
+
+ /**
+ * Creates a new instance with the given listener
+ * and content length.
+ *
+ * @param listener The listener to invoke.
+ * @param contentLength The expected content length.
+ */
+ ProgressNotifier(final ProgressListener listener, final long contentLength) {
+ this.listener = listener;
+ this.contentLength = contentLength;
+ }
+
+ /**
+ * Called to indicate that bytes have been read.
+ *
+ * @param count Number of bytes, which have been read.
+ */
+ void noteBytesRead(final int count) {
+ /* Indicates, that the given number of bytes have been read from
+ * the input stream.
+ */
+ bytesRead += count;
+ notifyListener();
+ }
+
+ /**
+ * Called to indicate, that a new file item has been detected.
+ */
+ void noteItem() {
+ ++items;
+ notifyListener();
+ }
+
+ /**
+ * Called for notifying the listener.
+ */
+ private void notifyListener() {
+ if (listener != null) {
+ listener.update(bytesRead, contentLength, items);
+ }
+ }
+
+ }
+
+ /**
+ * The Carriage Return ASCII character value.
+ */
+ public static final byte CR = 0x0D;
+
+ /**
+ * The Line Feed ASCII character value.
+ */
+ public static final byte LF = 0x0A;
+
+ /**
+ * The dash (-) ASCII character value.
+ */
+ public static final byte DASH = 0x2D;
+
+ /**
+ * The maximum length of {@code header-part} that will be
+ * processed (10 kilobytes = 10240 bytes.).
+ *
+ * @deprecated Unused. Replaced by {@link #getPartHeaderSizeMax()}.
+ */
+ @Deprecated
+ public static final int HEADER_PART_SIZE_MAX = 10240;
+
+ /**
+ * The default length of the buffer used for processing a request.
+ */
+ protected static final int DEFAULT_BUFSIZE = 4096;
+
+ /**
+ * A byte sequence that marks the end of {@code header-part}
+ * ({@code CRLFCRLF}).
+ */
+ protected static final byte[] HEADER_SEPARATOR = {CR, LF, CR, LF};
+
+ /**
+ * A byte sequence that follows a delimiter that will be
+ * followed by an encapsulation ({@code CRLF}).
+ */
+ protected static final byte[] FIELD_SEPARATOR = {CR, LF};
+
+ /**
+ * A byte sequence that follows a delimiter of the last
+ * encapsulation in the stream ({@code --}).
+ */
+ protected static final byte[] STREAM_TERMINATOR = {DASH, DASH};
+
+ /**
+ * A byte sequence that precedes a boundary ({@code CRLF--}).
+ */
+ protected static final byte[] BOUNDARY_PREFIX = {CR, LF, DASH, DASH};
+
+ /**
+ * Compares {@code count} first bytes in the arrays
+ * {@code a} and {@code b}.
+ *
+ * @param a The first array to compare.
+ * @param b The second array to compare.
+ * @param count How many bytes should be compared.
+ * @return {@code true} if {@code count} first bytes in arrays
+ * {@code a} and {@code b} are equal.
+ */
+ public static boolean arrayequals(final byte[] a,
+ final byte[] b,
+ final int count) {
+ for (int i = 0; i < count; i++) {
+ if (a[i] != b[i]) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
* The input stream from which data is read.
*/
private final InputStream input;
/**
- * The length of the boundary token plus the leading CRLF--
.
+ * The length of the boundary token plus the leading {@code CRLF--}.
*/
private int boundaryLength;
@@ -244,14 +575,12 @@ private void notifyListener() {
/**
* The index of first valid character in the buffer.
- *
* 0 <= head < bufSize
*/
private int head;
/**
* The index of last valid character in the buffer + 1.
- *
* 0 <= tail <= bufSize
*/
private int tail;
@@ -266,7 +595,10 @@ private void notifyListener() {
*/
private final ProgressNotifier notifier;
- // ----------------------------------------------------------- Constructors
+ /**
+ * The maximum permitted size of the headers provided with a single part in bytes.
+ */
+ private int partHeaderSizeMax = FileUploadBase.DEFAULT_PART_HEADER_SIZE_MAX;
/**
* Creates a new instance.
@@ -280,51 +612,56 @@ public MultipartStream() {
}
/**
- * MultipartStream
with a custom size buffer
- * and no progress notifier.
+ * Constructs a {@code MultipartStream} with a default size buffer.
*
- * InputStream
to serve as a data source.
+ * @param input The {@code InputStream} to serve as a data source.
* @param boundary The token used for dividing the stream into
- * encapsulations
.
- * @param bufSize The size of the buffer to be used, in bytes.
+ * {@code encapsulations}.
*
* @deprecated 1.2.1 Use {@link #MultipartStream(InputStream, byte[], int,
- * ProgressNotifier)}.
+ * ProgressNotifier)}.
+ */
+ @Deprecated
+ public MultipartStream(final InputStream input,
+ final byte[] boundary) {
+ this(input, boundary, DEFAULT_BUFSIZE, null);
+ }
+
+ /**
+ * Constructs a {@code MultipartStream} with a custom size buffer and no progress notifier.
+ *
+ * MultipartStream
with a custom size buffer.
+ * Constructs a {@code MultipartStream} with a custom size buffer.
*
- * InputStream
to serve as a data source.
- * @param boundary The token used for dividing the stream into
- * encapsulations
.
+ * @param input The {@code InputStream} to serve as a data source.
+ * @param boundary The token used for dividing the stream into {@code encapsulations}.
* @param bufSize The size of the buffer to be used, in bytes.
- * @param pNotifier The notifier, which is used for calling the
- * progress listener, if any.
+ * @param notifier The notifier, which is used for calling the progress listener, if any.
*
* @throws IllegalArgumentException If the buffer size is too small
- *
* @since 1.3.1
*/
- public MultipartStream(InputStream input,
- byte[] boundary,
- int bufSize,
- ProgressNotifier pNotifier) {
-
+ public MultipartStream(final InputStream input, final byte[] boundary, final int bufSize, final ProgressNotifier notifier) {
if (boundary == null) {
throw new IllegalArgumentException("boundary may not be null");
}
@@ -332,117 +669,177 @@ public MultipartStream(InputStream input,
// body-data tokens.
this.boundaryLength = boundary.length + BOUNDARY_PREFIX.length;
if (bufSize < this.boundaryLength + 1) {
- throw new IllegalArgumentException(
- "The buffer size specified for the MultipartStream is too small");
+ throw new IllegalArgumentException("The buffer size specified for the MultipartStream is too small");
}
-
this.input = input;
this.bufSize = Math.max(bufSize, boundaryLength * 2);
this.buffer = new byte[this.bufSize];
- this.notifier = pNotifier;
-
+ this.notifier = notifier;
this.boundary = new byte[this.boundaryLength];
this.boundaryTable = new int[this.boundaryLength + 1];
this.keepRegion = this.boundary.length;
-
- System.arraycopy(BOUNDARY_PREFIX, 0, this.boundary, 0,
- BOUNDARY_PREFIX.length);
- System.arraycopy(boundary, 0, this.boundary, BOUNDARY_PREFIX.length,
- boundary.length);
+ System.arraycopy(BOUNDARY_PREFIX, 0, this.boundary, 0, BOUNDARY_PREFIX.length);
+ System.arraycopy(boundary, 0, this.boundary, BOUNDARY_PREFIX.length, boundary.length);
computeBoundaryTable();
-
head = 0;
tail = 0;
}
/**
- * MultipartStream
with a default size buffer.
+ * Constructs a {@code MultipartStream} with a default size buffer.
*
- * @param input The InputStream
to serve as a data source.
+ * @param input The {@code InputStream} to serve as a data source.
* @param boundary The token used for dividing the stream into
- * encapsulations
.
- * @param pNotifier An object for calling the progress listener, if any.
+ * {@code encapsulations}.
+ * @param notifier An object for calling the progress listener, if any.
*
*
* @see #MultipartStream(InputStream, byte[], int, ProgressNotifier)
*/
- MultipartStream(InputStream input,
- byte[] boundary,
- ProgressNotifier pNotifier) {
- this(input, boundary, DEFAULT_BUFSIZE, pNotifier);
+ MultipartStream(final InputStream input, final byte[] boundary, final ProgressNotifier notifier) {
+ this(input, boundary, DEFAULT_BUFSIZE, notifier);
}
/**
- * MultipartStream
with a default size buffer.
- *
- * @param input The InputStream
to serve as a data source.
- * @param boundary The token used for dividing the stream into
- * encapsulations
.
- *
- * @deprecated 1.2.1 Use {@link #MultipartStream(InputStream, byte[], int,
- * ProgressNotifier)}.
+ * Compute the table used for Knuth-Morris-Pratt search algorithm.
*/
- @Deprecated
- public MultipartStream(InputStream input,
- byte[] boundary) {
- this(input, boundary, DEFAULT_BUFSIZE, null);
- }
+ private void computeBoundaryTable() {
+ int position = 2;
+ int candidate = 0;
+
+ boundaryTable[0] = -1;
+ boundaryTable[1] = 0;
- // --------------------------------------------------------- Public methods
+ while (position <= boundaryLength) {
+ if (boundary[position - 1] == boundary[candidate]) {
+ boundaryTable[position] = candidate + 1;
+ candidate++;
+ position++;
+ } else if (candidate > 0) {
+ candidate = boundaryTable[candidate];
+ } else {
+ boundaryTable[position] = 0;
+ position++;
+ }
+ }
+ }
/**
- * Retrieves the character encoding used when reading the headers of an
- * individual part. When not specified, or null
, the platform
- * default encoding is used.
+ * Reads {@code body-data} from the current {@code encapsulation} and discards it.
*
- * @return The encoding used to read part headers.
+ * null
, the platform
- * default encoding is used.
+ * Searches for a byte of specified value in the {@code buffer},
+ * starting at the specified {@code position}.
*
- * @param encoding The encoding used to read part headers.
+ * @param value The value to find.
+ * @param pos The starting position for searching.
+ * @return The position of byte found, counting from beginning of the
+ * {@code buffer}, or {@code -1} if not found.
*/
- public void setHeaderEncoding(String encoding) {
- headerEncoding = encoding;
+ protected int findByte(final byte value,
+ final int pos) {
+ for (int i = pos; i < tail; i++) {
+ if (buffer[i] == value) {
+ return i;
+ }
+ }
+
+ return -1;
}
/**
- * Reads a byte from the buffer
, and refills it as
- * necessary.
- *
- * @return The next byte from the input stream.
+ * Searches for the {@code boundary} in the {@code buffer}
+ * region delimited by {@code head} and {@code tail}.
*
- * @throws IOException if there is no more data available.
+ * @return The position of the boundary found, counting from the
+ * beginning of the {@code buffer}, or {@code -1} if
+ * not found.
*/
- public byte readByte() throws IOException {
- // Buffer depleted ?
- if (head == tail) {
- head = 0;
- // Refill.
- tail = input.read(buffer, head, bufSize);
- if (tail == -1) {
- // No more data available.
- throw new IOException("No more data is available");
+ protected int findSeparator() {
+
+ int bufferPos = head;
+ int tablePos = 0;
+
+ while (bufferPos < tail) {
+ while (tablePos >= 0 && buffer[bufferPos] != boundary[tablePos]) {
+ tablePos = boundaryTable[tablePos];
}
- if (notifier != null) {
- notifier.noteBytesRead(tail);
+ bufferPos++;
+ tablePos++;
+ if (tablePos == boundaryLength) {
+ return bufferPos - boundaryLength;
}
}
- return buffer[head++];
+ return -1;
}
/**
- * Skips a boundary
token, and checks whether more
- * encapsulations
are contained in the stream.
+ * Gets the character encoding used when reading the headers of an
+ * individual part. When not specified, or {@code null}, the platform
+ * default encoding is used.
*
- * @return true
if there are more encapsulations in
- * this stream; false
otherwise.
+ * @return The encoding used to read part headers.
+ */
+ public String getHeaderEncoding() {
+ return headerEncoding;
+ }
+
+ /**
+ * Obtain the per part size limit for headers.
+ *
+ * @return The maximum size of the headers for a single part in bytes.
+ *
+ * @since 1.6.0
+ */
+ public int getPartHeaderSizeMax() {
+ return partHeaderSizeMax;
+ }
+
+ /**
+ * Creates a new {@link ItemInputStream}.
+ * @return A new instance of {@link ItemInputStream}.
+ */
+ ItemInputStream newInputStream() {
+ return new ItemInputStream();
+ }
+
+ /**
+ * Reads {@code body-data} from the current {@code encapsulation} and writes its contents into the output {@code Stream}.
+ *
+ * required
- * to be of the same length as the boundary token in parent stream.
- *
- * boundary
- * has a different length than the one
- * being currently parsed.
- */
- public void setBoundary(byte[] boundary)
- throws IllegalBoundaryException {
- if (boundary.length != boundaryLength - BOUNDARY_PREFIX.length) {
- throw new IllegalBoundaryException(
- "The length of a boundary token cannot be changed");
- }
- System.arraycopy(boundary, 0, this.boundary, BOUNDARY_PREFIX.length,
- boundary.length);
- computeBoundaryTable();
- }
-
- /**
- * Compute the table used for Knuth-Morris-Pratt search algorithm.
+ * @return The next byte from the input stream.
+ * @throws IOException if there is no more data available.
*/
- private void computeBoundaryTable() {
- int position = 2;
- int candidate = 0;
-
- boundaryTable[0] = -1;
- boundaryTable[1] = 0;
-
- while (position <= boundaryLength) {
- if (boundary[position - 1] == boundary[candidate]) {
- boundaryTable[position] = candidate + 1;
- candidate++;
- position++;
- } else if (candidate > 0) {
- candidate = boundaryTable[candidate];
- } else {
- boundaryTable[position] = 0;
- position++;
+ public byte readByte() throws IOException {
+ // Buffer depleted ?
+ if (head == tail) {
+ head = 0;
+ // Refill.
+ tail = input.read(buffer, head, bufSize);
+ if (tail == -1) {
+ // No more data available.
+ throw new IOException("No more data is available");
+ }
+ if (notifier != null) {
+ notifier.noteBytesRead(tail);
}
}
+ return buffer[head++];
}
/**
- * header-part
of the current
- * encapsulation
.
- *
- * CRLF
marker. Parsing is left to the
- * application.
- *
- * header-part
of the current encapsulation.
+ * Reads the {@code header-part} of the current {@code encapsulation}.
+ * body-data
from the current
- * encapsulation
and writes its contents into the
- * output Stream
.
+ * Changes the boundary token used for partitioning the stream.
*
- * Stream
to write data into. May
- * be null, in which case this method is equivalent
- * to {@link #discardBodyData()}.
+ * body-data
from the current
- * encapsulation
and discards it.
+ * Sets the per part size limit for headers.
*
- * encapsulation
.
+ * Finds the beginning of the first {@code encapsulation}.
*
- * @return true
if an encapsulation
was found in
+ * @return {@code true} if an {@code encapsulation} was found in
* the stream.
*
* @throws IOException if an i/o error occurs.
*/
public boolean skipPreamble() throws IOException {
- // First delimiter may be not preceeded with a CRLF.
+ // First delimiter may be not preceded with a CRLF.
System.arraycopy(boundary, 2, boundary, 0, boundary.length - 2);
boundaryLength = boundary.length - 2;
computeBoundaryTable();
@@ -666,7 +1026,7 @@ public boolean skipPreamble() throws IOException {
// Read boundary - if succeeded, the stream contains an
// encapsulation.
return readBoundary();
- } catch (MalformedStreamException e) {
+ } catch (final MalformedStreamException e) {
return false;
} finally {
// Restore delimiter.
@@ -678,384 +1038,4 @@ public boolean skipPreamble() throws IOException {
}
}
- /**
- * Compares count
first bytes in the arrays
- * a
and b
.
- *
- * @param a The first array to compare.
- * @param b The second array to compare.
- * @param count How many bytes should be compared.
- *
- * @return true
if count
first bytes in arrays
- * a
and b
are equal.
- */
- public static boolean arrayequals(byte[] a,
- byte[] b,
- int count) {
- for (int i = 0; i < count; i++) {
- if (a[i] != b[i]) {
- return false;
- }
- }
- return true;
- }
-
- /**
- * Searches for a byte of specified value in the buffer
,
- * starting at the specified position
.
- *
- * @param value The value to find.
- * @param pos The starting position for searching.
- *
- * @return The position of byte found, counting from beginning of the
- * buffer
, or -1
if not found.
- */
- protected int findByte(byte value,
- int pos) {
- for (int i = pos; i < tail; i++) {
- if (buffer[i] == value) {
- return i;
- }
- }
-
- return -1;
- }
-
- /**
- * Searches for the boundary
in the buffer
- * region delimited by head
and tail
.
- *
- * @return The position of the boundary found, counting from the
- * beginning of the buffer
, or -1
if
- * not found.
- */
- protected int findSeparator() {
-
- int bufferPos = this.head;
- int tablePos = 0;
-
- while (bufferPos < this.tail) {
- while (tablePos >= 0 && buffer[bufferPos] != boundary[tablePos]) {
- tablePos = boundaryTable[tablePos];
- }
- bufferPos++;
- tablePos++;
- if (tablePos == boundaryLength) {
- return bufferPos - boundaryLength;
- }
- }
- return -1;
- }
-
- /**
- * Thrown to indicate that the input stream fails to follow the
- * required syntax.
- */
- public static class MalformedStreamException extends IOException {
-
- /**
- * The UID to use when serializing this instance.
- */
- private static final long serialVersionUID = 6466926458059796677L;
-
- /**
- * Constructs a MalformedStreamException
with no
- * detail message.
- */
- public MalformedStreamException() {
- super();
- }
-
- /**
- * Constructs an MalformedStreamException
with
- * the specified detail message.
- *
- * @param message The detail message.
- */
- public MalformedStreamException(String message) {
- super(message);
- }
-
- }
-
- /**
- * Thrown upon attempt of setting an invalid boundary token.
- */
- public static class IllegalBoundaryException extends IOException {
-
- /**
- * The UID to use when serializing this instance.
- */
- private static final long serialVersionUID = -161533165102632918L;
-
- /**
- * Constructs an IllegalBoundaryException
with no
- * detail message.
- */
- public IllegalBoundaryException() {
- super();
- }
-
- /**
- * Constructs an IllegalBoundaryException
with
- * the specified detail message.
- *
- * @param message The detail message.
- */
- public IllegalBoundaryException(String message) {
- super(message);
- }
-
- }
-
- /**
- * An {@link InputStream} for reading an items contents.
- */
- public class ItemInputStream extends InputStream implements Closeable {
-
- /**
- * The number of bytes, which have been read so far.
- */
- private long total;
-
- /**
- * The number of bytes, which must be hold, because
- * they might be a part of the boundary.
- */
- private int pad;
-
- /**
- * The current offset in the buffer.
- */
- private int pos;
-
- /**
- * Whether the stream is already closed.
- */
- private boolean closed;
-
- /**
- * Creates a new instance.
- */
- ItemInputStream() {
- findSeparator();
- }
-
- /**
- * Called for finding the separator.
- */
- private void findSeparator() {
- pos = MultipartStream.this.findSeparator();
- if (pos == -1) {
- if (tail - head > keepRegion) {
- pad = keepRegion;
- } else {
- pad = tail - head;
- }
- }
- }
-
- /**
- * Returns the number of bytes, which have been read
- * by the stream.
- *
- * @return Number of bytes, which have been read so far.
- */
- public long getBytesRead() {
- return total;
- }
-
- /**
- * Returns the number of bytes, which are currently
- * available, without blocking.
- *
- * @throws IOException An I/O error occurs.
- * @return Number of bytes in the buffer.
- */
- @Override
- public int available() throws IOException {
- if (pos == -1) {
- return tail - head - pad;
- }
- return pos - head;
- }
-
- /**
- * Offset when converting negative bytes to integers.
- */
- private static final int BYTE_POSITIVE_OFFSET = 256;
-
- /**
- * Returns the next byte in the stream.
- *
- * @return The next byte in the stream, as a non-negative
- * integer, or -1 for EOF.
- * @throws IOException An I/O error occurred.
- */
- @Override
- public int read() throws IOException {
- if (closed) {
- throw new FileItemStream.ItemSkippedException();
- }
- if (available() == 0 && makeAvailable() == 0) {
- return -1;
- }
- ++total;
- int b = buffer[head++];
- if (b >= 0) {
- return b;
- }
- return b + BYTE_POSITIVE_OFFSET;
- }
-
- /**
- * Reads bytes into the given buffer.
- *
- * @param b The destination buffer, where to write to.
- * @param off Offset of the first byte in the buffer.
- * @param len Maximum number of bytes to read.
- * @return Number of bytes, which have been actually read,
- * or -1 for EOF.
- * @throws IOException An I/O error occurred.
- */
- @Override
- public int read(byte[] b, int off, int len) throws IOException {
- if (closed) {
- throw new FileItemStream.ItemSkippedException();
- }
- if (len == 0) {
- return 0;
- }
- int res = available();
- if (res == 0) {
- res = makeAvailable();
- if (res == 0) {
- return -1;
- }
- }
- res = Math.min(res, len);
- System.arraycopy(buffer, head, b, off, res);
- head += res;
- total += res;
- return res;
- }
-
- /**
- * Closes the input stream.
- *
- * @throws IOException An I/O error occurred.
- */
- @Override
- public void close() throws IOException {
- close(false);
- }
-
- /**
- * Closes the input stream.
- *
- * @param pCloseUnderlying Whether to close the underlying stream
- * (hard close)
- * @throws IOException An I/O error occurred.
- */
- public void close(boolean pCloseUnderlying) throws IOException {
- if (closed) {
- return;
- }
- if (pCloseUnderlying) {
- closed = true;
- input.close();
- } else {
- for (;;) {
- int av = available();
- if (av == 0) {
- av = makeAvailable();
- if (av == 0) {
- break;
- }
- }
- skip(av);
- }
- }
- closed = true;
- }
-
- /**
- * Skips the given number of bytes.
- *
- * @param bytes Number of bytes to skip.
- * @return The number of bytes, which have actually been
- * skipped.
- * @throws IOException An I/O error occurred.
- */
- @Override
- public long skip(long bytes) throws IOException {
- if (closed) {
- throw new FileItemStream.ItemSkippedException();
- }
- int av = available();
- if (av == 0) {
- av = makeAvailable();
- if (av == 0) {
- return 0;
- }
- }
- long res = Math.min(av, bytes);
- head += res;
- return res;
- }
-
- /**
- * Attempts to read more data.
- *
- * @return Number of available bytes
- * @throws IOException An I/O error occurred.
- */
- private int makeAvailable() throws IOException {
- if (pos != -1) {
- return 0;
- }
-
- // Move the data to the beginning of the buffer.
- total += tail - head - pad;
- System.arraycopy(buffer, tail - pad, buffer, 0, pad);
-
- // Refill buffer with new data.
- head = 0;
- tail = pad;
-
- for (;;) {
- int bytesRead = input.read(buffer, tail, bufSize - tail);
- if (bytesRead == -1) {
- // The last pad amount is left in the buffer.
- // Boundary can't be in there so signal an error
- // condition.
- final String msg = "Stream ended unexpectedly";
- throw new MalformedStreamException(msg);
- }
- if (notifier != null) {
- notifier.noteBytesRead(bytesRead);
- }
- tail += bytesRead;
-
- findSeparator();
- int av = available();
-
- if (av > 0 || pos != -1) {
- return av;
- }
- }
- }
-
- /**
- * Returns, whether the stream is closed.
- *
- * @return True, if the stream is closed, otherwise false.
- */
- @Override
- public boolean isClosed() {
- return closed;
- }
-
- }
-
}
diff --git a/src/main/java/org/apache/commons/fileupload/ParameterParser.java b/src/main/java/org/apache/commons/fileupload/ParameterParser.java
index e6454584cf..f44eb3f678 100644
--- a/src/main/java/org/apache/commons/fileupload/ParameterParser.java
+++ b/src/main/java/org/apache/commons/fileupload/ParameterParser.java
@@ -31,7 +31,7 @@
* Parameter values are optional and can be omitted.
*
* param1 = value; param2 = "anything goes; really"; param3
+ * {@code param1 = value; param2 = "anything goes; really"; param3}
* null
if
- * not defined.
+ * The content type passed by the browser, or {@code null} if not defined.
*/
private final String contentType;
/**
- * Whether or not this item is a simple form field.
+ * Default content charset to be used when no explicit charset parameter is provided by the sender.
*/
- private boolean isFormField;
+ private String defaultCharset = DEFAULT_CHARSET;
/**
- * The original filename in the user's filesystem.
+ * Output stream for this item.
*/
- private final String fileName;
+ private transient DeferredFileOutputStream dfos;
/**
- * The size of the item, in bytes. This is used to cache the size when a
- * file item is moved from its original location.
+ * The name of the form field as provided by the browser.
*/
- private long size = -1;
-
+ private String fieldName;
/**
- * The threshold above which uploads will be stored on disk.
+ * The original file name in the user's file system.
*/
- private final int sizeThreshold;
+ private final String fileName;
/**
- * The directory in which uploaded files will be stored, if stored on disk.
+ * The file items headers.
*/
- private final File repository;
+ private FileItemHeaders headers;
/**
- * Cached contents of the file.
+ * Whether or not this item is a simple form field.
*/
- private byte[] cachedContent;
+ private boolean formField;
/**
- * Output stream for this item.
+ * The directory in which uploaded files will be stored, if stored on disk.
*/
- private transient DeferredFileOutputStream dfos;
+ private final File repository;
/**
- * The temporary file to use.
+ * The size of the item, in bytes. This is used to cache the size when a
+ * file item is moved from its original location.
*/
- private transient File tempFile;
+ private long size = -1;
/**
- * The file items headers.
+ * The threshold above which uploads will be stored on disk.
*/
- private FileItemHeaders headers;
+ private final int sizeThreshold;
/**
- * Default content charset to be used when no explicit charset
- * parameter is provided by the sender.
+ * The temporary file to use.
*/
- private String defaultCharset = DEFAULT_CHARSET;
-
- // ----------------------------------------------------------- Constructors
+ private transient File tempFile;
/**
- * Constructs a new DiskFileItem
instance.
+ * Constructs a new {@code DiskFileItem} instance.
*
* @param fieldName The name of the form field.
- * @param contentType The content type passed by the browser or
- * null
if not specified.
- * @param isFormField Whether or not this item is a plain form field, as
- * opposed to a file upload.
- * @param fileName The original filename in the user's filesystem, or
- * null
if not specified.
- * @param sizeThreshold The threshold, in bytes, below which items will be
- * retained in memory and above which they will be
- * stored as a file.
- * @param repository The data repository, which is the directory in
- * which files will be created, should the item size
- * exceed the threshold.
- */
- public DiskFileItem(String fieldName,
- String contentType, boolean isFormField, String fileName,
- int sizeThreshold, File repository) {
+ * @param contentType The content type passed by the browser or {@code null} if not specified.
+ * @param isFormField Whether or not this item is a plain form field, as opposed to a file upload.
+ * @param fileName The original file name in the user's file system, or {@code null} if not specified.
+ * @param sizeThreshold The threshold, in bytes, below which items will be retained in memory and above which they
+ * will be stored as a file.
+ * @param repository The data repository, which is the directory in which files will be created, should the item
+ * size exceed the threshold.
+ */
+ public DiskFileItem(final String fieldName, final String contentType, final boolean isFormField,
+ final String fileName, final int sizeThreshold, final File repository) {
this.fieldName = fieldName;
this.contentType = contentType;
- this.isFormField = isFormField;
+ this.formField = isFormField;
this.fileName = fileName;
this.sizeThreshold = sizeThreshold;
this.repository = repository;
}
- // ------------------------------- Methods from javax.activation.DataSource
-
- /**
- * Returns an {@link java.io.InputStream InputStream} that can be
- * used to retrieve the contents of the file.
- *
- * @return An {@link java.io.InputStream InputStream} that can be
- * used to retrieve the contents of the file.
- *
- * @throws IOException if an error occurs.
- */
- @Override
- public InputStream getInputStream()
- throws IOException {
- if (!isInMemory()) {
- return new FileInputStream(dfos.getFile());
- }
-
- if (cachedContent == null) {
- cachedContent = dfos.getData();
- }
- return new ByteArrayInputStream(cachedContent);
- }
-
- /**
- * Returns the content type passed by the agent or null
if
- * not defined.
- *
- * @return The content type passed by the agent or null
if
- * not defined.
- */
- @Override
- public String getContentType() {
- return contentType;
- }
-
/**
- * Returns the content charset passed by the agent or null
if
- * not defined.
- *
- * @return The content charset passed by the agent or null
if
- * not defined.
+ * Clears the cache.
*/
- public String getCharSet() {
- ParameterParser parser = new ParameterParser();
- parser.setLowerCaseNames(true);
- // Parameter parser can handle null input
- Maptrue
if the file contents will be read
- * from memory; false
otherwise.
+ * Deletes the underlying storage for a file item, including deleting any
+ * associated temporary disk file. Although this storage will be deleted
+ * automatically when the {@code FileItem} instance is garbage
+ * collected, this method can be used to ensure that this is done at an
+ * earlier time, thus preserving system resources.
*/
@Override
- public boolean isInMemory() {
- if (cachedContent != null) {
- return true;
+ public void delete() {
+ clear();
+ final File outputFile = getStoreLocation();
+ if (outputFile != null && !isInMemory() && outputFile.exists()) {
+ outputFile.delete();
}
- return dfos.isInMemory();
}
/**
- * Returns the size of the file.
+ * Removes the file contents from the temporary storage.
*
- * @return The size of the file, in bytes.
+ * @throws Throwable Thrown by {@link Object#finalize()}.
*/
@Override
- public long getSize() {
- if (size >= 0) {
- return size;
- } else if (cachedContent != null) {
- return cachedContent.length;
- } else if (dfos.isInMemory()) {
- return dfos.getData().length;
- } else {
- return dfos.getFile().length();
+ protected void finalize() throws Throwable {
+ if (dfos == null || dfos.isInMemory()) {
+ return;
+ }
+ final File outputFile = dfos.getFile();
+ if (outputFile != null && outputFile.exists()) {
+ outputFile.delete();
}
+ super.finalize();
}
/**
- * Returns the contents of the file as an array of bytes. If the
- * contents of the file were not yet cached in memory, they will be
- * loaded from the disk storage and cached.
+ * Gets the contents of the file as an array of bytes. If the contents of the file were not yet cached in memory, they will be loaded from the disk storage
+ * and cached.
*
- * @return The contents of the file as an array of bytes
- * or {@code null} if the data cannot be read
+ * @return The contents of the file as an array of bytes or {@code null} if the data cannot be read.
+ * @throws UncheckedIOException if an I/O error occurs.
+ * @throws OutOfMemoryError if an array of the required size cannot be allocated, for example the file is larger that {@code 2GB}.
*/
@Override
public byte[] get() {
@@ -302,142 +234,48 @@ public byte[] get() {
if (cachedContent == null && dfos != null) {
cachedContent = dfos.getData();
}
- return cachedContent;
+ return cachedContent != null ? cachedContent.clone() : new byte[0];
}
-
- byte[] fileData = new byte[(int) getSize()];
- InputStream fis = null;
-
- try {
- fis = new FileInputStream(dfos.getFile());
- IOUtils.readFully(fis, fileData);
- } catch (IOException e) {
- fileData = null;
- } finally {
- IOUtils.closeQuietly(fis);
- }
-
- return fileData;
+ return Uncheck.get(() -> Files.readAllBytes(dfos.getFile().toPath()));
}
/**
- * Returns the contents of the file as a String, using the specified
- * encoding. This method uses {@link #get()} to retrieve the
- * contents of the file.
+ * Gets the content charset passed by the agent or {@code null} if not defined.
*
- * @param charset The charset to use.
- *
- * @return The contents of the file, as a string.
- *
- * @throws UnsupportedEncodingException if the requested character
- * encoding is not available.
+ * @return The content charset passed by the agent or {@code null} if not defined.
*/
- @Override
- public String getString(final String charset)
- throws UnsupportedEncodingException {
- return new String(get(), charset);
+ public String getCharSet() {
+ final ParameterParser parser = new ParameterParser();
+ parser.setLowerCaseNames(true);
+ // Parameter parser can handle null input
+ final MapFile
into which the uploaded item should
- * be stored.
- *
- * @throws Exception if an error occurs.
- */
- @Override
- public void write(File file) throws Exception {
- if (isInMemory()) {
- FileOutputStream fout = null;
- try {
- fout = new FileOutputStream(file);
- fout.write(get());
- fout.close();
- } finally {
- IOUtils.closeQuietly(fout);
- }
- } else {
- File outputFile = getStoreLocation();
- if (outputFile != null) {
- // Save the length of the file
- size = outputFile.length();
- /*
- * The uploaded file is being stored on disk
- * in a temporary location so move it to the
- * desired file.
- */
- FileUtils.moveFile(outputFile, file);
- } else {
- /*
- * For whatever reason we cannot write the
- * file to disk.
- */
- throw new FileUploadException(
- "Cannot write uploaded file to disk!");
- }
- }
- }
-
- /**
- * Deletes the underlying storage for a file item, including deleting any
- * associated temporary disk file. Although this storage will be deleted
- * automatically when the FileItem
instance is garbage
- * collected, this method can be used to ensure that this is done at an
- * earlier time, thus preserving system resources.
+ * @return the default charset
*/
- @Override
- public void delete() {
- cachedContent = null;
- File outputFile = getStoreLocation();
- if (outputFile != null && !isInMemory() && outputFile.exists()) {
- outputFile.delete();
- }
+ public String getDefaultCharset() {
+ return defaultCharset;
}
/**
- * Returns the name of the field in the multipart form corresponding to
- * this file item.
+ * Gets the name of the field in the multipart form corresponding to this file item.
*
* @return The name of the form field.
- *
- * @see #setFieldName(java.lang.String)
- *
+ * @see #setFieldName(String)
*/
@Override
public String getFieldName() {
@@ -445,81 +283,98 @@ public String getFieldName() {
}
/**
- * Sets the field name used to reference this file item.
- *
- * @param fieldName The name of the form field.
- *
- * @see #getFieldName()
+ * Gets the file item headers.
*
+ * @return The file items headers.
*/
@Override
- public void setFieldName(String fieldName) {
- this.fieldName = fieldName;
+ public FileItemHeaders getHeaders() {
+ return headers;
}
/**
- * Determines whether or not a FileItem
instance represents
- * a simple form field.
- *
- * @return true
if the instance represents a simple form
- * field; false
if it represents an uploaded file.
+ * Gets an {@link java.io.InputStream InputStream} that can be used to retrieve the contents of the file.
*
- * @see #setFormField(boolean)
+ * @return An {@link java.io.InputStream InputStream} that can be used to retrieve the contents of the file.
*
+ * @throws IOException if an error occurs.
*/
@Override
- public boolean isFormField() {
- return isFormField;
+ public InputStream getInputStream() throws IOException {
+ if (!isInMemory()) {
+ return Files.newInputStream(dfos.getPath());
+ }
+ if (cachedContent == null) {
+ cachedContent = dfos.getData();
+ }
+ return new ByteArrayInputStream(cachedContent);
}
/**
- * Specifies whether or not a FileItem
instance represents
- * a simple form field.
- *
- * @param state true
if the instance represents a simple form
- * field; false
if it represents an uploaded file.
- *
- * @see #isFormField()
+ * Gets the original file name in the client's file system.
*
+ * @return The original file name in the client's file system.
+ * @throws org.apache.commons.fileupload.InvalidFileNameException The file name contains a NUL character,
+ * which might be an indicator of a security attack. If you intend to
+ * use the file name anyways, catch the exception and use
+ * {@link org.apache.commons.fileupload.InvalidFileNameException#getName()}.
*/
@Override
- public void setFormField(boolean state) {
- isFormField = state;
+ public String getName() {
+ return Streams.checkFileName(fileName);
}
/**
- * Returns an {@link java.io.OutputStream OutputStream} that can
- * be used for storing the contents of the file.
+ * Gets an {@link java.io.OutputStream OutputStream} that can be used for storing the contents of the file.
*
- * @return An {@link java.io.OutputStream OutputStream} that can be used
- * for storing the contents of the file.
+ * @return An {@link java.io.OutputStream OutputStream} that can be used for storing the contents of the file.
*
- * @throws IOException if an error occurs.
+ * @throws IOException if an error occurs (never happens).
*/
@Override
- public OutputStream getOutputStream()
- throws IOException {
+ public OutputStream getOutputStream() throws IOException {
if (dfos == null) {
- File outputFile = getTempFile();
- dfos = new DeferredFileOutputStream(sizeThreshold, outputFile);
+ final File outputFile = getTempFile();
+ // @formatter:off
+ dfos = DeferredFileOutputStream.builder()
+ .setThreshold(sizeThreshold)
+ .setOutputFile(outputFile)
+ .get();
+ // @formatter:on
}
return dfos;
}
- // --------------------------------------------------------- Public methods
+ /**
+ * Gets the size of the file.
+ *
+ * @return The size of the file, in bytes.
+ */
+ @Override
+ public long getSize() {
+ if (size >= 0) {
+ return size;
+ }
+ if (cachedContent != null) {
+ return cachedContent.length;
+ }
+ if (dfos.isInMemory()) {
+ return dfos.getData().length;
+ }
+ return dfos.getFile().length();
+ }
/**
- * Returns the {@link java.io.File} object for the FileItem
's
+ * Gets the {@link java.io.File} object for the {@code FileItem}'s
* data's temporary location on the disk. Note that for
- * FileItem
s that have their data stored in memory,
- * this method will return null
. When handling large
+ * {@code FileItem}s that have their data stored in memory,
+ * this method will return {@code null}. When handling large
* files, you can use {@link java.io.File#renameTo(java.io.File)} to
* move the file to new location without copying the data, if the
* source and destination locations reside within the same logical
* volume.
*
- * @return The data file, or null
if the data is stored in
- * memory.
+ * @return The data file, or {@code null} if the data is stored in memory.
*/
public File getStoreLocation() {
if (dfos == null) {
@@ -531,31 +386,47 @@ public File getStoreLocation() {
return dfos.getFile();
}
- // ------------------------------------------------------ Protected methods
-
/**
- * Removes the file contents from the temporary storage.
+ * Gets the contents of the file as a String, using the default character encoding. This method uses
+ * {@link #get()} to retrieve the contents of the file.
+ * FileItem
instance;
- * the file will be deleted when the instance is garbage collected.
+ * Gets the contents of the file as a String, using the specified encoding. This method uses {@link #get()} to
+ * retrieve the contents of the file.
+ *
+ * @param charset The charset to use.
+ * @return The contents of the file, as a string.
+ * @throws UnsupportedEncodingException if the requested character encoding is not available.
+ */
+ @Override
+ public String getString(final String charset) throws UnsupportedEncodingException {
+ return new String(get(), charset);
+ }
+
+ /**
+ * Creates and returns a {@link java.io.File File} representing a uniquely named temporary file in the configured
+ * repository path. The lifetime of the file is tied to the lifetime of the {@code FileItem} instance; the file will
+ * be deleted when the instance is garbage collected.
*
*
* System.getProperty("java.io.tmpdir")
.javax.servlet.context.tempdir
+ * by the ServletContext attribute {@code javax.servlet.context.tempdir}
* may be used.
* true
if this is a plain form field;
- * false
otherwise.
- * @param fileName The name of the uploaded file, if any, as supplied
- * by the browser or other client.
+ * Gets the size threshold beyond which files are written directly to
+ * disk. The default value is 10240 bytes.
*
- * @return The newly created file item.
+ * @return The size threshold, in bytes.
+ * @see #setSizeThreshold(int)
*/
- @Override
- public FileItem createItem(String fieldName, String contentType,
- boolean isFormField, String fileName) {
- DiskFileItem result = new DiskFileItem(fieldName, contentType,
- isFormField, fileName, sizeThreshold, repository);
- result.setDefaultCharset(defaultCharset);
- FileCleaningTracker tracker = getFileCleaningTracker();
- if (tracker != null) {
- tracker.track(result.getTempFile(), result);
- }
- return result;
+ public int getSizeThreshold() {
+ return sizeThreshold;
}
/**
- * Returns the tracker, which is responsible for deleting temporary
- * files.
+ * Sets the default charset for use when no explicit charset
+ * parameter is provided by the sender.
*
- * @return An instance of {@link FileCleaningTracker}, or null
- * (default), if temporary files aren't tracked.
+ * @param charset the default charset
*/
- public FileCleaningTracker getFileCleaningTracker() {
- return fileCleaningTracker;
+ public void setDefaultCharset(final String charset) {
+ this.defaultCharset = charset;
}
/**
* Sets the tracker, which is responsible for deleting temporary
* files.
*
- * @param pTracker An instance of {@link FileCleaningTracker},
+ * @param fileCleaningTracker An instance of {@link FileCleaningTracker},
* which will from now on track the created files, or null
* (default), to disable tracking.
*/
- public void setFileCleaningTracker(FileCleaningTracker pTracker) {
- fileCleaningTracker = pTracker;
+ public void setFileCleaningTracker(final FileCleaningTracker fileCleaningTracker) {
+ this.fileCleaningTracker = fileCleaningTracker;
}
/**
- * Returns the default charset for use when no explicit charset
- * parameter is provided by the sender.
- * @return the default charset
+ * Sets the directory used to temporarily store files that are larger
+ * than the configured size threshold.
+ *
+ * @param repository The directory in which temporary files will be located.
+ * @see #getRepository()
+ *
*/
- public String getDefaultCharset() {
- return defaultCharset;
+ public void setRepository(final File repository) {
+ this.repository = repository;
}
/**
- * Sets the default charset for use when no explicit charset
- * parameter is provided by the sender.
- * @param pCharset the default charset
+ * Sets the size threshold beyond which files are written directly to disk.
+ *
+ * @param sizeThreshold The size threshold, in bytes.
+ * @see #getSizeThreshold()
+ *
*/
- public void setDefaultCharset(String pCharset) {
- defaultCharset = pCharset;
+ public void setSizeThreshold(final int sizeThreshold) {
+ this.sizeThreshold = sizeThreshold;
}
}
diff --git a/src/main/java/org/apache/commons/fileupload/disk/package-info.java b/src/main/java/org/apache/commons/fileupload/disk/package-info.java
index ab05fcdbbc..6e6cf4bbd5 100644
--- a/src/main/java/org/apache/commons/fileupload/disk/package-info.java
+++ b/src/main/java/org/apache/commons/fileupload/disk/package-info.java
@@ -47,7 +47,7 @@
*
* String
. Before calling the getString
method,
+ * {@code String}. Before calling the {@code getString} method,
* the data may have been in memory or on disk depending on its size. The
* second file we assume it will be large and therefore never explicitly
* load it into memory, though if it is less than 4096 bytes it will be
@@ -78,7 +78,7 @@
* multipart/mixed
encoding type, as specified by
+ * {@code multipart/mixed} encoding type, as specified by
* RFC 1867. Use
* {@link org.apache.commons.fileupload.servlet.ServletFileUpload
* #parseRequest(javax.servlet.http.HttpServletRequest)} to acquire a list
@@ -48,102 +48,90 @@
*/
public class PortletFileUpload extends FileUpload {
- // ---------------------------------------------------------- Class methods
-
/**
* Utility method that determines whether the request contains multipart
* content.
*
* @param request The portlet request to be evaluated. Must be non-null.
- *
- * @return true
if the request is multipart;
- * false
otherwise.
+ * @return {@code true} if the request is multipart;
+ * {@code false} otherwise.
*/
- public static final boolean isMultipartContent(ActionRequest request) {
+ public static final boolean isMultipartContent(final ActionRequest request) {
return FileUploadBase.isMultipartContent(
new PortletRequestContext(request));
}
- // ----------------------------------------------------------- Constructors
-
/**
- * Constructs an uninitialised instance of this class. A factory must be
- * configured, using setFileItemFactory()
, before attempting
+ * Constructs an uninitialized instance of this class. A factory must be
+ * configured, using {@code setFileItemFactory()}, before attempting
* to parse requests.
*
* @see FileUpload#FileUpload(FileItemFactory)
*/
public PortletFileUpload() {
- super();
}
/**
* Constructs an instance of this class which uses the supplied factory to
- * create FileItem
instances.
+ * create {@code FileItem} instances.
*
* @see FileUpload#FileUpload()
* @param fileItemFactory The factory to use for creating file items.
*/
- public PortletFileUpload(FileItemFactory fileItemFactory) {
+ public PortletFileUpload(final FileItemFactory fileItemFactory) {
super(fileItemFactory);
}
- // --------------------------------------------------------- Public methods
-
/**
* Processes an RFC 1867
- * compliant multipart/form-data
stream.
+ * compliant {@code multipart/form-data} stream.
*
* @param request The portlet request to be parsed.
- *
- * @return A list of FileItem
instances parsed from the
- * request, in the order that they were transmitted.
+ * @return An iterator to instances of {@code FileItemStream}
+ * parsed from the request, in the order that they were
+ * transmitted.
*
* @throws FileUploadException if there are problems reading/parsing
* the request or storing files.
+ * @throws IOException An I/O error occurred. This may be a network
+ * error while communicating with the client or a problem while
+ * storing the uploaded content.
*/
- public Listmultipart/form-data
stream.
+ * compliant {@code multipart/form-data} stream.
*
* @param request The portlet request to be parsed.
- *
- * @return A map of FileItem
instances parsed from the request.
- *
+ * @return A map of {@code FileItem} instances parsed from the request.
* @throws FileUploadException if there are problems reading/parsing
* the request or storing files.
*
* @since 1.3
*/
- public Mapmultipart/form-data
stream.
+ * compliant {@code multipart/form-data} stream.
*
* @param request The portlet request to be parsed.
- *
- * @return An iterator to instances of FileItemStream
- * parsed from the request, in the order that they were
- * transmitted.
+ * @return A list of {@code FileItem} instances parsed from the
+ * request, in the order that they were transmitted.
*
* @throws FileUploadException if there are problems reading/parsing
* the request or storing files.
- * @throws IOException An I/O error occurred. This may be a network
- * error while communicating with the client or a problem while
- * storing the uploaded content.
*/
- public FileItemIterator getItemIterator(ActionRequest request)
- throws FileUploadException, IOException {
- return super.getItemIterator(new PortletRequestContext(request));
+ public ListActionRequest
instance,
+ * only access to the portlet's current {@code ActionRequest} instance,
* and a suitable
* {@link org.apache.commons.fileupload.FileItemFactory FileItemFactory}
* implementation, such as
diff --git a/src/main/java/org/apache/commons/fileupload/servlet/FileCleanerCleanup.java b/src/main/java/org/apache/commons/fileupload/servlet/FileCleanerCleanup.java
index cb8b30d252..75be752cb5 100644
--- a/src/main/java/org/apache/commons/fileupload/servlet/FileCleanerCleanup.java
+++ b/src/main/java/org/apache/commons/fileupload/servlet/FileCleanerCleanup.java
@@ -17,8 +17,8 @@
package org.apache.commons.fileupload.servlet;
import javax.servlet.ServletContext;
-import javax.servlet.ServletContextListener;
import javax.servlet.ServletContextEvent;
+import javax.servlet.ServletContextListener;
import org.apache.commons.io.FileCleaningTracker;
@@ -40,38 +40,29 @@ public class FileCleanerCleanup implements ServletContextListener {
* Returns the instance of {@link FileCleaningTracker}, which is
* associated with the given {@link ServletContext}.
*
- * @param pServletContext The servlet context to query
+ * @param servletContext The servlet context to query
* @return The contexts tracker
*/
- public static FileCleaningTracker
- getFileCleaningTracker(ServletContext pServletContext) {
- return (FileCleaningTracker)
- pServletContext.getAttribute(FILE_CLEANING_TRACKER_ATTRIBUTE);
+ public static FileCleaningTracker getFileCleaningTracker(final ServletContext servletContext) {
+ return (FileCleaningTracker) servletContext.getAttribute(FILE_CLEANING_TRACKER_ATTRIBUTE);
}
/**
* Sets the instance of {@link FileCleaningTracker}, which is
* associated with the given {@link ServletContext}.
*
- * @param pServletContext The servlet context to modify
- * @param pTracker The tracker to set
+ * @param servletContext The servlet context to modify
+ * @param tracker The tracker to set
*/
- public static void setFileCleaningTracker(ServletContext pServletContext,
- FileCleaningTracker pTracker) {
- pServletContext.setAttribute(FILE_CLEANING_TRACKER_ATTRIBUTE, pTracker);
+ public static void setFileCleaningTracker(final ServletContext servletContext, final FileCleaningTracker tracker) {
+ servletContext.setAttribute(FILE_CLEANING_TRACKER_ATTRIBUTE, tracker);
}
/**
- * Called when the web application is initialized. Does
- * nothing.
- *
- * @param sce The servlet context, used for calling
- * {@link #setFileCleaningTracker(ServletContext, FileCleaningTracker)}.
+ * Constructs a new instance.
*/
- @Override
- public void contextInitialized(ServletContextEvent sce) {
- setFileCleaningTracker(sce.getServletContext(),
- new FileCleaningTracker());
+ public FileCleanerCleanup() {
+ // empty
}
/**
@@ -82,8 +73,21 @@ public void contextInitialized(ServletContextEvent sce) {
* {@link #getFileCleaningTracker(ServletContext)}.
*/
@Override
- public void contextDestroyed(ServletContextEvent sce) {
+ public void contextDestroyed(final ServletContextEvent sce) {
getFileCleaningTracker(sce.getServletContext()).exitWhenFinished();
}
+ /**
+ * Called when the web application is initialized. Does
+ * nothing.
+ *
+ * @param sce The servlet context, used for calling
+ * {@link #setFileCleaningTracker(ServletContext, FileCleaningTracker)}.
+ */
+ @Override
+ public void contextInitialized(final ServletContextEvent sce) {
+ setFileCleaningTracker(sce.getServletContext(),
+ new FileCleaningTracker());
+ }
+
}
diff --git a/src/main/java/org/apache/commons/fileupload/servlet/ServletFileUpload.java b/src/main/java/org/apache/commons/fileupload/servlet/ServletFileUpload.java
index c0e2b5e447..835cbc1977 100644
--- a/src/main/java/org/apache/commons/fileupload/servlet/ServletFileUpload.java
+++ b/src/main/java/org/apache/commons/fileupload/servlet/ServletFileUpload.java
@@ -33,7 +33,7 @@
* multipart/mixed
encoding type, as specified by
+ * {@code multipart/mixed} encoding type, as specified by
* RFC 1867. Use {@link
* #parseRequest(HttpServletRequest)} to acquire a list of {@link
* org.apache.commons.fileupload.FileItem}s associated with a given HTML
@@ -50,106 +50,94 @@ public class ServletFileUpload extends FileUpload {
*/
private static final String POST_METHOD = "POST";
- // ---------------------------------------------------------- Class methods
-
/**
* Utility method that determines whether the request contains multipart
* content.
*
* @param request The servlet request to be evaluated. Must be non-null.
- *
- * @return true
if the request is multipart;
- * false
otherwise.
+ * @return {@code true} if the request is multipart;
+ * {@code false} otherwise.
*/
public static final boolean isMultipartContent(
- HttpServletRequest request) {
+ final HttpServletRequest request) {
if (!POST_METHOD.equalsIgnoreCase(request.getMethod())) {
return false;
}
return FileUploadBase.isMultipartContent(new ServletRequestContext(request));
}
- // ----------------------------------------------------------- Constructors
-
/**
- * Constructs an uninitialised instance of this class. A factory must be
- * configured, using setFileItemFactory()
, before attempting
+ * Constructs an uninitialized instance of this class. A factory must be
+ * configured, using {@code setFileItemFactory()}, before attempting
* to parse requests.
*
* @see FileUpload#FileUpload(FileItemFactory)
*/
public ServletFileUpload() {
- super();
}
/**
* Constructs an instance of this class which uses the supplied factory to
- * create FileItem
instances.
+ * create {@code FileItem} instances.
*
* @see FileUpload#FileUpload()
* @param fileItemFactory The factory to use for creating file items.
*/
- public ServletFileUpload(FileItemFactory fileItemFactory) {
+ public ServletFileUpload(final FileItemFactory fileItemFactory) {
super(fileItemFactory);
}
- // --------------------------------------------------------- Public methods
-
/**
* Processes an RFC 1867
- * compliant multipart/form-data
stream.
+ * compliant {@code multipart/form-data} stream.
*
* @param request The servlet request to be parsed.
- *
- * @return A list of FileItem
instances parsed from the
- * request, in the order that they were transmitted.
+ * @return An iterator to instances of {@code FileItemStream}
+ * parsed from the request, in the order that they were
+ * transmitted.
*
* @throws FileUploadException if there are problems reading/parsing
* the request or storing files.
+ * @throws IOException An I/O error occurred. This may be a network
+ * error while communicating with the client or a problem while
+ * storing the uploaded content.
*/
- @Override
- public Listmultipart/form-data
stream.
+ * compliant {@code multipart/form-data} stream.
*
* @param request The servlet request to be parsed.
- *
- * @return A map of FileItem
instances parsed from the request.
- *
+ * @return A map of {@code FileItem} instances parsed from the request.
* @throws FileUploadException if there are problems reading/parsing
* the request or storing files.
*
* @since 1.3
*/
- public Mapmultipart/form-data
stream.
+ * compliant {@code multipart/form-data} stream.
*
* @param request The servlet request to be parsed.
- *
- * @return An iterator to instances of FileItemStream
- * parsed from the request, in the order that they were
- * transmitted.
+ * @return A list of {@code FileItem} instances parsed from the
+ * request, in the order that they were transmitted.
*
* @throws FileUploadException if there are problems reading/parsing
* the request or storing files.
- * @throws IOException An I/O error occurred. This may be a network
- * error while communicating with the client or a problem while
- * storing the uploaded content.
*/
- public FileItemIterator getItemIterator(HttpServletRequest request)
- throws FileUploadException, IOException {
- return super.getItemIterator(new ServletRequestContext(request));
+ @Override
+ public ListHttpServletRequest
+ * only access to the servlet's current {@code HttpServletRequest}
* instance, and a suitable
* {@link org.apache.commons.fileupload.FileItemFactory FileItemFactory}
* implementation, such as
@@ -38,7 +38,7 @@
*
* String
keys to a List
of
- * String
instances.
+ * Map of {@code String} keys to a {@code List} of
+ * {@code String} instances.
*/
- private final Mapint
in the range
- * 0
to 255
. If no byte is available
+ * byte is returned as an {@code int} in the range
+ * {@code 0} to {@code 255}. If no byte is available
* because the end of the stream has been reached, the value
- * -1
is returned. This method blocks until input data
+ * {@code -1} is returned. This method blocks until input data
* is available, the end of the stream is detected, or an exception
* is thrown.
* in.read()
and returns the result.
+ * simply performs {@code in.read()} and returns the result.
+ * -1
if the end of the
+ * @return the next byte of data, or {@code -1} if the end of the
* stream is reached.
* @throws IOException if an I/O error occurs.
* @see java.io.FilterInputStream#in
*/
@Override
public int read() throws IOException {
- int res = super.read();
+ final int res = super.read();
if (res != -1) {
count++;
checkLimit();
@@ -105,31 +131,32 @@ public int read() throws IOException {
}
/**
- * Reads up to len
bytes of data from this input stream
- * into an array of bytes. If len
is not zero, the method
+ * Reads up to {@code len} bytes of data from this input stream
+ * into an array of bytes. If {@code len} is not zero, the method
* blocks until some input is available; otherwise, no
- * bytes are read and 0
is returned.
+ * bytes are read and {@code 0} is returned.
* in.read(b, off, len)
+ * This method simply performs {@code in.read(b, off, len)}
* and returns the result.
+ * b
.
+ * {@code b}.
* @param len the maximum number of bytes read.
* @return the total number of bytes read into the buffer, or
- * -1
if there is no more data because the end of
+ * {@code -1} if there is no more data because the end of
* the stream has been reached.
- * @throws NullPointerException If b
is null
.
- * @throws IndexOutOfBoundsException If off
is negative,
- * len
is negative, or len
is greater than
- * b.length - off
+ * @throws NullPointerException If {@code b} is {@code null}.
+ * @throws IndexOutOfBoundsException If {@code off} is negative,
+ * {@code len} is negative, or {@code len} is greater than
+ * {@code b.length - off}
* @throws IOException if an I/O error occurs.
* @see java.io.FilterInputStream#in
*/
@Override
- public int read(byte[] b, int off, int len) throws IOException {
- int res = super.read(b, off, len);
+ public int read(final byte[] b, final int off, final int len) throws IOException {
+ final int res = super.read(b, off, len);
if (res > 0) {
count += res;
checkLimit();
@@ -137,30 +164,4 @@ public int read(byte[] b, int off, int len) throws IOException {
return res;
}
- /**
- * Returns, whether this stream is already closed.
- *
- * @return True, if the stream is closed, otherwise false.
- * @throws IOException An I/O error occurred.
- */
- @Override
- public boolean isClosed() throws IOException {
- return closed;
- }
-
- /**
- * Closes this input stream and releases any system resources
- * associated with the stream.
- * This
- * method simply performs in.close()
.
- *
- * @throws IOException if an I/O error occurs.
- * @see java.io.FilterInputStream#in
- */
- @Override
- public void close() throws IOException {
- closed = true;
- super.close();
- }
-
}
diff --git a/src/main/java/org/apache/commons/fileupload/util/Streams.java b/src/main/java/org/apache/commons/fileupload/util/Streams.java
index 17d1fb9c3d..b6b2599aa3 100644
--- a/src/main/java/org/apache/commons/fileupload/util/Streams.java
+++ b/src/main/java/org/apache/commons/fileupload/util/Streams.java
@@ -23,107 +23,18 @@
import org.apache.commons.fileupload.InvalidFileNameException;
import org.apache.commons.io.IOUtils;
+import org.apache.commons.io.output.NullOutputStream;
/**
* Utility class for working with streams.
*/
public final class Streams {
- /**
- * Private constructor, to prevent instantiation.
- * This class has only static methods.
- */
- private Streams() {
- // Does nothing
- }
-
/**
* Default buffer size for use in
* {@link #copy(InputStream, OutputStream, boolean)}.
*/
- private static final int DEFAULT_BUFFER_SIZE = 8192;
-
- /**
- * Copies the contents of the given {@link InputStream}
- * to the given {@link OutputStream}. Shortcut for
- *
- * copy(pInputStream, pOutputStream, new byte[8192]);
- *
- *
- * @param inputStream The input stream, which is being read.
- * It is guaranteed, that {@link InputStream#close()} is called
- * on the stream.
- * @param outputStream The output stream, to which data should
- * be written. May be null, in which case the input streams
- * contents are simply discarded.
- * @param closeOutputStream True guarantees, that {@link OutputStream#close()}
- * is called on the stream. False indicates, that only
- * {@link OutputStream#flush()} should be called finally.
- *
- * @return Number of bytes, which have been copied.
- * @throws IOException An I/O error occurred.
- */
- public static long copy(InputStream inputStream, OutputStream outputStream, boolean closeOutputStream)
- throws IOException {
- return copy(inputStream, outputStream, closeOutputStream, new byte[DEFAULT_BUFFER_SIZE]);
- }
-
- /**
- * Copies the contents of the given {@link InputStream}
- * to the given {@link OutputStream}.
- *
- * @param inputStream The input stream, which is being read.
- * It is guaranteed, that {@link InputStream#close()} is called
- * on the stream.
- * @param outputStream The output stream, to which data should
- * be written. May be null, in which case the input streams
- * contents are simply discarded.
- * @param closeOutputStream True guarantees, that {@link OutputStream#close()}
- * is called on the stream. False indicates, that only
- * {@link OutputStream#flush()} should be called finally.
- * @param buffer Temporary buffer, which is to be used for
- * copying data.
- * @return Number of bytes, which have been copied.
- * @throws IOException An I/O error occurred.
- */
- public static long copy(InputStream inputStream,
- OutputStream outputStream, boolean closeOutputStream,
- byte[] buffer)
- throws IOException {
- OutputStream out = outputStream;
- InputStream in = inputStream;
- try {
- long total = 0;
- for (;;) {
- int res = in.read(buffer);
- if (res == -1) {
- break;
- }
- if (res > 0) {
- total += res;
- if (out != null) {
- out.write(buffer, 0, res);
- }
- }
- }
- if (out != null) {
- if (closeOutputStream) {
- out.close();
- } else {
- out.flush();
- }
- out = null;
- }
- in.close();
- in = null;
- return total;
- } finally {
- IOUtils.closeQuietly(in);
- if (closeOutputStream) {
- IOUtils.closeQuietly(out);
- }
- }
- }
+ public static final int DEFAULT_BUFFER_SIZE = 8192;
/**
* This convenience method allows to read a
@@ -136,8 +47,8 @@ public static long copy(InputStream inputStream,
* @return The streams contents, as a string.
* @throws IOException An I/O error occurred.
*/
- public static String asString(InputStream inputStream) throws IOException {
- ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ public static String asString(final InputStream inputStream) throws IOException {
+ final ByteArrayOutputStream baos = new ByteArrayOutputStream();
copy(inputStream, baos, true);
return baos.toString();
}
@@ -153,8 +64,8 @@ public static String asString(InputStream inputStream) throws IOException {
* @return The streams contents, as a string.
* @throws IOException An I/O error occurred.
*/
- public static String asString(InputStream inputStream, String encoding) throws IOException {
- ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ public static String asString(final InputStream inputStream, final String encoding) throws IOException {
+ final ByteArrayOutputStream baos = new ByteArrayOutputStream();
copy(inputStream, baos, true);
return baos.toString(encoding);
}
@@ -169,12 +80,12 @@ public static String asString(InputStream inputStream, String encoding) throws I
* @return Unmodified file name, if valid.
* @throws InvalidFileNameException The file name was found to be invalid.
*/
- public static String checkFileName(String fileName) {
+ public static String checkFileName(final String fileName) {
if (fileName != null && fileName.indexOf('\u0000') != -1) {
- // pFileName.replace("\u0000", "\\0")
+ // fileName.replace("\u0000", "\\0")
final StringBuilder sb = new StringBuilder();
for (int i = 0; i < fileName.length(); i++) {
- char c = fileName.charAt(i);
+ final char c = fileName.charAt(i);
switch (c) {
case 0:
sb.append("\\0");
@@ -190,4 +101,68 @@ public static String checkFileName(String fileName) {
return fileName;
}
+ /**
+ * Copies the contents of the given {@link InputStream}
+ * to the given {@link OutputStream}. Shortcut for
+ *
+ * copy(pInputStream, outputStream, new byte[8192]);
+ *
+ *
+ * @param inputStream The input stream, which is being read.
+ * It is guaranteed, that {@link InputStream#close()} is called
+ * on the stream.
+ * @param outputStream The output stream, to which data should
+ * be written. May be null, in which case the input streams
+ * contents are simply discarded.
+ * @param closeOutputStream True guarantees, that {@link OutputStream#close()}
+ * is called on the stream. False indicates, that only
+ * {@link OutputStream#flush()} should be called finally.
+ *
+ * @return Number of bytes, which have been copied.
+ * @throws IOException An I/O error occurred.
+ */
+ public static long copy(final InputStream inputStream, final OutputStream outputStream,
+ final boolean closeOutputStream)
+ throws IOException {
+ return copy(inputStream, outputStream, closeOutputStream, new byte[DEFAULT_BUFFER_SIZE]);
+ }
+
+ /**
+ * Copies the contents of the given {@link InputStream}
+ * to the given {@link OutputStream}.
+ *
+ * @param inputStream The input stream, which is being read.
+ * It is guaranteed, that {@link InputStream#close()} is called
+ * on the stream.
+ * @param outputStream The output stream, to which data should
+ * be written. May be null, in which case the input streams
+ * contents are simply discarded.
+ * @param closeOutputStream True guarantees, that {@link OutputStream#close()}
+ * is called on the stream. False indicates, that only
+ * {@link OutputStream#flush()} should be called finally.
+ * @param buffer Temporary buffer, which is to be used for
+ * copying data.
+ * @return Number of bytes, which have been copied.
+ * @throws IOException An I/O error occurred.
+ */
+ public static long copy(final InputStream inputStream, final OutputStream outputStream, final boolean closeOutputStream, final byte[] buffer)
+ throws IOException {
+ try {
+ return IOUtils.copyLarge(inputStream, outputStream != null ? outputStream : NullOutputStream.INSTANCE, buffer);
+ } finally {
+ IOUtils.closeQuietly(inputStream);
+ if (closeOutputStream) {
+ IOUtils.closeQuietly(outputStream);
+ }
+ }
+ }
+
+ /**
+ * Private constructor, to prevent instantiation.
+ * This class has only static methods.
+ */
+ private Streams() {
+ // Does nothing
+ }
+
}
diff --git a/src/main/java/org/apache/commons/fileupload/util/mime/Base64Decoder.java b/src/main/java/org/apache/commons/fileupload/util/mime/Base64Decoder.java
deleted file mode 100644
index 07b089e4e5..0000000000
--- a/src/main/java/org/apache/commons/fileupload/util/mime/Base64Decoder.java
+++ /dev/null
@@ -1,152 +0,0 @@
-/*
- * 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.commons.fileupload.util.mime;
-
-import java.io.IOException;
-import java.io.OutputStream;
-
-/**
- * @since 1.3
- */
-final class Base64Decoder {
-
- /**
- * Decoding table value for invalid bytes.
- */
- private static final int INVALID_BYTE = -1; // must be outside range 0-63
-
- /**
- * Decoding table value for padding bytes, so can detect PAD afer conversion.
- */
- private static final int PAD_BYTE = -2; // must be outside range 0-63
-
- /**
- * Mask to treat byte as unsigned integer.
- */
- private static final int MASK_BYTE_UNSIGNED = 0xFF;
-
- /**
- * Number of bytes per encoded chunk - 4 6bit bytes produce 3 8bit bytes on output.
- */
- private static final int INPUT_BYTES_PER_CHUNK = 4;
-
- /**
- * Set up the encoding table.
- */
- private static final byte[] ENCODING_TABLE = {
- (byte) 'A', (byte) 'B', (byte) 'C', (byte) 'D', (byte) 'E', (byte) 'F', (byte) 'G',
- (byte) 'H', (byte) 'I', (byte) 'J', (byte) 'K', (byte) 'L', (byte) 'M', (byte) 'N',
- (byte) 'O', (byte) 'P', (byte) 'Q', (byte) 'R', (byte) 'S', (byte) 'T', (byte) 'U',
- (byte) 'V', (byte) 'W', (byte) 'X', (byte) 'Y', (byte) 'Z',
- (byte) 'a', (byte) 'b', (byte) 'c', (byte) 'd', (byte) 'e', (byte) 'f', (byte) 'g',
- (byte) 'h', (byte) 'i', (byte) 'j', (byte) 'k', (byte) 'l', (byte) 'm', (byte) 'n',
- (byte) 'o', (byte) 'p', (byte) 'q', (byte) 'r', (byte) 's', (byte) 't', (byte) 'u',
- (byte) 'v', (byte) 'w', (byte) 'x', (byte) 'y', (byte) 'z',
- (byte) '0', (byte) '1', (byte) '2', (byte) '3', (byte) '4', (byte) '5', (byte) '6',
- (byte) '7', (byte) '8', (byte) '9',
- (byte) '+', (byte) '/'
- };
-
- /**
- * The padding byte.
- */
- private static final byte PADDING = (byte) '=';
-
- /**
- * Set up the decoding table; this is indexed by a byte converted to an unsigned int,
- * so must be at least as large as the number of different byte values,
- * positive and negative and zero.
- */
- private static final byte[] DECODING_TABLE = new byte[Byte.MAX_VALUE - Byte.MIN_VALUE + 1];
-
- static {
- // Initialise as all invalid characters
- for (int i = 0; i < DECODING_TABLE.length; i++) {
- DECODING_TABLE[i] = INVALID_BYTE;
- }
- // set up valid characters
- for (int i = 0; i < ENCODING_TABLE.length; i++) {
- DECODING_TABLE[ENCODING_TABLE[i]] = (byte) i;
- }
- // Allow pad byte to be easily detected after conversion
- DECODING_TABLE[PADDING] = PAD_BYTE;
- }
-
- /**
- * Hidden constructor, this class must not be instantiated.
- */
- private Base64Decoder() {
- // do nothing
- }
-
- /**
- * Decode the base 64 encoded byte data writing it to the given output stream,
- * whitespace characters will be ignored.
- *
- * @param data the buffer containing the Base64-encoded data
- * @param out the output stream to hold the decoded bytes
- *
- * @return the number of bytes produced.
- * @throws IOException thrown when the padding is incorrect or the input is truncated.
- */
- public static int decode(byte[] data, OutputStream out) throws IOException {
- int outLen = 0;
- byte [] cache = new byte[INPUT_BYTES_PER_CHUNK];
- int cachedBytes = 0;
-
- for (byte b : data) {
- final byte d = DECODING_TABLE[MASK_BYTE_UNSIGNED & b];
- if (d == INVALID_BYTE) {
- continue; // Ignore invalid bytes
- }
- cache[cachedBytes++] = d;
- if (cachedBytes == INPUT_BYTES_PER_CHUNK) {
- // CHECKSTYLE IGNORE MagicNumber FOR NEXT 4 LINES
- final byte b1 = cache[0];
- final byte b2 = cache[1];
- final byte b3 = cache[2];
- final byte b4 = cache[3];
- if (b1 == PAD_BYTE || b2 == PAD_BYTE) {
- throw new IOException("Invalid Base64 input: incorrect padding, first two bytes cannot be padding");
- }
- // Convert 4 6-bit bytes to 3 8-bit bytes
- // CHECKSTYLE IGNORE MagicNumber FOR NEXT 1 LINE
- out.write((b1 << 2) | (b2 >> 4)); // 6 bits of b1 plus 2 bits of b2
- outLen++;
- if (b3 != PAD_BYTE) {
- // CHECKSTYLE IGNORE MagicNumber FOR NEXT 1 LINE
- out.write((b2 << 4) | (b3 >> 2)); // 4 bits of b2 plus 4 bits of b3
- outLen++;
- if (b4 != PAD_BYTE) {
- // CHECKSTYLE IGNORE MagicNumber FOR NEXT 1 LINE
- out.write((b3 << 6) | b4); // 2 bits of b3 plus 6 bits of b4
- outLen++;
- }
- } else if (b4 != PAD_BYTE) { // if byte 3 is pad, byte 4 must be pad too
- throw new // line wrap to avoid 120 char limit
- IOException("Invalid Base64 input: incorrect padding, 4th byte must be padding if 3rd byte is");
- }
- cachedBytes = 0;
- }
- }
- // Check for anything left over
- if (cachedBytes != 0) {
- throw new IOException("Invalid Base64 input: truncated");
- }
- return outLen;
- }
-}
diff --git a/src/main/java/org/apache/commons/fileupload/util/mime/MimeUtility.java b/src/main/java/org/apache/commons/fileupload/util/mime/MimeUtility.java
index b8a7e46e1a..83539bf2f9 100644
--- a/src/main/java/org/apache/commons/fileupload/util/mime/MimeUtility.java
+++ b/src/main/java/org/apache/commons/fileupload/util/mime/MimeUtility.java
@@ -17,8 +17,8 @@
package org.apache.commons.fileupload.util.mime;
import java.io.ByteArrayOutputStream;
-import java.io.IOException;
import java.io.UnsupportedEncodingException;
+import java.util.Base64;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
@@ -63,7 +63,7 @@ public final class MimeUtility {
/**
* Mappings between MIME and Java charset.
*/
- private static final Maporg.apache.commons.io
package.
- [if-any logo][end]
+ [if-any logo]
[end]
PGP
link downloads the OpenPGP compatible signature from our main site.
- The SHA256
link downloads the checksum from the main site.
+ It is essential that you
+ verify the integrity
+ of downloaded files, preferably using the PGP
signature (*.asc
files);
+ failing that using the SHA512
hash (*.sha512
checksum files).
+
-
-
-
- Commons FileUpload shares mailing lists with all the other - Commons Components. + Apache Commons FileUpload shares mailing lists with all the other + Commons Components. To make it easier for people to only read messages related to components they are interested in, the convention in Commons is to prefix the subject line of messages with the component's name, for example: -
- Questions related to the usage of Commons FileUpload should be posted to the
- User List.
+ Questions related to the usage of Apache Commons FileUpload should be posted to the
+ User List.
- The Developer List
- is for questions and discussion related to the development of Commons FileUpload.
+ The Developer List
+ is for questions and discussion related to the development of Apache Commons FileUpload.
Please do not cross-post; developers are also subscribed to the user list.
+
+ You must be subscribed to post to the mailing lists. Follow the Subscribe links below
+ to subscribe.
- Note: please don't send patches or attachments to any of the mailing lists. - Patches are best handled via the Issue Tracking system. - Otherwise, please upload the file to a public server and include the URL in the mail. + Note: please don't send patches or attachments to any of the mailing lists; + most of the lists are set up to drop attachments. + Patches are best handled via the Issue Tracking system. + If you have a GitHub account, most components also accept PRs (pull requests). + Otherwise, please upload the file to a public server and include the URL in the mail.
- Please prefix the subject line of any messages for Commons FileUpload
+ Please prefix the subject line of any messages for Apache Commons FileUpload
with [fileupload] - thanks!
@@ -96,16 +103,17 @@ limitations under the License.
Commons User List
- Questions on using Commons FileUpload.
+ Questions on using Apache Commons FileUpload.
Subscribe
Unsubscribe
Post
- mail-archives.apache.org
- markmail.org
- www.mail-archive.com
- news.gmane.org
+
+ lists.apache.org
+
+
+ www.mail-archive.com
@@ -114,16 +122,17 @@ limitations under the License.
Commons Developer List
- Discussion of development of Commons FileUpload.
+ Discussion of development of Apache Commons FileUpload.
Subscribe
Unsubscribe
Post
- mail-archives.apache.org
- markmail.org
- www.mail-archive.com
- news.gmane.org
+
+ lists.apache.org
+
+
+ www.mail-archive.com
@@ -138,9 +147,11 @@ limitations under the License.
Subscribe
Unsubscribe
read only
- mail-archives.apache.org
- markmail.org
- www.mail-archive.com
+
+ lists.apache.org
+
+
+ www.mail-archive.com
@@ -149,15 +160,17 @@ limitations under the License.
Commons Commits List
- Only for e-mails automatically generated by the source control sytem.
+ Only for e-mails automatically generated by the source control system.
Subscribe
Unsubscribe
read only
- mail-archives.apache.org
- markmail.org
- www.mail-archive.com
+
+ lists.apache.org
+
+
+ www.mail-archive.com
@@ -185,14 +198,14 @@ limitations under the License.
General announcements of Apache project releases.
- Subscribe
- Unsubscribe
+ Subscribe
+ Unsubscribe
read only
- mail-archives.apache.org
- markmail.org
- old.nabble.com
- www.mail-archive.com
- news.gmane.org
+
+ lists.apache.org
+
+
+ www.mail-archive.com
diff --git a/src/site/xdoc/overview.xml b/src/site/xdoc/overview.xml
index c73efb997f..1bd4d1b8bd 100644
--- a/src/site/xdoc/overview.xml
+++ b/src/site/xdoc/overview.xml
@@ -17,7 +17,7 @@
-->
Important: Denial of Service CVE-2023-24998
+ +Apache Commons FileUpload before 1.5 does not provide an option to + limit the number of request parts to be processed resulting in the + possibility of an attacker triggering a DoS with a malicious upload or + series of uploads. Note that, like all of the file upload limits, the + new configuration option (FileUploadBase#setFileCountMax) is not + enabled by default and must be explicitly configured.
+ +This was fixed in commit + e20c0499.
+ +Affects: 1.0? - 1.4
+Regarding potential security problems with the class called DiskFileItem, @@ -91,7 +109,7 @@ boundary is close to the size of the buffer in MultipartStream. This is also fixed for Apache Tomcat.
-This was fixed in revisions +
This was fixed in revision 1743480.
Affects: 1.0? - 1.3.1
@@ -107,7 +125,7 @@ loop and CPU consumption) via a crafted Content-Type header that bypasses a loop's intended exit conditions. -This was fixed in revisions +
This was fixed in revision 1565143.
Affects: 1.0? - 1.3
@@ -121,7 +139,7 @@Update the Javadoc and documentation to make it clear that setting a repository is required for a secure configuration if there are local, untrusted users.
-This was fixed in revisions +
This was fixed in revision 1453273.
Affects: 1.0 - 1.2.2
diff --git a/src/site/xdoc/streaming.xml b/src/site/xdoc/streaming.xml index a765975303..f00ebc1b2d 100644 --- a/src/site/xdoc/streaming.xml +++ b/src/site/xdoc/streaming.xml @@ -17,7 +17,7 @@ -->
- Again, the FileUpload
class is used for accessing the
+ Again, the {@code FileUpload} class is used for accessing the
form fields and fields in the order in which they have been sent
- by the client. However, the FileItemFactory
is completely
+ by the client. However, the {@code FileItemFactory} is completely
ignored.
FileItem
interface,
+ items. Each such item implements the {@code FileItem} interface,
regardless of its underlying implementation.
@@ -67,15 +67,15 @@
Each file item has a number of properties that might be of interest for
your application. For example, every item has a name and a content type,
- and can provide an InputStream
to access its data. On the
+ and can provide an {@code InputStream} to access its data. On the
other hand, you may need to process items differently, depending upon
whether the item is a regular form field - that is, the data came from
an ordinary text box or similar HTML field - or an uploaded file. The
- FileItem
interface provides the methods to make such a
+ {@code FileItem} interface provides the methods to make such a
determination, and to access the data in the most appropriate manner.
- FileUpload creates new file items using a FileItemFactory
.
+ FileUpload creates new file items using a {@code FileItemFactory}.
This is what gives FileUpload most of its flexibility. The factory has
ultimate control over how each item is created. The factory implementation
that currently ships with FileUpload stores the item's data in memory or
@@ -96,12 +96,12 @@
distinctions you should make as you read this document:
ServletFileUpload
class,
- substitute the PortletFileUpload
class.
+ Where you see references to the {@code ServletFileUpload} class,
+ substitute the {@code PortletFileUpload} class.
HttpServletRequest
class,
- substitute the ActionRequest
class.
+ Where you see references to the {@code HttpServletRequest} class,
+ substitute the {@code ActionRequest} class.
- The result of the parse is a List
of file items, each of
- which implements the FileItem
interface. Processing these
+ The result of the parse is a {@code List} of file items, each of
+ which implements the {@code FileItem} interface. Processing these
items is discussed below.
- Once the parse has completed, you will have a List
of file
+ Once the parse has completed, you will have a {@code List} of file
items that you need to process. In most cases, you will want to handle
file uploads differently from regular form fields, so you might process
the list like this:
@@ -220,7 +220,7 @@ while (iter.hasNext()) {
}]]>
For a regular form field, you will most likely be interested only in the
- name of the item, and its String
value. As you might expect,
+ name of the item, and its {@code String} value. As you might expect,
accessing these is very simple.
- Note that, in the default implementation of FileUpload, write()
+ Note that, in the default implementation of FileUpload, {@code write()}
will attempt to rename the file to the specified destination, if the data
is already in a temporary file. Actually copying the data is only done if
the the rename fails, for some reason, or if the data was in memory.
If you do need to access the uploaded data in memory, you need simply
- call the get()
method to obtain the data as an array of
+ call the {@code get()} method to obtain the data as an array of
bytes.
Such temporary files are deleted automatically, if they are no longer
- used (more precisely, if the corresponding instance of DiskFileItem
- is garbage collected. This is done silently by the org.apache.commons.io.FileCleanerTracker
+ used (more precisely, if the corresponding instance of {@code DiskFileItem}
+ is garbage collected. This is done silently by the {@code org.apache.commons.io.FileCleanerTracker}
class, which starts a reaper thread.
@@ -293,7 +293,7 @@ byte[] data = item.get();
a servlet environment, this is done by using a special servlet
context listener, called
FileCleanerCleanup.
- To do so, add a section like the following to your web.xml
:
+ To do so, add a section like the following to your {@code web.xml}:
The FileCleanerCleanup provides an instance of
- org.apache.commons.io.FileCleaningTracker
. This
+ {@code org.apache.commons.io.FileCleaningTracker}. This
instance must be used when creating a
- org.apache.commons.fileupload.disk.DiskFileItemFactory
.
+ {@code org.apache.commons.fileupload.disk.DiskFileItemFactory}.
This should be done by calling a method like the following:
To disable tracking of temporary files, you may set the
- FileCleaningTracker
to null. Consequently,
+ {@code FileCleaningTracker} to null. Consequently,
created files will no longer be tracked. In particular,
they will no longer be deleted automatically.
FileItemFactory
and returns it, obscuring
- * from the caller the underlying implementation of this interface.
- *
- * @param repository The directory within which temporary files will be
- * created.
- * @return the new FileItemFactory
instance.
- */
- protected FileItemFactory createFactory(File repository) {
- return new DefaultFileItemFactory(threshold, repository);
- }
-
- static final String CHARSET_ISO88591 = "ISO-8859-1";
-
- static final String CHARSET_ASCII = "US-ASCII";
-
- static final String CHARSET_UTF8 = "UTF-8";
-
- static final String CHARSET_KOI8_R = "KOI8_R";
-
- static final String CHARSET_WIN1251 = "Cp1251";
-
- static final int SWISS_GERMAN_STUFF_UNICODE [] = {
- 0x47, 0x72, 0xFC, 0x65, 0x7A, 0x69, 0x5F, 0x7A, 0xE4, 0x6D, 0xE4
- };
-
- static final int SWISS_GERMAN_STUFF_ISO8859_1 [] = {
- 0x47, 0x72, 0xFC, 0x65, 0x7A, 0x69, 0x5F, 0x7A, 0xE4, 0x6D, 0xE4
- };
-
- static final int SWISS_GERMAN_STUFF_UTF8 [] = {
- 0x47, 0x72, 0xC3, 0xBC, 0x65, 0x7A, 0x69, 0x5F, 0x7A, 0xC3, 0xA4,
- 0x6D, 0xC3, 0xA4
- };
-
- static final int RUSSIAN_STUFF_UNICODE [] = {
- 0x412, 0x441, 0x435, 0x43C, 0x5F, 0x43F, 0x440, 0x438,
- 0x432, 0x435, 0x442
- };
-
- static final int RUSSIAN_STUFF_UTF8 [] = {
- 0xD0, 0x92, 0xD1, 0x81, 0xD0, 0xB5, 0xD0, 0xBC, 0x5F,
- 0xD0, 0xBF, 0xD1, 0x80, 0xD0, 0xB8, 0xD0, 0xB2, 0xD0,
- 0xB5, 0xD1, 0x82
- };
-
- static final int RUSSIAN_STUFF_KOI8R [] = {
- 0xF7, 0xD3, 0xC5, 0xCD, 0x5F, 0xD0, 0xD2, 0xC9, 0xD7,
- 0xC5, 0xD4
- };
-
- static final int RUSSIAN_STUFF_WIN1251 [] = {
- 0xC2, 0xF1, 0xE5, 0xEC, 0x5F, 0xEF, 0xF0, 0xE8, 0xE2,
- 0xE5, 0xF2
- };
-
- private static String constructString(int[] unicodeChars) {
- StringBuilder buffer = new StringBuilder();
- if (unicodeChars != null) {
- for (int unicodeChar : unicodeChars) {
- buffer.append((char) unicodeChar);
- }
- }
- return buffer.toString();
}
/**
* Test construction of content charset.
*/
+ @Test
public void testContentCharSet() throws Exception {
- FileItemFactory factory = createFactory(null);
-
+ final FileItemFactory factory = createFactory(null);
String teststr = constructString(SWISS_GERMAN_STUFF_UNICODE);
-
- FileItem item =
- factory.createItem(
- "doesnotmatter",
- "text/plain; charset=" + CHARSET_ISO88591,
- true,
- null);
- OutputStream outstream = item.getOutputStream();
- for (int element : SWISS_GERMAN_STUFF_ISO8859_1) {
- outstream.write(element);
+ FileItem item = factory.createItem("doesnotmatter", "text/plain; charset=" + CHARSET_ISO_8859_1, true, null);
+ try (OutputStream out = item.getOutputStream()) {
+ for (final int element : SWISS_GERMAN_STUFF_ISO8859_1) {
+ out.write(element);
+ }
}
- outstream.close();
assertEquals(teststr, teststr, item.getString());
-
- item =
- factory.createItem(
- "doesnotmatter",
- "text/plain; charset=" + CHARSET_UTF8,
- true,
- null);
- outstream = item.getOutputStream();
- for (int element : SWISS_GERMAN_STUFF_UTF8) {
- outstream.write(element);
+ item = factory.createItem("doesnotmatter", "text/plain; charset=" + CHARSET_UTF8, true, null);
+ try (OutputStream out = item.getOutputStream()) {
+ for (final int element : SWISS_GERMAN_STUFF_UTF8) {
+ out.write(element);
+ }
}
- outstream.close();
assertEquals(teststr, teststr, item.getString());
-
teststr = constructString(RUSSIAN_STUFF_UNICODE);
-
- item =
- factory.createItem(
- "doesnotmatter",
- "text/plain; charset=" + CHARSET_KOI8_R,
- true,
- null);
- outstream = item.getOutputStream();
- for (int element : RUSSIAN_STUFF_KOI8R) {
- outstream.write(element);
+ item = factory.createItem("doesnotmatter", "text/plain; charset=" + CHARSET_KOI8_R, true, null);
+ try (OutputStream out = item.getOutputStream()) {
+ for (final int element : RUSSIAN_STUFF_KOI8R) {
+ out.write(element);
+ }
}
- outstream.close();
assertEquals(teststr, teststr, item.getString());
-
- item =
- factory.createItem(
- "doesnotmatter",
- "text/plain; charset=" + CHARSET_WIN1251,
- true,
- null);
- outstream = item.getOutputStream();
- for (int element : RUSSIAN_STUFF_WIN1251) {
- outstream.write(element);
+ item = factory.createItem("doesnotmatter", "text/plain; charset=" + CHARSET_WIN1251, true, null);
+ try (OutputStream out = item.getOutputStream()) {
+ for (final int element : RUSSIAN_STUFF_WIN1251) {
+ out.write(element);
+ }
}
- outstream.close();
assertEquals(teststr, teststr, item.getString());
-
- item =
- factory.createItem(
- "doesnotmatter",
- "text/plain; charset=" + CHARSET_UTF8,
- true,
- null);
- outstream = item.getOutputStream();
- for (int element : RUSSIAN_STUFF_UTF8) {
- outstream.write(element);
+ item = factory.createItem("doesnotmatter", "text/plain; charset=" + CHARSET_UTF8, true, null);
+ try (OutputStream out = item.getOutputStream()) {
+ for (final int element : RUSSIAN_STUFF_UTF8) {
+ out.write(element);
+ }
}
- outstream.close();
assertEquals(teststr, teststr, item.getString());
}
+ /**
+ * Test construction of a file field.
+ */
+ @Test
+ public void testFileFieldConstruction() {
+ final FileItemFactory factory = createFactory(null);
+ final String fileFieldName = "fileField";
+ final String fileName = "originalFileName";
+ final FileItem item = factory.createItem(fileFieldName, CONTENT_TYPE_FILE, false, fileName);
+ assertNotNull(item);
+ assertEquals(item.getFieldName(), fileFieldName);
+ assertEquals(item.getContentType(), CONTENT_TYPE_FILE);
+ assertFalse(item.isFormField());
+ assertEquals(item.getName(), fileName);
+ }
+
+ /**
+ * Test construction of a regular text field.
+ */
+ @Test
+ public void testTextFieldConstruction() {
+ final FileItemFactory factory = createFactory(null);
+ final String textFieldName = "textField";
+ final FileItem item = factory.createItem(textFieldName, CONTENT_TYPE_TEXT, true, null);
+ assertNotNull(item);
+ assertEquals(item.getFieldName(), textFieldName);
+ assertEquals(item.getContentType(), CONTENT_TYPE_TEXT);
+ assertTrue(item.isFormField());
+ assertNull(item.getName());
+ }
}
diff --git a/src/test/java/org/apache/commons/fileupload/DiskFileItemSerializeTest.java b/src/test/java/org/apache/commons/fileupload/DiskFileItemSerializeTest.java
index 4507d58495..1d398a131d 100644
--- a/src/test/java/org/apache/commons/fileupload/DiskFileItemSerializeTest.java
+++ b/src/test/java/org/apache/commons/fileupload/DiskFileItemSerializeTest.java
@@ -14,6 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+
package org.apache.commons.fileupload;
import static org.junit.Assert.assertEquals;
@@ -29,6 +30,7 @@
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
+import java.nio.file.InvalidPathException;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.io.FileUtils;
@@ -37,217 +39,212 @@
import org.junit.Test;
/**
- * Serialization Unit tests for
- * {@link org.apache.commons.fileupload.disk.DiskFileItem}.
+ * Serialization Unit tests for {@link org.apache.commons.fileupload.disk.DiskFileItem}.
*/
public class DiskFileItemSerializeTest {
// Use a private repo to catch any files left over by tests
private static final File REPO = new File(System.getProperty("java.io.tmpdir"), "diskfileitemrepo");
- @Before
- public void setUp() throws Exception {
- if (REPO.exists()) {
- FileUtils.deleteDirectory(REPO);
- }
- FileUtils.forceMkdir(REPO);
- }
-
- @After
- public void tearDown() throws IOException {
- for(File file : FileUtils.listFiles(REPO, null, true)) {
- System.out.println("Found leftover file " + file);
- }
- FileUtils.deleteDirectory(REPO);
- }
-
/**
* Content type for regular form items.
*/
- private static final String textContentType = "text/plain";
+ private static final String CONTENT_TYPE_TEXT = "text/plain";
/**
* Very low threshold for testing memory versus disk options.
*/
- private static final int threshold = 16;
+ private static final int THRESHOLD = 16;
/**
- * Helper method to test creation of a field when a repository is used.
+ * Compare content bytes.
*/
- public void testInMemoryObject(byte[] testFieldValueBytes, File repository) {
- FileItem item = createFileItem(testFieldValueBytes, repository);
+ private void compareBytes(final String text, final byte[] origBytes, final byte[] newBytes) {
+ assertNotNull("origBytes must not be null", origBytes);
+ assertNotNull("newBytes must not be null", newBytes);
+ assertEquals(text + " byte[] length", origBytes.length, newBytes.length);
+ for (int i = 0; i < origBytes.length; i++) {
+ assertEquals(text + " byte[" + i + "]", origBytes[i], newBytes[i]);
+ }
+ }
- // Check state is as expected
- assertTrue("Initial: in memory", item.isInMemory());
- assertEquals("Initial: size", item.getSize(), testFieldValueBytes.length);
- compareBytes("Initial", item.get(), testFieldValueBytes);
- item.delete();
+ /**
+ * Create content bytes of a specified size.
+ */
+ private byte[] createContentBytes(final int size) {
+ final StringBuilder buffer = new StringBuilder(size);
+ byte count = 0;
+ for (int i = 0; i < size; i++) {
+ buffer.append(count + "");
+ count++;
+ if (count > 9) {
+ count = 0;
+ }
+ }
+ return buffer.toString().getBytes();
}
/**
- * Helper method to test creation of a field.
+ * Create a FileItem with the specfied content bytes.
+ *
+ * @throws IOException test failure.
*/
- private void testInMemoryObject(byte[] testFieldValueBytes) {
- testInMemoryObject(testFieldValueBytes, REPO);
+ private FileItem createFileItem(final byte[] contentBytes) throws IOException {
+ return createFileItem(contentBytes, REPO);
}
/**
- * Test creation of a field for which the amount of data falls below the
- * configured threshold.
+ * Create a FileItem with the specfied content bytes and repository.
+ *
+ * @throws IOException test failure.
+ */
+ private FileItem createFileItem(final byte[] contentBytes, final File repository) throws IOException {
+ final FileItemFactory factory = new DiskFileItemFactory(THRESHOLD, repository);
+ final String textFieldName = "textField";
+ final FileItem item = factory.createItem(textFieldName, CONTENT_TYPE_TEXT, true, "My File Name");
+ try (OutputStream os = item.getOutputStream()) {
+ os.write(contentBytes);
+ }
+ return item;
+ }
+
+ /**
+ * Tests deserialization.
*/
- @Test
- public void testBelowThreshold() {
- // Create the FileItem
- byte[] testFieldValueBytes = createContentBytes(threshold - 1);
- testInMemoryObject(testFieldValueBytes);
+ private Object deserialize(final ByteArrayOutputStream baos) throws Exception {
+ Object result = null;
+ final ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
+ final ObjectInputStream ois = new ObjectInputStream(bais);
+ result = ois.readObject();
+ bais.close();
+ return result;
}
/**
- * Test creation of a field for which the amount of data equals the
- * configured threshold.
+ * Tests serialization.
*/
- @Test
- public void testThreshold() {
- // Create the FileItem
- byte[] testFieldValueBytes = createContentBytes(threshold);
- testInMemoryObject(testFieldValueBytes);
+ private ByteArrayOutputStream serialize(final Object target) throws Exception {
+ final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ try (ObjectOutputStream oos = new ObjectOutputStream(baos)) {
+ oos.writeObject(target);
+ oos.flush();
+ }
+ return baos;
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ if (REPO.exists()) {
+ FileUtils.deleteDirectory(REPO);
+ }
+ FileUtils.forceMkdir(REPO);
+ }
+
+ @After
+ public void tearDown() throws IOException {
+ for (final File file : FileUtils.listFiles(REPO, null, true)) {
+ System.out.println("Found leftover file " + file);
+ }
+ FileUtils.deleteDirectory(REPO);
}
/**
- * Test creation of a field for which the amount of data falls above the
- * configured threshold.
+ * Test creation of a field for which the amount of data falls above the configured threshold.
+ *
+ * @throws IOException test failure.
*/
@Test
- public void testAboveThreshold() {
+ public void testAboveThreshold() throws IOException {
// Create the FileItem
- byte[] testFieldValueBytes = createContentBytes(threshold + 1);
- FileItem item = createFileItem(testFieldValueBytes);
-
+ final byte[] testFieldValueBytes = createContentBytes(THRESHOLD + 1);
+ final FileItem item = createFileItem(testFieldValueBytes);
// Check state is as expected
assertFalse("Initial: in memory", item.isInMemory());
assertEquals("Initial: size", item.getSize(), testFieldValueBytes.length);
compareBytes("Initial", item.get(), testFieldValueBytes);
-
item.delete();
}
/**
- * Test serialization and deserialization when repository is not null.
+ * Test creation of a field for which the amount of data falls below the configured threshold.
+ *
+ * @throws IOException test failure.
*/
@Test
- public void testValidRepository() {
+ public void testBelowThreshold() throws IOException {
// Create the FileItem
- byte[] testFieldValueBytes = createContentBytes(threshold);
+ final byte[] testFieldValueBytes = createContentBytes(THRESHOLD - 1);
+ testInMemoryObject(testFieldValueBytes);
+ }
+
+ /**
+ * Helper method to test creation of a field.
+ *
+ * @throws IOException test failure.
+ */
+ private void testInMemoryObject(final byte[] testFieldValueBytes) throws IOException {
testInMemoryObject(testFieldValueBytes, REPO);
}
+ /**
+ * Helper method to test creation of a field when a repository is used.
+ *
+ * @throws IOException test failure.
+ */
+ private void testInMemoryObject(final byte[] testFieldValueBytes, final File repository) throws IOException {
+ final FileItem item = createFileItem(testFieldValueBytes, repository);
+ // Check state is as expected
+ assertTrue("Initial: in memory", item.isInMemory());
+ assertEquals("Initial: size", item.getSize(), testFieldValueBytes.length);
+ compareBytes("Initial", item.get(), testFieldValueBytes);
+ item.delete();
+ }
+
/**
* Test deserialization fails when repository is not valid.
*/
- @Test(expected=IOException.class)
+ @Test(expected = IOException.class)
public void testInvalidRepository() throws Exception {
// Create the FileItem
- byte[] testFieldValueBytes = createContentBytes(threshold);
- File repository = new File(System.getProperty("java.io.tmpdir"), "file");
- FileItem item = createFileItem(testFieldValueBytes, repository);
+ final byte[] testFieldValueBytes = createContentBytes(THRESHOLD);
+ final File repository = new File(System.getProperty("java.io.tmpdir"), "file");
+ final FileItem item = createFileItem(testFieldValueBytes, repository);
deserialize(serialize(item));
}
/**
* Test deserialization fails when repository contains a null character.
*/
- @Test(expected=IOException.class)
+ @Test(expected = InvalidPathException.class)
public void testInvalidRepositoryWithNullChar() throws Exception {
// Create the FileItem
- byte[] testFieldValueBytes = createContentBytes(threshold);
- File repository = new File(System.getProperty("java.io.tmpdir"), "\0");
- FileItem item = createFileItem(testFieldValueBytes, repository);
+ final byte[] testFieldValueBytes = createContentBytes(THRESHOLD);
+ final File repository = new File(System.getProperty("java.io.tmpdir"), "\0");
+ final FileItem item = createFileItem(testFieldValueBytes, repository);
deserialize(serialize(item));
}
/**
- * Compare content bytes.
+ * Test creation of a field for which the amount of data equals the configured threshold.
+ *
+ * @throws IOException test failure.
*/
- private void compareBytes(String text, byte[] origBytes, byte[] newBytes) {
- assertNotNull("origBytes must not be null", origBytes);
- assertNotNull("newBytes must not be null", newBytes);
- assertEquals(text + " byte[] length", origBytes.length, newBytes.length);
- for (int i = 0; i < origBytes.length; i++) {
- assertEquals(text + " byte[" + i + "]", origBytes[i], newBytes[i]);
- }
- }
-
- /**
- * Create content bytes of a specified size.
- */
- private byte[] createContentBytes(int size) {
- StringBuilder buffer = new StringBuilder(size);
- byte count = 0;
- for (int i = 0; i < size; i++) {
- buffer.append(count+"");
- count++;
- if (count > 9) {
- count = 0;
- }
- }
- return buffer.toString().getBytes();
- }
-
- /**
- * Create a FileItem with the specfied content bytes and repository.
- */
- private FileItem createFileItem(byte[] contentBytes, File repository) {
- FileItemFactory factory = new DiskFileItemFactory(threshold, repository);
- String textFieldName = "textField";
-
- FileItem item = factory.createItem(
- textFieldName,
- textContentType,
- true,
- "My File Name"
- );
- try {
- OutputStream os = item.getOutputStream();
- os.write(contentBytes);
- os.close();
- } catch(IOException e) {
- fail("Unexpected IOException" + e);
- }
-
- return item;
-
- }
-
- /**
- * Create a FileItem with the specfied content bytes.
- */
- private FileItem createFileItem(byte[] contentBytes) {
- return createFileItem(contentBytes, REPO);
- }
-
- /**
- * Do serialization
- */
- private ByteArrayOutputStream serialize(Object target) throws Exception {
- ByteArrayOutputStream baos = new ByteArrayOutputStream();
- ObjectOutputStream oos = new ObjectOutputStream(baos);
- oos.writeObject(target);
- oos.flush();
- oos.close();
- return baos;
+ @Test
+ public void testThreshold() throws IOException {
+ // Create the FileItem
+ final byte[] testFieldValueBytes = createContentBytes(THRESHOLD);
+ testInMemoryObject(testFieldValueBytes);
}
/**
- * Do deserialization
+ * Test serialization and deserialization when repository is not null.
+ *
+ * @throws IOException test failure.
*/
- private Object deserialize(ByteArrayOutputStream baos) throws Exception {
- Object result = null;
- ByteArrayInputStream bais =
- new ByteArrayInputStream(baos.toByteArray());
- ObjectInputStream ois = new ObjectInputStream(bais);
- result = ois.readObject();
- bais.close();
-
- return result;
+ @Test
+ public void testValidRepository() throws IOException {
+ // Create the FileItem
+ final byte[] testFieldValueBytes = createContentBytes(THRESHOLD);
+ testInMemoryObject(testFieldValueBytes, REPO);
}
}
diff --git a/src/test/java/org/apache/commons/fileupload/DiskFileUploadTest.java b/src/test/java/org/apache/commons/fileupload/DiskFileUploadTest.java
index 49f65f05fd..72be20984e 100644
--- a/src/test/java/org/apache/commons/fileupload/DiskFileUploadTest.java
+++ b/src/test/java/org/apache/commons/fileupload/DiskFileUploadTest.java
@@ -16,10 +16,18 @@
*/
package org.apache.commons.fileupload;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.fail;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertThrowsExactly;
+
+import java.io.File;
+import java.util.List;
import javax.servlet.http.HttpServletRequest;
+import org.apache.commons.fileupload.disk.DiskFileItem;
import org.junit.Before;
import org.junit.Test;
@@ -38,30 +46,40 @@ public void setUp() {
upload = new DiskFileUpload();
}
+ /** Proposed test for FILEUPLOAD-293. As of yet, doesn't reproduce the problem.
+ */
@Test
- public void testWithInvalidRequest() {
- HttpServletRequest req = HttpServletRequestFactory.createInvalidHttpServletRequest();
+ public void testMoveFile() throws Exception {
+ final DiskFileUpload myUpload = new DiskFileUpload();
+ myUpload.setSizeThreshold(0);
+ final String content =
+ "-----1234\r\n" +
+ "Content-Disposition: form-data; name=\"file\";" +
+ "filename=\"foo.tab\"\r\n" +
+ "Content-Type: text/whatever\r\n" +
+ "\r\n" +
+ "This is the content of the file\n" +
+ "\r\n" +
+ "-----1234--\r\n";
+ final byte[] contentBytes = content.getBytes("US-ASCII");
+ final HttpServletRequest request = new MockHttpServletRequest(contentBytes, Constants.CONTENT_TYPE);
+ final List