You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@tomcat.apache.org by ma...@apache.org on 2025/06/03 13:37:59 UTC

(tomcat) branch main updated: Update fork of Commons FileUpload to latest 1.x

This is an automated email from the ASF dual-hosted git repository.

markt pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tomcat.git


The following commit(s) were added to refs/heads/main by this push:
     new 81f27a990f Update fork of Commons FileUpload to latest 1.x
81f27a990f is described below

commit 81f27a990fd780c6a4ced79f2f830716f455e695
Author: Mark Thomas <ma...@apache.org>
AuthorDate: Tue Jun 3 14:37:38 2025 +0100

    Update fork of Commons FileUpload to latest 1.x
---
 MERGE.txt                                          |    2 +-
 .../tomcat/util/http/fileupload/FileItem.java      |  140 ++-
 .../util/http/fileupload/FileItemHeaders.java      |   22 +-
 .../util/http/fileupload/FileItemStream.java       |   49 +-
 .../tomcat/util/http/fileupload/FileUpload.java    |   12 +-
 .../util/http/fileupload/FileUploadBase.java       |  472 ++++----
 .../util/http/fileupload/FileUploadException.java  |   12 +-
 .../http/fileupload/InvalidFileNameException.java  |   10 +-
 .../util/http/fileupload/MultipartStream.java      | 1197 ++++++++++----------
 .../util/http/fileupload/ParameterParser.java      |  291 +++--
 .../util/http/fileupload/ProgressListener.java     |    8 +-
 .../util/http/fileupload/disk/DiskFileItem.java    |  652 +++++------
 .../http/fileupload/disk/DiskFileItemFactory.java  |  115 +-
 .../tomcat/util/http/fileupload/package-info.java  |    4 +-
 .../fileupload/servlet/ServletRequestContext.java  |   40 +-
 .../http/fileupload/util/FileItemHeadersImpl.java  |   37 +-
 .../http/fileupload/util/LimitedInputStream.java   |   81 +-
 .../tomcat/util/http/fileupload/util/Streams.java  |   74 +-
 .../http/fileupload/util/mime/MimeUtility.java     |   18 +-
 .../http/fileupload/util/mime/ParseException.java  |    2 +
 .../util/mime/QuotedPrintableDecoder.java          |   53 +-
 .../http/fileupload/util/mime/RFC2231Utility.java  |   93 +-
 webapps/docs/changelog.xml                         |    4 +
 23 files changed, 1650 insertions(+), 1738 deletions(-)

diff --git a/MERGE.txt b/MERGE.txt
index c81293c991..89ba88208a 100644
--- a/MERGE.txt
+++ b/MERGE.txt
@@ -46,7 +46,7 @@ Branch: 1.x
 Sub-tree:
 src/main/java/org/apache/commons/fileupload
 The SHA1 ID / tag for the most recent commit to be merged to Tomcat is:
-7a8c3241cfa8d036452cd4fc3f92d57cff189bca (2023-09-16)
+abe5d94b7ef6ff7164b48b5bb3781979ebbafb0e (2025-06-03)
 
 Note: Tomcat's copy of fileupload also includes classes copied manually from
       Commons IO.
diff --git a/java/org/apache/tomcat/util/http/fileupload/FileItem.java b/java/org/apache/tomcat/util/http/fileupload/FileItem.java
index e6f035fc7b..00316cc3ed 100644
--- a/java/org/apache/tomcat/util/http/fileupload/FileItem.java
+++ b/java/org/apache/tomcat/util/http/fileupload/FileItem.java
@@ -20,7 +20,6 @@ import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
-import java.io.UncheckedIOException;
 import java.io.UnsupportedEncodingException;
 
 /**
@@ -47,18 +46,21 @@ import java.io.UnsupportedEncodingException;
  */
 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 {@code null} if
@@ -69,6 +71,25 @@ public interface FileItem extends FileItemHeadersSupport {
      */
     String getContentType();
 
+    /**
+     * 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
@@ -83,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 {@code true} if the file contents will be read from memory;
-     *         {@code 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.
@@ -102,13 +123,13 @@ public interface FileItem extends FileItemHeadersSupport {
     long getSize();
 
     /**
-     * Returns the contents of the file item as an array of bytes.
-     *
-     * @return 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.
      *
-     * @throws UncheckedIOException if an I/O error occurs
+     * @return The contents of the item, as a string.
      */
-    byte[] get() throws UncheckedIOException;
+    String getString();
 
     /**
      * Returns the contents of the file item as a String, using the specified
@@ -121,53 +142,26 @@ public interface FileItem extends FileItemHeadersSupport {
      *
      * @throws UnsupportedEncodingException if the requested character
      *                                      encoding is not available.
-     * @throws IOException if an I/O error occurs
-     */
-    String getString(String encoding) throws UnsupportedEncodingException, IOException;
-
-    /**
-     * 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();
+    String getString(String encoding) throws UnsupportedEncodingException;
 
     /**
-     * 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.
-     * <p>
-     * 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 {@code 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.
-     */
-    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 {@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.
+     * @return {@code true} if the instance represents a simple form
+     *         field; {@code false} if it represents an uploaded file.
      */
-    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.
@@ -176,15 +170,6 @@ public interface FileItem extends FileItemHeadersSupport {
      */
     void setFieldName(String name);
 
-    /**
-     * Determines whether or not a {@code FileItem} instance represents
-     * a simple form field.
-     *
-     * @return {@code true} if the instance represents a simple form
-     *         field; {@code false} if it represents an uploaded file.
-     */
-    boolean isFormField();
-
     /**
      * Specifies whether or not a {@code FileItem} instance represents
      * a simple form field.
@@ -195,14 +180,21 @@ public interface FileItem extends FileItemHeadersSupport {
     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.
+     * <p>
+     * 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 contents 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/java/org/apache/tomcat/util/http/fileupload/FileItemHeaders.java b/java/org/apache/tomcat/util/http/fileupload/FileItemHeaders.java
index 890f4397ae..30b1d40e25 100644
--- a/java/org/apache/tomcat/util/http/fileupload/FileItemHeaders.java
+++ b/java/org/apache/tomcat/util/http/fileupload/FileItemHeaders.java
@@ -42,6 +42,17 @@ public interface FileItemHeaders {
      */
     String getHeader(String name);
 
+    /**
+     * <p>
+     * Returns an {@code Iterator} of all the header names.
+     * </p>
+     *
+     * @return an {@code Iterator} containing all of the names of
+     *         headers provided with this file item. If the item does not have
+     *         any headers return an empty {@code Iterator}
+     */
+    Iterator<String> getHeaderNames();
+
     /**
      * <p>
      * Returns all the values of the specified item header as an
@@ -60,15 +71,4 @@ public interface FileItemHeaders {
      */
     Iterator<String> getHeaders(String name);
 
-    /**
-     * <p>
-     * Returns an {@code Iterator} of all the header names.
-     * </p>
-     *
-     * @return an {@code Iterator} containing all of the names of
-     *         headers provided with this file item. If the item does not have
-     *         any headers return an empty {@code Iterator}
-     */
-    Iterator<String> getHeaderNames();
-
 }
diff --git a/java/org/apache/tomcat/util/http/fileupload/FileItemStream.java b/java/org/apache/tomcat/util/http/fileupload/FileItemStream.java
index c31d9eb13b..e8734e4c93 100644
--- a/java/org/apache/tomcat/util/http/fileupload/FileItemStream.java
+++ b/java/org/apache/tomcat/util/http/fileupload/FileItemStream.java
@@ -48,20 +48,14 @@ public interface FileItemStream extends FileItemHeadersSupport {
          */
         private static final long serialVersionUID = -7280778431581963740L;
 
-    }
+        /**
+         * Constructs a new instance.
+         */
+        public ItemSkippedException() {
+            // empty
+        }
 
-    /**
-     * Creates an {@link InputStream}, which allows to read the
-     * items contents.
-     *
-     * @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
-     */
-    InputStream openStream() throws IOException;
+    }
 
     /**
      * Returns the content type passed by the browser or {@code null} if
@@ -72,6 +66,14 @@ public interface FileItemStream extends FileItemHeadersSupport {
      */
     String getContentType();
 
+    /**
+     * 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 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
@@ -82,14 +84,6 @@ public interface FileItemStream extends FileItemHeadersSupport {
      */
     String getName();
 
-    /**
-     * Returns the name of the field in the multipart form corresponding to
-     * this file item.
-     *
-     * @return The name of the form field.
-     */
-    String getFieldName();
-
     /**
      * Determines whether or not a {@code FileItem} instance represents
      * a simple form field.
@@ -99,4 +93,17 @@ public interface FileItemStream extends FileItemHeadersSupport {
      */
     boolean isFormField();
 
+    /**
+     * Creates an {@link InputStream}, which allows to read the
+     * items contents.
+     *
+     * @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
+     */
+    InputStream openStream() throws IOException;
+
 }
