View Javadoc

    1   /*
    2    * Licensed to the Apache Software Foundation (ASF) under one or more
    3    * contributor license agreements.  See the NOTICE file distributed with
    4    * this work for additional information regarding copyright ownership.
    5    * The ASF licenses this file to You under the Apache License, Version 2.0
    6    * (the "License"); you may not use this file except in compliance with
    7    * the License.  You may obtain a copy of the License at
    8    *
    9    *      http://www.apache.org/licenses/LICENSE-2.0
    10   *
    11   * Unless required by applicable law or agreed to in writing, software
    12   * distributed under the License is distributed on an "AS IS" BASIS,
    13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    14   * See the License for the specific language governing permissions and
    15   * limitations under the License.
    16   */
    17  package org.apache.commons.fileupload;
    18
    19  import static java.lang.String.format;
    20
    21  import java.io.IOException;
    22  import java.io.InputStream;
    23  import java.nio.charset.StandardCharsets;
    24  import java.util.ArrayList;
    25  import java.util.HashMap;
    26  import java.util.Iterator;
    27  import java.util.List;
    28  import java.util.Locale;
    29  import java.util.Map;
    30  import java.util.NoSuchElementException;
    31  import java.util.Objects;
    32
    33  import javax.servlet.http.HttpServletRequest;
    34
    35  import org.apache.commons.fileupload.MultipartStream.ItemInputStream;
    36  import org.apache.commons.fileupload.servlet.ServletFileUpload;
    37  import org.apache.commons.fileupload.servlet.ServletRequestContext;
    38  import org.apache.commons.fileupload.util.Closeable;
    39  import org.apache.commons.fileupload.util.FileItemHeadersImpl;
    40  import org.apache.commons.fileupload.util.LimitedInputStream;
    41  import org.apache.commons.fileupload.util.Streams;
    42  import org.apache.commons.io.IOUtils;
    43
    44  /**
    45   * High level API for processing file uploads.
    46   *
    47   * <p>
    48   * This class handles multiple files per single HTML widget, sent using {@code multipart/mixed} encoding type, as specified by
    49   * <a href="http://www.ietf.org/rfc/rfc1867.txt">RFC 1867</a>. Use {@link #parseRequest(RequestContext)} to acquire a list of
    50   * {@link org.apache.commons.fileupload.FileItem}s associated with a given HTML widget.
    51   * </p>
    52   *
    53   * <p>
    54   * How the data for individual parts is stored is determined by the factory used to create them; a given part may be in memory, on disk, or somewhere else.
    55   * </p>
    56   */
    57  public abstract class FileUploadBase {
    58
    59      /**
    60       * The iterator, which is returned by
    61       * {@link FileUploadBase#getItemIterator(RequestContext)}.
    62       */
    63      private class FileItemIteratorImpl implements FileItemIterator {
    64
    65          /**
    66           * Default implementation of {@link FileItemStream}.
    67           */
    68          private final class FileItemStreamImpl implements FileItemStream {
    69
    70              /**
    71               * The file items content type.
    72               */
    73              private final String contentType;
    74
    75              /**
    76               * The file items field name.
    77               */
    78              private final String fieldName;
    79
    80              /**
    81               * The file items file name.
    82               */
    83              private final String name;
    84
    85              /**
    86               * Whether the file item is a form field.
    87               */
    88              private final boolean formField;
    89
    90              /**
    91               * The file items input stream.
    92               */
    93              private final InputStream inputStream;
    94
    95              /**
    96               * The headers, if any.
    97               */
    98              private FileItemHeaders headers;
    99
    100             /**
    101              * Creates a new instance.
    102              *
    103              * @param name          The items file name, or null.
    104              * @param fieldName     The items field name.
    105              * @param contentType   The items content type, or null.
    106              * @param formField     Whether the item is a form field.
    107              * @param contentLength The items content length, if known, or -1
    108              * @throws IOException Creating the file item failed.
    109              */
    110             FileItemStreamImpl(final String name, final String fieldName, final String contentType, final boolean formField, final long contentLength)
    111                     throws IOException {
    112                 this.name = name;
    113                 this.fieldName = fieldName;
    114                 this.contentType = contentType;
    115                 this.formField = formField;
    116                 // Check if limit is already exceeded
    117                 if (fileSizeMax != -1 && contentLength != -1 && contentLength > fileSizeMax) {
    118                     final FileSizeLimitExceededException e = new FileSizeLimitExceededException(
    119                             format("The field %s exceeds its maximum permitted size of %s bytes.", fieldName, Long.valueOf(fileSizeMax)), contentLength,
    120                             fileSizeMax);
    121                     e.setFileName(name);
    122                     e.setFieldName(fieldName);
    123                     throw new FileUploadIOException(e);
    124                 }
    125                 // OK to construct stream now
    126                 final ItemInputStream itemStream = multi.newInputStream();
    127                 InputStream istream = itemStream;
    128                 if (fileSizeMax != -1) {
    129                     istream = new LimitedInputStream(istream, fileSizeMax) {
    130
    131                         @Override
    132                         protected void raiseError(final long sizeMax, final long count) throws IOException {
    133                             itemStream.close(true);
    134                             final FileSizeLimitExceededException e = new FileSizeLimitExceededException(
    135                                     format("The field %s exceeds its maximum permitted size of %s bytes.", fieldName, Long.valueOf(sizeMax)), count, sizeMax);
    136                             e.setFieldName(fieldName);
    137                             e.setFileName(name);
    138                             throw new FileUploadIOException(e);
    139                         }
    140                     };
    141                 }
    142                 inputStream = istream;
    143             }
    144
    145             /**
    146              * Closes the file item.
    147              *
    148              * @throws IOException An I/O error occurred.
    149              */
    150             void close() throws IOException {
    151                 inputStream.close();
    152             }
    153
    154             /**
    155              * Returns the items content type, or null.
    156              *
    157              * @return Content type, if known, or null.
    158              */
    159             @Override
    160             public String getContentType() {
    161                 return contentType;
    162             }
    163
    164             /**
    165              * Returns the items field name.
    166              *
    167              * @return Field name.
    168              */
    169             @Override
    170             public String getFieldName() {
    171                 return fieldName;
    172             }
    173
    174             /**
    175              * Returns the file item headers.
    176              *
    177              * @return The items header object
    178              */
    179             @Override
    180             public FileItemHeaders getHeaders() {
    181                 return headers;
    182             }
    183
    184             /**
    185              * Returns the items file name.
    186              *
    187              * @return File name, if known, or null.
    188              * @throws InvalidFileNameException The file name contains a NUL character,
    189              *   which might be an indicator of a security attack. If you intend to
    190              *   use the file name anyways, catch the exception and use
    191              *   InvalidFileNameException#getName().
    192              */
    193             @Override
    194             public String getName() {
    195                 return Streams.checkFileName(name);
    196             }
    197
    198             /**
    199              * Returns, whether this is a form field.
    200              *
    201              * @return True, if the item is a form field,
    202              *   otherwise false.
    203              */
    204             @Override
    205             public boolean isFormField() {
    206                 return formField;
    207             }
    208
    209             /**
    210              * Returns an input stream, which may be used to
    211              * read the items contents.
    212              *
    213              * @return Opened input stream.
    214              * @throws IOException An I/O error occurred.
    215              */
    216             @Override
    217             public InputStream openStream() throws IOException {
    218                 if (((Closeable) inputStream).isClosed()) {
    219                     throw new FileItemStream.ItemSkippedException();
    220                 }
    221                 return inputStream;
    222             }
    223
    224             /**
    225              * Sets the file item headers.
    226              *
    227              * @param headers The items header object
    228              */
    229             @Override
    230             public void setHeaders(final FileItemHeaders headers) {
    231                 this.headers = headers;
    232             }
    233
    234         }
    235
    236         /**
    237          * The multi part stream to process.
    238          */
    239         private final MultipartStream multi;
    240
    241         /**
    242          * The notifier, which used for triggering the
    243          * {@link ProgressListener}.
    244          */
    245         private final MultipartStream.ProgressNotifier notifier;
    246
    247         /**
    248          * The boundary, which separates the various parts.
    249          */
    250         private final byte[] boundary;
    251
    252         /**
    253          * The item, which we currently process.
    254          */
    255         private FileItemStreamImpl currentItem;
    256
    257         /**
    258          * The current items field name.
    259          */
    260         private String currentFieldName;
    261
    262         /**
    263          * Whether we are currently skipping the preamble.
    264          */
    265         private boolean skipPreamble;
    266
    267         /**
    268          * Whether the current item may still be read.
    269          */
    270         private boolean itemValid;
    271
    272         /**
    273          * Whether we have seen the end of the file.
    274          */
    275         private boolean eof;
    276
    277         /**
    278          * Is this a multipart/related Request.
    279          */
    280         private final boolean multipartRelated;
    281
    282         /**
    283          * Creates a new instance.
    284          *
    285          * @param ctx The request context.
    286          * @throws FileUploadException An error occurred while
    287          *   parsing the request.
    288          * @throws IOException An I/O error occurred.
    289          */
    290         FileItemIteratorImpl(final RequestContext ctx) throws FileUploadException, IOException {
    291             Objects.requireNonNull(ctx, "ctx");
    292             final String contentType = ctx.getContentType();
    293             if (null == contentType || !contentType.toLowerCase(Locale.ROOT).startsWith(MULTIPART)) {
    294                 throw new InvalidContentTypeException(format("the request neither contains a %s nor a %s nor a %s stream, content type header is %s",
    295                         MULTIPART_FORM_DATA, MULTIPART_MIXED, MULTIPART_RELATED, contentType));
    296             }
    297             multipartRelated = contentType.toLowerCase(Locale.ROOT).startsWith(MULTIPART_RELATED);
    298             @SuppressWarnings("deprecation") // still has to be backward compatible
    299             final int contentLengthInt = ctx.getContentLength();
    300             final long requestSize = UploadContext.class.isAssignableFrom(ctx.getClass())
    301                     // Inline conditional is OK here CHECKSTYLE:OFF
    302                     ? ((UploadContext) ctx).contentLength()
    303                     : contentLengthInt;
    304             // CHECKSTYLE:ON
    305             final InputStream input; // this is eventually closed in MultipartStream processing
    306             if (sizeMax >= 0) {
    307                 if (requestSize != -1 && requestSize > sizeMax) {
    308                     throw new SizeLimitExceededException(format("the request was rejected because its size (%s) exceeds the configured maximum (%s)",
    309                             Long.valueOf(requestSize), Long.valueOf(sizeMax)), requestSize, sizeMax);
    310                 }
    311                 // this is eventually closed in MultipartStream processing
    312                 input = new LimitedInputStream(ctx.getInputStream(), sizeMax) {
    313
    314                     @Override
    315                     protected void raiseError(final long sizeMax, final long count) throws IOException {
    316                         final FileUploadException ex = new SizeLimitExceededException(
    317                                 format("the request was rejected because its size (%s) exceeds the configured maximum (%s)", Long.valueOf(count),
    318                                         Long.valueOf(sizeMax)),
    319                                 count, sizeMax);
    320                         throw new FileUploadIOException(ex);
    321                     }
    322                 };
    323             } else {
    324                 input = ctx.getInputStream();
    325             }
    326             String charEncoding = headerEncoding;
    327             if (charEncoding == null) {
    328                 charEncoding = ctx.getCharacterEncoding();
    329             }
    330             boundary = getBoundary(contentType);
    331             if (boundary == null) {
    332                 IOUtils.closeQuietly(input); // avoid possible resource leak
    333                 throw new FileUploadException("the request was rejected because no multipart boundary was found");
    334             }
    335             notifier = new MultipartStream.ProgressNotifier(listener, requestSize);
    336             try {
    337                 multi = new MultipartStream(input, boundary, notifier);
    338             } catch (final IllegalArgumentException iae) {
    339                 IOUtils.closeQuietly(input); // avoid possible resource leak
    340                 throw new InvalidContentTypeException(format("The boundary specified in the %s header is too long", CONTENT_TYPE), iae);
    341             }
    342             multi.setHeaderEncoding(charEncoding);
    343             multi.setPartHeaderSizeMax(getPartHeaderSizeMax());
    344             skipPreamble = true;
    345             findNextItem();
    346         }
    347
    348         /**
    349          * Called for finding the next item, if any.
    350          *
    351          * @return True, if an next item was found, otherwise false.
    352          * @throws IOException An I/O error occurred.
    353          */
    354         private boolean findNextItem() throws IOException {
    355             if (eof) {
    356                 return false;
    357             }
    358             if (currentItem != null) {
    359                 currentItem.close();
    360                 currentItem = null;
    361             }
    362             for (;;) {
    363                 final boolean nextPart;
    364                 if (skipPreamble) {
    365                     nextPart = multi.skipPreamble();
    366                 } else {
    367                     nextPart = multi.readBoundary();
    368                 }
    369                 if (!nextPart) {
    370                     if (currentFieldName == null) {
    371                         // Outer multipart terminated -> No more data
    372                         eof = true;
    373                         return false;
    374                     }
    375                     // Inner multipart terminated -> Return to parsing the outer
    376                     multi.setBoundary(boundary);
    377                     currentFieldName = null;
    378                     continue;
    379                 }
    380                 final FileItemHeaders headers = getParsedHeaders(multi.readHeaders());
    381                 if (multipartRelated) {
    382                     currentFieldName = "";
    383                     currentItem = new FileItemStreamImpl(null, null, headers.getHeader(CONTENT_TYPE), false, getContentLength(headers));
    384                     currentItem.setHeaders(headers);
    385                     notifier.noteItem();
    386                     itemValid = true;
    387                     return true;
    388                 }
    389                 if (currentFieldName == null) {
    390                     // We're parsing the outer multipart
    391                     final String fieldName = getFieldName(headers);
    392                     if (fieldName != null) {
    393                         final String subContentType = headers.getHeader(CONTENT_TYPE);
    394                         if (subContentType != null && subContentType.toLowerCase(Locale.ROOT).startsWith(MULTIPART_MIXED)) {
    395                             currentFieldName = fieldName;
    396                             // Multiple files associated with this field name
    397                             final byte[] subBoundary = getBoundary(subContentType);
    398                             multi.setBoundary(subBoundary);
    399                             skipPreamble = true;
    400                             continue;
    401                         }
    402                         final String fileName = getFileName(headers);
    403                         currentItem = new FileItemStreamImpl(fileName, fieldName, headers.getHeader(CONTENT_TYPE), fileName == null, getContentLength(headers));
    404                         currentItem.setHeaders(headers);
    405                         notifier.noteItem();
    406                         itemValid = true;
    407                         return true;
    408                     }
    409                 } else {
    410                     final String fileName = getFileName(headers);
    411                     if (fileName != null) {
    412                         currentItem = new FileItemStreamImpl(fileName, currentFieldName, headers.getHeader(CONTENT_TYPE), false, getContentLength(headers));
    413                         currentItem.setHeaders(headers);
    414                         notifier.noteItem();
    415                         itemValid = true;
    416                         return true;
    417                     }
    418                 }
    419                 multi.discardBodyData();
    420             }
    421         }
    422
    423         private long getContentLength(final FileItemHeaders headers) {
    424             try {
    425                 return Long.parseLong(headers.getHeader(CONTENT_LENGTH));
    426             } catch (final Exception e) {
    427                 return -1;
    428             }
    429         }
    430
    431         /**
    432          * Returns, whether another instance of {@link FileItemStream}
    433          * is available.
    434          *
    435          * @throws FileUploadException Parsing or processing the
    436          *   file item failed.
    437          * @throws IOException Reading the file item failed.
    438          * @return True, if one or more additional file items
    439          *   are available, otherwise false.
    440          */
    441         @Override
    442         public boolean hasNext() throws FileUploadException, IOException {
    443             if (eof) {
    444                 return false;
    445             }
    446             if (itemValid) {
    447                 return true;
    448             }
    449             try {
    450                 return findNextItem();
    451             } catch (final FileUploadIOException e) {
    452                 // unwrap encapsulated SizeException
    453                 throw (FileUploadException) e.getCause();
    454             }
    455         }
    456
    457         /**
    458          * Returns the next available {@link FileItemStream}.
    459          *
    460          * @throws java.util.NoSuchElementException No more items are
    461          *   available. Use {@link #hasNext()} to prevent this exception.
    462          * @throws FileUploadException Parsing or processing the
    463          *   file item failed.
    464          * @throws IOException Reading the file item failed.
    465          * @return FileItemStream instance, which provides
    466          *   access to the next file item.
    467          */
    468         @Override
    469         public FileItemStream next() throws FileUploadException, IOException {
    470             if (eof || !itemValid && !hasNext()) {
    471                 throw new NoSuchElementException();
    472             }
    473             itemValid = false;
    474             return currentItem;
    475         }
    476
    477     }
    478
    479     /**
    480      * Thrown to indicate that A files size exceeds the configured maximum.
    481      */
    482     public static class FileSizeLimitExceededException
    483             extends SizeException {
    484
    485         /**
    486          * The exceptions UID, for serializing an instance.
    487          */
    488         private static final long serialVersionUID = 8150776562029630058L;
    489
    490         /**
    491          * File name of the item, which caused the exception.
    492          */
    493         private String fileName;
    494
    495         /**
    496          * Field name of the item, which caused the exception.
    497          */
    498         private String fieldName;
    499
    500         /**
    501          * Constructs a {@code SizeExceededException} with
    502          * the specified detail message, and actual and permitted sizes.
    503          *
    504          * @param message   The detail message.
    505          * @param actual    The actual request size.
    506          * @param permitted The maximum permitted request size.
    507          */
    508         public FileSizeLimitExceededException(final String message, final long actual,
    509                 final long permitted) {
    510             super(message, actual, permitted);
    511         }
    512
    513         /**
    514          * Returns the field name of the item, which caused the
    515          * exception.
    516          *
    517          * @return Field name, if known, or null.
    518          */
    519         public String getFieldName() {
    520             return fieldName;
    521         }
    522
    523         /**
    524          * Returns the file name of the item, which caused the
    525          * exception.
    526          *
    527          * @return File name, if known, or null.
    528          */
    529         public String getFileName() {
    530             return fileName;
    531         }
    532
    533         /**
    534          * Sets the field name of the item, which caused the
    535          * exception.
    536          *
    537          * @param fieldName the field name of the item,
    538          *        which caused the exception.
    539          */
    540         public void setFieldName(final String fieldName) {
    541             this.fieldName = fieldName;
    542         }
    543
    544         /**
    545          * Sets the file name of the item, which caused the
    546          * exception.
    547          *
    548          * @param fileName the file name of the item, which caused the exception.
    549          */
    550         public void setFileName(final String fileName) {
    551             this.fileName = fileName;
    552         }
    553
    554     }
    555
    556     /**
    557      * Signals that a FileUpload I/O exception of some sort has occurred. This class is the general class of exceptions produced by failed or interrupted
    558      * FileUpload I/O operations.
    559      *
    560      * This exception wraps a {@link FileUploadException}.
    561      */
    562     public static class FileUploadIOException extends IOException {
    563
    564         /**
    565          * The exceptions UID, for serializing an instance.
    566          */
    567         private static final long serialVersionUID = -7047616958165584154L;
    568
    569         /**
    570          * Creates a {@code FileUploadIOException} with the given cause.
    571          *
    572          * @param cause The exceptions cause, if any, or null.
    573          */
    574         public FileUploadIOException(final FileUploadException cause) {
    575             super(cause);
    576         }
    577     }
    578
    579     /**
    580      * Thrown to indicate that the request is not a multipart request.
    581      */
    582     public static class InvalidContentTypeException
    583             extends FileUploadException {
    584
    585         /**
    586          * The exceptions UID, for serializing an instance.
    587          */
    588         private static final long serialVersionUID = -9073026332015646668L;
    589
    590         /**
    591          * Constructs a {@code InvalidContentTypeException} with no
    592          * detail message.
    593          */
    594         public InvalidContentTypeException() {
    595         }
    596
    597         /**
    598          * Constructs an {@code InvalidContentTypeException} with
    599          * the specified detail message.
    600          *
    601          * @param message The detail message.
    602          */
    603         public InvalidContentTypeException(final String message) {
    604             super(message);
    605         }
    606
    607         /**
    608          * Constructs an {@code InvalidContentTypeException} with
    609          * the specified detail message and cause.
    610          *
    611          * @param message The detail message.
    612          * @param cause the original cause
    613          * @since 1.3.1
    614          */
    615         public InvalidContentTypeException(final String message, final Throwable cause) {
    616             super(message, cause);
    617         }
    618     }
    619
    620     /**
    621      * Thrown to indicate an IOException.
    622      */
    623     public static class IOFileUploadException extends FileUploadException {
    624
    625         /**
    626          * The exceptions UID, for serializing an instance.
    627          */
    628         private static final long serialVersionUID = 1749796615868477269L;
    629
    630         /**
    631          * Creates a new instance with the given cause.
    632          *
    633          * @param message The detail message.
    634          * @param cause The exceptions cause.
    635          */
    636         public IOFileUploadException(final String message, final IOException cause) {
    637             super(message, cause);
    638         }
    639
    640     }
    641
    642     /**
    643      * This exception is thrown, if a requests permitted size
    644      * is exceeded.
    645      */
    646     protected abstract static class SizeException extends FileUploadException {
    647
    648         /**
    649          * Serial version UID, being used, if serialized.
    650          */
    651         private static final long serialVersionUID = -8776225574705254126L;
    652
    653         /**
    654          * The actual size of the request.
    655          */
    656         private final long actual;
    657
    658         /**
    659          * The maximum permitted size of the request.
    660          */
    661         private final long permitted;
    662
    663         /**
    664          * Creates a new instance.
    665          *
    666          * @param message The detail message.
    667          * @param actual The actual number of bytes in the request.
    668          * @param permitted The requests size limit, in bytes.
    669          */
    670         protected SizeException(final String message, final long actual, final long permitted) {
    671             super(message);
    672             this.actual = actual;
    673             this.permitted = permitted;
    674         }
    675
    676         /**
    677          * Gets the actual size of the request.
    678          *
    679          * @return The actual size of the request.
    680          * @since 1.3
    681          */
    682         public long getActualSize() {
    683             return actual;
    684         }
    685
    686         /**
    687          * Gets the permitted size of the request.
    688          *
    689          * @return The permitted size of the request.
    690          * @since 1.3
    691          */
    692         public long getPermittedSize() {
    693             return permitted;
    694         }
    695
    696     }
    697
    698     /**
    699      * Thrown to indicate that the request size exceeds the configured maximum.
    700      */
    701     public static class SizeLimitExceededException
    702             extends SizeException {
    703
    704         /**
    705          * The exceptions UID, for serializing an instance.
    706          */
    707         private static final long serialVersionUID = -2474893167098052828L;
    708
    709         /**
    710          * @deprecated 1.2 Replaced by
    711          * {@link #SizeLimitExceededException(String, long, long)}
    712          */
    713         @Deprecated
    714         public SizeLimitExceededException() {
    715             this(null, 0, 0);
    716         }
    717
    718         /**
    719          * @deprecated 1.2 Replaced by
    720          * {@link #SizeLimitExceededException(String, long, long)}
    721          * @param message The exceptions detail message.
    722          */
    723         @Deprecated
    724         public SizeLimitExceededException(final String message) {
    725             this(message, 0, 0);
    726         }
    727
    728         /**
    729          * Constructs a {@code SizeExceededException} with
    730          * the specified detail message, and actual and permitted sizes.
    731          *
    732          * @param message   The detail message.
    733          * @param actual    The actual request size.
    734          * @param permitted The maximum permitted request size.
    735          */
    736         public SizeLimitExceededException(final String message, final long actual,
    737                 final long permitted) {
    738             super(message, actual, permitted);
    739         }
    740
    741     }
    742
    743     /**
    744      * Thrown to indicate that the request size is not specified. In other
    745      * words, it is thrown, if the content-length header is missing or
    746      * contains the value -1.
    747      *
    748      * @deprecated 1.2 As of commons-fileupload 1.2, the presence of a
    749      *   content-length header is no longer required.
    750      */
    751     @Deprecated
    752     public static class UnknownSizeException extends FileUploadException {
    753
    754         /**
    755          * The exceptions UID, for serializing an instance.
    756          */
    757         private static final long serialVersionUID = 7062279004812015273L;
    758
    759         /**
    760          * Constructs a {@code UnknownSizeException} with no
    761          * detail message.
    762          */
    763         public UnknownSizeException() {
    764         }
    765
    766         /**
    767          * Constructs an {@code UnknownSizeException} with
    768          * the specified detail message.
    769          *
    770          * @param message The detail message.
    771          */
    772         public UnknownSizeException(final String message) {
    773             super(message);
    774         }
    775
    776     }
    777
    778     /**
    779      * Line feed.
    780      */
    781     private static final char LF = '\n';
    782
    783     /**
    784      * Carriage return.
    785      */
    786     private static final char CR = '\r';
    787
    788     /**
    789      * HTTP content type header name.
    790      */
    791     public static final String CONTENT_TYPE = "Content-type";
    792
    793     /**
    794      * HTTP content disposition header name.
    795      */
    796     public static final String CONTENT_DISPOSITION = "Content-disposition";
    797
    798     /**
    799      * HTTP content length header name.
    800      */
    801     public static final String CONTENT_LENGTH = "Content-length";
    802
    803     /**
    804      * Content-disposition value for form data.
    805      */
    806     public static final String FORM_DATA = "form-data";
    807
    808     /**
    809      * Content-disposition value for file attachment.
    810      */
    811     public static final String ATTACHMENT = "attachment";
    812
    813     /**
    814      * Part of HTTP content type header.
    815      */
    816     public static final String MULTIPART = "multipart/";
    817
    818     /**
    819      * HTTP content type header for multipart forms.
    820      */
    821     public static final String MULTIPART_FORM_DATA = "multipart/form-data";
    822
    823     /**
    824      * HTTP content type header for multiple uploads.
    825      */
    826     public static final String MULTIPART_MIXED = "multipart/mixed";
    827
    828     /**
    829      * HTTP content type header for multiple related data.
    830      *
    831      * @since 1.6.0
    832      */
    833     public static final String MULTIPART_RELATED = "multipart/related";
    834
    835     /**
    836      * The maximum length of a single header line that will be parsed
    837      * (1024 bytes).
    838      * @deprecated This constant is no longer used. As of commons-fileupload
    839      *   1.6, the applicable limit is the total size of a single part's headers,
    840      *   {@link #getPartHeaderSizeMax()} in bytes.
    841      */
    842     @Deprecated
    843     public static final int MAX_HEADER_SIZE = 1024;
    844
    845     /**
    846      * Default per part header size limit in bytes.
    847      *
    848      * @since 1.6.0
    849      */
    850     public static final int DEFAULT_PART_HEADER_SIZE_MAX = 512;
    851
    852
    853     /**
    854      * Utility method that determines whether the request contains multipart
    855      * content.
    856      *
    857      * @param req The servlet request to be evaluated. Must be non-null.
    858      * @return {@code true} if the request is multipart;
    859      *         {@code false} otherwise.
    860      *
    861      * @deprecated 1.1 Use the method on {@code ServletFileUpload} instead.
    862      */
    863     @Deprecated
    864     public static boolean isMultipartContent(final HttpServletRequest req) {
    865         return ServletFileUpload.isMultipartContent(req);
    866     }
    867
    868     /**
    869      * <p>Utility method that determines whether the request contains multipart
    870      * content.</p>
    871      *
    872      * <p><strong>NOTE:</strong>This method will be moved to the
    873      * {@code ServletFileUpload} class after the FileUpload 1.1 release.
    874      * Unfortunately, since this method is static, it is not possible to
    875      * provide its replacement until this method is removed.</p>
    876      *
    877      * @param ctx The request context to be evaluated. Must be non-null.
    878      * @return {@code true} if the request is multipart;
    879      *         {@code false} otherwise.
    880      */
    881     public static final boolean isMultipartContent(final RequestContext ctx) {
    882         final String contentType = ctx.getContentType();
    883         if (contentType == null) {
    884             return false;
    885         }
    886         return contentType.toLowerCase(Locale.ROOT).startsWith(MULTIPART);
    887     }
    888
    889     /**
    890      * The maximum size permitted for the complete request, as opposed to
    891      * {@link #fileSizeMax}. A value of -1 indicates no maximum.
    892      */
    893     private long sizeMax = -1;
    894
    895     /**
    896      * The maximum size permitted for a single uploaded file, as opposed
    897      * to {@link #sizeMax}. A value of -1 indicates no maximum.
    898      */
    899     private long fileSizeMax = -1;
    900
    901     /**
    902      * The maximum permitted number of files that may be uploaded in a single
    903      * request. A value of -1 indicates no maximum.
    904      */
    905     private long fileCountMax = -1;
    906
    907     /**
    908      * The maximum permitted size of the headers provided with a single part in bytes.
    909      */
    910     private int partHeaderSizeMax = DEFAULT_PART_HEADER_SIZE_MAX;
    911
    912     /**
    913      * The content encoding to use when reading part headers.
    914      */
    915     private String headerEncoding;
    916
    917     /**
    918      * The progress listener.
    919      */
    920     private ProgressListener listener;
    921
    922     /**
    923      * Constructs a new instance.
    924      */
    925     public FileUploadBase() {
    926         // empty
    927     }
    928
    929     /**
    930      * Creates a new {@link FileItem} instance.
    931      *
    932      * @param headers       A {@code Map} containing the HTTP request
    933      *                      headers.
    934      * @param isFormField   Whether or not this item is a form field, as
    935      *                      opposed to a file.
    936      *
    937      * @return A newly created {@code FileItem} instance.
    938      * @deprecated 1.2 This method is no longer used in favor of
    939      *   internally created instances of {@link FileItem}.
    940      */
    941     @Deprecated
    942     protected FileItem createItem(final Map<String, String> headers, final boolean isFormField) {
    943         return getFileItemFactory().createItem(getFieldName(headers), getHeader(headers, CONTENT_TYPE), isFormField, getFileName(headers));
    944     }
    945
    946     /**
    947      * Gets the boundary from the {@code Content-type} header.
    948      *
    949      * @param contentType The value of the content type header from which to
    950      *                    extract the boundary value.
    951      *
    952      * @return The boundary, as a byte array.
    953      */
    954     protected byte[] getBoundary(final String contentType) {
    955         final ParameterParser parser = new ParameterParser();
    956         parser.setLowerCaseNames(true);
    957         // Parameter parser can handle null input
    958         final Map<String, String> params = parser.parse(contentType, new char[] { ';', ',' });
    959         final String boundaryStr = params.get("boundary");
    960         if (boundaryStr == null) {
    961             return null; // NOPMD
    962         }
    963         return boundaryStr.getBytes(StandardCharsets.ISO_8859_1);
    964     }
    965
    966     /**
    967      * Gets the field name from the {@code Content-disposition}
    968      * header.
    969      *
    970      * @param headers A {@code Map} containing the HTTP request headers.
    971      * @return The field name for the current {@code encapsulation}.
    972      */
    973     protected String getFieldName(final FileItemHeaders headers) {
    974         return getFieldName(headers.getHeader(CONTENT_DISPOSITION));
    975     }
    976
    977     /**
    978      * Gets the field name from the {@code Content-disposition}
    979      * header.
    980      *
    981      * @param headers A {@code Map} containing the HTTP request headers.
    982      * @return The field name for the current {@code encapsulation}.
    983      * @deprecated 1.2.1 Use {@link #getFieldName(FileItemHeaders)}.
    984      */
    985     @Deprecated
    986     protected String getFieldName(final Map<String, String> headers) {
    987         return getFieldName(getHeader(headers, CONTENT_DISPOSITION));
    988     }
    989
    990     /**
    991      * Returns the field name, which is given by the content-disposition
    992      * header.
    993      * @param contentDisposition The content-dispositions header value.
    994      * @return The field name.
    995      */
    996     private String getFieldName(final String contentDisposition) {
    997         String fieldName = null;
    998         if (contentDisposition != null && contentDisposition.toLowerCase(Locale.ROOT).startsWith(FORM_DATA)) {
    999             final ParameterParser parser = new ParameterParser();
    1000             parser.setLowerCaseNames(true);
    1001             // Parameter parser can handle null input
    1002             final Map<String, String> params = parser.parse(contentDisposition, ';');
    1003             fieldName = params.get("name");
    1004             if (fieldName != null) {
    1005                 fieldName = fieldName.trim();
    1006             }
    1007         }
    1008         return fieldName;
    1009     }
    1010
    1011     /**
    1012      * Returns the maximum number of files allowed in a single request.
    1013      *
    1014      * @return The maximum number of files allowed in a single request.
    1015      */
    1016     public long getFileCountMax() {
    1017         return fileCountMax;
    1018     }
    1019
    1020     /**
    1021      * Returns the factory class used when creating file items.
    1022      *
    1023      * @return The factory class for new file items.
    1024      */
    1025     public abstract FileItemFactory getFileItemFactory();
    1026
    1027     /**
    1028      * Gets the file name from the {@code Content-disposition}
    1029      * header.
    1030      *
    1031      * @param headers The HTTP headers object.
    1032      * @return The file name for the current {@code encapsulation}.
    1033      */
    1034     protected String getFileName(final FileItemHeaders headers) {
    1035         return getFileName(headers.getHeader(CONTENT_DISPOSITION));
    1036     }
    1037
    1038     /**
    1039      * Gets the file name from the {@code Content-disposition}
    1040      * header.
    1041      *
    1042      * @param headers A {@code Map} containing the HTTP request headers.
    1043      * @return The file name for the current {@code encapsulation}.
    1044      * @deprecated 1.2.1 Use {@link #getFileName(FileItemHeaders)}.
    1045      */
    1046     @Deprecated
    1047     protected String getFileName(final Map<String, String> headers) {
    1048         return getFileName(getHeader(headers, CONTENT_DISPOSITION));
    1049     }
    1050
    1051     /**
    1052      * Returns the given content-disposition headers file name.
    1053      * @param contentDisposition The content-disposition headers value.
    1054      * @return The file name
    1055      */
    1056     private String getFileName(final String contentDisposition) {
    1057         String fileName = null;
    1058         if (contentDisposition != null) {
    1059             final String cdl = contentDisposition.toLowerCase(Locale.ROOT);
    1060             if (cdl.startsWith(FORM_DATA) || cdl.startsWith(ATTACHMENT)) {
    1061                 final ParameterParser parser = new ParameterParser();
    1062                 parser.setLowerCaseNames(true);
    1063                 // Parameter parser can handle null input
    1064                 final Map<String, String> params = parser.parse(contentDisposition, ';');
    1065                 if (params.containsKey("filename")) {
    1066                     fileName = params.get("filename");
    1067                     if (fileName != null) {
    1068                         fileName = fileName.trim();
    1069                     } else {
    1070                         // Even if there is no value, the parameter is present,
    1071                         // so we return an empty file name rather than no file
    1072                         // name.
    1073                         fileName = "";
    1074                     }
    1075                 }
    1076             }
    1077         }
    1078         return fileName;
    1079     }
    1080
    1081     /**
    1082      * Returns the maximum allowed size of a single uploaded file,
    1083      * as opposed to {@link #getSizeMax()}.
    1084      *
    1085      * @see #setFileSizeMax(long)
    1086      * @return Maximum size of a single uploaded file.
    1087      */
    1088     public long getFileSizeMax() {
    1089         return fileSizeMax;
    1090     }
    1091
    1092     /**
    1093      * Returns the header with the specified name from the supplied map. The
    1094      * header lookup is case-insensitive.
    1095      *
    1096      * @param headers A {@code Map} containing the HTTP request headers.
    1097      * @param name    The name of the header to return.
    1098      * @return The value of specified header, or a comma-separated list if
    1099      *         there were multiple headers of that name.
    1100      * @deprecated 1.2.1 Use {@link FileItemHeaders#getHeader(String)}.
    1101      */
    1102     @Deprecated
    1103     protected final String getHeader(final Map<String, String> headers,
    1104             final String name) {
    1105         return headers.get(name.toLowerCase(Locale.ROOT));
    1106     }
    1107
    1108     /**
    1109      * Gets the character encoding used when reading the headers of an
    1110      * individual part. When not specified, or {@code null}, the request
    1111      * encoding is used. If that is also not specified, or {@code null},
    1112      * the platform default encoding is used.
    1113      *
    1114      * @return The encoding used to read part headers.
    1115      */
    1116     public String getHeaderEncoding() {
    1117         return headerEncoding;
    1118     }
    1119
    1120     /**
    1121      * Processes an <a href="http://www.ietf.org/rfc/rfc1867.txt">RFC 1867</a>
    1122      * compliant {@code multipart/form-data} stream.
    1123      *
    1124      * @param ctx The context for the request to be parsed.
    1125      * @return An iterator to instances of {@code FileItemStream}
    1126      *         parsed from the request, in the order that they were
    1127      *         transmitted.
    1128      *
    1129      * @throws FileUploadException if there are problems reading/parsing
    1130      *                             the request or storing files.
    1131      * @throws IOException An I/O error occurred. This may be a network
    1132      *   error while communicating with the client or a problem while
    1133      *   storing the uploaded content.
    1134      */
    1135     public FileItemIterator getItemIterator(final RequestContext ctx)
    1136     throws FileUploadException, IOException {
    1137         try {
    1138             return new FileItemIteratorImpl(ctx);
    1139         } catch (final FileUploadIOException e) {
    1140             // unwrap encapsulated SizeException
    1141             throw (FileUploadException) e.getCause();
    1142         }
    1143     }
    1144
    1145     /**
    1146      * <p> Parses the {@code header-part} and returns as key/value
    1147      * pairs.
    1148      *
    1149      * <p> If there are multiple headers of the same names, the name
    1150      * will map to a comma-separated list containing the values.
    1151      *
    1152      * @param headerPart The {@code header-part} of the current
    1153      *                   {@code encapsulation}.
    1154      *
    1155      * @return A {@code Map} containing the parsed HTTP request headers.
    1156      */
    1157     protected FileItemHeaders getParsedHeaders(final String headerPart) {
    1158         final int len = headerPart.length();
    1159         final FileItemHeadersImpl headers = newFileItemHeaders();
    1160         int start = 0;
    1161         for (;;) {
    1162             int end = parseEndOfLine(headerPart, start);
    1163             if (start == end) {
    1164                 break;
    1165             }
    1166             final StringBuilder header = new StringBuilder(headerPart.substring(start, end));
    1167             start = end + 2;
    1168             while (start < len) {
    1169                 int nonWs = start;
    1170                 while (nonWs < len) {
    1171                     final char c = headerPart.charAt(nonWs);
    1172                     if (c != ' '  &&  c != '\t') {
    1173                         break;
    1174                     }
    1175                     ++nonWs;
    1176                 }
    1177                 if (nonWs == start) {
    1178                     break;
    1179                 }
    1180                 // Continuation line found
    1181                 end = parseEndOfLine(headerPart, nonWs);
    1182                 header.append(' ').append(headerPart, nonWs, end);
    1183                 start = end + 2;
    1184             }
    1185             parseHeaderLine(headers, header.toString());
    1186         }
    1187         return headers;
    1188     }
    1189
    1190     /**
    1191      * Obtain the per part size limit for headers.
    1192      *
    1193      * @return The maximum size of the headers for a single part in bytes.
    1194      *
    1195      * @since 1.6.0
    1196      */
    1197     public int getPartHeaderSizeMax() {
    1198         return partHeaderSizeMax;
    1199     }
    1200
    1201     /**
    1202      * Returns the progress listener.
    1203      *
    1204      * @return The progress listener, if any, or null.
    1205      */
    1206     public ProgressListener getProgressListener() {
    1207         return listener;
    1208     }
    1209
    1210     /**
    1211      * Returns the maximum allowed size of a complete request, as opposed
    1212      * to {@link #getFileSizeMax()}.
    1213      *
    1214      * @return The maximum allowed size, in bytes. The default value of
    1215      *   -1 indicates, that there is no limit.
    1216      *
    1217      * @see #setSizeMax(long)
    1218      *
    1219      */
    1220     public long getSizeMax() {
    1221         return sizeMax;
    1222     }
    1223
    1224     /**
    1225      * Creates a new instance of {@link FileItemHeaders}.
    1226      * @return The new instance.
    1227      */
    1228     protected FileItemHeadersImpl newFileItemHeaders() {
    1229         return new FileItemHeadersImpl();
    1230     }
    1231
    1232     /**
    1233      * Skips bytes until the end of the current line.
    1234      * @param headerPart The headers, which are being parsed.
    1235      * @param end Index of the last byte, which has yet been
    1236      *   processed.
    1237      * @return Index of the \r\n sequence, which indicates
    1238      *   end of line.
    1239      */
    1240     private int parseEndOfLine(final String headerPart, final int end) {
    1241         int index = end;
    1242         for (;;) {
    1243             final int offset = headerPart.indexOf(CR, index);
    1244             if (offset == -1  ||  offset + 1 >= headerPart.length()) {
    1245                 throw new IllegalStateException(
    1246                     "Expected headers to be terminated by an empty line.");
    1247             }
    1248             if (headerPart.charAt(offset + 1) == LF) {
    1249                 return offset;
    1250             }
    1251             index = offset + 1;
    1252         }
    1253     }
    1254
    1255     /**
    1256      * Reads the next header line.
    1257      * @param headers String with all headers.
    1258      * @param header Map where to store the current header.
    1259      */
    1260     private void parseHeaderLine(final FileItemHeadersImpl headers, final String header) {
    1261         final int colonOffset = header.indexOf(':');
    1262         if (colonOffset == -1) {
    1263             // This header line is malformed, skip it.
    1264             return;
    1265         }
    1266         final String headerName = header.substring(0, colonOffset).trim();
    1267         final String headerValue = header.substring(colonOffset + 1).trim();
    1268         headers.addHeader(headerName, headerValue);
    1269     }
    1270
    1271     /**
    1272      * <p> Parses the {@code header-part} and returns as key/value
    1273      * pairs.
    1274      *
    1275      * <p> If there are multiple headers of the same names, the name
    1276      * will map to a comma-separated list containing the values.
    1277      *
    1278      * @param headerPart The {@code header-part} of the current
    1279      *                   {@code encapsulation}.
    1280      *
    1281      * @return A {@code Map} containing the parsed HTTP request headers.
    1282      * @deprecated 1.2.1 Use {@link #getParsedHeaders(String)}
    1283      */
    1284     @Deprecated
    1285     protected Map<String, String> parseHeaders(final String headerPart) {
    1286         final FileItemHeaders headers = getParsedHeaders(headerPart);
    1287         final Map<String, String> result = new HashMap<>();
    1288         for (final Iterator<String> iter = headers.getHeaderNames();  iter.hasNext();) {
    1289             final String headerName = iter.next();
    1290             final Iterator<String> iter2 = headers.getHeaders(headerName);
    1291             final StringBuilder headerValue = new StringBuilder(iter2.next());
    1292             while (iter2.hasNext()) {
    1293                 headerValue.append(",").append(iter2.next());
    1294             }
    1295             result.put(headerName, headerValue.toString());
    1296         }
    1297         return result;
    1298     }
    1299
    1300     /**
    1301      * Processes an <a href="http://www.ietf.org/rfc/rfc1867.txt">RFC 1867</a>
    1302      * compliant {@code multipart/form-data} stream.
    1303      *
    1304      * @param ctx The context for the request to be parsed.
    1305      * @return A map of {@code FileItem} instances parsed from the request.
    1306      * @throws FileUploadException if there are problems reading/parsing
    1307      *                             the request or storing files.
    1308      *
    1309      * @since 1.3
    1310      */
    1311     public Map<String, List<FileItem>> parseParameterMap(final RequestContext ctx) throws FileUploadException {
    1312         final List<FileItem> items = parseRequest(ctx);
    1313         final Map<String, List<FileItem>> itemsMap = new HashMap<>(items.size());
    1314         for (final FileItem fileItem : items) {
    1315             final String fieldName = fileItem.getFieldName();
    1316             List<FileItem> mappedItems = itemsMap.get(fieldName);
    1317             if (mappedItems == null) {
    1318                 mappedItems = new ArrayList<>();
    1319                 itemsMap.put(fieldName, mappedItems);
    1320             }
    1321             mappedItems.add(fileItem);
    1322         }
    1323         return itemsMap;
    1324     }
    1325
    1326     /**
    1327      * Processes an <a href="http://www.ietf.org/rfc/rfc1867.txt">RFC 1867</a>
    1328      * compliant {@code multipart/form-data} stream.
    1329      *
    1330      * @param req The servlet request to be parsed.
    1331      * @return A list of {@code FileItem} instances parsed from the
    1332      *         request, in the order that they were transmitted.
    1333      *
    1334      * @throws FileUploadException if there are problems reading/parsing
    1335      *                             the request or storing files.
    1336      *
    1337      * @deprecated 1.1 Use {@link ServletFileUpload#parseRequest(HttpServletRequest)} instead.
    1338      */
    1339     @Deprecated
    1340     public List<FileItem> parseRequest(final HttpServletRequest req)
    1341     throws FileUploadException {
    1342         return parseRequest(new ServletRequestContext(req));
    1343     }
    1344
    1345     /**
    1346      * Processes an <a href="http://www.ietf.org/rfc/rfc1867.txt">RFC 1867</a>
    1347      * compliant {@code multipart/form-data} stream.
    1348      *
    1349      * @param ctx The context for the request to be parsed.
    1350      * @return A list of {@code FileItem} instances parsed from the
    1351      *         request, in the order that they were transmitted.
    1352      *
    1353      * @throws FileUploadException if there are problems reading/parsing
    1354      *                             the request or storing files.
    1355      */
    1356     public List<FileItem> parseRequest(final RequestContext ctx) throws FileUploadException {
    1357         final List<FileItem> items = new ArrayList<>();
    1358         boolean successful = false;
    1359         try {
    1360             final FileItemIterator iter = getItemIterator(ctx);
    1361             final FileItemFactory fileItemFactory = getFileItemFactory();
    1362             Objects.requireNonNull(fileItemFactory, "getFileItemFactory()");
    1363             final byte[] buffer = new byte[Streams.DEFAULT_BUFFER_SIZE];
    1364             while (iter.hasNext()) {
    1365                 if (items.size() == fileCountMax) {
    1366                     // The next item will exceed the limit.
    1367                     throw new FileCountLimitExceededException(ATTACHMENT, getFileCountMax());
    1368                 }
    1369                 final FileItemStream item = iter.next();
    1370                 // Don't use getName() here to prevent an InvalidFileNameException.
    1371                 final String fileName = ((FileItemIteratorImpl.FileItemStreamImpl) item).name;
    1372                 final FileItem fileItem = fileItemFactory.createItem(item.getFieldName(), item.getContentType(), item.isFormField(), fileName);
    1373                 items.add(fileItem);
    1374                 try {
    1375                     Streams.copy(item.openStream(), fileItem.getOutputStream(), true, buffer);
    1376                 } catch (final FileUploadIOException e) {
    1377                     throw (FileUploadException) e.getCause();
    1378                 } catch (final IOException e) {
    1379                     throw new IOFileUploadException(format("Processing of %s request failed. %s", MULTIPART_FORM_DATA, e.getMessage()), e);
    1380                 }
    1381                 final FileItemHeaders fih = item.getHeaders();
    1382                 fileItem.setHeaders(fih);
    1383             }
    1384             successful = true;
    1385             return items;
    1386         } catch (final FileUploadIOException e) {
    1387             throw (FileUploadException) e.getCause();
    1388         } catch (final IOException e) {
    1389             throw new FileUploadException(e.getMessage(), e);
    1390         } finally {
    1391             if (!successful) {
    1392                 for (final FileItem fileItem : items) {
    1393                     try {
    1394                         fileItem.delete();
    1395                     } catch (final Exception ignored) {
    1396                         // ignored TODO perhaps add to tracker delete failure list somehow?
    1397                     }
    1398                 }
    1399             }
    1400         }
    1401     }
    1402
    1403     /**
    1404      * Sets the maximum number of files allowed per request.
    1405      *
    1406      * @param fileCountMax The new limit. {@code -1} means no limit.
    1407      */
    1408     public void setFileCountMax(final long fileCountMax) {
    1409         this.fileCountMax = fileCountMax;
    1410     }
    1411
    1412     /**
    1413      * Sets the factory class to use when creating file items.
    1414      *
    1415      * @param factory The factory class for new file items.
    1416      */
    1417     public abstract void setFileItemFactory(FileItemFactory factory);
    1418
    1419     /**
    1420      * Sets the maximum allowed size of a single uploaded file,
    1421      * as opposed to {@link #getSizeMax()}.
    1422      *
    1423      * @see #getFileSizeMax()
    1424      * @param fileSizeMax Maximum size of a single uploaded file.
    1425      */
    1426     public void setFileSizeMax(final long fileSizeMax) {
    1427         this.fileSizeMax = fileSizeMax;
    1428     }
    1429
    1430     /**
    1431      * Specifies the character encoding to be used when reading the headers of
    1432      * individual part. When not specified, or {@code null}, the request
    1433      * encoding is used. If that is also not specified, or {@code null},
    1434      * the platform default encoding is used.
    1435      *
    1436      * @param encoding The encoding used to read part headers.
    1437      */
    1438     public void setHeaderEncoding(final String encoding) {
    1439         headerEncoding = encoding;
    1440     }
    1441
    1442     /**
    1443      * Sets the per part size limit for headers.
    1444      *
    1445      * @param partHeaderSizeMax The maximum size of the headers in bytes.
    1446      *
    1447      * @since 1.6.0
    1448      */
    1449     public void setPartHeaderSizeMax(final int partHeaderSizeMax) {
    1450         this.partHeaderSizeMax = partHeaderSizeMax;
    1451     }
    1452
    1453     /**
    1454      * Sets the progress listener.
    1455      *
    1456      * @param listener The progress listener, if any. Defaults to null.
    1457      */
    1458     public void setProgressListener(final ProgressListener listener) {
    1459         this.listener = listener;
    1460     }
    1461
    1462     /**
    1463      * Sets the maximum allowed size of a complete request, as opposed
    1464      * to {@link #setFileSizeMax(long)}.
    1465      *
    1466      * @param sizeMax The maximum allowed size, in bytes. The default value of
    1467      *   -1 indicates, that there is no limit.
    1468      *
    1469      * @see #getSizeMax()
    1470      *
    1471      */
    1472     public void setSizeMax(final long sizeMax) {
    1473         this.sizeMax = sizeMax;
    1474     }
    1475
    1476 }