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                 if (fileSizeMax != -1) { // Check if limit is already exceeded
768                     if (pContentLength != -1
769                             && pContentLength > fileSizeMax) {
770                         FileSizeLimitExceededException e =
771                                 new FileSizeLimitExceededException(
772                                         format("The field %s exceeds its maximum permitted size of %s bytes.",
773                                                 fieldName, Long.valueOf(fileSizeMax)),
774                                         pContentLength, fileSizeMax);
775                         e.setFileName(pName);
776                         e.setFieldName(pFieldName);
777                         throw new FileUploadIOException(e);
778                     }
779                 }
780                 // OK to construct stream now
781                 final ItemInputStream itemStream = multi.newInputStream();
782                 InputStream istream = itemStream;
783                 if (fileSizeMax != -1) {
784                     istream = new LimitedInputStream(istream, fileSizeMax) {
785                         @Override
786                         protected void raiseError(long pSizeMax, long pCount)
787                                 throws IOException {
788                             itemStream.close(true);
789                             FileSizeLimitExceededException e =
790                                 new FileSizeLimitExceededException(
791                                     format("The field %s exceeds its maximum permitted size of %s bytes.",
792                                            fieldName, Long.valueOf(pSizeMax)),
793                                     pCount, pSizeMax);
794                             e.setFieldName(fieldName);
795                             e.setFileName(name);
796                             throw new FileUploadIOException(e);
797                         }
798                     };
799                 }
800                 stream = istream;
801             }
802 
803             /**
804              * Returns the items content type, or null.
805              *
806              * @return Content type, if known, or null.
807              */
808             @Override
809             public String getContentType() {
810                 return contentType;
811             }
812 
813             /**
814              * Returns the items field name.
815              *
816              * @return Field name.
817              */
818             @Override
819             public String getFieldName() {
820                 return fieldName;
821             }
822 
823             /**
824              * Returns the items file name.
825              *
826              * @return File name, if known, or null.
827              * @throws InvalidFileNameException The file name contains a NUL character,
828              *   which might be an indicator of a security attack. If you intend to
829              *   use the file name anyways, catch the exception and use
830              *   InvalidFileNameException#getName().
831              */
832             @Override
833             public String getName() {
834                 return Streams.checkFileName(name);
835             }
836 
837             /**
838              * Returns, whether this is a form field.
839              *
840              * @return True, if the item is a form field,
841              *   otherwise false.
842              */
843             @Override
844             public boolean isFormField() {
845                 return formField;
846             }
847 
848             /**
849              * Returns an input stream, which may be used to
850              * read the items contents.
851              *
852              * @return Opened input stream.
853              * @throws IOException An I/O error occurred.
854              */
855             @Override
856             public InputStream openStream() throws IOException {
857                 if (opened) {
858                     throw new IllegalStateException(
859                             "The stream was already opened.");
860                 }
861                 if (((Closeable) stream).isClosed()) {
862                     throw new FileItemStream.ItemSkippedException();
863                 }
864                 return stream;
865             }
866 
867             /**
868              * Closes the file item.
869              *
870              * @throws IOException An I/O error occurred.
871              */
872             void close() throws IOException {
873                 stream.close();
874             }
875 
876             /**
877              * Returns the file item headers.
878              *
879              * @return The items header object
880              */
881             @Override
882             public FileItemHeaders getHeaders() {
883                 return headers;
884             }
885 
886             /**
887              * Sets the file item headers.
888              *
889              * @param pHeaders The items header object
890              */
891             @Override
892             public void setHeaders(FileItemHeaders pHeaders) {
893                 headers = pHeaders;
894             }
895 
896         }
897 
898         /**
899          * The multi part stream to process.
900          */
901         private final MultipartStream multi;
902 
903         /**
904          * The notifier, which used for triggering the
905          * {@link ProgressListener}.
906          */
907         private final MultipartStream.ProgressNotifier notifier;
908 
909         /**
910          * The boundary, which separates the various parts.
911          */
912         private final byte[] boundary;
913 
914         /**
915          * The item, which we currently process.
916          */
917         private FileItemStreamImpl currentItem;
918 
919         /**
920          * The current items field name.
921          */
922         private String currentFieldName;
923 
924         /**
925          * Whether we are currently skipping the preamble.
926          */
927         private boolean skipPreamble;
928 
929         /**
930          * Whether the current item may still be read.
931          */
932         private boolean itemValid;
933 
934         /**
935          * Whether we have seen the end of the file.
936          */
937         private boolean eof;
938 
939         /**
940          * Creates a new instance.
941          *
942          * @param ctx The request context.
943          * @throws FileUploadException An error occurred while
944          *   parsing the request.
945          * @throws IOException An I/O error occurred.
946          */
947         FileItemIteratorImpl(RequestContext ctx)
948                 throws FileUploadException, IOException {
949             if (ctx == null) {
950                 throw new NullPointerException("ctx parameter");
951             }
952 
953             String contentType = ctx.getContentType();
954             if ((null == contentType)
955                     || (!contentType.toLowerCase(Locale.ENGLISH).startsWith(MULTIPART))) {
956                 throw new InvalidContentTypeException(
957                         format("the request doesn't contain a %s or %s stream, content type header is %s",
958                                MULTIPART_FORM_DATA, MULTIPART_MIXED, contentType));
959             }
960 
961 
962             @SuppressWarnings("deprecation") // still has to be backward compatible
963             final int contentLengthInt = ctx.getContentLength();
964 
965             final long requestSize = UploadContext.class.isAssignableFrom(ctx.getClass())
966                                      // Inline conditional is OK here CHECKSTYLE:OFF
967                                      ? ((UploadContext) ctx).contentLength()
968                                      : contentLengthInt;
969                                      // CHECKSTYLE:ON
970 
971             InputStream input; // N.B. this is eventually closed in MultipartStream processing
972             if (sizeMax >= 0) {
973                 if (requestSize != -1 && requestSize > sizeMax) {
974                     throw new SizeLimitExceededException(
975                         format("the request was rejected because its size (%s) exceeds the configured maximum (%s)",
976                                 Long.valueOf(requestSize), Long.valueOf(sizeMax)),
977                                requestSize, sizeMax);
978                 }
979                 // N.B. this is eventually closed in MultipartStream processing
980                 input = new LimitedInputStream(ctx.getInputStream(), sizeMax) {
981                     @Override
982                     protected void raiseError(long pSizeMax, long pCount)
983                             throws IOException {
984                         FileUploadException ex = new SizeLimitExceededException(
985                         format("the request was rejected because its size (%s) exceeds the configured maximum (%s)",
986                                 Long.valueOf(pCount), Long.valueOf(pSizeMax)),
987                                pCount, pSizeMax);
988                         throw new FileUploadIOException(ex);
989                     }
990                 };
991             } else {
992                 input = ctx.getInputStream();
993             }
994 
995             String charEncoding = headerEncoding;
996             if (charEncoding == null) {
997                 charEncoding = ctx.getCharacterEncoding();
998             }
999 
1000             boundary = getBoundary(contentType);
1001             if (boundary == null) {
1002                 IOUtils.closeQuietly(input); // avoid possible resource leak
1003                 throw new FileUploadException("the request was rejected because no multipart boundary was found");
1004             }
1005 
1006             notifier = new MultipartStream.ProgressNotifier(listener, requestSize);
1007             try {
1008                 multi = new MultipartStream(input, boundary, notifier);
1009             } catch (IllegalArgumentException iae) {
1010                 IOUtils.closeQuietly(input); // avoid possible resource leak
1011                 throw new InvalidContentTypeException(
1012                         format("The boundary specified in the %s header is too long", CONTENT_TYPE), iae);
1013             }
1014             multi.setHeaderEncoding(charEncoding);
1015 
1016             skipPreamble = true;
1017             findNextItem();
1018         }
1019 
1020         /**
1021          * Called for finding the next item, if any.
1022          *
1023          * @return True, if an next item was found, otherwise false.
1024          * @throws IOException An I/O error occurred.
1025          */
1026         private boolean findNextItem() throws IOException {
1027             if (eof) {
1028                 return false;
1029             }
1030             if (currentItem != null) {
1031                 currentItem.close();
1032                 currentItem = null;
1033             }
1034             for (;;) {
1035                 boolean nextPart;
1036                 if (skipPreamble) {
1037                     nextPart = multi.skipPreamble();
1038                 } else {
1039                     nextPart = multi.readBoundary();
1040                 }
1041                 if (!nextPart) {
1042                     if (currentFieldName == null) {
1043                         // Outer multipart terminated -> No more data
1044                         eof = true;
1045                         return false;
1046                     }
1047                     // Inner multipart terminated -> Return to parsing the outer
1048                     multi.setBoundary(boundary);
1049                     currentFieldName = null;
1050                     continue;
1051                 }
1052                 FileItemHeaders headers = getParsedHeaders(multi.readHeaders());
1053                 if (currentFieldName == null) {
1054                     // We're parsing the outer multipart
1055                     String fieldName = getFieldName(headers);
1056                     if (fieldName != null) {
1057                         String subContentType = headers.getHeader(CONTENT_TYPE);
1058                         if (subContentType != null
1059                                 &&  subContentType.toLowerCase(Locale.ENGLISH)
1060                                         .startsWith(MULTIPART_MIXED)) {
1061                             currentFieldName = fieldName;
1062                             // Multiple files associated with this field name
1063                             byte[] subBoundary = getBoundary(subContentType);
1064                             multi.setBoundary(subBoundary);
1065                             skipPreamble = true;
1066                             continue;
1067                         }
1068                         String fileName = getFileName(headers);
1069                         currentItem = new FileItemStreamImpl(fileName,
1070                                 fieldName, headers.getHeader(CONTENT_TYPE),
1071                                 fileName == null, getContentLength(headers));
1072                         currentItem.setHeaders(headers);
1073                         notifier.noteItem();
1074                         itemValid = true;
1075                         return true;
1076                     }
1077                 } else {
1078                     String fileName = getFileName(headers);
1079                     if (fileName != null) {
1080                         currentItem = new FileItemStreamImpl(fileName,
1081                                 currentFieldName,
1082                                 headers.getHeader(CONTENT_TYPE),
1083                                 false, getContentLength(headers));
1084                         currentItem.setHeaders(headers);
1085                         notifier.noteItem();
1086                         itemValid = true;
1087                         return true;
1088                     }
1089                 }
1090                 multi.discardBodyData();
1091             }
1092         }
1093 
1094         private long getContentLength(FileItemHeaders pHeaders) {
1095             try {
1096                 return Long.parseLong(pHeaders.getHeader(CONTENT_LENGTH));
1097             } catch (Exception e) {
1098                 return -1;
1099             }
1100         }
1101 
1102         /**
1103          * Returns, whether another instance of {@link FileItemStream}
1104          * is available.
1105          *
1106          * @throws FileUploadException Parsing or processing the
1107          *   file item failed.
1108          * @throws IOException Reading the file item failed.
1109          * @return True, if one or more additional file items
1110          *   are available, otherwise false.
1111          */
1112         @Override
1113         public boolean hasNext() throws FileUploadException, IOException {
1114             if (eof) {
1115                 return false;
1116             }
1117             if (itemValid) {
1118                 return true;
1119             }
1120             try {
1121                 return findNextItem();
1122             } catch (FileUploadIOException e) {
1123                 // unwrap encapsulated SizeException
1124                 throw (FileUploadException) e.getCause();
1125             }
1126         }
1127 
1128         /**
1129          * Returns the next available {@link FileItemStream}.
1130          *
1131          * @throws java.util.NoSuchElementException No more items are
1132          *   available. Use {@link #hasNext()} to prevent this exception.
1133          * @throws FileUploadException Parsing or processing the
1134          *   file item failed.
1135          * @throws IOException Reading the file item failed.
1136          * @return FileItemStream instance, which provides
1137          *   access to the next file item.
1138          */
1139         @Override
1140         public FileItemStream next() throws FileUploadException, IOException {
1141             if (eof  ||  (!itemValid && !hasNext())) {
1142                 throw new NoSuchElementException();
1143             }
1144             itemValid = false;
1145             return currentItem;
1146         }
1147 
1148     }
1149 
1150     /**
1151      * This exception is thrown for hiding an inner
1152      * {@link FileUploadException} in an {@link IOException}.
1153      */
1154     public static class FileUploadIOException extends IOException {
1155 
1156         /**
1157          * The exceptions UID, for serializing an instance.
1158          */
1159         private static final long serialVersionUID = -7047616958165584154L;
1160 
1161         /**
1162          * The exceptions cause; we overwrite the parent
1163          * classes field, which is available since Java
1164          * 1.4 only.
1165          */
1166         private final FileUploadException cause;
1167 
1168         /**
1169          * Creates a <code>FileUploadIOException</code> with the
1170          * given cause.
1171          *
1172          * @param pCause The exceptions cause, if any, or null.
1173          */
1174         public FileUploadIOException(FileUploadException pCause) {
1175             // We're not doing super(pCause) cause of 1.3 compatibility.
1176             cause = pCause;
1177         }
1178 
1179         /**
1180          * Returns the exceptions cause.
1181          *
1182          * @return The exceptions cause, if any, or null.
1183          */
1184         @Override
1185         public Throwable getCause() {
1186             return cause;
1187         }
1188 
1189     }
1190 
1191     /**
1192      * Thrown to indicate that the request is not a multipart request.
1193      */
1194     public static class InvalidContentTypeException
1195             extends FileUploadException {
1196 
1197         /**
1198          * The exceptions UID, for serializing an instance.
1199          */
1200         private static final long serialVersionUID = -9073026332015646668L;
1201 
1202         /**
1203          * Constructs a <code>InvalidContentTypeException</code> with no
1204          * detail message.
1205          */
1206         public InvalidContentTypeException() {
1207             super();
1208         }
1209 
1210         /**
1211          * Constructs an <code>InvalidContentTypeException</code> with
1212          * the specified detail message.
1213          *
1214          * @param message The detail message.
1215          */
1216         public InvalidContentTypeException(String message) {
1217             super(message);
1218         }
1219 
1220         /**
1221          * Constructs an <code>InvalidContentTypeException</code> with
1222          * the specified detail message and cause.
1223          *
1224          * @param msg The detail message.
1225          * @param cause the original cause
1226          *
1227          * @since 1.3.1
1228          */
1229         public InvalidContentTypeException(String msg, Throwable cause) {
1230             super(msg, cause);
1231         }
1232     }
1233 
1234     /**
1235      * Thrown to indicate an IOException.
1236      */
1237     public static class IOFileUploadException extends FileUploadException {
1238 
1239         /**
1240          * The exceptions UID, for serializing an instance.
1241          */
1242         private static final long serialVersionUID = 1749796615868477269L;
1243 
1244         /**
1245          * The exceptions cause; we overwrite the parent
1246          * classes field, which is available since Java
1247          * 1.4 only.
1248          */
1249         private final IOException cause;
1250 
1251         /**
1252          * Creates a new instance with the given cause.
1253          *
1254          * @param pMsg The detail message.
1255          * @param pException The exceptions cause.
1256          */
1257         public IOFileUploadException(String pMsg, IOException pException) {
1258             super(pMsg);
1259             cause = pException;
1260         }
1261 
1262         /**
1263          * Returns the exceptions cause.
1264          *
1265          * @return The exceptions cause, if any, or null.
1266          */
1267         @Override
1268         public Throwable getCause() {
1269             return cause;
1270         }
1271 
1272     }
1273 
1274     /**
1275      * This exception is thrown, if a requests permitted size
1276      * is exceeded.
1277      */
1278     protected abstract static class SizeException extends FileUploadException {
1279 
1280         /**
1281          * Serial version UID, being used, if serialized.
1282          */
1283         private static final long serialVersionUID = -8776225574705254126L;
1284 
1285         /**
1286          * The actual size of the request.
1287          */
1288         private final long actual;
1289 
1290         /**
1291          * The maximum permitted size of the request.
1292          */
1293         private final long permitted;
1294 
1295         /**
1296          * Creates a new instance.
1297          *
1298          * @param message The detail message.
1299          * @param actual The actual number of bytes in the request.
1300          * @param permitted The requests size limit, in bytes.
1301          */
1302         protected SizeException(String message, long actual, long permitted) {
1303             super(message);
1304             this.actual = actual;
1305             this.permitted = permitted;
1306         }
1307 
1308         /**
1309          * Retrieves the actual size of the request.
1310          *
1311          * @return The actual size of the request.
1312          * @since 1.3
1313          */
1314         public long getActualSize() {
1315             return actual;
1316         }
1317 
1318         /**
1319          * Retrieves the permitted size of the request.
1320          *
1321          * @return The permitted size of the request.
1322          * @since 1.3
1323          */
1324         public long getPermittedSize() {
1325             return permitted;
1326         }
1327 
1328     }
1329 
1330     /**
1331      * Thrown to indicate that the request size is not specified. In other
1332      * words, it is thrown, if the content-length header is missing or
1333      * contains the value -1.
1334      *
1335      * @deprecated 1.2 As of commons-fileupload 1.2, the presence of a
1336      *   content-length header is no longer required.
1337      */
1338     @Deprecated
1339     public static class UnknownSizeException
1340         extends FileUploadException {
1341 
1342         /**
1343          * The exceptions UID, for serializing an instance.
1344          */
1345         private static final long serialVersionUID = 7062279004812015273L;
1346 
1347         /**
1348          * Constructs a <code>UnknownSizeException</code> with no
1349          * detail message.
1350          */
1351         public UnknownSizeException() {
1352             super();
1353         }
1354 
1355         /**
1356          * Constructs an <code>UnknownSizeException</code> with
1357          * the specified detail message.
1358          *
1359          * @param message The detail message.
1360          */
1361         public UnknownSizeException(String message) {
1362             super(message);
1363         }
1364 
1365     }
1366 
1367     /**
1368      * Thrown to indicate that the request size exceeds the configured maximum.
1369      */
1370     public static class SizeLimitExceededException
1371             extends SizeException {
1372 
1373         /**
1374          * The exceptions UID, for serializing an instance.
1375          */
1376         private static final long serialVersionUID = -2474893167098052828L;
1377 
1378         /**
1379          * @deprecated 1.2 Replaced by
1380          * {@link #SizeLimitExceededException(String, long, long)}
1381          */
1382         @Deprecated
1383         public SizeLimitExceededException() {
1384             this(null, 0, 0);
1385         }
1386 
1387         /**
1388          * @deprecated 1.2 Replaced by
1389          * {@link #SizeLimitExceededException(String, long, long)}
1390          * @param message The exceptions detail message.
1391          */
1392         @Deprecated
1393         public SizeLimitExceededException(String message) {
1394             this(message, 0, 0);
1395         }
1396 
1397         /**
1398          * Constructs a <code>SizeExceededException</code> with
1399          * the specified detail message, and actual and permitted sizes.
1400          *
1401          * @param message   The detail message.
1402          * @param actual    The actual request size.
1403          * @param permitted The maximum permitted request size.
1404          */
1405         public SizeLimitExceededException(String message, long actual,
1406                 long permitted) {
1407             super(message, actual, permitted);
1408         }
1409 
1410     }
1411 
1412     /**
1413      * Thrown to indicate that A files size exceeds the configured maximum.
1414      */
1415     public static class FileSizeLimitExceededException
1416             extends SizeException {
1417 
1418         /**
1419          * The exceptions UID, for serializing an instance.
1420          */
1421         private static final long serialVersionUID = 8150776562029630058L;
1422 
1423         /**
1424          * File name of the item, which caused the exception.
1425          */
1426         private String fileName;
1427 
1428         /**
1429          * Field name of the item, which caused the exception.
1430          */
1431         private String fieldName;
1432 
1433         /**
1434          * Constructs a <code>SizeExceededException</code> with
1435          * the specified detail message, and actual and permitted sizes.
1436          *
1437          * @param message   The detail message.
1438          * @param actual    The actual request size.
1439          * @param permitted The maximum permitted request size.
1440          */
1441         public FileSizeLimitExceededException(String message, long actual,
1442                 long permitted) {
1443             super(message, actual, permitted);
1444         }
1445 
1446         /**
1447          * Returns the file name of the item, which caused the
1448          * exception.
1449          *
1450          * @return File name, if known, or null.
1451          */
1452         public String getFileName() {
1453             return fileName;
1454         }
1455 
1456         /**
1457          * Sets the file name of the item, which caused the
1458          * exception.
1459          *
1460          * @param pFileName the file name of the item, which caused the exception.
1461          */
1462         public void setFileName(String pFileName) {
1463             fileName = pFileName;
1464         }
1465 
1466         /**
1467          * Returns the field name of the item, which caused the
1468          * exception.
1469          *
1470          * @return Field name, if known, or null.
1471          */
1472         public String getFieldName() {
1473             return fieldName;
1474         }
1475 
1476         /**
1477          * Sets the field name of the item, which caused the
1478          * exception.
1479          *
1480          * @param pFieldName the field name of the item,
1481          *        which caused the exception.
1482          */
1483         public void setFieldName(String pFieldName) {
1484             fieldName = pFieldName;
1485         }
1486 
1487     }
1488 
1489     /**
1490      * Returns the progress listener.
1491      *
1492      * @return The progress listener, if any, or null.
1493      */
1494     public ProgressListener getProgressListener() {
1495         return listener;
1496     }
1497 
1498     /**
1499      * Sets the progress listener.
1500      *
1501      * @param pListener The progress listener, if any. Defaults to null.
1502      */
1503     public void setProgressListener(ProgressListener pListener) {
1504         listener = pListener;
1505     }
1506 
1507 }