diff --git a/java/org/apache/tomcat/util/http/fileupload/FileUpload.java b/java/org/apache/tomcat/util/http/fileupload/FileUpload.java
index 06846f18d6..cd16f07f83 100644
--- a/java/org/apache/tomcat/util/http/fileupload/FileUpload.java
+++ b/java/org/apache/tomcat/util/http/fileupload/FileUpload.java
@@ -33,15 +33,11 @@ package org.apache.tomcat.util.http.fileupload;
 public class FileUpload
     extends FileUploadBase {
 
-    // ----------------------------------------------------------- Data members
-
     /**
      * The factory to use to create new form items.
      */
     private FileItemFactory fileItemFactory;
 
-    // ----------------------------------------------------------- Constructors
-
     /**
      * Constructs an uninitialized instance of this class.
      *
@@ -52,8 +48,6 @@ public class FileUpload
     public FileUpload() {
     }
 
-    // ----------------------------------------------------- Property accessors
-
     /**
      * Returns the factory class used when creating file items.
      *
@@ -67,11 +61,11 @@ public class FileUpload
     /**
      * 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(final FileItemFactory factory) {
-        this.fileItemFactory = factory;
+    public void setFileItemFactory(final FileItemFactory fileItemFactory) {
+        this.fileItemFactory = fileItemFactory;
     }
 
 }
diff --git a/java/org/apache/tomcat/util/http/fileupload/FileUploadBase.java b/java/org/apache/tomcat/util/http/fileupload/FileUploadBase.java
index b9f5cfe174..73e5f1e63e 100644
--- a/java/org/apache/tomcat/util/http/fileupload/FileUploadBase.java
+++ b/java/org/apache/tomcat/util/http/fileupload/FileUploadBase.java
@@ -33,9 +33,10 @@ import org.apache.tomcat.util.http.fileupload.util.Streams;
 
 
 /**
- * <p>High level API for processing file uploads.</p>
+ * High level API for processing file uploads.
  *
- * <p>This class handles multiple files per single HTML widget, sent using
+ * <p>
+ * This class handles multiple files per single HTML widget, sent using
  * {@code multipart/mixed} encoding type, as specified by
  * <a href="http://www.ietf.org/rfc/rfc1867.txt">RFC 1867</a>.  Use {@link
  * #parseRequest(RequestContext)} to acquire a list of {@link
@@ -48,7 +49,15 @@ import org.apache.tomcat.util.http.fileupload.util.Streams;
  */
 public abstract class FileUploadBase {
 
-    // ----------------------------------------------------- Manifest constants
+    /**
+     * Line feed.
+     */
+    private static final char LF = '\n';
+
+    /**
+     * Carriage return.
+     */
+    private static final char CR = '\r';
 
     /**
      * HTTP content type header name.
@@ -90,8 +99,6 @@ public abstract class FileUploadBase {
      */
     public static final String MULTIPART_MIXED = "multipart/mixed";
 
-    // ----------------------------------------------------------- Data members
-
     /**
      * The maximum size permitted for the complete request, as opposed to
      * {@link #fileSizeMax}. A value of -1 indicates no maximum.
@@ -120,70 +127,65 @@ public abstract class FileUploadBase {
      */
     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)
-     *
+     * Constructs a new instance.
      */
-    public long getSizeMax() {
-        return sizeMax;
+    public FileUploadBase() {
+        // empty
     }
 
     /**
-     * Sets the maximum allowed size of a complete request, as opposed
-     * to {@link #setFileSizeMax(long)}.
+     * Gets the boundary from the {@code Content-type} header.
      *
-     * @param sizeMax The maximum allowed size, in bytes. The default value of
-     *   -1 indicates, that there is no limit.
-     *
-     * @see #getSizeMax()
+     * @param contentType The value of the content type header from which to
+     *                    extract the boundary value.
      *
+     * @return The boundary, as a byte array.
      */
-    public void setSizeMax(final long sizeMax) {
-        this.sizeMax = sizeMax;
+    public byte[] getBoundary(final String contentType) {
+        final ParameterParser parser = new ParameterParser();
+        parser.setLowerCaseNames(true);
+        // Parameter parser can handle null input
+        final Map<String, String> params = parser.parse(contentType, new char[] {';', ','});
+        final String boundaryStr = params.get("boundary");
+
+        if (boundaryStr == null) {
+            return null;
+        }
+        return boundaryStr.getBytes(StandardCharsets.ISO_8859_1);
     }
 
     /**
-     * Returns the maximum allowed size of a single uploaded file,
-     * as opposed to {@link #getSizeMax()}.
+     * Gets the field name from the {@code Content-disposition}
+     * header.
      *
-     * @see #setFileSizeMax(long)
-     * @return Maximum size of a single uploaded file.
+     * @param headers A {@code Map} containing the HTTP request headers.
+     *
+     * @return The field name for the current {@code encapsulation}.
      */
-    public long getFileSizeMax() {
-        return fileSizeMax;
+    public String getFieldName(final FileItemHeaders headers) {
+        return getFieldName(headers.getHeader(CONTENT_DISPOSITION));
     }
 
     /**
-     * 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.
+     * Returns the field name, which is given by the content-disposition
+     * header.
+     * @param contentDisposition The content-dispositions header value.
+     * @return The field name.
      */
-    public void setFileSizeMax(final long fileSizeMax) {
-        this.fileSizeMax = fileSizeMax;
+    private String getFieldName(final String contentDisposition) {
+        String fieldName = null;
+        if (contentDisposition != null && contentDisposition.toLowerCase(Locale.ROOT).startsWith(FORM_DATA)) {
+            final ParameterParser parser = new ParameterParser();
+            parser.setLowerCaseNames(true);
+            // Parameter parser can handle null input
+            final Map<String, String> params = parser.parse(contentDisposition, ';');
+            fieldName = params.get("name");
+            if (fieldName != null) {
+                fieldName = fieldName.trim();
+            }
+        }
+        return fieldName;
     }
 
     /**
@@ -196,155 +198,14 @@ public abstract class FileUploadBase {
     }
 
     /**
-     * Sets the maximum number of files allowed per request.
-     *
-     * @param fileCountMax The new limit. {@code -1} means no limit.
-     */
-    public void setFileCountMax(final long fileCountMax) {
-        this.fileCountMax = fileCountMax;
-    }
-
-    /**
-     * Retrieves the character encoding used when reading the headers of an
-     * individual part. When not specified, or {@code null}, the request
-     * encoding is used. If that is also not specified, or {@code 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 {@code null}, the request
-     * encoding is used. If that is also not specified, or {@code null},
-     * the platform default encoding is used.
-     *
-     * @param encoding The encoding used to read part headers.
-     */
-    public void setHeaderEncoding(final String encoding) {
-        headerEncoding = encoding;
-    }
-
-    // --------------------------------------------------------- Public methods
-
-    /**
-     * Processes an <a href="http://www.ietf.org/rfc/rfc1867.txt">RFC 1867</a>
-     * compliant {@code multipart/form-data} stream.
-     *
-     * @param ctx The context for the request to be parsed.
-     *
-     * @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 FileItemIterator getItemIterator(final RequestContext ctx)
-    throws FileUploadException, IOException {
-        try {
-            return new FileItemIteratorImpl(this, ctx);
-        } catch (final FileUploadIOException e) {
-            // unwrap encapsulated SizeException
-            throw (FileUploadException) e.getCause();
-        }
-    }
-
-    /**
-     * Processes an <a href="http://www.ietf.org/rfc/rfc1867.txt">RFC 1867</a>
-     * compliant {@code multipart/form-data} stream.
-     *
-     * @param ctx The context for the request to be parsed.
-     *
-     * @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.
-     */
-    public List<FileItem> parseRequest(final RequestContext ctx)
-            throws FileUploadException {
-        final List<FileItem> items = new ArrayList<>();
-        boolean successful = false;
-        try {
-            final FileItemIterator iter = getItemIterator(ctx);
-            final FileItemFactory fileItemFactory = Objects.requireNonNull(getFileItemFactory(),
-                    "No FileItemFactory has been set.");
-            final byte[] buffer = new byte[Streams.DEFAULT_BUFFER_SIZE];
-            while (iter.hasNext()) {
-                if (items.size() == fileCountMax) {
-                    // The next item will exceed the limit.
-                    throw new FileCountLimitExceededException(ATTACHMENT, getFileCountMax());
-                }
-                final FileItemStream item = iter.next();
-                // Don't use getName() here to prevent an InvalidFileNameException.
-                final String fileName = item.getName();
-                final FileItem fileItem = fileItemFactory.createItem(item.getFieldName(), item.getContentType(),
-                                                   item.isFormField(), fileName);
-                items.add(fileItem);
-                try {
-                    Streams.copy(item.openStream(), fileItem.getOutputStream(), true, buffer);
-                } catch (final FileUploadIOException e) {
-                    throw (FileUploadException) e.getCause();
-                } catch (final IOException e) {
-                    throw new IOFileUploadException(String.format("Processing of %s request failed. %s",
-                                                           MULTIPART_FORM_DATA, e.getMessage()), e);
-                }
-                final FileItemHeaders fih = item.getHeaders();
-                fileItem.setHeaders(fih);
-            }
-            successful = true;
-            return items;
-        } catch (final FileUploadException e) {
-            throw e;
-        } catch (final IOException e) {
-            throw new FileUploadException(e.getMessage(), e);
-        } finally {
-            if (!successful) {
-                for (final FileItem fileItem : items) {
-                    try {
-                        fileItem.delete();
-                    } catch (final Exception ignored) {
-                        // ignored TODO perhaps add to tracker delete failure list somehow?
-                    }
-                }
-            }
-        }
-    }
-
-    // ------------------------------------------------------ Protected methods
-
-    /**
-     * Retrieves the boundary from the {@code Content-type} header.
-     *
-     * @param contentType The value of the content type header from which to
-     *                    extract the boundary value.
+     * Returns the factory class used when creating file items.
      *
-     * @return The boundary, as a byte array.
+     * @return The factory class for new file items.
      */
-    public byte[] getBoundary(final String contentType) {
-        final ParameterParser parser = new ParameterParser();
-        parser.setLowerCaseNames(true);
-        // Parameter parser can handle null input
-        final Map<String, String> params = parser.parse(contentType, new char[] {';', ','});
-        final String boundaryStr = params.get("boundary");
-
-        if (boundaryStr == null) {
-            return null;
-        }
-        final byte[] boundary;
-        boundary = boundaryStr.getBytes(StandardCharsets.ISO_8859_1);
-        return boundary;
-    }
+    public abstract FileItemFactory getFileItemFactory();
 
     /**
-     * Retrieves the file name from the {@code Content-disposition}
+     * Gets the file name from the {@code Content-disposition}
      * header.
      *
      * @param headers The HTTP headers object.
@@ -357,18 +218,18 @@ public abstract class FileUploadBase {
 
     /**
      * Returns the given content-disposition headers file name.
-     * @param pContentDisposition The content-disposition headers value.
+     * @param contentDisposition The content-disposition headers value.
      * @return The file name
      */
-    private String getFileName(final String pContentDisposition) {
+    private String getFileName(final String contentDisposition) {
         String fileName = null;
-        if (pContentDisposition != null) {
-            final String cdl = pContentDisposition.toLowerCase(Locale.ENGLISH);
+        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 Map<String, String> params = parser.parse(pContentDisposition, ';');
+                final Map<String, String> params = parser.parse(contentDisposition, ';');
                 if (params.containsKey("filename")) {
                     fileName = params.get("filename");
                     if (fileName != null) {
@@ -386,37 +247,52 @@ public abstract class FileUploadBase {
     }
 
     /**
-     * Retrieves the field name from the {@code Content-disposition}
-     * header.
+     * Returns the maximum allowed size of a single uploaded file,
+     * as opposed to {@link #getSizeMax()}.
      *
-     * @param headers A {@code Map} containing the HTTP request headers.
+     * @see #setFileSizeMax(long)
+     * @return Maximum size of a single uploaded file.
+     */
+    public long getFileSizeMax() {
+        return fileSizeMax;
+    }
+
+    /**
+     * Gets the character encoding used when reading the headers of an
+     * individual part. When not specified, or {@code null}, the request
+     * encoding is used. If that is also not specified, or {@code null},
+     * the platform default encoding is used.
      *
-     * @return The field name for the current {@code encapsulation}.
+     * @return The encoding used to read part headers.
      */
-    public String getFieldName(final FileItemHeaders headers) {
-        return getFieldName(headers.getHeader(CONTENT_DISPOSITION));
+    public String getHeaderEncoding() {
+        return headerEncoding;
     }
 
     /**
-     * Returns the field name, which is given by the content-disposition
-     * header.
-     * @param pContentDisposition The content-dispositions header value.
-     * @return The field jake
+     * Processes an <a href="http://www.ietf.org/rfc/rfc1867.txt">RFC 1867</a>
+     * compliant {@code multipart/form-data} stream.
+     *
+     * @param ctx The context for the request to be parsed.
+     *
+     * @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.
      */
-    private String getFieldName(final String pContentDisposition) {
-        String fieldName = null;
-        if (pContentDisposition != null
-                && pContentDisposition.toLowerCase(Locale.ENGLISH).startsWith(FORM_DATA)) {
-            final ParameterParser parser = new ParameterParser();
-            parser.setLowerCaseNames(true);
-            // Parameter parser can handle null input
-            final Map<String, String> params = parser.parse(pContentDisposition, ';');
-            fieldName = params.get("name");
-            if (fieldName != null) {
-                fieldName = fieldName.trim();
-            }
+    public FileItemIterator getItemIterator(final RequestContext ctx)
+    throws FileUploadException, IOException {
+        try {
+            return new FileItemIteratorImpl(this, ctx);
+        } catch (final FileUploadIOException e) {
+            // unwrap encapsulated SizeException
+            throw (FileUploadException) e.getCause();
         }
-        return fieldName;
     }
 
     /**
@@ -464,6 +340,29 @@ public abstract class FileUploadBase {
         return headers;
     }
 
+    /**
+     * Returns the progress listener.
+     *
+     * @return The progress listener, if any, or null.
+     */
+    public ProgressListener getProgressListener() {
+        return listener;
+    }
+
+    /**
+     * 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 of {@link FileItemHeaders}.
      * @return The new instance.
@@ -483,12 +382,12 @@ public abstract class FileUploadBase {
     private int parseEndOfLine(final String headerPart, final int end) {
         int index = end;
         for (;;) {
-            final int offset = headerPart.indexOf('\r', index);
+            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) == '\n') {
+            if (headerPart.charAt(offset + 1) == LF) {
                 return offset;
             }
             index = offset + 1;
@@ -512,21 +411,126 @@ public abstract class FileUploadBase {
     }
 
     /**
-     * Returns the progress listener.
+     * Processes an <a href="http://www.ietf.org/rfc/rfc1867.txt">RFC 1867</a>
+     * compliant {@code multipart/form-data} stream.
      *
-     * @return The progress listener, if any, or null.
+     * @param ctx The context for the request to be parsed.
+     *
+     * @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.
      */
-    public ProgressListener getProgressListener() {
-        return listener;
+    public List<FileItem> parseRequest(final RequestContext ctx) throws FileUploadException {
+        final List<FileItem> items = new ArrayList<>();
+        boolean successful = false;
+        try {
+            final FileItemIterator iter = getItemIterator(ctx);
+            final FileItemFactory fileItemFactory = Objects.requireNonNull(getFileItemFactory(),
+                    "No FileItemFactory has been set.");
+            final byte[] buffer = new byte[Streams.DEFAULT_BUFFER_SIZE];
+            while (iter.hasNext()) {
+                if (items.size() == fileCountMax) {
+                    // The next item will exceed the limit.
+                    throw new FileCountLimitExceededException(ATTACHMENT, getFileCountMax());
+                }
+                final FileItemStream item = iter.next();
+                // Don't use getName() here to prevent an InvalidFileNameException.
+                final String fileName = item.getName();
+                final FileItem fileItem = fileItemFactory.createItem(item.getFieldName(), item.getContentType(),
+                                                   item.isFormField(), fileName);
+                items.add(fileItem);
+                try {
+                    Streams.copy(item.openStream(), fileItem.getOutputStream(), true, buffer);
+                } catch (final FileUploadIOException e) {
+                    throw (FileUploadException) e.getCause();
+                } catch (final IOException e) {
+                    throw new IOFileUploadException(String.format("Processing of %s request failed. %s",
+                                                           MULTIPART_FORM_DATA, e.getMessage()), e);
+                }
+                final FileItemHeaders fih = item.getHeaders();
+                fileItem.setHeaders(fih);
+            }
+            successful = true;
+            return items;
+        } catch (final FileUploadException e) {
+            throw e;
+        } catch (final IOException e) {
+            throw new FileUploadException(e.getMessage(), e);
+        } finally {
+            if (!successful) {
+                for (final FileItem fileItem : items) {
+                    try {
+                        fileItem.delete();
+                    } catch (final Exception ignored) {
+                        // ignored TODO perhaps add to tracker delete failure list somehow?
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Sets the maximum number of files allowed per request.
+     *
+     * @param fileCountMax The new limit. {@code -1} means no limit.
+     */
+    public void setFileCountMax(final long fileCountMax) {
+        this.fileCountMax = fileCountMax;
+    }
+
+    /**
+     * 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);
+
+    /**
+     * 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(final long fileSizeMax) {
+        this.fileSizeMax = fileSizeMax;
+    }
+
+    /**
+     * Specifies the character encoding to be used when reading the headers of
+     * individual part. When not specified, or {@code null}, the request
+     * encoding is used. If that is also not specified, or {@code null},
+     * the platform default encoding is used.
+     *
+     * @param encoding The encoding used to read part headers.
+     */
+    public void setHeaderEncoding(final String encoding) {
+        headerEncoding = encoding;
     }
 
     /**
      * Sets the progress listener.
      *
-     * @param pListener The progress listener, if any. Defaults to null.
+     * @param listener The progress listener, if any. Defaults to null.
      */
-    public void setProgressListener(final ProgressListener pListener) {
-        listener = pListener;
+    public void setProgressListener(final ProgressListener listener) {
+        this.listener = listener;
+    }
+
+    /**
+     * 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(final long sizeMax) {
+        this.sizeMax = sizeMax;
     }
 
 }
diff --git a/java/org/apache/tomcat/util/http/fileupload/FileUploadException.java b/java/org/apache/tomcat/util/http/fileupload/FileUploadException.java
index d7a8d11819..ea4226e140 100644
--- a/java/org/apache/tomcat/util/http/fileupload/FileUploadException.java
+++ b/java/org/apache/tomcat/util/http/fileupload/FileUploadException.java
@@ -36,20 +36,20 @@ public class FileUploadException extends IOException {
      * Constructs a new {@code FileUploadException} with specified detail
      * message.
      *
-     * @param msg the error message.
+     * @param message the error message.
      */
-    public FileUploadException(final String msg) {
-        super(msg);
+    public FileUploadException(final String message) {
+        super(message);
     }
 
     /**
      * 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(final String msg, final Throwable cause) {
-        super(msg, cause);
+    public FileUploadException(final String message, final Throwable cause) {
+        super(message, cause);
     }
 }
diff --git a/java/org/apache/tomcat/util/http/fileupload/InvalidFileNameException.java b/java/org/apache/tomcat/util/http/fileupload/InvalidFileNameException.java
index 5018620fac..ecc1dce896 100644
--- a/java/org/apache/tomcat/util/http/fileupload/InvalidFileNameException.java
+++ b/java/org/apache/tomcat/util/http/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(final String pName, final String pMessage) {
-        super(pMessage);
-        name = pName;
+    public InvalidFileNameException(final String name, final String message) {
+        super(message);
+        this.name = name;
     }
 
     /**
diff --git a/java/org/apache/tomcat/util/http/fileupload/MultipartStream.java b/java/org/apache/tomcat/util/http/fileupload/MultipartStream.java
index 44e52563e2..97c412da66 100644
--- a/java/org/apache/tomcat/util/http/fileupload/MultipartStream.java
+++ b/java/org/apache/tomcat/util/http/fileupload/MultipartStream.java
@@ -27,7 +27,7 @@ import org.apache.tomcat.util.http.fileupload.util.Closeable;
 import org.apache.tomcat.util.http.fileupload.util.Streams;
 
 /**
- * <p> Low level API for processing file uploads.
+ * Low level API for processing file uploads.
  *
  * <p> This class can be used to process data streams conforming to MIME
  * 'multipart' format as defined in
@@ -58,7 +58,9 @@ import org.apache.tomcat.util.http.fileupload.util.Streams;
  * boundary token of the same length as the parent stream (see {@link
  * #setBoundary(byte[])}).
  *
- * <p>Here is an example of usage of this class.<br>
+ * <p>
+ * Here is an example of usage of this class.
+ * </p>
  *
  * <pre>
  *   try {
@@ -82,145 +84,473 @@ import org.apache.tomcat.util.http.fileupload.util.Streams;
 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.
+         */
+        private int pos;
+
+        /**
+         * Whether the stream is already closed.
+         */
+        private boolean closed;
+
+        /**
+         * Creates a new instance.
          */
-        public ProgressNotifier(final ProgressListener pListener, final long pContentLength) {
-            listener = pListener;
-            contentLength = pContentLength;
+        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(final 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.
          */
-        public 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
-
-    /**
-     * 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.).
-     */
-    public static final int HEADER_PART_SIZE_MAX = 10240;
+        /**
+         * 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 default length of the buffer used for processing a request.
-     */
-    protected static final int DEFAULT_BUFSIZE = 4096;
+        /**
+         * 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;
+        }
 
-    /**
-     * A byte sequence that marks the end of {@code header-part}
-     * ({@code CRLFCRLF}).
-     */
-    protected static final byte[] HEADER_SEPARATOR = {CR, LF, CR, LF};
+        /**
+         * Returns, whether the stream is closed.
+         *
+         * @return True, if the stream is closed, otherwise false.
+         */
+        @Override
+        public boolean isClosed() {
+            return closed;
+        }
 
-    /**
-     * A byte sequence that follows a delimiter that will be
-     * followed by an encapsulation ({@code CRLF}).
-     */
-    protected static final byte[] FIELD_SEPARATOR = {CR, LF};
+        /**
+         * 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;
+            }
 
-    /**
-     * A byte sequence that follows a delimiter of the last
-     * encapsulation in the stream ({@code --}).
-     */
-    protected static final byte[] STREAM_TERMINATOR = {DASH, DASH};
+            // Move the data to the beginning of the buffer.
+            total += tail - head - pad;
+            System.arraycopy(buffer, tail - pad, buffer, 0, pad);
 
-    /**
-     * A byte sequence that precedes a boundary ({@code CRLF--}).
-     */
-    protected static final byte[] BOUNDARY_PREFIX = {CR, LF, DASH, DASH};
+            // Refill buffer with new data.
+            head = 0;
+            tail = pad;
 
-    // ----------------------------------------------------------- Data members
+            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;
 
-    /**
-     * The input stream from which data is read.
-     */
-    private final InputStream input;
+                findSeparator();
+                final int av = available();
 
-    /**
-     * The length of the boundary token plus the leading {@code CRLF--}.
-     */
-    private int boundaryLength;
+                if (av > 0 || pos != -1) {
+                    return av;
+                }
+            }
+        }
 
-    /**
-     * The amount of data, in bytes, that must be kept in the buffer in order
-     * to detect delimiters reliably.
-     */
-    private final int keepRegion;
+        /**
+         * 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;
+        }
 
-    /**
+        /**
+         * 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.
+         */
+        public 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.
+         */
+        public 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.).
+     */
+    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 {@code CRLF--}.
+     */
+    private int boundaryLength;
+
+    /**
+     * The amount of data, in bytes, that must be kept in the buffer in order
+     * to detect delimiters reliably.
+     */
+    private final int keepRegion;
+
+    /**
      * The byte sequence that partitions the stream.
      */
     private final byte[] boundary;
@@ -264,31 +594,23 @@ public class MultipartStream {
      */
     private final ProgressNotifier notifier;
 
-    // ----------------------------------------------------------- Constructors
-
     /**
-     * <p> Constructs a {@code MultipartStream} with a custom size buffer.
-     *
-     * <p> 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 {@code MultipartStream} with a custom size buffer.
+     * <p>
+     * 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 {@code InputStream} to serve as a data source.
-     * @param boundary The token used for dividing the stream into
-     *                 {@code encapsulations}.
+     * @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 FileUpload 1.3.1
      */
-    public MultipartStream(final InputStream input,
-            final byte[] boundary,
-            final int bufSize,
-            final 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");
@@ -297,93 +619,138 @@ public class MultipartStream {
         // 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;
     }
 
     /**
-     * <p> Constructs a {@code MultipartStream} with a default size buffer.
+     * Constructs a {@code MultipartStream} with a default size buffer.
      *
      * @param input    The {@code InputStream} to serve as a data source.
      * @param boundary The token used for dividing the stream into
      *                 {@code encapsulations}.
-     * @param pNotifier An object for calling the progress listener, if any.
+     * @param notifier An object for calling the progress listener, if any.
      *
      *
      * @see #MultipartStream(InputStream, byte[], int, ProgressNotifier)
      */
-    public MultipartStream(final InputStream input,
-            final byte[] boundary,
-            final ProgressNotifier pNotifier) {
-        this(input, boundary, DEFAULT_BUFSIZE, pNotifier);
+    public MultipartStream(final InputStream input, final byte[] boundary, final ProgressNotifier notifier) {
+        this(input, boundary, DEFAULT_BUFSIZE, notifier);
     }
 
-    // --------------------------------------------------------- Public methods
-
     /**
-     * Retrieves 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 The encoding used to read part headers.
+     * Compute the table used for Knuth-Morris-Pratt search algorithm.
      */
-    public String getHeaderEncoding() {
-        return headerEncoding;
-    }
+    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++;
+            }
+        }
+    }
 
     /**
-     * Specifies the character encoding to be used when reading the headers of
-     * individual parts. When not specified, or {@code null}, the platform
-     * default encoding is used.
+     * Reads {@code body-data} from the current {@code encapsulation} and discards it.
+     * <p>
+     * Use this method to skip encapsulations you don't need or don't understand.
      *
-     * @param encoding The encoding used to read part headers.
+     * @return The amount of data discarded.
+     *
+     * @throws MalformedStreamException if the stream ends unexpectedly.
+     * @throws IOException              if an i/o error occurs.
      */
-    public void setHeaderEncoding(final String encoding) {
-        headerEncoding = encoding;
+    public int discardBodyData() throws MalformedStreamException, IOException {
+        return readBodyData(null);
     }
 
     /**
-     * Reads a byte from the {@code 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;
+    }
+
+    /**
+     * 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 The encoding used to read part headers.
+     */
+    public String getHeaderEncoding() {
+        return headerEncoding;
+    }
+
+    /**
+     * Creates a new {@link ItemInputStream}.
+     * @return A new instance of {@link ItemInputStream}.
+     */
+    public ItemInputStream newInputStream() {
+        return new ItemInputStream();
+    }
+
+    /**
+     * Reads {@code body-data} from the current {@code encapsulation} and writes its contents into the output
+     * {@code Stream}.
+     * <p>
+     * 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}).
+     *
+     * @param output The {@code Stream} to write data into. May be null, in which case this method is equivalent to
+     *                   {@link #discardBodyData()}.
+     *
+     * @return the amount of data written.
+     *
+     * @throws MalformedStreamException if the stream ends unexpectedly.
+     * @throws IOException              if an i/o error occurs.
+     */
+    public int readBodyData(final OutputStream output)
+            throws MalformedStreamException, IOException {
+        return (int) Streams.copy(newInputStream(), output, false); // Streams.copy closes the input stream
     }
 
     /**
@@ -434,69 +801,37 @@ public class MultipartStream {
     }
 
     /**
-     * <p>Changes the boundary token used for partitioning the stream.
-     *
-     * <p>This method allows single pass processing of nested multipart
-     * streams.
-     *
-     * <p>The boundary token of the nested stream is {@code required}
-     * to be of the same length as the boundary token in parent stream.
-     *
-     * <p>Restoring the parent stream boundary token after processing of a
-     * nested stream is left to the application.
+     * Reads a byte from the {@code buffer}, and refills it as
+     * necessary.
      *
-     * @param boundary The boundary to be used for parsing of the nested
-     *                 stream.
+     * @return The next byte from the input stream.
      *
-     * @throws IllegalBoundaryException if the {@code boundary}
-     *                                  has a different length than the one
-     *                                  being currently parsed.
-     */
-    public void setBoundary(final 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.
+     * @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++];
     }
 
     /**
-     * <p>Reads the {@code header-part} of the current
-     * {@code encapsulation}.
-     *
-     * <p>Headers are returned verbatim to the input stream, including the
-     * trailing {@code CRLF} marker. Parsing is left to the
-     * application.
-     *
-     * <p><strong>TODO</strong> allow limiting maximum header size to
-     * protect against abuse.
+     * Reads the {@code header-part} of the current {@code encapsulation}.
+     * <p>
+     * Headers are returned verbatim to the input stream, including the trailing {@code CRLF} marker. Parsing is left to
+     * the application.
+     * <p>
+     * <strong>TODO</strong> allow limiting maximum header size to protect against abuse.
      *
      * @return The {@code header-part} of the current encapsulation.
      *
@@ -518,7 +853,8 @@ public class MultipartStream {
             } catch (final IOException e) {
                 throw new MalformedStreamException("Stream ended unexpectedly");
             }
-            if (++size > HEADER_PART_SIZE_MAX) {
+            size++;
+            if (size > HEADER_PART_SIZE_MAX) {
                 throw new MalformedStreamException(String.format(
                         "Header section has more than %s bytes (maybe it is not properly terminated)",
                         Integer.valueOf(HEADER_PART_SIZE_MAX)));
@@ -530,7 +866,6 @@ public class MultipartStream {
             }
             baos.write(b);
         }
-
         String headers;
         if (headerEncoding != null) {
             try {
@@ -543,56 +878,41 @@ public class MultipartStream {
         } else {
             headers = baos.toString();
         }
-
         return headers;
     }
 
     /**
-     * <p>Reads {@code body-data} from the current
-     * {@code encapsulation} and writes its contents into the
-     * output {@code Stream}.
+     * Changes the boundary token used for partitioning the stream.
+     * <p>
+     * This method allows single pass processing of nested multipart streams.
+     * <p>
+     * The boundary token of the nested stream is {@code required} to be of the same length as the boundary token in
+     * parent stream.
+     * <p>
+     * Restoring the parent stream boundary token after processing of a nested stream is left to the application.
      *
-     * <p>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}).
+     * @param boundary The boundary to be used for parsing of the nested stream.
      *
-     * @param output The {@code Stream} to write data into. May
-     *               be null, in which case this method is equivalent
-     *               to {@link #discardBodyData()}.
-     *
-     * @return the amount of data written.
-     *
-     * @throws MalformedStreamException if the stream ends unexpectedly.
-     * @throws IOException              if an i/o error occurs.
-     */
-    public int readBodyData(final OutputStream output)
-            throws MalformedStreamException, IOException {
-        return (int) Streams.copy(newInputStream(), output, false); // N.B. Streams.copy closes the input stream
-    }
-
-    /**
-     * Creates a new {@link ItemInputStream}.
-     * @return A new instance of {@link ItemInputStream}.
+     * @throws IllegalBoundaryException if the {@code boundary} has a different length than the one being currently
+     *                                      parsed.
      */
-    public ItemInputStream newInputStream() {
-        return new ItemInputStream();
+    public void setBoundary(final 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();
     }
 
     /**
-     * <p> Reads {@code body-data} from the current
-     * {@code encapsulation} and discards it.
-     *
-     * <p>Use this method to skip encapsulations you don't need or don't
-     * understand.
-     *
-     * @return The amount of data discarded.
+     * Specifies the character encoding to be used when reading the headers of
+     * individual parts. When not specified, or {@code null}, the platform
+     * default encoding is used.
      *
-     * @throws MalformedStreamException if the stream ends unexpectedly.
-     * @throws IOException              if an i/o error occurs.
+     * @param encoding The encoding used to read part headers.
      */
-    public int discardBodyData() throws MalformedStreamException, IOException {
-        return readBodyData(null);
+    public void setHeaderEncoding(final String encoding) {
+        headerEncoding = encoding;
     }
 
     /**
@@ -627,361 +947,4 @@ public class MultipartStream {
         }
     }
 
-    /**
-     * 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;
-    }
-
-    /**
-     * Searches for the {@code boundary} in the {@code buffer}
-     * region delimited by {@code head} and {@code tail}.
-     *
-     * @return The position of the boundary found, counting from the
-     *         beginning of the {@code buffer}, or {@code -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 {@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);
-        }
-
-    }
-
-    /**
-     * 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 {@code IllegalBoundaryException} with no
-         * detail message.
-         */
-        public IllegalBoundaryException() {
-        }
-
-        /**
-         * Constructs an {@code IllegalBoundaryException} with
-         * the specified detail message.
-         *
-         * @param message The detail message.
-         */
-        public IllegalBoundaryException(final 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;
-            final 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(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;
-        }
-
-        /**
-         * 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(final 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(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;
-        }
-
-        /**
-         * 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 (;;) {
-                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;
-
-                findSeparator();
-                final 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/java/org/apache/tomcat/util/http/fileupload/ParameterParser.java b/java/org/apache/tomcat/util/http/fileupload/ParameterParser.java
index 91a97aeae0..4404d9f719 100644
--- a/java/org/apache/tomcat/util/http/fileupload/ParameterParser.java
+++ b/java/org/apache/tomcat/util/http/fileupload/ParameterParser.java
@@ -40,32 +40,32 @@ public class ParameterParser {
     /**
      * String to be parsed.
      */
-    private char[] chars = null;
+    private char[] chars;
 
     /**
      * Current position in the string.
      */
-    private int pos = 0;
+    private int pos;
 
     /**
      * Maximum position in the string.
      */
-    private int len = 0;
+    private int len;
 
     /**
      * Start of a token.
      */
-    private int i1 = 0;
+    private int i1;
 
     /**
      * End of a token.
      */
-    private int i2 = 0;
+    private int i2;
 
     /**
      * Whether names stored in the map should be converted to lower case.
      */
-    private boolean lowerCaseNames = false;
+    private boolean lowerCaseNames;
 
     /**
      * Default ParameterParser constructor.
@@ -73,16 +73,6 @@ public class ParameterParser {
     public ParameterParser() {
     }
 
-    /**
-     * Are there any characters left to parse?
-     *
-     * @return {@code true} if there are unparsed characters,
-     *         {@code false} otherwise.
-     */
-    private boolean hasChar() {
-        return this.pos < this.len;
-    }
-
     /**
      * A helper method to process the parsed token. This method removes
      * leading and trailing blanks as well as enclosing quotation marks,
@@ -94,18 +84,18 @@ public class ParameterParser {
      */
     private String getToken(final boolean quoted) {
         // Trim leading white spaces
-        while ((i1 < i2) && (Character.isWhitespace(chars[i1]))) {
+        while (i1 < i2 && Character.isWhitespace(chars[i1])) {
             i1++;
         }
         // Trim trailing white spaces
-        while ((i2 > i1) && (Character.isWhitespace(chars[i2 - 1]))) {
+        while (i2 > i1 && Character.isWhitespace(chars[i2 - 1])) {
             i2--;
         }
         // Strip away quotation marks if necessary
         if (quoted
-            && ((i2 - i1) >= 2)
-            && (chars[i1] == '"')
-            && (chars[i2 - 1] == '"')) {
+            && i2 - i1 >= 2
+            && chars[i1] == '"'
+            && chars[i2 - 1] == '"') {
             i1++;
             i2--;
         }
@@ -116,6 +106,28 @@ public class ParameterParser {
         return result;
     }
 
+    /**
+     * Are there any characters left to parse?
+     *
+     * @return {@code true} if there are unparsed characters,
+     *         {@code false} otherwise.
+     */
+    private boolean hasChar() {
+        return pos < len;
+    }
+
+    /**
+     * Returns {@code true} if parameter names are to be converted to lower
+     * case when name/value pairs are parsed.
+     *
+     * @return {@code true} if parameter names are to be
+     * converted to lower case when name/value pairs are parsed.
+     * Otherwise returns {@code false}
+     */
+    public boolean isLowerCaseNames() {
+        return lowerCaseNames;
+    }
+
     /**
      * Tests if the given character is present in the array of characters.
      *
@@ -137,83 +149,83 @@ public class ParameterParser {
     }
 
     /**
-     * Parses out a token until any of the given terminators
-     * is encountered.
+     * Extracts a map of name/value pairs from the given array of
+     * characters. Names are expected to be unique.
      *
-     * @param terminators the array of terminating characters. Any of these
-     * characters when encountered signify the end of the token
+     * @param charArray the array of characters that contains a sequence of
+     * name/value pairs
+     * @param separator the name/value pairs separator
      *
-     * @return the token
+     * @return a map of name/value pairs
      */
-    private String parseToken(final char[] terminators) {
-        char ch;
-        i1 = pos;
-        i2 = pos;
-        while (hasChar()) {
-            ch = chars[pos];
-            if (isOneOf(ch, terminators)) {
-                break;
-            }
-            i2++;
-            pos++;
+    public Map<String, String> parse(final char[] charArray, final char separator) {
+        if (charArray == null) {
+            return new HashMap<>();
         }
-        return getToken(false);
+        return parse(charArray, 0, charArray.length, separator);
     }
 
     /**
-     * Parses out a token until any of the given terminators
-     * is encountered outside the quotation marks.
+     * Extracts a map of name/value pairs from the given array of characters. Names are expected to be unique.
      *
-     * @param terminators the array of terminating characters. Any of these
-     * characters when encountered outside the quotation marks signify the end
-     * of the token
+     * @param charArray the array of characters that contains a sequence of name/value pairs
+     * @param offset    the initial offset.
+     * @param length    the length.
+     * @param separator the name/value pairs separator
      *
-     * @return the token
+     * @return a map of name/value pairs
      */
-    private String parseQuotedToken(final char[] terminators) {
-        char ch;
-        i1 = pos;
-        i2 = pos;
-        boolean quoted = false;
-        boolean charEscaped = false;
+    public Map<String, String> parse(final char[] charArray, final int offset, final int length, final char separator) {
+        if (charArray == null) {
+            return new HashMap<>();
+        }
+        final HashMap<String, String> params = new HashMap<>();
+        chars = charArray.clone();
+        pos = offset;
+        len = length;
         while (hasChar()) {
-            ch = chars[pos];
-            if (!quoted && isOneOf(ch, terminators)) {
-                break;
+            String paramName = parseToken(new char[] { '=', separator });
+            String paramValue = null;
+            if (hasChar() && charArray[pos] == '=') {
+                pos++; // skip '='
+                paramValue = parseQuotedToken(new char[] { separator });
+                if (paramValue != null) {
+                    try {
+                        paramValue = RFC2231Utility.hasEncodedValue(paramName) ? RFC2231Utility.decodeText(paramValue)
+                                : MimeUtility.decodeText(paramValue);
+                    } catch (final UnsupportedEncodingException e) {
+                        // let's keep the original value in this case
+                    }
+                }
             }
-            if (!charEscaped && ch == '"') {
-                quoted = !quoted;
+            if (hasChar() && charArray[pos] == separator) {
+                pos++; // skip separator
+            }
+            if (paramName != null && !paramName.isEmpty()) {
+                paramName = RFC2231Utility.stripDelimiter(paramName);
+                if (lowerCaseNames) {
+                    paramName = paramName.toLowerCase(Locale.ROOT);
+                }
+                params.put(paramName, paramValue);
             }
-            charEscaped = (!charEscaped && ch == '\\');
-            i2++;
-            pos++;
-
         }
-        return getToken(true);
+        return params;
     }
 
     /**
-     * Returns {@code true} if parameter names are to be converted to lower
-     * case when name/value pairs are parsed.
+     * Extracts a map of name/value pairs from the given string. Names are
+     * expected to be unique.
      *
-     * @return {@code true} if parameter names are to be
-     * converted to lower case when name/value pairs are parsed.
-     * Otherwise returns {@code false}
-     */
-    public boolean isLowerCaseNames() {
-        return this.lowerCaseNames;
-    }
-
-    /**
-     * Sets the flag if parameter names are to be converted to lower case when
-     * name/value pairs are parsed.
+     * @param str the string that contains a sequence of name/value pairs
+     * @param separator the name/value pairs separator
      *
-     * @param b {@code true} if parameter names are to be
-     * converted to lower case when name/value pairs are parsed.
-     * {@code false} otherwise.
+     * @return a map of name/value pairs
      */
-    public void setLowerCaseNames(final boolean b) {
-        this.lowerCaseNames = b;
+    public Map<String, String> parse(final String str, final char separator) {
+        if (str == null) {
+            return new HashMap<>();
+        }
+        return parse(str.toCharArray(), separator);
     }
 
     /**
@@ -245,96 +257,71 @@ public class ParameterParser {
     }
 
     /**
-     * Extracts a map of name/value pairs from the given string. Names are
-     * expected to be unique.
+     * Parses out a token until any of the given terminators
+     * is encountered outside the quotation marks.
      *
-     * @param str the string that contains a sequence of name/value pairs
-     * @param separator the name/value pairs separator
+     * @param terminators the array of terminating characters. Any of these
+     * characters when encountered outside the quotation marks signify the end
+     * of the token
      *
-     * @return a map of name/value pairs
+     * @return the token
      */
-    public Map<String, String> parse(final String str, final char separator) {
-        if (str == null) {
-            return new HashMap<>();
+    private String parseQuotedToken(final char[] terminators) {
+        char ch;
+        i1 = pos;
+        i2 = pos;
+        boolean quoted = false;
+        boolean charEscaped = false;
+        while (hasChar()) {
+            ch = chars[pos];
+            if (!quoted && isOneOf(ch, terminators)) {
+                break;
+            }
+            if (!charEscaped && ch == '"') {
+                quoted = !quoted;
+            }
+            charEscaped = !charEscaped && ch == '\\';
+            i2++;
+            pos++;
+
         }
-        return parse(str.toCharArray(), separator);
+        return getToken(true);
     }
 
     /**
-     * Extracts a map of name/value pairs from the given array of
-     * characters. Names are expected to be unique.
+     * Parses out a token until any of the given terminators
+     * is encountered.
      *
-     * @param charArray the array of characters that contains a sequence of
-     * name/value pairs
-     * @param separator the name/value pairs separator
+     * @param terminators the array of terminating characters. Any of these
+     * characters when encountered signify the end of the token
      *
-     * @return a map of name/value pairs
+     * @return the token
      */
-    public Map<String, String> parse(final char[] charArray, final char separator) {
-        if (charArray == null) {
-            return new HashMap<>();
+    private String parseToken(final char[] terminators) {
+        char ch;
+        i1 = pos;
+        i2 = pos;
+        while (hasChar()) {
+            ch = chars[pos];
+            if (isOneOf(ch, terminators)) {
+                break;
+            }
+            i2++;
+            pos++;
         }
-        return parse(charArray, 0, charArray.length, separator);
+        return getToken(false);
     }
 
     /**
-     * Extracts a map of name/value pairs from the given array of
-     * characters. Names are expected to be unique.
-     *
-     * @param charArray the array of characters that contains a sequence of
-     * name/value pairs
-     * @param offset - the initial offset.
-     * @param length - the length.
-     * @param separator the name/value pairs separator
+     * Sets the flag if parameter names are to be converted to lower case when
+     * name/value pairs are parsed.
      *
-     * @return a map of name/value pairs
+     * @param b {@code true} if parameter names are to be
+     * converted to lower case when name/value pairs are parsed.
+     * {@code false} otherwise.
      */
-    public Map<String, String> parse(
-        final char[] charArray,
-        final int offset,
-        final int length,
-        final char separator) {
-
-        if (charArray == null) {
-            return new HashMap<>();
-        }
-        final HashMap<String, String> params = new HashMap<>();
-        this.chars = charArray.clone();
-        this.pos = offset;
-        this.len = length;
-
-        String paramName;
-        String paramValue;
-        while (hasChar()) {
-            paramName = parseToken(new char[] {
-                    '=', separator });
-            paramValue = null;
-            if (hasChar() && (charArray[pos] == '=')) {
-                pos++; // skip '='
-                paramValue = parseQuotedToken(new char[] {
-                        separator });
-
-                if (paramValue != null) {
-                    try {
-                        paramValue = RFC2231Utility.hasEncodedValue(paramName) ? RFC2231Utility.decodeText(paramValue)
-                                : MimeUtility.decodeText(paramValue);
-                    } catch (final UnsupportedEncodingException e) {
-                        // let's keep the original value in this case
-                    }
-                }
-            }
-            if (hasChar() && (charArray[pos] == separator)) {
-                pos++; // skip separator
-            }
-            if ((paramName != null) && !paramName.isEmpty()) {
-                paramName = RFC2231Utility.stripDelimiter(paramName);
-                if (this.lowerCaseNames) {
-                    paramName = paramName.toLowerCase(Locale.ENGLISH);
-                }
-                params.put(paramName, paramValue);
-            }
-        }
-        return params;
+    public void setLowerCaseNames(final boolean b) {
+        lowerCaseNames = b;
     }
 
 }
diff --git a/java/org/apache/tomcat/util/http/fileupload/ProgressListener.java b/java/org/apache/tomcat/util/http/fileupload/ProgressListener.java
index 931c99d1d8..c7f49c9095 100644
--- a/java/org/apache/tomcat/util/http/fileupload/ProgressListener.java
+++ b/java/org/apache/tomcat/util/http/fileupload/ProgressListener.java
@@ -25,13 +25,13 @@ public interface ProgressListener {
     /**
      * Updates the listeners status information.
      *
-     * @param pBytesRead The total number of bytes, which have been read
+     * @param bytesRead The total number of bytes, which have been read
      *   so far.
-     * @param pContentLength The total number of bytes, which are being
+     * @param contentLength The total number of bytes, which are being
      *   read. May be -1, if this number is unknown.
-     * @param pItems The number of the field, which is currently being
+     * @param items The number of the field, which is currently being
      *   read. (0 = no item so far, 1 = first item is being read, ...)
      */
-    void update(long pBytesRead, long pContentLength, int pItems);
+    void update(long bytesRead, long contentLength, int items);
 
 }
diff --git a/java/org/apache/tomcat/util/http/fileupload/disk/DiskFileItem.java b/java/org/apache/tomcat/util/http/fileupload/disk/DiskFileItem.java
index e35c1ea237..22fe3c18ab 100644
--- a/java/org/apache/tomcat/util/http/fileupload/disk/DiskFileItem.java
+++ b/java/org/apache/tomcat/util/http/fileupload/disk/DiskFileItem.java
@@ -37,259 +37,175 @@ import org.apache.tomcat.util.http.fileupload.FileItem;
 import org.apache.tomcat.util.http.fileupload.FileItemHeaders;
 import org.apache.tomcat.util.http.fileupload.FileUploadException;
 import org.apache.tomcat.util.http.fileupload.IOUtils;
+import org.apache.tomcat.util.http.fileupload.InvalidFileNameException;
 import org.apache.tomcat.util.http.fileupload.ParameterParser;
 import org.apache.tomcat.util.http.fileupload.util.Streams;
 
 /**
- * <p> The default implementation of the
- * {@link org.apache.tomcat.util.http.fileupload.FileItem FileItem} interface.
- *
- * <p> After retrieving an instance of this class from a {@link
- * org.apache.tomcat.util.http.fileupload.FileUpload FileUpload} instance (see
+ * The default implementation of the {@link org.apache.tomcat.util.http.fileupload.FileItem FileItem} interface.
+ * <p>
+ * After retrieving an instance of this class from a {@link DiskFileItemFactory} instance (see
  * {@link org.apache.tomcat.util.http.fileupload.FileUpload
- * #parseRequest(org.apache.tomcat.util.http.fileupload.RequestContext)}), 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.
- *
- * <p>Temporary files, which are created for file items, will be deleted when
- * the associated request is recycled.</p>
+ * #parseRequest(org.apache.tomcat.util.http.fileupload.RequestContext)}), 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.
+ * <p>
+ * Temporary files, which are created for file items, will be deleted when the associated request is recycled.
+ * </p>
  *
  * @since FileUpload 1.1
  */
-public class DiskFileItem
-    implements FileItem {
+public class DiskFileItem implements FileItem {
 
-    // ----------------------------------------------------- Manifest constants
+    /**
+     * Counter used in unique identifier generation.
+     */
+    private static final AtomicInteger COUNTER = new AtomicInteger(0);
 
     /**
-     * Default content charset to be used when no explicit charset
-     * parameter is provided by the sender. Media subtypes of the
-     * "text" type are defined to have a default charset value of
-     * "ISO-8859-1" when received via HTTP.
+     * Default content charset to be used when no explicit charset parameter is provided by the sender. Media subtypes
+     * of the "text" type are defined to have a default charset value of "ISO-8859-1" when received via HTTP.
      */
     public static final String DEFAULT_CHARSET = "ISO-8859-1";
 
-    // ----------------------------------------------------------- Data members
-
     /**
      * UID used in unique file name generation.
      */
-    private static final String UID =
-            UUID.randomUUID().toString().replace('-', '_');
+    private static final String UID = UUID.randomUUID().toString().replace('-', '_');
 
     /**
-     * Counter used in unique identifier generation.
+     * Returns an identifier that is unique within the class loader used to load this class, but does not have
+     * random-like appearance.
+     *
+     * @return A String with the non-random looking instance identifier.
      */
-    private static final AtomicInteger COUNTER = new AtomicInteger(0);
+    private static String getUniqueId() {
+        final int limit = 100000000;
+        final int current = COUNTER.getAndIncrement();
+        String id = Integer.toString(current);
+
+        // 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());
+        }
+        return id;
+    }
 
     /**
-     * The name of the form field as provided by the browser.
+     * Cached contents of the file.
      */
-    private String fieldName;
+    private byte[] cachedContent;
 
     /**
-     * The content type passed by the browser, or {@code 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 file name in the user's file system.
+     * 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 {@code DiskFileItem} instance.
      *
      * @param fieldName     The name of the form field.
-     * @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) {
+     * @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 Files.newInputStream(dfos.getFile().toPath());
-        }
-
-        if (cachedContent == null) {
-            cachedContent = dfos.getData();
-        }
-        return new ByteArrayInputStream(cachedContent);
-    }
-
-    /**
-     * Returns the content type passed by the agent or {@code null} if
-     * not defined.
-     *
-     * @return The content type passed by the agent or {@code null} if
-     *         not defined.
-     */
-    @Override
-    public String getContentType() {
-        return contentType;
-    }
-
-    /**
-     * Returns the content charset passed by the agent or {@code null} if
-     * not defined.
-     *
-     * @return The content charset passed by the agent or {@code null} if
-     *         not defined.
-     */
-    public String getCharSet() {
-        final ParameterParser parser = new ParameterParser();
-        parser.setLowerCaseNames(true);
-        // Parameter parser can handle null input
-        final Map<String, String> params = parser.parse(getContentType(), ';');
-        return params.get("charset");
-    }
-
-    /**
-     * Returns the original file name in the client's file system.
-     *
-     * @return The original file name in the client's file system.
-     * @throws org.apache.tomcat.util.http.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.tomcat.util.http.fileupload.InvalidFileNameException#getName()}.
-     */
-    @Override
-    public String getName() {
-        return Streams.checkFileName(fileName);
-    }
-
-    // ------------------------------------------------------- FileItem methods
-
     /**
-     * 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.
+     * Clears the cache.
      */
-    @Override
-    public boolean isInMemory() {
-        if (cachedContent != null) {
-            return true;
-        }
-        return dfos.isInMemory();
+    private void clear() {
+        cachedContent = null; // NOPMD
     }
 
     /**
-     * Returns the size of the file.
-     *
-     * @return The size of the file, in bytes.
+     * Deletes the underlying storage for a file item, including deleting any associated temporary disk file. This
+     * method can be used to ensure that this is done at an earlier time, thus preserving system resources.
      */
     @Override
-    public long getSize() {
-        if (size >= 0) {
-            return size;
-        }
-        if (cachedContent != null) {
-            return cachedContent.length;
-        }
-        if (dfos.isInMemory()) {
-            return dfos.getData().length;
+    public void delete() {
+        clear();
+        final File outputFile = getStoreLocation();
+        if (outputFile != null && !isInMemory() && outputFile.exists()) {
+            if (!outputFile.delete()) {
+                final String desc = "Cannot delete " + outputFile.toString();
+                throw new UncheckedIOException(desc, new IOException(desc));
+            }
         }
-        return dfos.getFile().length();
     }
 
     /**
-     * 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 ArithmeticException if the file {@code size} overflows an int
+     * @throws ArithmeticException  if the file {@code size} overflows an int
      */
     @Override
     public byte[] get() throws UncheckedIOException {
@@ -311,132 +227,43 @@ public class DiskFileItem
     }
 
     /**
-     * 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.
-     *
-     * @param charset The charset to use.
+     * Gets the content charset passed by the agent or {@code null} if not defined.
      *
-     * @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, IOException {
-        return new String(get(), charset);
+    public String getCharSet() {
+        final ParameterParser parser = new ParameterParser();
+        parser.setLowerCaseNames(true);
+        // Parameter parser can handle null input
+        final Map<String,String> params = parser.parse(getContentType(), ';');
+        return params.get("charset");
     }
 
     /**
-     * Returns 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.
-     *
-     * <b>TODO</b> Consider making this method throw UnsupportedEncodingException.
+     * Gets the content type passed by the agent or {@code null} if not defined.
      *
-     * @return The contents of the file, as a string.
+     * @return The content type passed by the agent or {@code null} if not defined.
      */
     @Override
-    public String getString() {
-        try {
-            final byte[] rawData = get();
-            String charset = getCharSet();
-            if (charset == null) {
-                charset = defaultCharset;
-            }
-            return new String(rawData, charset);
-        } catch (final IOException e) {
-            return "";
-        }
+    public String getContentType() {
+        return contentType;
     }
 
     /**
-     * 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.
-     * <p>
-     * 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.
-     * <p>
-     * This method is only guaranteed to work <em>once</em>, 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.
-     *
-     * @param file The {@code File} into which the uploaded item should
-     *             be stored.
+     * Gets the default charset for use when no explicit charset parameter is provided by the sender.
      *
-     * @throws Exception if an error occurs.
-     */
-    @Override
-    public void write(final File file) throws Exception {
-        if (isInMemory()) {
-            try (OutputStream fout = Files.newOutputStream(file.toPath())) {
-                fout.write(get());
-            }
-        } else {
-            final File outputFile = getStoreLocation();
-            if (outputFile == null) {
-                /*
-                 * For whatever reason we cannot write the
-                 * file to disk.
-                 */
-                throw new FileUploadException(
-                    "Cannot write uploaded file to disk!");
-            }
-            // 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.
-             */
-            if (file.exists() && !file.delete()) {
-                throw new FileUploadException("Cannot write uploaded file to disk!");
-            }
-            if (!outputFile.renameTo(file)) {
-                BufferedInputStream in = null;
-                BufferedOutputStream out = null;
-                try {
-                    in = new BufferedInputStream(new FileInputStream(outputFile));
-                    out = new BufferedOutputStream(new FileOutputStream(file));
-                    IOUtils.copy(in, out);
-                    out.close();
-                } finally {
-                    IOUtils.closeQuietly(in);
-                    IOUtils.closeQuietly(out);
-                }
-            }
-        }
-    }
-
-    /**
-     * Deletes the underlying storage for a file item, including deleting any associated temporary disk file.
-     * 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;
-        final File outputFile = getStoreLocation();
-        if (outputFile != null && !isInMemory() && outputFile.exists()) {
-            if (!outputFile.delete()) {
-                final String desc = "Cannot delete " + outputFile.toString();
-                throw new UncheckedIOException(desc, new IOException(desc));
-            }
-        }
+    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(String)
-     *
      */
     @Override
     public String getFieldName() {
@@ -444,55 +271,52 @@ public class DiskFileItem
     }
 
     /**
-     * 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(final String fieldName) {
-        this.fieldName = fieldName;
+    public FileItemHeaders getHeaders() {
+        return headers;
     }
 
     /**
-     * Determines whether or not a {@code FileItem} instance represents
-     * a simple form field.
-     *
-     * @return {@code true} if the instance represents a simple form
-     *         field; {@code 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.getFile().toPath());
+        }
+        if (cachedContent == null) {
+            cachedContent = dfos.getData();
+        }
+        return new ByteArrayInputStream(cachedContent);
     }
 
     /**
-     * Specifies whether or not a {@code FileItem} instance represents
-     * a simple form field.
-     *
-     * @param state {@code true} if the instance represents a simple form
-     *              field; {@code false} if it represents an uploaded file.
+     * Gets the original file name in the client's file system.
      *
-     * @see #isFormField()
+     * @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
+     *                                      {@link org.apache.tomcat.util.http.fileupload.InvalidFileNameException#getName()}.
      */
     @Override
-    public void setFormField(final 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.
-     *
-     * @return 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.
      */
     @Override
     public OutputStream getOutputStream() {
@@ -503,20 +327,32 @@ public class DiskFileItem
         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 {@code FileItem}'s
-     * data's temporary location on the disk. Note that for
-     * {@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.
+     * Gets the {@link java.io.File} object for the {@code FileItem}'s data's temporary location on the disk. Note
+     * that for {@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 {@code 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) {
@@ -528,16 +364,49 @@ public class DiskFileItem
         return dfos.getFile();
     }
 
-    // ------------------------------------------------------ Protected methods
+    /**
+     * 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.
+     * <p>
+     * <strong>TODO</strong> Consider making this method throw UnsupportedEncodingException.
+     *
+     * @return The contents of the file, as a string.
+     */
+    @Override
+    public String getString() {
+        final byte[] rawData = get();
+        String charset = getCharSet();
+        if (charset == null) {
+            charset = defaultCharset;
+        }
+        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 {@code 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.
      * <p>
-     * <b>Note: Subclasses that override this method must ensure that they return the
-     * same File each time.</b>
+     * <b>Note: Subclasses that override this method must ensure that they return the same File each time.</b>
      *
      * @return The {@link java.io.File File} to be used for temporary storage.
      */
@@ -555,71 +424,138 @@ public class DiskFileItem
         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;
-        final 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 String.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(final 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.
+     * <p>
+     * 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.
+     * <p>
+     * This method is only guaranteed to work <em>once</em>, 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.
+     *
+     * @param file The {@code File} into which the uploaded item should be stored.
+     *
+     * @throws Exception if an error occurs.
      */
-    public void setDefaultCharset(final String charset) {
-        defaultCharset = charset;
+    @Override
+    public void write(final File file) throws Exception {
+        if (isInMemory()) {
+            try (OutputStream fout = Files.newOutputStream(file.toPath())) {
+                fout.write(get());
+            }
+        } else {
+            final File outputFile = getStoreLocation();
+            if (outputFile == null) {
+                /*
+                 * For whatever reason we cannot write the file to disk.
+                 */
+                throw new FileUploadException("Cannot write uploaded file to disk!");
+            }
+            // 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.
+             */
+            if (file.exists() && !file.delete()) {
+                throw new FileUploadException("Cannot write uploaded file to disk!");
+            }
+            if (!outputFile.renameTo(file)) {
+                BufferedInputStream in = null;
+                BufferedOutputStream out = null;
+                try {
+                    in = new BufferedInputStream(new FileInputStream(outputFile));
+                    out = new BufferedOutputStream(new FileOutputStream(file));
+                    IOUtils.copy(in, out);
+                    out.close();
+                } finally {
+                    IOUtils.closeQuietly(in);
+                    IOUtils.closeQuietly(out);
+                }
+            }
+        }
     }
 }
diff --git a/java/org/apache/tomcat/util/http/fileupload/disk/DiskFileItemFactory.java b/java/org/apache/tomcat/util/http/fileupload/disk/DiskFileItemFactory.java
index 8b0073da3f..06817f599b 100644
--- a/java/org/apache/tomcat/util/http/fileupload/disk/DiskFileItemFactory.java
+++ b/java/org/apache/tomcat/util/http/fileupload/disk/DiskFileItemFactory.java
@@ -39,7 +39,7 @@ import org.apache.tomcat.util.http.fileupload.FileItemFactory;
  *       {@code System.getProperty("java.io.tmpdir")}.</li>
  * </ul>
  * <p>
- * <b>NOTE</b>: Files are created in the system default temp directory with
+ * <strong>NOTE</strong>: 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
@@ -58,15 +58,11 @@ import org.apache.tomcat.util.http.fileupload.FileItemFactory;
  */
 public class DiskFileItemFactory implements FileItemFactory {
 
-    // ----------------------------------------------------- Manifest constants
-
     /**
      * The default threshold above which uploads will be stored on disk.
      */
     public static final int DEFAULT_SIZE_THRESHOLD = 10240;
 
-    // ----------------------------------------------------- Instance Variables
-
     /**
      * The directory in which uploaded files will be stored, if stored on disk.
      */
@@ -83,8 +79,6 @@ public class DiskFileItemFactory implements FileItemFactory {
      */
     private String defaultCharset = DiskFileItem.DEFAULT_CHARSET;
 
-    // ----------------------------------------------------------- Constructors
-
     /**
      * Constructs an unconfigured instance of this class. The resulting factory
      * may be configured by calling the appropriate setter methods.
@@ -108,36 +102,53 @@ public class DiskFileItemFactory implements FileItemFactory {
         this.repository = repository;
     }
 
-    // ------------------------------------------------------------- Properties
-
     /**
-     * Returns the directory used to temporarily store files that are larger
-     * than the configured size threshold.
-     *
-     * @return The directory in which temporary files will be located.
+     * Create a new {@link DiskFileItem}
+     * instance from the supplied parameters and the local factory
+     * configuration.
      *
-     * @see #setRepository(java.io.File)
+     * @param fieldName   The name of the form field.
+     * @param contentType The content type of the form field.
+     * @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.
      */
-    public File getRepository() {
-        return repository;
+    @Override
+    public FileItem createItem(final String fieldName, final String contentType,
+            final boolean isFormField, final String fileName) {
+        final DiskFileItem result = new DiskFileItem(fieldName, contentType,
+                isFormField, fileName, sizeThreshold, repository);
+        result.setDefaultCharset(defaultCharset);
+        return result;
     }
 
     /**
-     * Sets the directory used to temporarily store files that are larger
+     * Gets the default charset for use when no explicit charset
+     * parameter is provided by the sender.
+     * @return the default charset
+     */
+    public String getDefaultCharset() {
+        return defaultCharset;
+    }
+
+    /**
+     * Gets 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.
+     * @return The directory in which temporary files will be located.
      *
-     * @see #getRepository()
+     * @see #setRepository(java.io.File)
      *
      */
-    public void setRepository(final File repository) {
-        this.repository = repository;
+    public File getRepository() {
+        return repository;
     }
 
     /**
-     * Returns the size threshold beyond which files are written directly to
+     * Gets the size threshold beyond which files are written directly to
      * disk. The default value is 10240 bytes.
      *
      * @return The size threshold, in bytes.
@@ -149,57 +160,37 @@ public class DiskFileItemFactory implements FileItemFactory {
     }
 
     /**
-     * Sets the size threshold beyond which files are written directly to disk.
-     *
-     * @param sizeThreshold The size threshold, in bytes.
-     *
-     * @see #getSizeThreshold()
+     * Sets the default charset for use when no explicit charset
+     * parameter is provided by the sender.
      *
+     * @param charset the default charset
      */
-    public void setSizeThreshold(final int sizeThreshold) {
-        this.sizeThreshold = sizeThreshold;
+    public void setDefaultCharset(final String charset) {
+        this.defaultCharset = charset;
     }
 
-    // --------------------------------------------------------- Public Methods
-
     /**
-     * Create a new {@link DiskFileItem}
-     * instance from the supplied parameters and the local factory
-     * configuration.
+     * Sets the directory used to temporarily store files that are larger
+     * than the configured size threshold.
      *
-     * @param fieldName   The name of the form field.
-     * @param contentType The content type of the form field.
-     * @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.
+     * @param repository The directory in which temporary files will be located.
+     *
+     * @see #getRepository()
      *
-     * @return The newly created file item.
-     */
-    @Override
-    public FileItem createItem(final String fieldName, final String contentType,
-            final boolean isFormField, final String fileName) {
-        final DiskFileItem result = new DiskFileItem(fieldName, contentType,
-                isFormField, fileName, sizeThreshold, repository);
-        result.setDefaultCharset(defaultCharset);
-        return result;
-    }
-
-    /**
-     * Returns the default charset for use when no explicit charset
-     * parameter is provided by the sender.
-     * @return the default charset
      */
-    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(final String pCharset) {
-        defaultCharset = pCharset;
+    public void setSizeThreshold(final int sizeThreshold) {
+        this.sizeThreshold = sizeThreshold;
     }
 }
diff --git a/java/org/apache/tomcat/util/http/fileupload/package-info.java b/java/org/apache/tomcat/util/http/fileupload/package-info.java
index dee2bb19fc..91c25bc5f0 100644
--- a/java/org/apache/tomcat/util/http/fileupload/package-info.java
+++ b/java/org/apache/tomcat/util/http/fileupload/package-info.java
@@ -16,8 +16,8 @@
  */
 
 /**
- * <p><b>NOTE:</b> This code has been copied from commons-fileupload trunk
- * 1.3 and commons-io 1.4 and package renamed to avoid clashes with
+ * <p><b>NOTE:</b> This code has been copied from commons-fileupload
+ * 1.x and commons-io 1.4 and package renamed to avoid clashes with
  * any web apps that may wish to use these libraries.
  * </p>
  * <p>
diff --git a/java/org/apache/tomcat/util/http/fileupload/servlet/ServletRequestContext.java b/java/org/apache/tomcat/util/http/fileupload/servlet/ServletRequestContext.java
index 90f05d1f78..eb6c62a3a1 100644
--- a/java/org/apache/tomcat/util/http/fileupload/servlet/ServletRequestContext.java
+++ b/java/org/apache/tomcat/util/http/fileupload/servlet/ServletRequestContext.java
@@ -32,15 +32,11 @@ import org.apache.tomcat.util.http.fileupload.UploadContext;
  */
 public class ServletRequestContext implements UploadContext {
 
-    // ----------------------------------------------------- Instance Variables
-
     /**
      * The request for which the context is being provided.
      */
     private final HttpServletRequest request;
 
-    // ----------------------------------------------------------- Constructors
-
     /**
      * Construct a context for this request.
      *
@@ -50,7 +46,22 @@ public class ServletRequestContext implements UploadContext {
         this.request = request;
     }
 
-    // --------------------------------------------------------- Public Methods
+    /**
+     * Retrieve the content length of the request.
+     *
+     * @return The content length of the request.
+     * @since FileUpload 1.3
+     */
+    @Override
+    public long contentLength() {
+        long size;
+        try {
+            size = Long.parseLong(request.getHeader(FileUploadBase.CONTENT_LENGTH));
+        } catch (final NumberFormatException e) {
+            size = request.getContentLength();
+        }
+        return size;
+    }
 
     /**
      * Retrieve the character encoding for the request.
@@ -72,23 +83,6 @@ public class ServletRequestContext implements UploadContext {
         return request.getContentType();
     }
 
-    /**
-     * Retrieve the content length of the request.
-     *
-     * @return The content length of the request.
-     * @since FileUpload 1.3
-     */
-    @Override
-    public long contentLength() {
-        long size;
-        try {
-            size = Long.parseLong(request.getHeader(FileUploadBase.CONTENT_LENGTH));
-        } catch (final NumberFormatException e) {
-            size = request.getContentLength();
-        }
-        return size;
-    }
-
     /**
      * Retrieve the input stream for the request.
      *
@@ -109,7 +103,7 @@ public class ServletRequestContext implements UploadContext {
     @Override
     public String toString() {
         return String.format("ContentLength=%s, ContentType=%s",
-                Long.valueOf(this.contentLength()), this.getContentType());
+                Long.valueOf(contentLength()), getContentType());
     }
 
 }
diff --git a/java/org/apache/tomcat/util/http/fileupload/util/FileItemHeadersImpl.java b/java/org/apache/tomcat/util/http/fileupload/util/FileItemHeadersImpl.java
index a70345ff43..d4e20ece7f 100644
--- a/java/org/apache/tomcat/util/http/fileupload/util/FileItemHeadersImpl.java
+++ b/java/org/apache/tomcat/util/http/fileupload/util/FileItemHeadersImpl.java
@@ -45,9 +45,29 @@ public class FileItemHeadersImpl implements FileItemHeaders, Serializable {
      */
     private final Map<String, List<String>> headerNameToValueListMap = new LinkedHashMap<>();
 
+    /**
+     * Constructs a new instance.
+     */
+    public FileItemHeadersImpl() {
+        // empty
+    }
+
+    /**
+     * Method to add header values to this instance.
+     *
+     * @param name name of this header
+     * @param value value of this header
+     */
+    public synchronized void addHeader(final String name, final String value) {
+        final String nameLower = name.toLowerCase(Locale.ROOT);
+        final List<String> headerValueList = headerNameToValueListMap.
+                computeIfAbsent(nameLower, k -> new ArrayList<>());
+        headerValueList.add(value);
+    }
+
     @Override
     public String getHeader(final String name) {
-        final String nameLower = name.toLowerCase(Locale.ENGLISH);
+        final String nameLower = name.toLowerCase(Locale.ROOT);
         final List<String> headerValueList = headerNameToValueListMap.get(nameLower);
         if (null == headerValueList) {
             return null;
@@ -62,7 +82,7 @@ public class FileItemHeadersImpl implements FileItemHeaders, Serializable {
 
     @Override
     public Iterator<String> getHeaders(final String name) {
-        final String nameLower = name.toLowerCase(Locale.ENGLISH);
+        final String nameLower = name.toLowerCase(Locale.ROOT);
         List<String> headerValueList = headerNameToValueListMap.get(nameLower);
         if (null == headerValueList) {
             headerValueList = Collections.emptyList();
@@ -70,17 +90,4 @@ public class FileItemHeadersImpl implements FileItemHeaders, Serializable {
         return headerValueList.iterator();
     }
 
-    /**
-     * Method to add header values to this instance.
-     *
-     * @param name name of this header
-     * @param value value of this header
-     */
-    public synchronized void addHeader(final String name, final String value) {
-        final String nameLower = name.toLowerCase(Locale.ENGLISH);
-        final List<String> headerValueList = headerNameToValueListMap.
-                computeIfAbsent(nameLower, k -> new ArrayList<>());
-        headerValueList.add(value);
-    }
-
 }
diff --git a/java/org/apache/tomcat/util/http/fileupload/util/LimitedInputStream.java b/java/org/apache/tomcat/util/http/fileupload/util/LimitedInputStream.java
index def25ca125..add304c16a 100644
--- a/java/org/apache/tomcat/util/http/fileupload/util/LimitedInputStream.java
+++ b/java/org/apache/tomcat/util/http/fileupload/util/LimitedInputStream.java
@@ -45,26 +45,14 @@ public abstract class LimitedInputStream extends FilterInputStream implements Cl
      * Creates a new instance.
      *
      * @param inputStream The input stream, which shall be limited.
-     * @param pSizeMax The limit; no more than this number of bytes
+     * @param sizeMax The limit; no more than this number of bytes
      *   shall be returned by the source stream.
      */
-    public LimitedInputStream(final InputStream inputStream, final long pSizeMax) {
+    public LimitedInputStream(final InputStream inputStream, final long sizeMax) {
         super(inputStream);
-        sizeMax = pSizeMax;
+        this.sizeMax = sizeMax;
     }
 
-    /**
-     * Called to indicate, that the input streams limit has
-     * been exceeded.
-     *
-     * @param pSizeMax The input streams limit, in bytes.
-     * @param pCount The actual number of bytes.
-     * @throws IOException The called method is expected
-     *   to raise an IOException.
-     */
-    protected abstract void raiseError(long pSizeMax, long pCount)
-            throws IOException;
-
     /**
      * Called to check, whether the input streams
      * limit is reached.
@@ -77,6 +65,43 @@ public abstract class LimitedInputStream extends FilterInputStream implements Cl
         }
     }
 
+    /**
+     * Closes this input stream and releases any system resources
+     * associated with the stream.
+     * This
+     * method simply performs {@code 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();
+    }
+
+    /**
+     * 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;
+    }
+
+    /**
+     * Called to indicate, that the input streams limit has
+     * been exceeded.
+     *
+     * @param sizeMax The input streams limit, in bytes.
+     * @param count The actual number of bytes.
+     * @throws IOException The called method is expected
+     *   to raise an IOException.
+     */
+    protected abstract void raiseError(long sizeMax, long count) throws IOException;
+
     /**
      * Reads the next byte of data from this input stream. The value
      * byte is returned as an {@code int} in the range
@@ -137,30 +162,4 @@ public abstract class LimitedInputStream extends FilterInputStream implements Cl
         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 {@code 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/java/org/apache/tomcat/util/http/fileupload/util/Streams.java b/java/org/apache/tomcat/util/http/fileupload/util/Streams.java
index 7289d06665..ecf82c25d0 100644
--- a/java/org/apache/tomcat/util/http/fileupload/util/Streams.java
+++ b/java/org/apache/tomcat/util/http/fileupload/util/Streams.java
@@ -27,14 +27,6 @@ import org.apache.tomcat.util.http.fileupload.InvalidFileNameException;
  */
 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)}.
@@ -42,10 +34,41 @@ public final class Streams {
     public static final int DEFAULT_BUFFER_SIZE = 8192;
 
     /**
+     * Checks, whether the given file name is valid in the sense,
+     * that it doesn't contain any NUL characters. If the file name
+     * is valid, it will be returned without any modifications. Otherwise,
+     * an {@link InvalidFileNameException} is raised.
+     *
+     * @param fileName The file name to check
+     * @return Unmodified file name, if valid.
+     * @throws InvalidFileNameException The file name was found to be invalid.
+     */
+    public static String checkFileName(final String fileName) {
+        if (fileName != null  &&  fileName.indexOf('\u0000') != -1) {
+            // fileName.replace("\u0000", "\\0")
+            final StringBuilder sb = new StringBuilder();
+            for (int i = 0;  i < fileName.length();  i++) {
+                final char c = fileName.charAt(i);
+                switch (c) {
+                    case 0:
+                        sb.append("\\0");
+                        break;
+                    default:
+                        sb.append(c);
+                        break;
+                }
+            }
+            throw new InvalidFileNameException(fileName,
+                    "Invalid file name: " + sb);
+        }
+        return fileName;
+    }
+
+     /**
      * Copies the contents of the given {@link InputStream}
      * to the given {@link OutputStream}. Shortcut for
      * <pre>
-     *   copy(pInputStream, pOutputStream, new byte[8192]);
+     *   copy(pInputStream, outputStream, new byte[8192]);
      * </pre>
      *
      * @param inputStream The input stream, which is being read.
@@ -116,35 +139,12 @@ public final class Streams {
         }
     }
 
-    /**
-     * Checks, whether the given file name is valid in the sense,
-     * that it doesn't contain any NUL characters. If the file name
-     * is valid, it will be returned without any modifications. Otherwise,
-     * an {@link InvalidFileNameException} is raised.
-     *
-     * @param fileName The file name to check
-     * @return Unmodified file name, if valid.
-     * @throws InvalidFileNameException The file name was found to be invalid.
+   /**
+     * Private constructor, to prevent instantiation.
+     * This class has only static methods.
      */
-    public static String checkFileName(final String fileName) {
-        if (fileName != null  &&  fileName.indexOf('\u0000') != -1) {
-            // pFileName.replace("\u0000", "\\0")
-            final StringBuilder sb = new StringBuilder();
-            for (int i = 0;  i < fileName.length();  i++) {
-                final char c = fileName.charAt(i);
-                switch (c) {
-                    case 0:
-                        sb.append("\\0");
-                        break;
-                    default:
-                        sb.append(c);
-                        break;
-                }
-            }
-            throw new InvalidFileNameException(fileName,
-                    "Invalid file name: " + sb);
-        }
-        return fileName;
+    private Streams() {
+        // Does nothing
     }
 
 }
diff --git a/java/org/apache/tomcat/util/http/fileupload/util/mime/MimeUtility.java b/java/org/apache/tomcat/util/http/fileupload/util/mime/MimeUtility.java
index 7debaf915e..461b00b354 100644
--- a/java/org/apache/tomcat/util/http/fileupload/util/mime/MimeUtility.java
+++ b/java/org/apache/tomcat/util/http/fileupload/util/mime/MimeUtility.java
@@ -75,13 +75,6 @@ public final class MimeUtility {
         MIME2JAVA.put("x-us-ascii", "ISO-8859-1");
     }
 
-    /**
-     * Hidden constructor, this class must not be instantiated.
-     */
-    private MimeUtility() {
-        // do nothing
-    }
-
     /**
      * Decode a string of text obtained from a mail header into
      * its proper form.  The text generally will consist of a
@@ -207,7 +200,7 @@ public final class MimeUtility {
         }
 
         // pull out the character set information (this is the MIME name at this point).
-        final String charset = word.substring(2, charsetPos).toLowerCase(Locale.ENGLISH);
+        final String charset = word.substring(2, charsetPos).toLowerCase(Locale.ROOT);
 
         // now pull out the encoding token the same way.
         final int encodingPos = word.indexOf('?', charsetPos + 1);
@@ -266,7 +259,7 @@ public final class MimeUtility {
             return null;
         }
 
-        final String mappedCharset = MIME2JAVA.get(charset.toLowerCase(Locale.ENGLISH));
+        final String mappedCharset = MIME2JAVA.get(charset.toLowerCase(Locale.ROOT));
         // if there is no mapping, then the original name is used.  Many of the MIME character set
         // names map directly back into Java.  The reverse isn't necessarily true.
         if (mappedCharset == null) {
@@ -275,4 +268,11 @@ public final class MimeUtility {
         return mappedCharset;
     }
 
+    /**
+     * Hidden constructor, this class must not be instantiated.
+     */
+    private MimeUtility() {
+        // do nothing
+    }
+
 }
diff --git a/java/org/apache/tomcat/util/http/fileupload/util/mime/ParseException.java b/java/org/apache/tomcat/util/http/fileupload/util/mime/ParseException.java
index 72e9896fe6..a49fe432a7 100644
--- a/java/org/apache/tomcat/util/http/fileupload/util/mime/ParseException.java
+++ b/java/org/apache/tomcat/util/http/fileupload/util/mime/ParseException.java
@@ -17,6 +17,8 @@
 package org.apache.tomcat.util.http.fileupload.util.mime;
 
 /**
+ * Thrown for a parsing problem.
+ *
  * @since FileUpload 1.3
  */
 final class ParseException extends Exception {
diff --git a/java/org/apache/tomcat/util/http/fileupload/util/mime/QuotedPrintableDecoder.java b/java/org/apache/tomcat/util/http/fileupload/util/mime/QuotedPrintableDecoder.java
index 6a94f3c202..d1caac1a57 100644
--- a/java/org/apache/tomcat/util/http/fileupload/util/mime/QuotedPrintableDecoder.java
+++ b/java/org/apache/tomcat/util/http/fileupload/util/mime/QuotedPrintableDecoder.java
@@ -25,17 +25,35 @@ import java.io.OutputStream;
 final class QuotedPrintableDecoder {
 
     /**
-     * The shift value required to create the upper nibble
-     * from the first of 2 byte values converted from ascii hex.
+     * Carriage return character '{@value}'.
      */
-    private static final int UPPER_NIBBLE_SHIFT = Byte.SIZE / 2;
+    private static final char CR = '\r';
 
     /**
-     * Hidden constructor, this class must not be instantiated.
+     * Equal character '{@value}'.
      */
-    private QuotedPrintableDecoder() {
-        // do nothing
-    }
+    private static final char EQUAL = '=';
+
+    /**
+     * Line feed character '{@value}'.
+     */
+    private static final char LF = '\n';
+
+    /**
+     * Space character '{@value}'.
+     */
+    private static final char SP = ' ';
+
+    /**
+     * Underscore character '{@value}'.
+     */
+    private static final char UNDERSCORE = '_';
+
+    /**
+     * The shift value required to create the upper nibble
+     * from the first of 2 byte values converted from ASCII hex.
+     */
+    private static final int UPPER_NIBBLE_SHIFT = Byte.SIZE / 2;
 
     /**
      * Decode the encoded byte data writing it to the given output stream.
@@ -56,9 +74,9 @@ final class QuotedPrintableDecoder {
             final byte ch = data[off++];
 
             // space characters were translated to '_' on encode, so we need to translate them back.
-            if (ch == '_') {
-                out.write(' ');
-            } else if (ch == '=') {
+            if (ch == UNDERSCORE) {
+                out.write(SP);
+            } else if (ch == EQUAL) {
                 // we found an encoded character.  Reduce the 3 char sequence to one.
                 // but first, make sure we have two characters to work with.
                 if (off + 1 >= endOffset) {
@@ -69,8 +87,8 @@ final class QuotedPrintableDecoder {
                 final byte b2 = data[off++];
 
                 // we've found an encoded carriage return.  The next char needs to be a newline
-                if (b1 == '\r') {
-                    if (b2 != '\n') {
+                if (b1 == CR) {
+                    if (b2 != LF) {
                         throw new IOException("Invalid quoted printable encoding; CR must be followed by LF");
                     }
                     // this was a soft linebreak inserted by the encoding.  We just toss this away
@@ -79,7 +97,7 @@ final class QuotedPrintableDecoder {
                     // this is a hex pair we need to convert back to a single byte.
                     final int c1 = hexToBinary(b1);
                     final int c2 = hexToBinary(b2);
-                    out.write((c1 << UPPER_NIBBLE_SHIFT) | c2);
+                    out.write(c1 << UPPER_NIBBLE_SHIFT | c2);
                     // 3 bytes in, one byte out
                     bytesWritten++;
                 }
@@ -96,7 +114,7 @@ final class QuotedPrintableDecoder {
     /**
      * Convert a hex digit to the binary value it represents.
      *
-     * @param b the ascii hex byte to convert (0-0, A-F, a-f)
+     * @param b the ASCII hex byte to convert (0-0, A-F, a-f)
      * @return the int value of the hex byte, 0-15
      * @throws IOException if the byte is not a valid hex digit.
      */
@@ -109,4 +127,11 @@ final class QuotedPrintableDecoder {
         return i;
     }
 
+    /**
+     * Hidden constructor, this class must not be instantiated.
+     */
+    private QuotedPrintableDecoder() {
+        // do nothing
+    }
+
 }
diff --git a/java/org/apache/tomcat/util/http/fileupload/util/mime/RFC2231Utility.java b/java/org/apache/tomcat/util/http/fileupload/util/mime/RFC2231Utility.java
index 8a522a313f..c1a7ba0d0c 100644
--- a/java/org/apache/tomcat/util/http/fileupload/util/mime/RFC2231Utility.java
+++ b/java/org/apache/tomcat/util/http/fileupload/util/mime/RFC2231Utility.java
@@ -30,6 +30,12 @@ import java.io.UnsupportedEncodingException;
  * @see <a href="https://tools.ietf.org/html/rfc5987">RFC 5987</a>
  */
 public final class RFC2231Utility {
+
+    /**
+     * Percent character '{@value}'.
+     */
+    private static final char PERCENT = '%';
+
     /**
      * The Hexadecimal values char array.
      */
@@ -55,54 +61,19 @@ public final class RFC2231Utility {
         }
     }
 
-    /**
-     * Private constructor so that no instances can be created. This class
-     * contains only static utility methods.
-     */
-    private RFC2231Utility() {
-    }
-
-    /**
-     * Checks if Asterisk (*) at the end of parameter name to indicate,
-     * if it has charset and language information to decode the value.
-     * @param paramName The parameter, which is being checked.
-     * @return {@code true}, if encoded as per RFC 2231, {@code false} otherwise
-     */
-    public static boolean hasEncodedValue(final String paramName) {
-        if (paramName != null) {
-            return paramName.lastIndexOf('*') == (paramName.length() - 1);
-        }
-        return false;
-    }
-
-    /**
-     * If {@code paramName} has Asterisk (*) at the end, it will be stripped off,
-     * else the passed value will be returned.
-     * @param paramName The parameter, which is being inspected.
-     * @return stripped {@code paramName} of Asterisk (*), if RFC2231 encoded
-     */
-    public static String stripDelimiter(final String paramName) {
-        if (hasEncodedValue(paramName)) {
-            final StringBuilder paramBuilder = new StringBuilder(paramName);
-            paramBuilder.deleteCharAt(paramName.lastIndexOf('*'));
-            return paramBuilder.toString();
-        }
-        return paramName;
-    }
-
     /**
      * Decode a string of text obtained from a HTTP header as per RFC 2231
      * <p>
-     * <b>Eg 1.</b> {@code us-ascii'en-us'This%20is%20%2A%2A%2Afun%2A%2A%2A}
+     * <strong>Eg 1.</strong> {@code us-ascii'en-us'This%20is%20%2A%2A%2Afun%2A%2A%2A}
      * will be decoded to {@code This is ***fun***}
      * <p>
-     * <b>Eg 2.</b> {@code iso-8859-1'en'%A3%20rate}
+     * <strong>Eg 2.</strong> {@code iso-8859-1'en'%A3%20rate}
      * will be decoded to {@code £ rate}
      * <p>
-     * <b>Eg 3.</b> {@code UTF-8''%c2%a3%20and%20%e2%82%ac%20rates}
+     * <strong>Eg 3.</strong> {@code UTF-8''%c2%a3%20and%20%e2%82%ac%20rates}
      * will be decoded to {@code £ and € rates}
      *
-     * @param encodedText - Text to be decoded has a format of {@code <charset>'<language>'<encoded_value>}
+     * @param encodedText   Text to be decoded has a format of {@code <charset>'<language>'<encoded_value>}
      * and ASCII only
      * @return Decoded text based on charset encoding
      * @throws UnsupportedEncodingException The requested character set wasn't found.
@@ -124,8 +95,9 @@ public final class RFC2231Utility {
     }
 
     /**
-     * Convert {@code text} to their corresponding Hex value.
-     * @param text - ASCII text input
+     * Converts {@code text} to their corresponding Hex value.
+     *
+     * @param text ASCII text input
      * @return Byte array of characters decoded from ASCII table
      */
     private static byte[] fromHex(final String text) {
@@ -133,13 +105,13 @@ public final class RFC2231Utility {
         final ByteArrayOutputStream out = new ByteArrayOutputStream(text.length());
         for (int i = 0; i < text.length();) {
             final char c = text.charAt(i++);
-            if (c == '%') {
+            if (c == PERCENT) {
                 if (i > text.length() - 2) {
                     break; // unterminated sequence
                 }
                 final byte b1 = HEX_DECODE[text.charAt(i++) & MASK];
                 final byte b2 = HEX_DECODE[text.charAt(i++) & MASK];
-                out.write((b1 << shift) | b2);
+                out.write(b1 << shift | b2);
             } else {
                 out.write((byte) c);
             }
@@ -151,4 +123,39 @@ public final class RFC2231Utility {
         // good enough for standard values
         return mimeCharset;
     }
+
+    /**
+     * Checks if Asterisk (*) at the end of parameter name to indicate,
+     * if it has charset and language information to decode the value.
+     * @param paramName The parameter, which is being checked.
+     * @return {@code true}, if encoded as per RFC 2231, {@code false} otherwise
+     */
+    public static boolean hasEncodedValue(final String paramName) {
+        if (paramName != null) {
+            return paramName.lastIndexOf('*') == paramName.length() - 1;
+        }
+        return false;
+    }
+
+    /**
+     * If {@code paramName} has Asterisk (*) at the end, it will be stripped off,
+     * else the passed value will be returned.
+     * @param paramName The parameter, which is being inspected.
+     * @return stripped {@code paramName} of Asterisk (*), if RFC2231 encoded
+     */
+    public static String stripDelimiter(final String paramName) {
+        if (hasEncodedValue(paramName)) {
+            final StringBuilder paramBuilder = new StringBuilder(paramName);
+            paramBuilder.deleteCharAt(paramName.lastIndexOf('*'));
+            return paramBuilder.toString();
+        }
+        return paramName;
+    }
+
+    /**
+     * Private constructor so that no instances can be created. This class
+     * contains only static utility methods.
+     */
+    private RFC2231Utility() {
+    }
 }
diff --git a/webapps/docs/changelog.xml b/webapps/docs/changelog.xml
index 9666aa852c..4aa28e1847 100644
--- a/webapps/docs/changelog.xml
+++ b/webapps/docs/changelog.xml
@@ -277,6 +277,10 @@
       <update>
         Update Tomcat Native to 2.0.9. (markt)
       </update>
+      <update>
+        Update the internal fork of Apache Commons FileUpload to 7a8c324
+        (2023-06-03, 1.x-SNAPSHOT). (markt)
+      </update>
     </changelog>
   </subsection>
 </section>


---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@tomcat.apache.org
For additional commands, e-mail: dev-help@tomcat.apache.org


Re: (tomcat) branch main updated: Update fork of Commons FileUpload to latest 1.x

Posted by Mark Thomas <ma...@apache.org>.
On 03/06/2025 14:37, markt@apache.org wrote:
> This is an automated email from the ASF dual-hosted git repository.
> 
> markt pushed a commit to branch main
> in repository https://gitbox.apache.org/repos/asf/tomcat.git
> 
> 
> The following commit(s) were added to refs/heads/main by this push:
>       new 81f27a990f Update fork of Commons FileUpload to latest 1.x
> 81f27a990f is described below
> 
> commit 81f27a990fd780c6a4ced79f2f830716f455e695
> Author: Mark Thomas <ma...@apache.org>
> AuthorDate: Tue Jun 3 14:37:38 2025 +0100
> 
>      Update fork of Commons FileUpload to latest 1.x

I'm currently working on some FileUpload improvements. This merge is an 
interim step as I align our fork with Commons FleUpload (we had a small 
number of fixes that needed to be ported back). I hope to be able to 
merge 1.6 (or at least a 1.6 RC) shortly.

Mark

> ---
>   MERGE.txt                                          |    2 +-
>   .../tomcat/util/http/fileupload/FileItem.java      |  140 ++-
>   .../util/http/fileupload/FileItemHeaders.java      |   22 +-
>   .../util/http/fileupload/FileItemStream.java       |   49 +-
>   .../tomcat/util/http/fileupload/FileUpload.java    |   12 +-
>   .../util/http/fileupload/FileUploadBase.java       |  472 ++++----
>   .../util/http/fileupload/FileUploadException.java  |   12 +-
>   .../http/fileupload/InvalidFileNameException.java  |   10 +-
>   .../util/http/fileupload/MultipartStream.java      | 1197 ++++++++++----------
>   .../util/http/fileupload/ParameterParser.java      |  291 +++--
>   .../util/http/fileupload/ProgressListener.java     |    8 +-
>   .../util/http/fileupload/disk/DiskFileItem.java    |  652 +++++------
>   .../http/fileupload/disk/DiskFileItemFactory.java  |  115 +-
>   .../tomcat/util/http/fileupload/package-info.java  |    4 +-
>   .../fileupload/servlet/ServletRequestContext.java  |   40 +-
>   .../http/fileupload/util/FileItemHeadersImpl.java  |   37 +-
>   .../http/fileupload/util/LimitedInputStream.java   |   81 +-
>   .../tomcat/util/http/fileupload/util/Streams.java  |   74 +-
>   .../http/fileupload/util/mime/MimeUtility.java     |   18 +-
>   .../http/fileupload/util/mime/ParseException.java  |    2 +
>   .../util/mime/QuotedPrintableDecoder.java          |   53 +-
>   .../http/fileupload/util/mime/RFC2231Utility.java  |   93 +-
>   webapps/docs/changelog.xml                         |    4 +
>   23 files changed, 1650 insertions(+), 1738 deletions(-)
> 
> diff --git a/MERGE.txt b/MERGE.txt
> index c81293c991..89ba88208a 100644
> --- a/MERGE.txt
> +++ b/MERGE.txt
> @@ -46,7 +46,7 @@ Branch: 1.x
>   Sub-tree:
>   src/main/java/org/apache/commons/fileupload
>   The SHA1 ID / tag for the most recent commit to be merged to Tomcat is:
> -7a8c3241cfa8d036452cd4fc3f92d57cff189bca (2023-09-16)
> +abe5d94b7ef6ff7164b48b5bb3781979ebbafb0e (2025-06-03)
>   
>   Note: Tomcat's copy of fileupload also includes classes copied manually from
>         Commons IO.
> diff --git a/java/org/apache/tomcat/util/http/fileupload/FileItem.java b/java/org/apache/tomcat/util/http/fileupload/FileItem.java
> index e6f035fc7b..00316cc3ed 100644
> --- a/java/org/apache/tomcat/util/http/fileupload/FileItem.java
> +++ b/java/org/apache/tomcat/util/http/fileupload/FileItem.java
> @@ -20,7 +20,6 @@ import java.io.File;
>   import java.io.IOException;
>   import java.io.InputStream;
>   import java.io.OutputStream;
> -import java.io.UncheckedIOException;
>   import java.io.UnsupportedEncodingException;
>   
>   /**
> @@ -47,18 +46,21 @@ import java.io.UnsupportedEncodingException;
>    */
>   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 {@code null} if
> @@ -69,6 +71,25 @@ public interface FileItem extends FileItemHeadersSupport {
>        */
>       String getContentType();
>   
> +    /**
> +     * 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
> @@ -83,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 {@code true} if the file contents will be read from memory;
> -     *         {@code 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.
> @@ -102,13 +123,13 @@ public interface FileItem extends FileItemHeadersSupport {
>       long getSize();
>   
>       /**
> -     * Returns the contents of the file item as an array of bytes.
> -     *
> -     * @return 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.
>        *
> -     * @throws UncheckedIOException if an I/O error occurs
> +     * @return The contents of the item, as a string.
>        */
> -    byte[] get() throws UncheckedIOException;
> +    String getString();
>   
>       /**
>        * Returns the contents of the file item as a String, using the specified
> @@ -121,53 +142,26 @@ public interface FileItem extends FileItemHeadersSupport {
>        *
>        * @throws UnsupportedEncodingException if the requested character
>        *                                      encoding is not available.
> -     * @throws IOException if an I/O error occurs
> -     */
> -    String getString(String encoding) throws UnsupportedEncodingException, IOException;
> -
> -    /**
> -     * 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();
> +    String getString(String encoding) throws UnsupportedEncodingException;
>   
>       /**
> -     * 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.
> -     * <p>
> -     * 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 {@code 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.
> -     */
> -    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 {@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.
> +     * @return {@code true} if the instance represents a simple form
> +     *         field; {@code false} if it represents an uploaded file.
>        */
> -    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.
> @@ -176,15 +170,6 @@ public interface FileItem extends FileItemHeadersSupport {
>        */
>       void setFieldName(String name);
>   
> -    /**
> -     * Determines whether or not a {@code FileItem} instance represents
> -     * a simple form field.
> -     *
> -     * @return {@code true} if the instance represents a simple form
> -     *         field; {@code false} if it represents an uploaded file.
> -     */
> -    boolean isFormField();
> -
>       /**
>        * Specifies whether or not a {@code FileItem} instance represents
>        * a simple form field.
> @@ -195,14 +180,21 @@ public interface FileItem extends FileItemHeadersSupport {
>       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.
> +     * <p>
> +     * 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 contents 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/java/org/apache/tomcat/util/http/fileupload/FileItemHeaders.java b/java/org/apache/tomcat/util/http/fileupload/FileItemHeaders.java
> index 890f4397ae..30b1d40e25 100644
> --- a/java/org/apache/tomcat/util/http/fileupload/FileItemHeaders.java
> +++ b/java/org/apache/tomcat/util/http/fileupload/FileItemHeaders.java
> @@ -42,6 +42,17 @@ public interface FileItemHeaders {
>        */
>       String getHeader(String name);
>   
> +    /**
> +     * <p>
> +     * Returns an {@code Iterator} of all the header names.
> +     * </p>
> +     *
> +     * @return an {@code Iterator} containing all of the names of
> +     *         headers provided with this file item. If the item does not have
> +     *         any headers return an empty {@code Iterator}
> +     */
> +    Iterator<String> getHeaderNames();
> +
>       /**
>        * <p>
>        * Returns all the values of the specified item header as an
> @@ -60,15 +71,4 @@ public interface FileItemHeaders {
>        */
>       Iterator<String> getHeaders(String name);
>   
> -    /**
> -     * <p>
> -     * Returns an {@code Iterator} of all the header names.
> -     * </p>
> -     *
> -     * @return an {@code Iterator} containing all of the names of
> -     *         headers provided with this file item. If the item does not have
> -     *         any headers return an empty {@code Iterator}
> -     */
> -    Iterator<String> getHeaderNames();
> -
>   }
> diff --git a/java/org/apache/tomcat/util/http/fileupload/FileItemStream.java b/java/org/apache/tomcat/util/http/fileupload/FileItemStream.java
> index c31d9eb13b..e8734e4c93 100644
> --- a/java/org/apache/tomcat/util/http/fileupload/FileItemStream.java
> +++ b/java/org/apache/tomcat/util/http/fileupload/FileItemStream.java
> @@ -48,20 +48,14 @@ public interface FileItemStream extends FileItemHeadersSupport {
>            */
>           private static final long serialVersionUID = -7280778431581963740L;
>   
> -    }
> +        /**
> +         * Constructs a new instance.
> +         */
> +        public ItemSkippedException() {
> +            // empty
> +        }
>   
> -    /**
> -     * Creates an {@link InputStream}, which allows to read the
> -     * items contents.
> -     *
> -     * @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
> -     */
> -    InputStream openStream() throws IOException;
> +    }
>   
>       /**
>        * Returns the content type passed by the browser or {@code null} if
> @@ -72,6 +66,14 @@ public interface FileItemStream extends FileItemHeadersSupport {
>        */
>       String getContentType();
>   
> +    /**
> +     * 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 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
> @@ -82,14 +84,6 @@ public interface FileItemStream extends FileItemHeadersSupport {
>        */
>       String getName();
>   
> -    /**
> -     * Returns the name of the field in the multipart form corresponding to
> -     * this file item.
> -     *
> -     * @return The name of the form field.
> -     */
> -    String getFieldName();
> -
>       /**
>        * Determines whether or not a {@code FileItem} instance represents
>        * a simple form field.
> @@ -99,4 +93,17 @@ public interface FileItemStream extends FileItemHeadersSupport {
>        */
>       boolean isFormField();
>   
> +    /**
> +     * Creates an {@link InputStream}, which allows to read the
> +     * items contents.
> +     *
> +     * @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
> +     */
> +    InputStream openStream() throws IOException;
> +
>   }
> diff --git a/java/org/apache/tomcat/util/http/fileupload/FileUpload.java b/java/org/apache/tomcat/util/http/fileupload/FileUpload.java
> index 06846f18d6..cd16f07f83 100644
> --- a/java/org/apache/tomcat/util/http/fileupload/FileUpload.java
> +++ b/java/org/apache/tomcat/util/http/fileupload/FileUpload.java
> @@ -33,15 +33,11 @@ package org.apache.tomcat.util.http.fileupload;
>   public class FileUpload
>       extends FileUploadBase {
>   
> -    // ----------------------------------------------------------- Data members
> -
>       /**
>        * The factory to use to create new form items.
>        */
>       private FileItemFactory fileItemFactory;
>   
> -    // ----------------------------------------------------------- Constructors
> -
>       /**
>        * Constructs an uninitialized instance of this class.
>        *
> @@ -52,8 +48,6 @@ public class FileUpload
>       public FileUpload() {
>       }
>   
> -    // ----------------------------------------------------- Property accessors
> -
>       /**
>        * Returns the factory class used when creating file items.
>        *
> @@ -67,11 +61,11 @@ public class FileUpload
>       /**
>        * 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(final FileItemFactory factory) {
> -        this.fileItemFactory = factory;
> +    public void setFileItemFactory(final FileItemFactory fileItemFactory) {
> +        this.fileItemFactory = fileItemFactory;
>       }
>   
>   }
> diff --git a/java/org/apache/tomcat/util/http/fileupload/FileUploadBase.java b/java/org/apache/tomcat/util/http/fileupload/FileUploadBase.java
> index b9f5cfe174..73e5f1e63e 100644
> --- a/java/org/apache/tomcat/util/http/fileupload/FileUploadBase.java
> +++ b/java/org/apache/tomcat/util/http/fileupload/FileUploadBase.java
> @@ -33,9 +33,10 @@ import org.apache.tomcat.util.http.fileupload.util.Streams;
>   
>   
>   /**
> - * <p>High level API for processing file uploads.</p>
> + * High level API for processing file uploads.
>    *
> - * <p>This class handles multiple files per single HTML widget, sent using
> + * <p>
> + * This class handles multiple files per single HTML widget, sent using
>    * {@code multipart/mixed} encoding type, as specified by
>    * <a href="http://www.ietf.org/rfc/rfc1867.txt">RFC 1867</a>.  Use {@link
>    * #parseRequest(RequestContext)} to acquire a list of {@link
> @@ -48,7 +49,15 @@ import org.apache.tomcat.util.http.fileupload.util.Streams;
>    */
>   public abstract class FileUploadBase {
>   
> -    // ----------------------------------------------------- Manifest constants
> +    /**
> +     * Line feed.
> +     */
> +    private static final char LF = '\n';
> +
> +    /**
> +     * Carriage return.
> +     */
> +    private static final char CR = '\r';
>   
>       /**
>        * HTTP content type header name.
> @@ -90,8 +99,6 @@ public abstract class FileUploadBase {
>        */
>       public static final String MULTIPART_MIXED = "multipart/mixed";
>   
> -    // ----------------------------------------------------------- Data members
> -
>       /**
>        * The maximum size permitted for the complete request, as opposed to
>        * {@link #fileSizeMax}. A value of -1 indicates no maximum.
> @@ -120,70 +127,65 @@ public abstract class FileUploadBase {
>        */
>       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)
> -     *
> +     * Constructs a new instance.
>        */
> -    public long getSizeMax() {
> -        return sizeMax;
> +    public FileUploadBase() {
> +        // empty
>       }
>   
>       /**
> -     * Sets the maximum allowed size of a complete request, as opposed
> -     * to {@link #setFileSizeMax(long)}.
> +     * Gets the boundary from the {@code Content-type} header.
>        *
> -     * @param sizeMax The maximum allowed size, in bytes. The default value of
> -     *   -1 indicates, that there is no limit.
> -     *
> -     * @see #getSizeMax()
> +     * @param contentType The value of the content type header from which to
> +     *                    extract the boundary value.
>        *
> +     * @return The boundary, as a byte array.
>        */
> -    public void setSizeMax(final long sizeMax) {
> -        this.sizeMax = sizeMax;
> +    public byte[] getBoundary(final String contentType) {
> +        final ParameterParser parser = new ParameterParser();
> +        parser.setLowerCaseNames(true);
> +        // Parameter parser can handle null input
> +        final Map<String, String> params = parser.parse(contentType, new char[] {';', ','});
> +        final String boundaryStr = params.get("boundary");
> +
> +        if (boundaryStr == null) {
> +            return null;
> +        }
> +        return boundaryStr.getBytes(StandardCharsets.ISO_8859_1);
>       }
>   
>       /**
> -     * Returns the maximum allowed size of a single uploaded file,
> -     * as opposed to {@link #getSizeMax()}.
> +     * Gets the field name from the {@code Content-disposition}
> +     * header.
>        *
> -     * @see #setFileSizeMax(long)
> -     * @return Maximum size of a single uploaded file.
> +     * @param headers A {@code Map} containing the HTTP request headers.
> +     *
> +     * @return The field name for the current {@code encapsulation}.
>        */
> -    public long getFileSizeMax() {
> -        return fileSizeMax;
> +    public String getFieldName(final FileItemHeaders headers) {
> +        return getFieldName(headers.getHeader(CONTENT_DISPOSITION));
>       }
>   
>       /**
> -     * 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.
> +     * Returns the field name, which is given by the content-disposition
> +     * header.
> +     * @param contentDisposition The content-dispositions header value.
> +     * @return The field name.
>        */
> -    public void setFileSizeMax(final long fileSizeMax) {
> -        this.fileSizeMax = fileSizeMax;
> +    private String getFieldName(final String contentDisposition) {
> +        String fieldName = null;
> +        if (contentDisposition != null && contentDisposition.toLowerCase(Locale.ROOT).startsWith(FORM_DATA)) {
> +            final ParameterParser parser = new ParameterParser();
> +            parser.setLowerCaseNames(true);
> +            // Parameter parser can handle null input
> +            final Map<String, String> params = parser.parse(contentDisposition, ';');
> +            fieldName = params.get("name");
> +            if (fieldName != null) {
> +                fieldName = fieldName.trim();
> +            }
> +        }
> +        return fieldName;
>       }
>   
>       /**
> @@ -196,155 +198,14 @@ public abstract class FileUploadBase {
>       }
>   
>       /**
> -     * Sets the maximum number of files allowed per request.
> -     *
> -     * @param fileCountMax The new limit. {@code -1} means no limit.
> -     */
> -    public void setFileCountMax(final long fileCountMax) {
> -        this.fileCountMax = fileCountMax;
> -    }
> -
> -    /**
> -     * Retrieves the character encoding used when reading the headers of an
> -     * individual part. When not specified, or {@code null}, the request
> -     * encoding is used. If that is also not specified, or {@code 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 {@code null}, the request
> -     * encoding is used. If that is also not specified, or {@code null},
> -     * the platform default encoding is used.
> -     *
> -     * @param encoding The encoding used to read part headers.
> -     */
> -    public void setHeaderEncoding(final String encoding) {
> -        headerEncoding = encoding;
> -    }
> -
> -    // --------------------------------------------------------- Public methods
> -
> -    /**
> -     * Processes an <a href="http://www.ietf.org/rfc/rfc1867.txt">RFC 1867</a>
> -     * compliant {@code multipart/form-data} stream.
> -     *
> -     * @param ctx The context for the request to be parsed.
> -     *
> -     * @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 FileItemIterator getItemIterator(final RequestContext ctx)
> -    throws FileUploadException, IOException {
> -        try {
> -            return new FileItemIteratorImpl(this, ctx);
> -        } catch (final FileUploadIOException e) {
> -            // unwrap encapsulated SizeException
> -            throw (FileUploadException) e.getCause();
> -        }
> -    }
> -
> -    /**
> -     * Processes an <a href="http://www.ietf.org/rfc/rfc1867.txt">RFC 1867</a>
> -     * compliant {@code multipart/form-data} stream.
> -     *
> -     * @param ctx The context for the request to be parsed.
> -     *
> -     * @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.
> -     */
> -    public List<FileItem> parseRequest(final RequestContext ctx)
> -            throws FileUploadException {
> -        final List<FileItem> items = new ArrayList<>();
> -        boolean successful = false;
> -        try {
> -            final FileItemIterator iter = getItemIterator(ctx);
> -            final FileItemFactory fileItemFactory = Objects.requireNonNull(getFileItemFactory(),
> -                    "No FileItemFactory has been set.");
> -            final byte[] buffer = new byte[Streams.DEFAULT_BUFFER_SIZE];
> -            while (iter.hasNext()) {
> -                if (items.size() == fileCountMax) {
> -                    // The next item will exceed the limit.
> -                    throw new FileCountLimitExceededException(ATTACHMENT, getFileCountMax());
> -                }
> -                final FileItemStream item = iter.next();
> -                // Don't use getName() here to prevent an InvalidFileNameException.
> -                final String fileName = item.getName();
> -                final FileItem fileItem = fileItemFactory.createItem(item.getFieldName(), item.getContentType(),
> -                                                   item.isFormField(), fileName);
> -                items.add(fileItem);
> -                try {
> -                    Streams.copy(item.openStream(), fileItem.getOutputStream(), true, buffer);
> -                } catch (final FileUploadIOException e) {
> -                    throw (FileUploadException) e.getCause();
> -                } catch (final IOException e) {
> -                    throw new IOFileUploadException(String.format("Processing of %s request failed. %s",
> -                                                           MULTIPART_FORM_DATA, e.getMessage()), e);
> -                }
> -                final FileItemHeaders fih = item.getHeaders();
> -                fileItem.setHeaders(fih);
> -            }
> -            successful = true;
> -            return items;
> -        } catch (final FileUploadException e) {
> -            throw e;
> -        } catch (final IOException e) {
> -            throw new FileUploadException(e.getMessage(), e);
> -        } finally {
> -            if (!successful) {
> -                for (final FileItem fileItem : items) {
> -                    try {
> -                        fileItem.delete();
> -                    } catch (final Exception ignored) {
> -                        // ignored TODO perhaps add to tracker delete failure list somehow?
> -                    }
> -                }
> -            }
> -        }
> -    }
> -
> -    // ------------------------------------------------------ Protected methods
> -
> -    /**
> -     * Retrieves the boundary from the {@code Content-type} header.
> -     *
> -     * @param contentType The value of the content type header from which to
> -     *                    extract the boundary value.
> +     * Returns the factory class used when creating file items.
>        *
> -     * @return The boundary, as a byte array.
> +     * @return The factory class for new file items.
>        */
> -    public byte[] getBoundary(final String contentType) {
> -        final ParameterParser parser = new ParameterParser();
> -        parser.setLowerCaseNames(true);
> -        // Parameter parser can handle null input
> -        final Map<String, String> params = parser.parse(contentType, new char[] {';', ','});
> -        final String boundaryStr = params.get("boundary");
> -
> -        if (boundaryStr == null) {
> -            return null;
> -        }
> -        final byte[] boundary;
> -        boundary = boundaryStr.getBytes(StandardCharsets.ISO_8859_1);
> -        return boundary;
> -    }
> +    public abstract FileItemFactory getFileItemFactory();
>   
>       /**
> -     * Retrieves the file name from the {@code Content-disposition}
> +     * Gets the file name from the {@code Content-disposition}
>        * header.
>        *
>        * @param headers The HTTP headers object.
> @@ -357,18 +218,18 @@ public abstract class FileUploadBase {
>   
>       /**
>        * Returns the given content-disposition headers file name.
> -     * @param pContentDisposition The content-disposition headers value.
> +     * @param contentDisposition The content-disposition headers value.
>        * @return The file name
>        */
> -    private String getFileName(final String pContentDisposition) {
> +    private String getFileName(final String contentDisposition) {
>           String fileName = null;
> -        if (pContentDisposition != null) {
> -            final String cdl = pContentDisposition.toLowerCase(Locale.ENGLISH);
> +        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 Map<String, String> params = parser.parse(pContentDisposition, ';');
> +                final Map<String, String> params = parser.parse(contentDisposition, ';');
>                   if (params.containsKey("filename")) {
>                       fileName = params.get("filename");
>                       if (fileName != null) {
> @@ -386,37 +247,52 @@ public abstract class FileUploadBase {
>       }
>   
>       /**
> -     * Retrieves the field name from the {@code Content-disposition}
> -     * header.
> +     * Returns the maximum allowed size of a single uploaded file,
> +     * as opposed to {@link #getSizeMax()}.
>        *
> -     * @param headers A {@code Map} containing the HTTP request headers.
> +     * @see #setFileSizeMax(long)
> +     * @return Maximum size of a single uploaded file.
> +     */
> +    public long getFileSizeMax() {
> +        return fileSizeMax;
> +    }
> +
> +    /**
> +     * Gets the character encoding used when reading the headers of an
> +     * individual part. When not specified, or {@code null}, the request
> +     * encoding is used. If that is also not specified, or {@code null},
> +     * the platform default encoding is used.
>        *
> -     * @return The field name for the current {@code encapsulation}.
> +     * @return The encoding used to read part headers.
>        */
> -    public String getFieldName(final FileItemHeaders headers) {
> -        return getFieldName(headers.getHeader(CONTENT_DISPOSITION));
> +    public String getHeaderEncoding() {
> +        return headerEncoding;
>       }
>   
>       /**
> -     * Returns the field name, which is given by the content-disposition
> -     * header.
> -     * @param pContentDisposition The content-dispositions header value.
> -     * @return The field jake
> +     * Processes an <a href="http://www.ietf.org/rfc/rfc1867.txt">RFC 1867</a>
> +     * compliant {@code multipart/form-data} stream.
> +     *
> +     * @param ctx The context for the request to be parsed.
> +     *
> +     * @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.
>        */
> -    private String getFieldName(final String pContentDisposition) {
> -        String fieldName = null;
> -        if (pContentDisposition != null
> -                && pContentDisposition.toLowerCase(Locale.ENGLISH).startsWith(FORM_DATA)) {
> -            final ParameterParser parser = new ParameterParser();
> -            parser.setLowerCaseNames(true);
> -            // Parameter parser can handle null input
> -            final Map<String, String> params = parser.parse(pContentDisposition, ';');
> -            fieldName = params.get("name");
> -            if (fieldName != null) {
> -                fieldName = fieldName.trim();
> -            }
> +    public FileItemIterator getItemIterator(final RequestContext ctx)
> +    throws FileUploadException, IOException {
> +        try {
> +            return new FileItemIteratorImpl(this, ctx);
> +        } catch (final FileUploadIOException e) {
> +            // unwrap encapsulated SizeException
> +            throw (FileUploadException) e.getCause();
>           }
> -        return fieldName;
>       }
>   
>       /**
> @@ -464,6 +340,29 @@ public abstract class FileUploadBase {
>           return headers;
>       }
>   
> +    /**
> +     * Returns the progress listener.
> +     *
> +     * @return The progress listener, if any, or null.
> +     */
> +    public ProgressListener getProgressListener() {
> +        return listener;
> +    }
> +
> +    /**
> +     * 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 of {@link FileItemHeaders}.
>        * @return The new instance.
> @@ -483,12 +382,12 @@ public abstract class FileUploadBase {
>       private int parseEndOfLine(final String headerPart, final int end) {
>           int index = end;
>           for (;;) {
> -            final int offset = headerPart.indexOf('\r', index);
> +            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) == '\n') {
> +            if (headerPart.charAt(offset + 1) == LF) {
>                   return offset;
>               }
>               index = offset + 1;
> @@ -512,21 +411,126 @@ public abstract class FileUploadBase {
>       }
>   
>       /**
> -     * Returns the progress listener.
> +     * Processes an <a href="http://www.ietf.org/rfc/rfc1867.txt">RFC 1867</a>
> +     * compliant {@code multipart/form-data} stream.
>        *
> -     * @return The progress listener, if any, or null.
> +     * @param ctx The context for the request to be parsed.
> +     *
> +     * @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.
>        */
> -    public ProgressListener getProgressListener() {
> -        return listener;
> +    public List<FileItem> parseRequest(final RequestContext ctx) throws FileUploadException {
> +        final List<FileItem> items = new ArrayList<>();
> +        boolean successful = false;
> +        try {
> +            final FileItemIterator iter = getItemIterator(ctx);
> +            final FileItemFactory fileItemFactory = Objects.requireNonNull(getFileItemFactory(),
> +                    "No FileItemFactory has been set.");
> +            final byte[] buffer = new byte[Streams.DEFAULT_BUFFER_SIZE];
> +            while (iter.hasNext()) {
> +                if (items.size() == fileCountMax) {
> +                    // The next item will exceed the limit.
> +                    throw new FileCountLimitExceededException(ATTACHMENT, getFileCountMax());
> +                }
> +                final FileItemStream item = iter.next();
> +                // Don't use getName() here to prevent an InvalidFileNameException.
> +                final String fileName = item.getName();
> +                final FileItem fileItem = fileItemFactory.createItem(item.getFieldName(), item.getContentType(),
> +                                                   item.isFormField(), fileName);
> +                items.add(fileItem);
> +                try {
> +                    Streams.copy(item.openStream(), fileItem.getOutputStream(), true, buffer);
> +                } catch (final FileUploadIOException e) {
> +                    throw (FileUploadException) e.getCause();
> +                } catch (final IOException e) {
> +                    throw new IOFileUploadException(String.format("Processing of %s request failed. %s",
> +                                                           MULTIPART_FORM_DATA, e.getMessage()), e);
> +                }
> +                final FileItemHeaders fih = item.getHeaders();
> +                fileItem.setHeaders(fih);
> +            }
> +            successful = true;
> +            return items;
> +        } catch (final FileUploadException e) {
> +            throw e;
> +        } catch (final IOException e) {
> +            throw new FileUploadException(e.getMessage(), e);
> +        } finally {
> +            if (!successful) {
> +                for (final FileItem fileItem : items) {
> +                    try {
> +                        fileItem.delete();
> +                    } catch (final Exception ignored) {
> +                        // ignored TODO perhaps add to tracker delete failure list somehow?
> +                    }
> +                }
> +            }
> +        }
> +    }
> +
> +    /**
> +     * Sets the maximum number of files allowed per request.
> +     *
> +     * @param fileCountMax The new limit. {@code -1} means no limit.
> +     */
> +    public void setFileCountMax(final long fileCountMax) {
> +        this.fileCountMax = fileCountMax;
> +    }
> +
> +    /**
> +     * 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);
> +
> +    /**
> +     * 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(final long fileSizeMax) {
> +        this.fileSizeMax = fileSizeMax;
> +    }
> +
> +    /**
> +     * Specifies the character encoding to be used when reading the headers of
> +     * individual part. When not specified, or {@code null}, the request
> +     * encoding is used. If that is also not specified, or {@code null},
> +     * the platform default encoding is used.
> +     *
> +     * @param encoding The encoding used to read part headers.
> +     */
> +    public void setHeaderEncoding(final String encoding) {
> +        headerEncoding = encoding;
>       }
>   
>       /**
>        * Sets the progress listener.
>        *
> -     * @param pListener The progress listener, if any. Defaults to null.
> +     * @param listener The progress listener, if any. Defaults to null.
>        */
> -    public void setProgressListener(final ProgressListener pListener) {
> -        listener = pListener;
> +    public void setProgressListener(final ProgressListener listener) {
> +        this.listener = listener;
> +    }
> +
> +    /**
> +     * 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(final long sizeMax) {
> +        this.sizeMax = sizeMax;
>       }
>   
>   }
> diff --git a/java/org/apache/tomcat/util/http/fileupload/FileUploadException.java b/java/org/apache/tomcat/util/http/fileupload/FileUploadException.java
> index d7a8d11819..ea4226e140 100644
> --- a/java/org/apache/tomcat/util/http/fileupload/FileUploadException.java
> +++ b/java/org/apache/tomcat/util/http/fileupload/FileUploadException.java
> @@ -36,20 +36,20 @@ public class FileUploadException extends IOException {
>        * Constructs a new {@code FileUploadException} with specified detail
>        * message.
>        *
> -     * @param msg the error message.
> +     * @param message the error message.
>        */
> -    public FileUploadException(final String msg) {
> -        super(msg);
> +    public FileUploadException(final String message) {
> +        super(message);
>       }
>   
>       /**
>        * 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(final String msg, final Throwable cause) {
> -        super(msg, cause);
> +    public FileUploadException(final String message, final Throwable cause) {
> +        super(message, cause);
>       }
>   }
> diff --git a/java/org/apache/tomcat/util/http/fileupload/InvalidFileNameException.java b/java/org/apache/tomcat/util/http/fileupload/InvalidFileNameException.java
> index 5018620fac..ecc1dce896 100644
> --- a/java/org/apache/tomcat/util/http/fileupload/InvalidFileNameException.java
> +++ b/java/org/apache/tomcat/util/http/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(final String pName, final String pMessage) {
> -        super(pMessage);
> -        name = pName;
> +    public InvalidFileNameException(final String name, final String message) {
> +        super(message);
> +        this.name = name;
>       }
>   
>       /**
> diff --git a/java/org/apache/tomcat/util/http/fileupload/MultipartStream.java b/java/org/apache/tomcat/util/http/fileupload/MultipartStream.java
> index 44e52563e2..97c412da66 100644
> --- a/java/org/apache/tomcat/util/http/fileupload/MultipartStream.java
> +++ b/java/org/apache/tomcat/util/http/fileupload/MultipartStream.java
> @@ -27,7 +27,7 @@ import org.apache.tomcat.util.http.fileupload.util.Closeable;
>   import org.apache.tomcat.util.http.fileupload.util.Streams;
>   
>   /**
> - * <p> Low level API for processing file uploads.
> + * Low level API for processing file uploads.
>    *
>    * <p> This class can be used to process data streams conforming to MIME
>    * 'multipart' format as defined in
> @@ -58,7 +58,9 @@ import org.apache.tomcat.util.http.fileupload.util.Streams;
>    * boundary token of the same length as the parent stream (see {@link
>    * #setBoundary(byte[])}).
>    *
> - * <p>Here is an example of usage of this class.<br>
> + * <p>
> + * Here is an example of usage of this class.
> + * </p>
>    *
>    * <pre>
>    *   try {
> @@ -82,145 +84,473 @@ import org.apache.tomcat.util.http.fileupload.util.Streams;
>   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.
> +         */
> +        private int pos;
> +
> +        /**
> +         * Whether the stream is already closed.
> +         */
> +        private boolean closed;
> +
> +        /**
> +         * Creates a new instance.
>            */
> -        public ProgressNotifier(final ProgressListener pListener, final long pContentLength) {
> -            listener = pListener;
> -            contentLength = pContentLength;
> +        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(final 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.
>            */
> -        public 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
> -
> -    /**
> -     * 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.).
> -     */
> -    public static final int HEADER_PART_SIZE_MAX = 10240;
> +        /**
> +         * 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 default length of the buffer used for processing a request.
> -     */
> -    protected static final int DEFAULT_BUFSIZE = 4096;
> +        /**
> +         * 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;
> +        }
>   
> -    /**
> -     * A byte sequence that marks the end of {@code header-part}
> -     * ({@code CRLFCRLF}).
> -     */
> -    protected static final byte[] HEADER_SEPARATOR = {CR, LF, CR, LF};
> +        /**
> +         * Returns, whether the stream is closed.
> +         *
> +         * @return True, if the stream is closed, otherwise false.
> +         */
> +        @Override
> +        public boolean isClosed() {
> +            return closed;
> +        }
>   
> -    /**
> -     * A byte sequence that follows a delimiter that will be
> -     * followed by an encapsulation ({@code CRLF}).
> -     */
> -    protected static final byte[] FIELD_SEPARATOR = {CR, LF};
> +        /**
> +         * 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;
> +            }
>   
> -    /**
> -     * A byte sequence that follows a delimiter of the last
> -     * encapsulation in the stream ({@code --}).
> -     */
> -    protected static final byte[] STREAM_TERMINATOR = {DASH, DASH};
> +            // Move the data to the beginning of the buffer.
> +            total += tail - head - pad;
> +            System.arraycopy(buffer, tail - pad, buffer, 0, pad);
>   
> -    /**
> -     * A byte sequence that precedes a boundary ({@code CRLF--}).
> -     */
> -    protected static final byte[] BOUNDARY_PREFIX = {CR, LF, DASH, DASH};
> +            // Refill buffer with new data.
> +            head = 0;
> +            tail = pad;
>   
> -    // ----------------------------------------------------------- Data members
> +            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;
>   
> -    /**
> -     * The input stream from which data is read.
> -     */
> -    private final InputStream input;
> +                findSeparator();
> +                final int av = available();
>   
> -    /**
> -     * The length of the boundary token plus the leading {@code CRLF--}.
> -     */
> -    private int boundaryLength;
> +                if (av > 0 || pos != -1) {
> +                    return av;
> +                }
> +            }
> +        }
>   
> -    /**
> -     * The amount of data, in bytes, that must be kept in the buffer in order
> -     * to detect delimiters reliably.
> -     */
> -    private final int keepRegion;
> +        /**
> +         * 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;
> +        }
>   
> -    /**
> +        /**
> +         * 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.
> +         */
> +        public 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.
> +         */
> +        public 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.).
> +     */
> +    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 {@code CRLF--}.
> +     */
> +    private int boundaryLength;
> +
> +    /**
> +     * The amount of data, in bytes, that must be kept in the buffer in order
> +     * to detect delimiters reliably.
> +     */
> +    private final int keepRegion;
> +
> +    /**
>        * The byte sequence that partitions the stream.
>        */
>       private final byte[] boundary;
> @@ -264,31 +594,23 @@ public class MultipartStream {
>        */
>       private final ProgressNotifier notifier;
>   
> -    // ----------------------------------------------------------- Constructors
> -
>       /**
> -     * <p> Constructs a {@code MultipartStream} with a custom size buffer.
> -     *
> -     * <p> 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 {@code MultipartStream} with a custom size buffer.
> +     * <p>
> +     * 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 {@code InputStream} to serve as a data source.
> -     * @param boundary The token used for dividing the stream into
> -     *                 {@code encapsulations}.
> +     * @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 FileUpload 1.3.1
>        */
> -    public MultipartStream(final InputStream input,
> -            final byte[] boundary,
> -            final int bufSize,
> -            final 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");
> @@ -297,93 +619,138 @@ public class MultipartStream {
>           // 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;
>       }
>   
>       /**
> -     * <p> Constructs a {@code MultipartStream} with a default size buffer.
> +     * Constructs a {@code MultipartStream} with a default size buffer.
>        *
>        * @param input    The {@code InputStream} to serve as a data source.
>        * @param boundary The token used for dividing the stream into
>        *                 {@code encapsulations}.
> -     * @param pNotifier An object for calling the progress listener, if any.
> +     * @param notifier An object for calling the progress listener, if any.
>        *
>        *
>        * @see #MultipartStream(InputStream, byte[], int, ProgressNotifier)
>        */
> -    public MultipartStream(final InputStream input,
> -            final byte[] boundary,
> -            final ProgressNotifier pNotifier) {
> -        this(input, boundary, DEFAULT_BUFSIZE, pNotifier);
> +    public MultipartStream(final InputStream input, final byte[] boundary, final ProgressNotifier notifier) {
> +        this(input, boundary, DEFAULT_BUFSIZE, notifier);
>       }
>   
> -    // --------------------------------------------------------- Public methods
> -
>       /**
> -     * Retrieves 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 The encoding used to read part headers.
> +     * Compute the table used for Knuth-Morris-Pratt search algorithm.
>        */
> -    public String getHeaderEncoding() {
> -        return headerEncoding;
> -    }
> +    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++;
> +            }
> +        }
> +    }
>   
>       /**
> -     * Specifies the character encoding to be used when reading the headers of
> -     * individual parts. When not specified, or {@code null}, the platform
> -     * default encoding is used.
> +     * Reads {@code body-data} from the current {@code encapsulation} and discards it.
> +     * <p>
> +     * Use this method to skip encapsulations you don't need or don't understand.
>        *
> -     * @param encoding The encoding used to read part headers.
> +     * @return The amount of data discarded.
> +     *
> +     * @throws MalformedStreamException if the stream ends unexpectedly.
> +     * @throws IOException              if an i/o error occurs.
>        */
> -    public void setHeaderEncoding(final String encoding) {
> -        headerEncoding = encoding;
> +    public int discardBodyData() throws MalformedStreamException, IOException {
> +        return readBodyData(null);
>       }
>   
>       /**
> -     * Reads a byte from the {@code 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;
> +    }
> +
> +    /**
> +     * 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 The encoding used to read part headers.
> +     */
> +    public String getHeaderEncoding() {
> +        return headerEncoding;
> +    }
> +
> +    /**
> +     * Creates a new {@link ItemInputStream}.
> +     * @return A new instance of {@link ItemInputStream}.
> +     */
> +    public ItemInputStream newInputStream() {
> +        return new ItemInputStream();
> +    }
> +
> +    /**
> +     * Reads {@code body-data} from the current {@code encapsulation} and writes its contents into the output
> +     * {@code Stream}.
> +     * <p>
> +     * 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}).
> +     *
> +     * @param output The {@code Stream} to write data into. May be null, in which case this method is equivalent to
> +     *                   {@link #discardBodyData()}.
> +     *
> +     * @return the amount of data written.
> +     *
> +     * @throws MalformedStreamException if the stream ends unexpectedly.
> +     * @throws IOException              if an i/o error occurs.
> +     */
> +    public int readBodyData(final OutputStream output)
> +            throws MalformedStreamException, IOException {
> +        return (int) Streams.copy(newInputStream(), output, false); // Streams.copy closes the input stream
>       }
>   
>       /**
> @@ -434,69 +801,37 @@ public class MultipartStream {
>       }
>   
>       /**
> -     * <p>Changes the boundary token used for partitioning the stream.
> -     *
> -     * <p>This method allows single pass processing of nested multipart
> -     * streams.
> -     *
> -     * <p>The boundary token of the nested stream is {@code required}
> -     * to be of the same length as the boundary token in parent stream.
> -     *
> -     * <p>Restoring the parent stream boundary token after processing of a
> -     * nested stream is left to the application.
> +     * Reads a byte from the {@code buffer}, and refills it as
> +     * necessary.
>        *
> -     * @param boundary The boundary to be used for parsing of the nested
> -     *                 stream.
> +     * @return The next byte from the input stream.
>        *
> -     * @throws IllegalBoundaryException if the {@code boundary}
> -     *                                  has a different length than the one
> -     *                                  being currently parsed.
> -     */
> -    public void setBoundary(final 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.
> +     * @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++];
>       }
>   
>       /**
> -     * <p>Reads the {@code header-part} of the current
> -     * {@code encapsulation}.
> -     *
> -     * <p>Headers are returned verbatim to the input stream, including the
> -     * trailing {@code CRLF} marker. Parsing is left to the
> -     * application.
> -     *
> -     * <p><strong>TODO</strong> allow limiting maximum header size to
> -     * protect against abuse.
> +     * Reads the {@code header-part} of the current {@code encapsulation}.
> +     * <p>
> +     * Headers are returned verbatim to the input stream, including the trailing {@code CRLF} marker. Parsing is left to
> +     * the application.
> +     * <p>
> +     * <strong>TODO</strong> allow limiting maximum header size to protect against abuse.
>        *
>        * @return The {@code header-part} of the current encapsulation.
>        *
> @@ -518,7 +853,8 @@ public class MultipartStream {
>               } catch (final IOException e) {
>                   throw new MalformedStreamException("Stream ended unexpectedly");
>               }
> -            if (++size > HEADER_PART_SIZE_MAX) {
> +            size++;
> +            if (size > HEADER_PART_SIZE_MAX) {
>                   throw new MalformedStreamException(String.format(
>                           "Header section has more than %s bytes (maybe it is not properly terminated)",
>                           Integer.valueOf(HEADER_PART_SIZE_MAX)));
> @@ -530,7 +866,6 @@ public class MultipartStream {
>               }
>               baos.write(b);
>           }
> -
>           String headers;
>           if (headerEncoding != null) {
>               try {
> @@ -543,56 +878,41 @@ public class MultipartStream {
>           } else {
>               headers = baos.toString();
>           }
> -
>           return headers;
>       }
>   
>       /**
> -     * <p>Reads {@code body-data} from the current
> -     * {@code encapsulation} and writes its contents into the
> -     * output {@code Stream}.
> +     * Changes the boundary token used for partitioning the stream.
> +     * <p>
> +     * This method allows single pass processing of nested multipart streams.
> +     * <p>
> +     * The boundary token of the nested stream is {@code required} to be of the same length as the boundary token in
> +     * parent stream.
> +     * <p>
> +     * Restoring the parent stream boundary token after processing of a nested stream is left to the application.
>        *
> -     * <p>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}).
> +     * @param boundary The boundary to be used for parsing of the nested stream.
>        *
> -     * @param output The {@code Stream} to write data into. May
> -     *               be null, in which case this method is equivalent
> -     *               to {@link #discardBodyData()}.
> -     *
> -     * @return the amount of data written.
> -     *
> -     * @throws MalformedStreamException if the stream ends unexpectedly.
> -     * @throws IOException              if an i/o error occurs.
> -     */
> -    public int readBodyData(final OutputStream output)
> -            throws MalformedStreamException, IOException {
> -        return (int) Streams.copy(newInputStream(), output, false); // N.B. Streams.copy closes the input stream
> -    }
> -
> -    /**
> -     * Creates a new {@link ItemInputStream}.
> -     * @return A new instance of {@link ItemInputStream}.
> +     * @throws IllegalBoundaryException if the {@code boundary} has a different length than the one being currently
> +     *                                      parsed.
>        */
> -    public ItemInputStream newInputStream() {
> -        return new ItemInputStream();
> +    public void setBoundary(final 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();
>       }
>   
>       /**
> -     * <p> Reads {@code body-data} from the current
> -     * {@code encapsulation} and discards it.
> -     *
> -     * <p>Use this method to skip encapsulations you don't need or don't
> -     * understand.
> -     *
> -     * @return The amount of data discarded.
> +     * Specifies the character encoding to be used when reading the headers of
> +     * individual parts. When not specified, or {@code null}, the platform
> +     * default encoding is used.
>        *
> -     * @throws MalformedStreamException if the stream ends unexpectedly.
> -     * @throws IOException              if an i/o error occurs.
> +     * @param encoding The encoding used to read part headers.
>        */
> -    public int discardBodyData() throws MalformedStreamException, IOException {
> -        return readBodyData(null);
> +    public void setHeaderEncoding(final String encoding) {
> +        headerEncoding = encoding;
>       }
>   
>       /**
> @@ -627,361 +947,4 @@ public class MultipartStream {
>           }
>       }
>   
> -    /**
> -     * 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;
> -    }
> -
> -    /**
> -     * Searches for the {@code boundary} in the {@code buffer}
> -     * region delimited by {@code head} and {@code tail}.
> -     *
> -     * @return The position of the boundary found, counting from the
> -     *         beginning of the {@code buffer}, or {@code -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 {@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);
> -        }
> -
> -    }
> -
> -    /**
> -     * 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 {@code IllegalBoundaryException} with no
> -         * detail message.
> -         */
> -        public IllegalBoundaryException() {
> -        }
> -
> -        /**
> -         * Constructs an {@code IllegalBoundaryException} with
> -         * the specified detail message.
> -         *
> -         * @param message The detail message.
> -         */
> -        public IllegalBoundaryException(final 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;
> -            final 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(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;
> -        }
> -
> -        /**
> -         * 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(final 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(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;
> -        }
> -
> -        /**
> -         * 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 (;;) {
> -                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;
> -
> -                findSeparator();
> -                final 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/java/org/apache/tomcat/util/http/fileupload/ParameterParser.java b/java/org/apache/tomcat/util/http/fileupload/ParameterParser.java
> index 91a97aeae0..4404d9f719 100644
> --- a/java/org/apache/tomcat/util/http/fileupload/ParameterParser.java
> +++ b/java/org/apache/tomcat/util/http/fileupload/ParameterParser.java
> @@ -40,32 +40,32 @@ public class ParameterParser {
>       /**
>        * String to be parsed.
>        */
> -    private char[] chars = null;
> +    private char[] chars;
>   
>       /**
>        * Current position in the string.
>        */
> -    private int pos = 0;
> +    private int pos;
>   
>       /**
>        * Maximum position in the string.
>        */
> -    private int len = 0;
> +    private int len;
>   
>       /**
>        * Start of a token.
>        */
> -    private int i1 = 0;
> +    private int i1;
>   
>       /**
>        * End of a token.
>        */
> -    private int i2 = 0;
> +    private int i2;
>   
>       /**
>        * Whether names stored in the map should be converted to lower case.
>        */
> -    private boolean lowerCaseNames = false;
> +    private boolean lowerCaseNames;
>   
>       /**
>        * Default ParameterParser constructor.
> @@ -73,16 +73,6 @@ public class ParameterParser {
>       public ParameterParser() {
>       }
>   
> -    /**
> -     * Are there any characters left to parse?
> -     *
> -     * @return {@code true} if there are unparsed characters,
> -     *         {@code false} otherwise.
> -     */
> -    private boolean hasChar() {
> -        return this.pos < this.len;
> -    }
> -
>       /**
>        * A helper method to process the parsed token. This method removes
>        * leading and trailing blanks as well as enclosing quotation marks,
> @@ -94,18 +84,18 @@ public class ParameterParser {
>        */
>       private String getToken(final boolean quoted) {
>           // Trim leading white spaces
> -        while ((i1 < i2) && (Character.isWhitespace(chars[i1]))) {
> +        while (i1 < i2 && Character.isWhitespace(chars[i1])) {
>               i1++;
>           }
>           // Trim trailing white spaces
> -        while ((i2 > i1) && (Character.isWhitespace(chars[i2 - 1]))) {
> +        while (i2 > i1 && Character.isWhitespace(chars[i2 - 1])) {
>               i2--;
>           }
>           // Strip away quotation marks if necessary
>           if (quoted
> -            && ((i2 - i1) >= 2)
> -            && (chars[i1] == '"')
> -            && (chars[i2 - 1] == '"')) {
> +            && i2 - i1 >= 2
> +            && chars[i1] == '"'
> +            && chars[i2 - 1] == '"') {
>               i1++;
>               i2--;
>           }
> @@ -116,6 +106,28 @@ public class ParameterParser {
>           return result;
>       }
>   
> +    /**
> +     * Are there any characters left to parse?
> +     *
> +     * @return {@code true} if there are unparsed characters,
> +     *         {@code false} otherwise.
> +     */
> +    private boolean hasChar() {
> +        return pos < len;
> +    }
> +
> +    /**
> +     * Returns {@code true} if parameter names are to be converted to lower
> +     * case when name/value pairs are parsed.
> +     *
> +     * @return {@code true} if parameter names are to be
> +     * converted to lower case when name/value pairs are parsed.
> +     * Otherwise returns {@code false}
> +     */
> +    public boolean isLowerCaseNames() {
> +        return lowerCaseNames;
> +    }
> +
>       /**
>        * Tests if the given character is present in the array of characters.
>        *
> @@ -137,83 +149,83 @@ public class ParameterParser {
>       }
>   
>       /**
> -     * Parses out a token until any of the given terminators
> -     * is encountered.
> +     * Extracts a map of name/value pairs from the given array of
> +     * characters. Names are expected to be unique.
>        *
> -     * @param terminators the array of terminating characters. Any of these
> -     * characters when encountered signify the end of the token
> +     * @param charArray the array of characters that contains a sequence of
> +     * name/value pairs
> +     * @param separator the name/value pairs separator
>        *
> -     * @return the token
> +     * @return a map of name/value pairs
>        */
> -    private String parseToken(final char[] terminators) {
> -        char ch;
> -        i1 = pos;
> -        i2 = pos;
> -        while (hasChar()) {
> -            ch = chars[pos];
> -            if (isOneOf(ch, terminators)) {
> -                break;
> -            }
> -            i2++;
> -            pos++;
> +    public Map<String, String> parse(final char[] charArray, final char separator) {
> +        if (charArray == null) {
> +            return new HashMap<>();
>           }
> -        return getToken(false);
> +        return parse(charArray, 0, charArray.length, separator);
>       }
>   
>       /**
> -     * Parses out a token until any of the given terminators
> -     * is encountered outside the quotation marks.
> +     * Extracts a map of name/value pairs from the given array of characters. Names are expected to be unique.
>        *
> -     * @param terminators the array of terminating characters. Any of these
> -     * characters when encountered outside the quotation marks signify the end
> -     * of the token
> +     * @param charArray the array of characters that contains a sequence of name/value pairs
> +     * @param offset    the initial offset.
> +     * @param length    the length.
> +     * @param separator the name/value pairs separator
>        *
> -     * @return the token
> +     * @return a map of name/value pairs
>        */
> -    private String parseQuotedToken(final char[] terminators) {
> -        char ch;
> -        i1 = pos;
> -        i2 = pos;
> -        boolean quoted = false;
> -        boolean charEscaped = false;
> +    public Map<String, String> parse(final char[] charArray, final int offset, final int length, final char separator) {
> +        if (charArray == null) {
> +            return new HashMap<>();
> +        }
> +        final HashMap<String, String> params = new HashMap<>();
> +        chars = charArray.clone();
> +        pos = offset;
> +        len = length;
>           while (hasChar()) {
> -            ch = chars[pos];
> -            if (!quoted && isOneOf(ch, terminators)) {
> -                break;
> +            String paramName = parseToken(new char[] { '=', separator });
> +            String paramValue = null;
> +            if (hasChar() && charArray[pos] == '=') {
> +                pos++; // skip '='
> +                paramValue = parseQuotedToken(new char[] { separator });
> +                if (paramValue != null) {
> +                    try {
> +                        paramValue = RFC2231Utility.hasEncodedValue(paramName) ? RFC2231Utility.decodeText(paramValue)
> +                                : MimeUtility.decodeText(paramValue);
> +                    } catch (final UnsupportedEncodingException e) {
> +                        // let's keep the original value in this case
> +                    }
> +                }
>               }
> -            if (!charEscaped && ch == '"') {
> -                quoted = !quoted;
> +            if (hasChar() && charArray[pos] == separator) {
> +                pos++; // skip separator
> +            }
> +            if (paramName != null && !paramName.isEmpty()) {
> +                paramName = RFC2231Utility.stripDelimiter(paramName);
> +                if (lowerCaseNames) {
> +                    paramName = paramName.toLowerCase(Locale.ROOT);
> +                }
> +                params.put(paramName, paramValue);
>               }
> -            charEscaped = (!charEscaped && ch == '\\');
> -            i2++;
> -            pos++;
> -
>           }
> -        return getToken(true);
> +        return params;
>       }
>   
>       /**
> -     * Returns {@code true} if parameter names are to be converted to lower
> -     * case when name/value pairs are parsed.
> +     * Extracts a map of name/value pairs from the given string. Names are
> +     * expected to be unique.
>        *
> -     * @return {@code true} if parameter names are to be
> -     * converted to lower case when name/value pairs are parsed.
> -     * Otherwise returns {@code false}
> -     */
> -    public boolean isLowerCaseNames() {
> -        return this.lowerCaseNames;
> -    }
> -
> -    /**
> -     * Sets the flag if parameter names are to be converted to lower case when
> -     * name/value pairs are parsed.
> +     * @param str the string that contains a sequence of name/value pairs
> +     * @param separator the name/value pairs separator
>        *
> -     * @param b {@code true} if parameter names are to be
> -     * converted to lower case when name/value pairs are parsed.
> -     * {@code false} otherwise.
> +     * @return a map of name/value pairs
>        */
> -    public void setLowerCaseNames(final boolean b) {
> -        this.lowerCaseNames = b;
> +    public Map<String, String> parse(final String str, final char separator) {
> +        if (str == null) {
> +            return new HashMap<>();
> +        }
> +        return parse(str.toCharArray(), separator);
>       }
>   
>       /**
> @@ -245,96 +257,71 @@ public class ParameterParser {
>       }
>   
>       /**
> -     * Extracts a map of name/value pairs from the given string. Names are
> -     * expected to be unique.
> +     * Parses out a token until any of the given terminators
> +     * is encountered outside the quotation marks.
>        *
> -     * @param str the string that contains a sequence of name/value pairs
> -     * @param separator the name/value pairs separator
> +     * @param terminators the array of terminating characters. Any of these
> +     * characters when encountered outside the quotation marks signify the end
> +     * of the token
>        *
> -     * @return a map of name/value pairs
> +     * @return the token
>        */
> -    public Map<String, String> parse(final String str, final char separator) {
> -        if (str == null) {
> -            return new HashMap<>();
> +    private String parseQuotedToken(final char[] terminators) {
> +        char ch;
> +        i1 = pos;
> +        i2 = pos;
> +        boolean quoted = false;
> +        boolean charEscaped = false;
> +        while (hasChar()) {
> +            ch = chars[pos];
> +            if (!quoted && isOneOf(ch, terminators)) {
> +                break;
> +            }
> +            if (!charEscaped && ch == '"') {
> +                quoted = !quoted;
> +            }
> +            charEscaped = !charEscaped && ch == '\\';
> +            i2++;
> +            pos++;
> +
>           }
> -        return parse(str.toCharArray(), separator);
> +        return getToken(true);
>       }
>   
>       /**
> -     * Extracts a map of name/value pairs from the given array of
> -     * characters. Names are expected to be unique.
> +     * Parses out a token until any of the given terminators
> +     * is encountered.
>        *
> -     * @param charArray the array of characters that contains a sequence of
> -     * name/value pairs
> -     * @param separator the name/value pairs separator
> +     * @param terminators the array of terminating characters. Any of these
> +     * characters when encountered signify the end of the token
>        *
> -     * @return a map of name/value pairs
> +     * @return the token
>        */
> -    public Map<String, String> parse(final char[] charArray, final char separator) {
> -        if (charArray == null) {
> -            return new HashMap<>();
> +    private String parseToken(final char[] terminators) {
> +        char ch;
> +        i1 = pos;
> +        i2 = pos;
> +        while (hasChar()) {
> +            ch = chars[pos];
> +            if (isOneOf(ch, terminators)) {
> +                break;
> +            }
> +            i2++;
> +            pos++;
>           }
> -        return parse(charArray, 0, charArray.length, separator);
> +        return getToken(false);
>       }
>   
>       /**
> -     * Extracts a map of name/value pairs from the given array of
> -     * characters. Names are expected to be unique.
> -     *
> -     * @param charArray the array of characters that contains a sequence of
> -     * name/value pairs
> -     * @param offset - the initial offset.
> -     * @param length - the length.
> -     * @param separator the name/value pairs separator
> +     * Sets the flag if parameter names are to be converted to lower case when
> +     * name/value pairs are parsed.
>        *
> -     * @return a map of name/value pairs
> +     * @param b {@code true} if parameter names are to be
> +     * converted to lower case when name/value pairs are parsed.
> +     * {@code false} otherwise.
>        */
> -    public Map<String, String> parse(
> -        final char[] charArray,
> -        final int offset,
> -        final int length,
> -        final char separator) {
> -
> -        if (charArray == null) {
> -            return new HashMap<>();
> -        }
> -        final HashMap<String, String> params = new HashMap<>();
> -        this.chars = charArray.clone();
> -        this.pos = offset;
> -        this.len = length;
> -
> -        String paramName;
> -        String paramValue;
> -        while (hasChar()) {
> -            paramName = parseToken(new char[] {
> -                    '=', separator });
> -            paramValue = null;
> -            if (hasChar() && (charArray[pos] == '=')) {
> -                pos++; // skip '='
> -                paramValue = parseQuotedToken(new char[] {
> -                        separator });
> -
> -                if (paramValue != null) {
> -                    try {
> -                        paramValue = RFC2231Utility.hasEncodedValue(paramName) ? RFC2231Utility.decodeText(paramValue)
> -                                : MimeUtility.decodeText(paramValue);
> -                    } catch (final UnsupportedEncodingException e) {
> -                        // let's keep the original value in this case
> -                    }
> -                }
> -            }
> -            if (hasChar() && (charArray[pos] == separator)) {
> -                pos++; // skip separator
> -            }
> -            if ((paramName != null) && !paramName.isEmpty()) {
> -                paramName = RFC2231Utility.stripDelimiter(paramName);
> -                if (this.lowerCaseNames) {
> -                    paramName = paramName.toLowerCase(Locale.ENGLISH);
> -                }
> -                params.put(paramName, paramValue);
> -            }
> -        }
> -        return params;
> +    public void setLowerCaseNames(final boolean b) {
> +        lowerCaseNames = b;
>       }
>   
>   }
> diff --git a/java/org/apache/tomcat/util/http/fileupload/ProgressListener.java b/java/org/apache/tomcat/util/http/fileupload/ProgressListener.java
> index 931c99d1d8..c7f49c9095 100644
> --- a/java/org/apache/tomcat/util/http/fileupload/ProgressListener.java
> +++ b/java/org/apache/tomcat/util/http/fileupload/ProgressListener.java
> @@ -25,13 +25,13 @@ public interface ProgressListener {
>       /**
>        * Updates the listeners status information.
>        *
> -     * @param pBytesRead The total number of bytes, which have been read
> +     * @param bytesRead The total number of bytes, which have been read
>        *   so far.
> -     * @param pContentLength The total number of bytes, which are being
> +     * @param contentLength The total number of bytes, which are being
>        *   read. May be -1, if this number is unknown.
> -     * @param pItems The number of the field, which is currently being
> +     * @param items The number of the field, which is currently being
>        *   read. (0 = no item so far, 1 = first item is being read, ...)
>        */
> -    void update(long pBytesRead, long pContentLength, int pItems);
> +    void update(long bytesRead, long contentLength, int items);
>   
>   }
> diff --git a/java/org/apache/tomcat/util/http/fileupload/disk/DiskFileItem.java b/java/org/apache/tomcat/util/http/fileupload/disk/DiskFileItem.java
> index e35c1ea237..22fe3c18ab 100644
> --- a/java/org/apache/tomcat/util/http/fileupload/disk/DiskFileItem.java
> +++ b/java/org/apache/tomcat/util/http/fileupload/disk/DiskFileItem.java
> @@ -37,259 +37,175 @@ import org.apache.tomcat.util.http.fileupload.FileItem;
>   import org.apache.tomcat.util.http.fileupload.FileItemHeaders;
>   import org.apache.tomcat.util.http.fileupload.FileUploadException;
>   import org.apache.tomcat.util.http.fileupload.IOUtils;
> +import org.apache.tomcat.util.http.fileupload.InvalidFileNameException;
>   import org.apache.tomcat.util.http.fileupload.ParameterParser;
>   import org.apache.tomcat.util.http.fileupload.util.Streams;
>   
>   /**
> - * <p> The default implementation of the
> - * {@link org.apache.tomcat.util.http.fileupload.FileItem FileItem} interface.
> - *
> - * <p> After retrieving an instance of this class from a {@link
> - * org.apache.tomcat.util.http.fileupload.FileUpload FileUpload} instance (see
> + * The default implementation of the {@link org.apache.tomcat.util.http.fileupload.FileItem FileItem} interface.
> + * <p>
> + * After retrieving an instance of this class from a {@link DiskFileItemFactory} instance (see
>    * {@link org.apache.tomcat.util.http.fileupload.FileUpload
> - * #parseRequest(org.apache.tomcat.util.http.fileupload.RequestContext)}), 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.
> - *
> - * <p>Temporary files, which are created for file items, will be deleted when
> - * the associated request is recycled.</p>
> + * #parseRequest(org.apache.tomcat.util.http.fileupload.RequestContext)}), 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.
> + * <p>
> + * Temporary files, which are created for file items, will be deleted when the associated request is recycled.
> + * </p>
>    *
>    * @since FileUpload 1.1
>    */
> -public class DiskFileItem
> -    implements FileItem {
> +public class DiskFileItem implements FileItem {
>   
> -    // ----------------------------------------------------- Manifest constants
> +    /**
> +     * Counter used in unique identifier generation.
> +     */
> +    private static final AtomicInteger COUNTER = new AtomicInteger(0);
>   
>       /**
> -     * Default content charset to be used when no explicit charset
> -     * parameter is provided by the sender. Media subtypes of the
> -     * "text" type are defined to have a default charset value of
> -     * "ISO-8859-1" when received via HTTP.
> +     * Default content charset to be used when no explicit charset parameter is provided by the sender. Media subtypes
> +     * of the "text" type are defined to have a default charset value of "ISO-8859-1" when received via HTTP.
>        */
>       public static final String DEFAULT_CHARSET = "ISO-8859-1";
>   
> -    // ----------------------------------------------------------- Data members
> -
>       /**
>        * UID used in unique file name generation.
>        */
> -    private static final String UID =
> -            UUID.randomUUID().toString().replace('-', '_');
> +    private static final String UID = UUID.randomUUID().toString().replace('-', '_');
>   
>       /**
> -     * Counter used in unique identifier generation.
> +     * Returns an identifier that is unique within the class loader used to load this class, but does not have
> +     * random-like appearance.
> +     *
> +     * @return A String with the non-random looking instance identifier.
>        */
> -    private static final AtomicInteger COUNTER = new AtomicInteger(0);
> +    private static String getUniqueId() {
> +        final int limit = 100000000;
> +        final int current = COUNTER.getAndIncrement();
> +        String id = Integer.toString(current);
> +
> +        // 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());
> +        }
> +        return id;
> +    }
>   
>       /**
> -     * The name of the form field as provided by the browser.
> +     * Cached contents of the file.
>        */
> -    private String fieldName;
> +    private byte[] cachedContent;
>   
>       /**
> -     * The content type passed by the browser, or {@code 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 file name in the user's file system.
> +     * 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 {@code DiskFileItem} instance.
>        *
>        * @param fieldName     The name of the form field.
> -     * @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) {
> +     * @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 Files.newInputStream(dfos.getFile().toPath());
> -        }
> -
> -        if (cachedContent == null) {
> -            cachedContent = dfos.getData();
> -        }
> -        return new ByteArrayInputStream(cachedContent);
> -    }
> -
> -    /**
> -     * Returns the content type passed by the agent or {@code null} if
> -     * not defined.
> -     *
> -     * @return The content type passed by the agent or {@code null} if
> -     *         not defined.
> -     */
> -    @Override
> -    public String getContentType() {
> -        return contentType;
> -    }
> -
> -    /**
> -     * Returns the content charset passed by the agent or {@code null} if
> -     * not defined.
> -     *
> -     * @return The content charset passed by the agent or {@code null} if
> -     *         not defined.
> -     */
> -    public String getCharSet() {
> -        final ParameterParser parser = new ParameterParser();
> -        parser.setLowerCaseNames(true);
> -        // Parameter parser can handle null input
> -        final Map<String, String> params = parser.parse(getContentType(), ';');
> -        return params.get("charset");
> -    }
> -
> -    /**
> -     * Returns the original file name in the client's file system.
> -     *
> -     * @return The original file name in the client's file system.
> -     * @throws org.apache.tomcat.util.http.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.tomcat.util.http.fileupload.InvalidFileNameException#getName()}.
> -     */
> -    @Override
> -    public String getName() {
> -        return Streams.checkFileName(fileName);
> -    }
> -
> -    // ------------------------------------------------------- FileItem methods
> -
>       /**
> -     * 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.
> +     * Clears the cache.
>        */
> -    @Override
> -    public boolean isInMemory() {
> -        if (cachedContent != null) {
> -            return true;
> -        }
> -        return dfos.isInMemory();
> +    private void clear() {
> +        cachedContent = null; // NOPMD
>       }
>   
>       /**
> -     * Returns the size of the file.
> -     *
> -     * @return The size of the file, in bytes.
> +     * Deletes the underlying storage for a file item, including deleting any associated temporary disk file. This
> +     * method can be used to ensure that this is done at an earlier time, thus preserving system resources.
>        */
>       @Override
> -    public long getSize() {
> -        if (size >= 0) {
> -            return size;
> -        }
> -        if (cachedContent != null) {
> -            return cachedContent.length;
> -        }
> -        if (dfos.isInMemory()) {
> -            return dfos.getData().length;
> +    public void delete() {
> +        clear();
> +        final File outputFile = getStoreLocation();
> +        if (outputFile != null && !isInMemory() && outputFile.exists()) {
> +            if (!outputFile.delete()) {
> +                final String desc = "Cannot delete " + outputFile.toString();
> +                throw new UncheckedIOException(desc, new IOException(desc));
> +            }
>           }
> -        return dfos.getFile().length();
>       }
>   
>       /**
> -     * 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 ArithmeticException if the file {@code size} overflows an int
> +     * @throws ArithmeticException  if the file {@code size} overflows an int
>        */
>       @Override
>       public byte[] get() throws UncheckedIOException {
> @@ -311,132 +227,43 @@ public class DiskFileItem
>       }
>   
>       /**
> -     * 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.
> -     *
> -     * @param charset The charset to use.
> +     * Gets the content charset passed by the agent or {@code null} if not defined.
>        *
> -     * @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, IOException {
> -        return new String(get(), charset);
> +    public String getCharSet() {
> +        final ParameterParser parser = new ParameterParser();
> +        parser.setLowerCaseNames(true);
> +        // Parameter parser can handle null input
> +        final Map<String,String> params = parser.parse(getContentType(), ';');
> +        return params.get("charset");
>       }
>   
>       /**
> -     * Returns 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.
> -     *
> -     * <b>TODO</b> Consider making this method throw UnsupportedEncodingException.
> +     * Gets the content type passed by the agent or {@code null} if not defined.
>        *
> -     * @return The contents of the file, as a string.
> +     * @return The content type passed by the agent or {@code null} if not defined.
>        */
>       @Override
> -    public String getString() {
> -        try {
> -            final byte[] rawData = get();
> -            String charset = getCharSet();
> -            if (charset == null) {
> -                charset = defaultCharset;
> -            }
> -            return new String(rawData, charset);
> -        } catch (final IOException e) {
> -            return "";
> -        }
> +    public String getContentType() {
> +        return contentType;
>       }
>   
>       /**
> -     * 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.
> -     * <p>
> -     * 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.
> -     * <p>
> -     * This method is only guaranteed to work <em>once</em>, 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.
> -     *
> -     * @param file The {@code File} into which the uploaded item should
> -     *             be stored.
> +     * Gets the default charset for use when no explicit charset parameter is provided by the sender.
>        *
> -     * @throws Exception if an error occurs.
> -     */
> -    @Override
> -    public void write(final File file) throws Exception {
> -        if (isInMemory()) {
> -            try (OutputStream fout = Files.newOutputStream(file.toPath())) {
> -                fout.write(get());
> -            }
> -        } else {
> -            final File outputFile = getStoreLocation();
> -            if (outputFile == null) {
> -                /*
> -                 * For whatever reason we cannot write the
> -                 * file to disk.
> -                 */
> -                throw new FileUploadException(
> -                    "Cannot write uploaded file to disk!");
> -            }
> -            // 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.
> -             */
> -            if (file.exists() && !file.delete()) {
> -                throw new FileUploadException("Cannot write uploaded file to disk!");
> -            }
> -            if (!outputFile.renameTo(file)) {
> -                BufferedInputStream in = null;
> -                BufferedOutputStream out = null;
> -                try {
> -                    in = new BufferedInputStream(new FileInputStream(outputFile));
> -                    out = new BufferedOutputStream(new FileOutputStream(file));
> -                    IOUtils.copy(in, out);
> -                    out.close();
> -                } finally {
> -                    IOUtils.closeQuietly(in);
> -                    IOUtils.closeQuietly(out);
> -                }
> -            }
> -        }
> -    }
> -
> -    /**
> -     * Deletes the underlying storage for a file item, including deleting any associated temporary disk file.
> -     * 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;
> -        final File outputFile = getStoreLocation();
> -        if (outputFile != null && !isInMemory() && outputFile.exists()) {
> -            if (!outputFile.delete()) {
> -                final String desc = "Cannot delete " + outputFile.toString();
> -                throw new UncheckedIOException(desc, new IOException(desc));
> -            }
> -        }
> +    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(String)
> -     *
>        */
>       @Override
>       public String getFieldName() {
> @@ -444,55 +271,52 @@ public class DiskFileItem
>       }
>   
>       /**
> -     * 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(final String fieldName) {
> -        this.fieldName = fieldName;
> +    public FileItemHeaders getHeaders() {
> +        return headers;
>       }
>   
>       /**
> -     * Determines whether or not a {@code FileItem} instance represents
> -     * a simple form field.
> -     *
> -     * @return {@code true} if the instance represents a simple form
> -     *         field; {@code 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.getFile().toPath());
> +        }
> +        if (cachedContent == null) {
> +            cachedContent = dfos.getData();
> +        }
> +        return new ByteArrayInputStream(cachedContent);
>       }
>   
>       /**
> -     * Specifies whether or not a {@code FileItem} instance represents
> -     * a simple form field.
> -     *
> -     * @param state {@code true} if the instance represents a simple form
> -     *              field; {@code false} if it represents an uploaded file.
> +     * Gets the original file name in the client's file system.
>        *
> -     * @see #isFormField()
> +     * @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
> +     *                                      {@link org.apache.tomcat.util.http.fileupload.InvalidFileNameException#getName()}.
>        */
>       @Override
> -    public void setFormField(final 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.
> -     *
> -     * @return 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.
>        */
>       @Override
>       public OutputStream getOutputStream() {
> @@ -503,20 +327,32 @@ public class DiskFileItem
>           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 {@code FileItem}'s
> -     * data's temporary location on the disk. Note that for
> -     * {@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.
> +     * Gets the {@link java.io.File} object for the {@code FileItem}'s data's temporary location on the disk. Note
> +     * that for {@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 {@code 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) {
> @@ -528,16 +364,49 @@ public class DiskFileItem
>           return dfos.getFile();
>       }
>   
> -    // ------------------------------------------------------ Protected methods
> +    /**
> +     * 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.
> +     * <p>
> +     * <strong>TODO</strong> Consider making this method throw UnsupportedEncodingException.
> +     *
> +     * @return The contents of the file, as a string.
> +     */
> +    @Override
> +    public String getString() {
> +        final byte[] rawData = get();
> +        String charset = getCharSet();
> +        if (charset == null) {
> +            charset = defaultCharset;
> +        }
> +        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 {@code 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.
>        * <p>
> -     * <b>Note: Subclasses that override this method must ensure that they return the
> -     * same File each time.</b>
> +     * <b>Note: Subclasses that override this method must ensure that they return the same File each time.</b>
>        *
>        * @return The {@link java.io.File File} to be used for temporary storage.
>        */
> @@ -555,71 +424,138 @@ public class DiskFileItem
>           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;
> -        final 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 String.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(final 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.
> +     * <p>
> +     * 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.
> +     * <p>
> +     * This method is only guaranteed to work <em>once</em>, 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.
> +     *
> +     * @param file The {@code File} into which the uploaded item should be stored.
> +     *
> +     * @throws Exception if an error occurs.
>        */
> -    public void setDefaultCharset(final String charset) {
> -        defaultCharset = charset;
> +    @Override
> +    public void write(final File file) throws Exception {
> +        if (isInMemory()) {
> +            try (OutputStream fout = Files.newOutputStream(file.toPath())) {
> +                fout.write(get());
> +            }
> +        } else {
> +            final File outputFile = getStoreLocation();
> +            if (outputFile == null) {
> +                /*
> +                 * For whatever reason we cannot write the file to disk.
> +                 */
> +                throw new FileUploadException("Cannot write uploaded file to disk!");
> +            }
> +            // 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.
> +             */
> +            if (file.exists() && !file.delete()) {
> +                throw new FileUploadException("Cannot write uploaded file to disk!");
> +            }
> +            if (!outputFile.renameTo(file)) {
> +                BufferedInputStream in = null;
> +                BufferedOutputStream out = null;
> +                try {
> +                    in = new BufferedInputStream(new FileInputStream(outputFile));
> +                    out = new BufferedOutputStream(new FileOutputStream(file));
> +                    IOUtils.copy(in, out);
> +                    out.close();
> +                } finally {
> +                    IOUtils.closeQuietly(in);
> +                    IOUtils.closeQuietly(out);
> +                }
> +            }
> +        }
>       }
>   }
> diff --git a/java/org/apache/tomcat/util/http/fileupload/disk/DiskFileItemFactory.java b/java/org/apache/tomcat/util/http/fileupload/disk/DiskFileItemFactory.java
> index 8b0073da3f..06817f599b 100644
> --- a/java/org/apache/tomcat/util/http/fileupload/disk/DiskFileItemFactory.java
> +++ b/java/org/apache/tomcat/util/http/fileupload/disk/DiskFileItemFactory.java
> @@ -39,7 +39,7 @@ import org.apache.tomcat.util.http.fileupload.FileItemFactory;
>    *       {@code System.getProperty("java.io.tmpdir")}.</li>
>    * </ul>
>    * <p>
> - * <b>NOTE</b>: Files are created in the system default temp directory with
> + * <strong>NOTE</strong>: 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
> @@ -58,15 +58,11 @@ import org.apache.tomcat.util.http.fileupload.FileItemFactory;
>    */
>   public class DiskFileItemFactory implements FileItemFactory {
>   
> -    // ----------------------------------------------------- Manifest constants
> -
>       /**
>        * The default threshold above which uploads will be stored on disk.
>        */
>       public static final int DEFAULT_SIZE_THRESHOLD = 10240;
>   
> -    // ----------------------------------------------------- Instance Variables
> -
>       /**
>        * The directory in which uploaded files will be stored, if stored on disk.
>        */
> @@ -83,8 +79,6 @@ public class DiskFileItemFactory implements FileItemFactory {
>        */
>       private String defaultCharset = DiskFileItem.DEFAULT_CHARSET;
>   
> -    // ----------------------------------------------------------- Constructors
> -
>       /**
>        * Constructs an unconfigured instance of this class. The resulting factory
>        * may be configured by calling the appropriate setter methods.
> @@ -108,36 +102,53 @@ public class DiskFileItemFactory implements FileItemFactory {
>           this.repository = repository;
>       }
>   
> -    // ------------------------------------------------------------- Properties
> -
>       /**
> -     * Returns the directory used to temporarily store files that are larger
> -     * than the configured size threshold.
> -     *
> -     * @return The directory in which temporary files will be located.
> +     * Create a new {@link DiskFileItem}
> +     * instance from the supplied parameters and the local factory
> +     * configuration.
>        *
> -     * @see #setRepository(java.io.File)
> +     * @param fieldName   The name of the form field.
> +     * @param contentType The content type of the form field.
> +     * @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.
>        */
> -    public File getRepository() {
> -        return repository;
> +    @Override
> +    public FileItem createItem(final String fieldName, final String contentType,
> +            final boolean isFormField, final String fileName) {
> +        final DiskFileItem result = new DiskFileItem(fieldName, contentType,
> +                isFormField, fileName, sizeThreshold, repository);
> +        result.setDefaultCharset(defaultCharset);
> +        return result;
>       }
>   
>       /**
> -     * Sets the directory used to temporarily store files that are larger
> +     * Gets the default charset for use when no explicit charset
> +     * parameter is provided by the sender.
> +     * @return the default charset
> +     */
> +    public String getDefaultCharset() {
> +        return defaultCharset;
> +    }
> +
> +    /**
> +     * Gets 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.
> +     * @return The directory in which temporary files will be located.
>        *
> -     * @see #getRepository()
> +     * @see #setRepository(java.io.File)
>        *
>        */
> -    public void setRepository(final File repository) {
> -        this.repository = repository;
> +    public File getRepository() {
> +        return repository;
>       }
>   
>       /**
> -     * Returns the size threshold beyond which files are written directly to
> +     * Gets the size threshold beyond which files are written directly to
>        * disk. The default value is 10240 bytes.
>        *
>        * @return The size threshold, in bytes.
> @@ -149,57 +160,37 @@ public class DiskFileItemFactory implements FileItemFactory {
>       }
>   
>       /**
> -     * Sets the size threshold beyond which files are written directly to disk.
> -     *
> -     * @param sizeThreshold The size threshold, in bytes.
> -     *
> -     * @see #getSizeThreshold()
> +     * Sets the default charset for use when no explicit charset
> +     * parameter is provided by the sender.
>        *
> +     * @param charset the default charset
>        */
> -    public void setSizeThreshold(final int sizeThreshold) {
> -        this.sizeThreshold = sizeThreshold;
> +    public void setDefaultCharset(final String charset) {
> +        this.defaultCharset = charset;
>       }
>   
> -    // --------------------------------------------------------- Public Methods
> -
>       /**
> -     * Create a new {@link DiskFileItem}
> -     * instance from the supplied parameters and the local factory
> -     * configuration.
> +     * Sets the directory used to temporarily store files that are larger
> +     * than the configured size threshold.
>        *
> -     * @param fieldName   The name of the form field.
> -     * @param contentType The content type of the form field.
> -     * @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.
> +     * @param repository The directory in which temporary files will be located.
> +     *
> +     * @see #getRepository()
>        *
> -     * @return The newly created file item.
> -     */
> -    @Override
> -    public FileItem createItem(final String fieldName, final String contentType,
> -            final boolean isFormField, final String fileName) {
> -        final DiskFileItem result = new DiskFileItem(fieldName, contentType,
> -                isFormField, fileName, sizeThreshold, repository);
> -        result.setDefaultCharset(defaultCharset);
> -        return result;
> -    }
> -
> -    /**
> -     * Returns the default charset for use when no explicit charset
> -     * parameter is provided by the sender.
> -     * @return the default charset
>        */
> -    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(final String pCharset) {
> -        defaultCharset = pCharset;
> +    public void setSizeThreshold(final int sizeThreshold) {
> +        this.sizeThreshold = sizeThreshold;
>       }
>   }
> diff --git a/java/org/apache/tomcat/util/http/fileupload/package-info.java b/java/org/apache/tomcat/util/http/fileupload/package-info.java
> index dee2bb19fc..91c25bc5f0 100644
> --- a/java/org/apache/tomcat/util/http/fileupload/package-info.java
> +++ b/java/org/apache/tomcat/util/http/fileupload/package-info.java
> @@ -16,8 +16,8 @@
>    */
>   
>   /**
> - * <p><b>NOTE:</b> This code has been copied from commons-fileupload trunk
> - * 1.3 and commons-io 1.4 and package renamed to avoid clashes with
> + * <p><b>NOTE:</b> This code has been copied from commons-fileupload
> + * 1.x and commons-io 1.4 and package renamed to avoid clashes with
>    * any web apps that may wish to use these libraries.
>    * </p>
>    * <p>
> diff --git a/java/org/apache/tomcat/util/http/fileupload/servlet/ServletRequestContext.java b/java/org/apache/tomcat/util/http/fileupload/servlet/ServletRequestContext.java
> index 90f05d1f78..eb6c62a3a1 100644
> --- a/java/org/apache/tomcat/util/http/fileupload/servlet/ServletRequestContext.java
> +++ b/java/org/apache/tomcat/util/http/fileupload/servlet/ServletRequestContext.java
> @@ -32,15 +32,11 @@ import org.apache.tomcat.util.http.fileupload.UploadContext;
>    */
>   public class ServletRequestContext implements UploadContext {
>   
> -    // ----------------------------------------------------- Instance Variables
> -
>       /**
>        * The request for which the context is being provided.
>        */
>       private final HttpServletRequest request;
>   
> -    // ----------------------------------------------------------- Constructors
> -
>       /**
>        * Construct a context for this request.
>        *
> @@ -50,7 +46,22 @@ public class ServletRequestContext implements UploadContext {
>           this.request = request;
>       }
>   
> -    // --------------------------------------------------------- Public Methods
> +    /**
> +     * Retrieve the content length of the request.
> +     *
> +     * @return The content length of the request.
> +     * @since FileUpload 1.3
> +     */
> +    @Override
> +    public long contentLength() {
> +        long size;
> +        try {
> +            size = Long.parseLong(request.getHeader(FileUploadBase.CONTENT_LENGTH));
> +        } catch (final NumberFormatException e) {
> +            size = request.getContentLength();
> +        }
> +        return size;
> +    }
>   
>       /**
>        * Retrieve the character encoding for the request.
> @@ -72,23 +83,6 @@ public class ServletRequestContext implements UploadContext {
>           return request.getContentType();
>       }
>   
> -    /**
> -     * Retrieve the content length of the request.
> -     *
> -     * @return The content length of the request.
> -     * @since FileUpload 1.3
> -     */
> -    @Override
> -    public long contentLength() {
> -        long size;
> -        try {
> -            size = Long.parseLong(request.getHeader(FileUploadBase.CONTENT_LENGTH));
> -        } catch (final NumberFormatException e) {
> -            size = request.getContentLength();
> -        }
> -        return size;
> -    }
> -
>       /**
>        * Retrieve the input stream for the request.
>        *
> @@ -109,7 +103,7 @@ public class ServletRequestContext implements UploadContext {
>       @Override
>       public String toString() {
>           return String.format("ContentLength=%s, ContentType=%s",
> -                Long.valueOf(this.contentLength()), this.getContentType());
> +                Long.valueOf(contentLength()), getContentType());
>       }
>   
>   }
> diff --git a/java/org/apache/tomcat/util/http/fileupload/util/FileItemHeadersImpl.java b/java/org/apache/tomcat/util/http/fileupload/util/FileItemHeadersImpl.java
> index a70345ff43..d4e20ece7f 100644
> --- a/java/org/apache/tomcat/util/http/fileupload/util/FileItemHeadersImpl.java
> +++ b/java/org/apache/tomcat/util/http/fileupload/util/FileItemHeadersImpl.java
> @@ -45,9 +45,29 @@ public class FileItemHeadersImpl implements FileItemHeaders, Serializable {
>        */
>       private final Map<String, List<String>> headerNameToValueListMap = new LinkedHashMap<>();
>   
> +    /**
> +     * Constructs a new instance.
> +     */
> +    public FileItemHeadersImpl() {
> +        // empty
> +    }
> +
> +    /**
> +     * Method to add header values to this instance.
> +     *
> +     * @param name name of this header
> +     * @param value value of this header
> +     */
> +    public synchronized void addHeader(final String name, final String value) {
> +        final String nameLower = name.toLowerCase(Locale.ROOT);
> +        final List<String> headerValueList = headerNameToValueListMap.
> +                computeIfAbsent(nameLower, k -> new ArrayList<>());
> +        headerValueList.add(value);
> +    }
> +
>       @Override
>       public String getHeader(final String name) {
> -        final String nameLower = name.toLowerCase(Locale.ENGLISH);
> +        final String nameLower = name.toLowerCase(Locale.ROOT);
>           final List<String> headerValueList = headerNameToValueListMap.get(nameLower);
>           if (null == headerValueList) {
>               return null;
> @@ -62,7 +82,7 @@ public class FileItemHeadersImpl implements FileItemHeaders, Serializable {
>   
>       @Override
>       public Iterator<String> getHeaders(final String name) {
> -        final String nameLower = name.toLowerCase(Locale.ENGLISH);
> +        final String nameLower = name.toLowerCase(Locale.ROOT);
>           List<String> headerValueList = headerNameToValueListMap.get(nameLower);
>           if (null == headerValueList) {
>               headerValueList = Collections.emptyList();
> @@ -70,17 +90,4 @@ public class FileItemHeadersImpl implements FileItemHeaders, Serializable {
>           return headerValueList.iterator();
>       }
>   
> -    /**
> -     * Method to add header values to this instance.
> -     *
> -     * @param name name of this header
> -     * @param value value of this header
> -     */
> -    public synchronized void addHeader(final String name, final String value) {
> -        final String nameLower = name.toLowerCase(Locale.ENGLISH);
> -        final List<String> headerValueList = headerNameToValueListMap.
> -                computeIfAbsent(nameLower, k -> new ArrayList<>());
> -        headerValueList.add(value);
> -    }
> -
>   }
> diff --git a/java/org/apache/tomcat/util/http/fileupload/util/LimitedInputStream.java b/java/org/apache/tomcat/util/http/fileupload/util/LimitedInputStream.java
> index def25ca125..add304c16a 100644
> --- a/java/org/apache/tomcat/util/http/fileupload/util/LimitedInputStream.java
> +++ b/java/org/apache/tomcat/util/http/fileupload/util/LimitedInputStream.java
> @@ -45,26 +45,14 @@ public abstract class LimitedInputStream extends FilterInputStream implements Cl
>        * Creates a new instance.
>        *
>        * @param inputStream The input stream, which shall be limited.
> -     * @param pSizeMax The limit; no more than this number of bytes
> +     * @param sizeMax The limit; no more than this number of bytes
>        *   shall be returned by the source stream.
>        */
> -    public LimitedInputStream(final InputStream inputStream, final long pSizeMax) {
> +    public LimitedInputStream(final InputStream inputStream, final long sizeMax) {
>           super(inputStream);
> -        sizeMax = pSizeMax;
> +        this.sizeMax = sizeMax;
>       }
>   
> -    /**
> -     * Called to indicate, that the input streams limit has
> -     * been exceeded.
> -     *
> -     * @param pSizeMax The input streams limit, in bytes.
> -     * @param pCount The actual number of bytes.
> -     * @throws IOException The called method is expected
> -     *   to raise an IOException.
> -     */
> -    protected abstract void raiseError(long pSizeMax, long pCount)
> -            throws IOException;
> -
>       /**
>        * Called to check, whether the input streams
>        * limit is reached.
> @@ -77,6 +65,43 @@ public abstract class LimitedInputStream extends FilterInputStream implements Cl
>           }
>       }
>   
> +    /**
> +     * Closes this input stream and releases any system resources
> +     * associated with the stream.
> +     * This
> +     * method simply performs {@code 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();
> +    }
> +
> +    /**
> +     * 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;
> +    }
> +
> +    /**
> +     * Called to indicate, that the input streams limit has
> +     * been exceeded.
> +     *
> +     * @param sizeMax The input streams limit, in bytes.
> +     * @param count The actual number of bytes.
> +     * @throws IOException The called method is expected
> +     *   to raise an IOException.
> +     */
> +    protected abstract void raiseError(long sizeMax, long count) throws IOException;
> +
>       /**
>        * Reads the next byte of data from this input stream. The value
>        * byte is returned as an {@code int} in the range
> @@ -137,30 +162,4 @@ public abstract class LimitedInputStream extends FilterInputStream implements Cl
>           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 {@code 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/java/org/apache/tomcat/util/http/fileupload/util/Streams.java b/java/org/apache/tomcat/util/http/fileupload/util/Streams.java
> index 7289d06665..ecf82c25d0 100644
> --- a/java/org/apache/tomcat/util/http/fileupload/util/Streams.java
> +++ b/java/org/apache/tomcat/util/http/fileupload/util/Streams.java
> @@ -27,14 +27,6 @@ import org.apache.tomcat.util.http.fileupload.InvalidFileNameException;
>    */
>   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)}.
> @@ -42,10 +34,41 @@ public final class Streams {
>       public static final int DEFAULT_BUFFER_SIZE = 8192;
>   
>       /**
> +     * Checks, whether the given file name is valid in the sense,
> +     * that it doesn't contain any NUL characters. If the file name
> +     * is valid, it will be returned without any modifications. Otherwise,
> +     * an {@link InvalidFileNameException} is raised.
> +     *
> +     * @param fileName The file name to check
> +     * @return Unmodified file name, if valid.
> +     * @throws InvalidFileNameException The file name was found to be invalid.
> +     */
> +    public static String checkFileName(final String fileName) {
> +        if (fileName != null  &&  fileName.indexOf('\u0000') != -1) {
> +            // fileName.replace("\u0000", "\\0")
> +            final StringBuilder sb = new StringBuilder();
> +            for (int i = 0;  i < fileName.length();  i++) {
> +                final char c = fileName.charAt(i);
> +                switch (c) {
> +                    case 0:
> +                        sb.append("\\0");
> +                        break;
> +                    default:
> +                        sb.append(c);
> +                        break;
> +                }
> +            }
> +            throw new InvalidFileNameException(fileName,
> +                    "Invalid file name: " + sb);
> +        }
> +        return fileName;
> +    }
> +
> +     /**
>        * Copies the contents of the given {@link InputStream}
>        * to the given {@link OutputStream}. Shortcut for
>        * <pre>
> -     *   copy(pInputStream, pOutputStream, new byte[8192]);
> +     *   copy(pInputStream, outputStream, new byte[8192]);
>        * </pre>
>        *
>        * @param inputStream The input stream, which is being read.
> @@ -116,35 +139,12 @@ public final class Streams {
>           }
>       }
>   
> -    /**
> -     * Checks, whether the given file name is valid in the sense,
> -     * that it doesn't contain any NUL characters. If the file name
> -     * is valid, it will be returned without any modifications. Otherwise,
> -     * an {@link InvalidFileNameException} is raised.
> -     *
> -     * @param fileName The file name to check
> -     * @return Unmodified file name, if valid.
> -     * @throws InvalidFileNameException The file name was found to be invalid.
> +   /**
> +     * Private constructor, to prevent instantiation.
> +     * This class has only static methods.
>        */
> -    public static String checkFileName(final String fileName) {
> -        if (fileName != null  &&  fileName.indexOf('\u0000') != -1) {
> -            // pFileName.replace("\u0000", "\\0")
> -            final StringBuilder sb = new StringBuilder();
> -            for (int i = 0;  i < fileName.length();  i++) {
> -                final char c = fileName.charAt(i);
> -                switch (c) {
> -                    case 0:
> -                        sb.append("\\0");
> -                        break;
> -                    default:
> -                        sb.append(c);
> -                        break;
> -                }
> -            }
> -            throw new InvalidFileNameException(fileName,
> -                    "Invalid file name: " + sb);
> -        }
> -        return fileName;
> +    private Streams() {
> +        // Does nothing
>       }
>   
>   }
> diff --git a/java/org/apache/tomcat/util/http/fileupload/util/mime/MimeUtility.java b/java/org/apache/tomcat/util/http/fileupload/util/mime/MimeUtility.java
> index 7debaf915e..461b00b354 100644
> --- a/java/org/apache/tomcat/util/http/fileupload/util/mime/MimeUtility.java
> +++ b/java/org/apache/tomcat/util/http/fileupload/util/mime/MimeUtility.java
> @@ -75,13 +75,6 @@ public final class MimeUtility {
>           MIME2JAVA.put("x-us-ascii", "ISO-8859-1");
>       }
>   
> -    /**
> -     * Hidden constructor, this class must not be instantiated.
> -     */
> -    private MimeUtility() {
> -        // do nothing
> -    }
> -
>       /**
>        * Decode a string of text obtained from a mail header into
>        * its proper form.  The text generally will consist of a
> @@ -207,7 +200,7 @@ public final class MimeUtility {
>           }
>   
>           // pull out the character set information (this is the MIME name at this point).
> -        final String charset = word.substring(2, charsetPos).toLowerCase(Locale.ENGLISH);
> +        final String charset = word.substring(2, charsetPos).toLowerCase(Locale.ROOT);
>   
>           // now pull out the encoding token the same way.
>           final int encodingPos = word.indexOf('?', charsetPos + 1);
> @@ -266,7 +259,7 @@ public final class MimeUtility {
>               return null;
>           }
>   
> -        final String mappedCharset = MIME2JAVA.get(charset.toLowerCase(Locale.ENGLISH));
> +        final String mappedCharset = MIME2JAVA.get(charset.toLowerCase(Locale.ROOT));
>           // if there is no mapping, then the original name is used.  Many of the MIME character set
>           // names map directly back into Java.  The reverse isn't necessarily true.
>           if (mappedCharset == null) {
> @@ -275,4 +268,11 @@ public final class MimeUtility {
>           return mappedCharset;
>       }
>   
> +    /**
> +     * Hidden constructor, this class must not be instantiated.
> +     */
> +    private MimeUtility() {
> +        // do nothing
> +    }
> +
>   }
> diff --git a/java/org/apache/tomcat/util/http/fileupload/util/mime/ParseException.java b/java/org/apache/tomcat/util/http/fileupload/util/mime/ParseException.java
> index 72e9896fe6..a49fe432a7 100644
> --- a/java/org/apache/tomcat/util/http/fileupload/util/mime/ParseException.java
> +++ b/java/org/apache/tomcat/util/http/fileupload/util/mime/ParseException.java
> @@ -17,6 +17,8 @@
>   package org.apache.tomcat.util.http.fileupload.util.mime;
>   
>   /**
> + * Thrown for a parsing problem.
> + *
>    * @since FileUpload 1.3
>    */
>   final class ParseException extends Exception {
> diff --git a/java/org/apache/tomcat/util/http/fileupload/util/mime/QuotedPrintableDecoder.java b/java/org/apache/tomcat/util/http/fileupload/util/mime/QuotedPrintableDecoder.java
> index 6a94f3c202..d1caac1a57 100644
> --- a/java/org/apache/tomcat/util/http/fileupload/util/mime/QuotedPrintableDecoder.java
> +++ b/java/org/apache/tomcat/util/http/fileupload/util/mime/QuotedPrintableDecoder.java
> @@ -25,17 +25,35 @@ import java.io.OutputStream;
>   final class QuotedPrintableDecoder {
>   
>       /**
> -     * The shift value required to create the upper nibble
> -     * from the first of 2 byte values converted from ascii hex.
> +     * Carriage return character '{@value}'.
>        */
> -    private static final int UPPER_NIBBLE_SHIFT = Byte.SIZE / 2;
> +    private static final char CR = '\r';
>   
>       /**
> -     * Hidden constructor, this class must not be instantiated.
> +     * Equal character '{@value}'.
>        */
> -    private QuotedPrintableDecoder() {
> -        // do nothing
> -    }
> +    private static final char EQUAL = '=';
> +
> +    /**
> +     * Line feed character '{@value}'.
> +     */
> +    private static final char LF = '\n';
> +
> +    /**
> +     * Space character '{@value}'.
> +     */
> +    private static final char SP = ' ';
> +
> +    /**
> +     * Underscore character '{@value}'.
> +     */
> +    private static final char UNDERSCORE = '_';
> +
> +    /**
> +     * The shift value required to create the upper nibble
> +     * from the first of 2 byte values converted from ASCII hex.
> +     */
> +    private static final int UPPER_NIBBLE_SHIFT = Byte.SIZE / 2;
>   
>       /**
>        * Decode the encoded byte data writing it to the given output stream.
> @@ -56,9 +74,9 @@ final class QuotedPrintableDecoder {
>               final byte ch = data[off++];
>   
>               // space characters were translated to '_' on encode, so we need to translate them back.
> -            if (ch == '_') {
> -                out.write(' ');
> -            } else if (ch == '=') {
> +            if (ch == UNDERSCORE) {
> +                out.write(SP);
> +            } else if (ch == EQUAL) {
>                   // we found an encoded character.  Reduce the 3 char sequence to one.
>                   // but first, make sure we have two characters to work with.
>                   if (off + 1 >= endOffset) {
> @@ -69,8 +87,8 @@ final class QuotedPrintableDecoder {
>                   final byte b2 = data[off++];
>   
>                   // we've found an encoded carriage return.  The next char needs to be a newline
> -                if (b1 == '\r') {
> -                    if (b2 != '\n') {
> +                if (b1 == CR) {
> +                    if (b2 != LF) {
>                           throw new IOException("Invalid quoted printable encoding; CR must be followed by LF");
>                       }
>                       // this was a soft linebreak inserted by the encoding.  We just toss this away
> @@ -79,7 +97,7 @@ final class QuotedPrintableDecoder {
>                       // this is a hex pair we need to convert back to a single byte.
>                       final int c1 = hexToBinary(b1);
>                       final int c2 = hexToBinary(b2);
> -                    out.write((c1 << UPPER_NIBBLE_SHIFT) | c2);
> +                    out.write(c1 << UPPER_NIBBLE_SHIFT | c2);
>                       // 3 bytes in, one byte out
>                       bytesWritten++;
>                   }
> @@ -96,7 +114,7 @@ final class QuotedPrintableDecoder {
>       /**
>        * Convert a hex digit to the binary value it represents.
>        *
> -     * @param b the ascii hex byte to convert (0-0, A-F, a-f)
> +     * @param b the ASCII hex byte to convert (0-0, A-F, a-f)
>        * @return the int value of the hex byte, 0-15
>        * @throws IOException if the byte is not a valid hex digit.
>        */
> @@ -109,4 +127,11 @@ final class QuotedPrintableDecoder {
>           return i;
>       }
>   
> +    /**
> +     * Hidden constructor, this class must not be instantiated.
> +     */
> +    private QuotedPrintableDecoder() {
> +        // do nothing
> +    }
> +
>   }
> diff --git a/java/org/apache/tomcat/util/http/fileupload/util/mime/RFC2231Utility.java b/java/org/apache/tomcat/util/http/fileupload/util/mime/RFC2231Utility.java
> index 8a522a313f..c1a7ba0d0c 100644
> --- a/java/org/apache/tomcat/util/http/fileupload/util/mime/RFC2231Utility.java
> +++ b/java/org/apache/tomcat/util/http/fileupload/util/mime/RFC2231Utility.java
> @@ -30,6 +30,12 @@ import java.io.UnsupportedEncodingException;
>    * @see <a href="https://tools.ietf.org/html/rfc5987">RFC 5987</a>
>    */
>   public final class RFC2231Utility {
> +
> +    /**
> +     * Percent character '{@value}'.
> +     */
> +    private static final char PERCENT = '%';
> +
>       /**
>        * The Hexadecimal values char array.
>        */
> @@ -55,54 +61,19 @@ public final class RFC2231Utility {
>           }
>       }
>   
> -    /**
> -     * Private constructor so that no instances can be created. This class
> -     * contains only static utility methods.
> -     */
> -    private RFC2231Utility() {
> -    }
> -
> -    /**
> -     * Checks if Asterisk (*) at the end of parameter name to indicate,
> -     * if it has charset and language information to decode the value.
> -     * @param paramName The parameter, which is being checked.
> -     * @return {@code true}, if encoded as per RFC 2231, {@code false} otherwise
> -     */
> -    public static boolean hasEncodedValue(final String paramName) {
> -        if (paramName != null) {
> -            return paramName.lastIndexOf('*') == (paramName.length() - 1);
> -        }
> -        return false;
> -    }
> -
> -    /**
> -     * If {@code paramName} has Asterisk (*) at the end, it will be stripped off,
> -     * else the passed value will be returned.
> -     * @param paramName The parameter, which is being inspected.
> -     * @return stripped {@code paramName} of Asterisk (*), if RFC2231 encoded
> -     */
> -    public static String stripDelimiter(final String paramName) {
> -        if (hasEncodedValue(paramName)) {
> -            final StringBuilder paramBuilder = new StringBuilder(paramName);
> -            paramBuilder.deleteCharAt(paramName.lastIndexOf('*'));
> -            return paramBuilder.toString();
> -        }
> -        return paramName;
> -    }
> -
>       /**
>        * Decode a string of text obtained from a HTTP header as per RFC 2231
>        * <p>
> -     * <b>Eg 1.</b> {@code us-ascii'en-us'This%20is%20%2A%2A%2Afun%2A%2A%2A}
> +     * <strong>Eg 1.</strong> {@code us-ascii'en-us'This%20is%20%2A%2A%2Afun%2A%2A%2A}
>        * will be decoded to {@code This is ***fun***}
>        * <p>
> -     * <b>Eg 2.</b> {@code iso-8859-1'en'%A3%20rate}
> +     * <strong>Eg 2.</strong> {@code iso-8859-1'en'%A3%20rate}
>        * will be decoded to {@code £ rate}
>        * <p>
> -     * <b>Eg 3.</b> {@code UTF-8''%c2%a3%20and%20%e2%82%ac%20rates}
> +     * <strong>Eg 3.</strong> {@code UTF-8''%c2%a3%20and%20%e2%82%ac%20rates}
>        * will be decoded to {@code £ and € rates}
>        *
> -     * @param encodedText - Text to be decoded has a format of {@code <charset>'<language>'<encoded_value>}
> +     * @param encodedText   Text to be decoded has a format of {@code <charset>'<language>'<encoded_value>}
>        * and ASCII only
>        * @return Decoded text based on charset encoding
>        * @throws UnsupportedEncodingException The requested character set wasn't found.
> @@ -124,8 +95,9 @@ public final class RFC2231Utility {
>       }
>   
>       /**
> -     * Convert {@code text} to their corresponding Hex value.
> -     * @param text - ASCII text input
> +     * Converts {@code text} to their corresponding Hex value.
> +     *
> +     * @param text ASCII text input
>        * @return Byte array of characters decoded from ASCII table
>        */
>       private static byte[] fromHex(final String text) {
> @@ -133,13 +105,13 @@ public final class RFC2231Utility {
>           final ByteArrayOutputStream out = new ByteArrayOutputStream(text.length());
>           for (int i = 0; i < text.length();) {
>               final char c = text.charAt(i++);
> -            if (c == '%') {
> +            if (c == PERCENT) {
>                   if (i > text.length() - 2) {
>                       break; // unterminated sequence
>                   }
>                   final byte b1 = HEX_DECODE[text.charAt(i++) & MASK];
>                   final byte b2 = HEX_DECODE[text.charAt(i++) & MASK];
> -                out.write((b1 << shift) | b2);
> +                out.write(b1 << shift | b2);
>               } else {
>                   out.write((byte) c);
>               }
> @@ -151,4 +123,39 @@ public final class RFC2231Utility {
>           // good enough for standard values
>           return mimeCharset;
>       }
> +
> +    /**
> +     * Checks if Asterisk (*) at the end of parameter name to indicate,
> +     * if it has charset and language information to decode the value.
> +     * @param paramName The parameter, which is being checked.
> +     * @return {@code true}, if encoded as per RFC 2231, {@code false} otherwise
> +     */
> +    public static boolean hasEncodedValue(final String paramName) {
> +        if (paramName != null) {
> +            return paramName.lastIndexOf('*') == paramName.length() - 1;
> +        }
> +        return false;
> +    }
> +
> +    /**
> +     * If {@code paramName} has Asterisk (*) at the end, it will be stripped off,
> +     * else the passed value will be returned.
> +     * @param paramName The parameter, which is being inspected.
> +     * @return stripped {@code paramName} of Asterisk (*), if RFC2231 encoded
> +     */
> +    public static String stripDelimiter(final String paramName) {
> +        if (hasEncodedValue(paramName)) {
> +            final StringBuilder paramBuilder = new StringBuilder(paramName);
> +            paramBuilder.deleteCharAt(paramName.lastIndexOf('*'));
> +            return paramBuilder.toString();
> +        }
> +        return paramName;
> +    }
> +
> +    /**
> +     * Private constructor so that no instances can be created. This class
> +     * contains only static utility methods.
> +     */
> +    private RFC2231Utility() {
> +    }
>   }
> diff --git a/webapps/docs/changelog.xml b/webapps/docs/changelog.xml
> index 9666aa852c..4aa28e1847 100644
> --- a/webapps/docs/changelog.xml
> +++ b/webapps/docs/changelog.xml
> @@ -277,6 +277,10 @@
>         <update>
>           Update Tomcat Native to 2.0.9. (markt)
>         </update>
> +      <update>
> +        Update the internal fork of Apache Commons FileUpload to 7a8c324
> +        (2023-06-03, 1.x-SNAPSHOT). (markt)
> +      </update>
>       </changelog>
>     </subsection>
>   </section>
> 
> 
> ---------------------------------------------------------------------
> To unsubscribe, e-mail: dev-unsubscribe@tomcat.apache.org
> For additional commands, e-mail: dev-help@tomcat.apache.org
> 


---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@tomcat.apache.org
For additional commands, e-mail: dev-help@tomcat.apache.org