    001/*
    002 * Licensed to the Apache Software Foundation (ASF) under one or more
    003 * contributor license agreements.  See the NOTICE file distributed with
    004 * this work for additional information regarding copyright ownership.
    005 * The ASF licenses this file to You under the Apache License, Version 2.0
    006 * (the "License"); you may not use this file except in compliance with
    007 * the License.  You may obtain a copy of the License at
    008 *
    009 *      http://www.apache.org/licenses/LICENSE-2.0
    010 *
    011 * Unless required by applicable law or agreed to in writing, software
    012 * distributed under the License is distributed on an "AS IS" BASIS,
    013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    014 * See the License for the specific language governing permissions and
    015 * limitations under the License.
    016 */
    017package org.apache.commons.fileupload;
    018
    019import static java.lang.String.format;
    020
    021import java.io.IOException;
    022import java.io.InputStream;
    023import java.nio.charset.StandardCharsets;
    024import java.util.ArrayList;
    025import java.util.HashMap;
    026import java.util.Iterator;
    027import java.util.List;
    028import java.util.Locale;
    029import java.util.Map;
    030import java.util.NoSuchElementException;
    031import java.util.Objects;
    032
    033import javax.servlet.http.HttpServletRequest;
    034
    035import org.apache.commons.fileupload.MultipartStream.ItemInputStream;
    036import org.apache.commons.fileupload.servlet.ServletFileUpload;
    037import org.apache.commons.fileupload.servlet.ServletRequestContext;
    038import org.apache.commons.fileupload.util.Closeable;
    039import org.apache.commons.fileupload.util.FileItemHeadersImpl;
    040import org.apache.commons.fileupload.util.LimitedInputStream;
    041import org.apache.commons.fileupload.util.Streams;
    042import org.apache.commons.io.IOUtils;
    043
    044/**
    045 * High level API for processing file uploads.
    046 *
    047 * <p>
    048 * This class handles multiple files per single HTML widget, sent using {@code multipart/mixed} encoding type, as specified by
    049 * <a href="http://www.ietf.org/rfc/rfc1867.txt">RFC 1867</a>. Use {@link #parseRequest(RequestContext)} to acquire a list of
    050 * {@link org.apache.commons.fileupload.FileItem}s associated with a given HTML widget.
    051 * </p>
    052 *
    053 * <p>
    054 * 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.
    055 * </p>
    056 */
    057public abstract class FileUploadBase {
    058
    059    /**
    060     * The iterator, which is returned by
    061     * {@link FileUploadBase#getItemIterator(RequestContext)}.
    062     */
    063    private class FileItemIteratorImpl implements FileItemIterator {
    064
    065        /**
    066         * Default implementation of {@link FileItemStream}.
    067         */
    068        private final class FileItemStreamImpl implements FileItemStream {
    069
    070            /**
    071             * The file items content type.
    072             */
    073            private final String contentType;
    074
    075            /**
    076             * The file items field name.
    077             */
    078            private final String fieldName;
    079
    080            /**
    081             * The file items file name.
    082             */
    083            private final String name;
    084
    085            /**
    086             * Whether the file item is a form field.
    087             */
    088            private final boolean formField;
    089
    090            /**
    091             * The file items input stream.
    092             */
    093            private final InputStream inputStream;
    094
    095            /**
    096             * The headers, if any.
    097             */
    098            private FileItemHeaders headers;
    099
    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}