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