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}