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