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