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