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 java.io.IOException;
20  import java.io.InputStream;
21  import java.io.UnsupportedEncodingException;
22  import java.util.ArrayList;
23  import java.util.HashMap;
24  import java.util.List;
25  import java.util.Map;
26  import java.util.NoSuchElementException;
27  
28  import javax.servlet.http.HttpServletRequest;
29  
30  import org.apache.commons.fileupload.servlet.ServletFileUpload;
31  import org.apache.commons.fileupload.servlet.ServletRequestContext;
32  import org.apache.commons.fileupload.util.Closeable;
33  import org.apache.commons.fileupload.util.LimitedInputStream;
34  import org.apache.commons.fileupload.util.Streams;
35  
36  
37  /**
38   * <p>High level API for processing file uploads.</p>
39   *
40   * <p>This class handles multiple files per single HTML widget, sent using
41   * <code>multipart/mixed</code> encoding type, as specified by
42   * <a href="http://www.ietf.org/rfc/rfc1867.txt">RFC 1867</a>.  Use {@link
43   * #parseRequest(HttpServletRequest)} to acquire a list of {@link
44   * org.apache.commons.fileupload.FileItem}s associated with a given HTML
45   * widget.</p>
46   *
47   * <p>How the data for individual parts is stored is determined by the factory
48   * used to create them; a given part may be in memory, on disk, or somewhere
49   * else.</p>
50   *
51   * @author <a href="mailto:Rafal.Krzewski@e-point.pl">Rafal Krzewski</a>
52   * @author <a href="mailto:dlr@collab.net">Daniel Rall</a>
53   * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a>
54   * @author <a href="mailto:jmcnally@collab.net">John McNally</a>
55   * @author <a href="mailto:martinc@apache.org">Martin Cooper</a>
56   * @author Sean C. Sullivan
57   *
58   * @version $Id: FileUploadBase.java 502350 2007-02-01 20:42:48Z jochen $
59   */
60  public abstract class FileUploadBase {
61  
62      // ---------------------------------------------------------- Class methods
63  
64  
65      /**
66       * <p>Utility method that determines whether the request contains multipart
67       * content.</p>
68       *
69       * <p><strong>NOTE:</strong>This method will be moved to the
70       * <code>ServletFileUpload</code> class after the FileUpload 1.1 release.
71       * Unfortunately, since this method is static, it is not possible to
72       * provide its replacement until this method is removed.</p>
73       *
74       * @param ctx The request context to be evaluated. Must be non-null.
75       *
76       * @return <code>true</code> if the request is multipart;
77       *         <code>false</code> otherwise.
78       */
79      public static final boolean isMultipartContent(RequestContext ctx) {
80          String contentType = ctx.getContentType();
81          if (contentType == null) {
82              return false;
83          }
84          if (contentType.toLowerCase().startsWith(MULTIPART)) {
85              return true;
86          }
87          return false;
88      }
89  
90  
91      /**
92       * Utility method that determines whether the request contains multipart
93       * content.
94       *
95       * @param req The servlet request to be evaluated. Must be non-null.
96       *
97       * @return <code>true</code> if the request is multipart;
98       *         <code>false</code> otherwise.
99       *
100      * @deprecated Use the method on <code>ServletFileUpload</code> instead.
101      */
102     public static boolean isMultipartContent(HttpServletRequest req) {
103         return ServletFileUpload.isMultipartContent(req);
104     }
105 
106 
107     // ----------------------------------------------------- Manifest constants
108 
109 
110     /**
111      * HTTP content type header name.
112      */
113     public static final String CONTENT_TYPE = "Content-type";
114 
115 
116     /**
117      * HTTP content disposition header name.
118      */
119     public static final String CONTENT_DISPOSITION = "Content-disposition";
120 
121 
122     /**
123      * Content-disposition value for form data.
124      */
125     public static final String FORM_DATA = "form-data";
126 
127 
128     /**
129      * Content-disposition value for file attachment.
130      */
131     public static final String ATTACHMENT = "attachment";
132 
133 
134     /**
135      * Part of HTTP content type header.
136      */
137     public static final String MULTIPART = "multipart/";
138 
139 
140     /**
141      * HTTP content type header for multipart forms.
142      */
143     public static final String MULTIPART_FORM_DATA = "multipart/form-data";
144 
145 
146     /**
147      * HTTP content type header for multiple uploads.
148      */
149     public static final String MULTIPART_MIXED = "multipart/mixed";
150 
151 
152     /**
153      * The maximum length of a single header line that will be parsed
154      * (1024 bytes).
155      * @deprecated This constant is no longer used. As of commons-fileupload
156      *   1.2, the only applicable limit is the total size of a parts headers,
157      *   {@link MultipartStream#HEADER_PART_SIZE_MAX}.
158      */
159     public static final int MAX_HEADER_SIZE = 1024;
160 
161 
162     // ----------------------------------------------------------- Data members
163 
164 
165     /**
166      * The maximum size permitted for the complete request, as opposed to
167      * {@link #fileSizeMax}. A value of -1 indicates no maximum.
168      */
169     private long sizeMax = -1;
170 
171     /**
172      * The maximum size permitted for a single uploaded file, as opposed
173      * to {@link #sizeMax}. A value of -1 indicates no maximum.
174      */
175     private long fileSizeMax = -1;
176 
177     /**
178      * The content encoding to use when reading part headers.
179      */
180     private String headerEncoding;
181 
182     /**
183      * The progress listener.
184      */
185     private ProgressListener listener;
186 
187     // ----------------------------------------------------- Property accessors
188 
189 
190     /**
191      * Returns the factory class used when creating file items.
192      *
193      * @return The factory class for new file items.
194      */
195     public abstract FileItemFactory getFileItemFactory();
196 
197 
198     /**
199      * Sets the factory class to use when creating file items.
200      *
201      * @param factory The factory class for new file items.
202      */
203     public abstract void setFileItemFactory(FileItemFactory factory);
204 
205 
206     /**
207      * Returns the maximum allowed size of a complete request, as opposed
208      * to {@link #getFileSizeMax()}.
209      *
210      * @return The maximum allowed size, in bytes. The default value of
211      *   -1 indicates, that there is no limit.
212      *
213      * @see #setSizeMax(long)
214      *
215      */
216     public long getSizeMax() {
217         return sizeMax;
218     }
219 
220 
221     /**
222      * Sets the maximum allowed size of a complete request, as opposed
223      * to {@link #setFileSizeMax(long)}.
224      *
225      * @param sizeMax The maximum allowed size, in bytes. The default value of
226      *   -1 indicates, that there is no limit.
227      *
228      * @see #getSizeMax()
229      *
230      */
231     public void setSizeMax(long sizeMax) {
232         this.sizeMax = sizeMax;
233     }
234 
235     /**
236      * Returns the maximum allowed size of a single uploaded file,
237      * as opposed to {@link #getSizeMax()}.
238      *
239      * @see #setFileSizeMax(long)
240      * @return Maximum size of a single uploaded file.
241      */
242     public long getFileSizeMax() {
243         return fileSizeMax;
244     }
245 
246     /**
247      * Sets the maximum allowed size of a single uploaded file,
248      * as opposed to {@link #getSizeMax()}.
249      *
250      * @see #getFileSizeMax()
251      * @param fileSizeMax Maximum size of a single uploaded file.
252      */
253     public void setFileSizeMax(long fileSizeMax) {
254         this.fileSizeMax = fileSizeMax;
255     }
256 
257     /**
258      * Retrieves the character encoding used when reading the headers of an
259      * individual part. When not specified, or <code>null</code>, the request
260      * encoding is used. If that is also not specified, or <code>null</code>,
261      * the platform default encoding is used.
262      *
263      * @return The encoding used to read part headers.
264      */
265     public String getHeaderEncoding() {
266         return headerEncoding;
267     }
268 
269 
270     /**
271      * Specifies the character encoding to be used when reading the headers of
272      * individual part. When not specified, or <code>null</code>, the request
273      * encoding is used. If that is also not specified, or <code>null</code>,
274      * the platform default encoding is used.
275      *
276      * @param encoding The encoding used to read part headers.
277      */
278     public void setHeaderEncoding(String encoding) {
279         headerEncoding = encoding;
280     }
281 
282 
283     // --------------------------------------------------------- Public methods
284 
285 
286     /**
287      * Processes an <a href="http://www.ietf.org/rfc/rfc1867.txt">RFC 1867</a>
288      * compliant <code>multipart/form-data</code> stream.
289      *
290      * @param req The servlet request to be parsed.
291      *
292      * @return A list of <code>FileItem</code> instances parsed from the
293      *         request, in the order that they were transmitted.
294      *
295      * @throws FileUploadException if there are problems reading/parsing
296      *                             the request or storing files.
297      *
298      * @deprecated Use the method in <code>ServletFileUpload</code> instead.
299      */
300     public List /* FileItem */ parseRequest(HttpServletRequest req)
301     throws FileUploadException {
302         return parseRequest(new ServletRequestContext(req));
303     }
304 
305     /**
306      * Processes an <a href="http://www.ietf.org/rfc/rfc1867.txt">RFC 1867</a>
307      * compliant <code>multipart/form-data</code> stream.
308      *
309      * @param ctx The context for the request to be parsed.
310      *
311      * @return An iterator to instances of <code>FileItemStream</code>
312      *         parsed from the request, in the order that they were
313      *         transmitted.
314      *
315      * @throws FileUploadException if there are problems reading/parsing
316      *                             the request or storing files.
317      * @throws IOException An I/O error occurred. This may be a network
318      *   error while communicating with the client or a problem while
319      *   storing the uploaded content.
320      */
321     public FileItemIterator getItemIterator(RequestContext ctx)
322     throws FileUploadException, IOException {
323         return new FileItemIteratorImpl(ctx);
324     }
325 
326     /**
327      * Processes an <a href="http://www.ietf.org/rfc/rfc1867.txt">RFC 1867</a>
328      * compliant <code>multipart/form-data</code> stream.
329      *
330      * @param ctx The context for the request to be parsed.
331      *
332      * @return A list of <code>FileItem</code> instances parsed from the
333      *         request, in the order that they were transmitted.
334      *
335      * @throws FileUploadException if there are problems reading/parsing
336      *                             the request or storing files.
337      */
338     public List /* FileItem */ parseRequest(RequestContext ctx)
339             throws FileUploadException {
340         try {
341             FileItemIterator iter = getItemIterator(ctx);
342             List items = new ArrayList();
343             FileItemFactory fac = getFileItemFactory();
344             if (fac == null) {
345                 throw new NullPointerException(
346                     "No FileItemFactory has been set.");
347             }
348             while (iter.hasNext()) {
349                 FileItemStream item = iter.next();
350                 FileItem fileItem = fac.createItem(item.getFieldName(),
351                         item.getContentType(), item.isFormField(),
352                         item.getName());
353                 try {
354                     Streams.copy(item.openStream(), fileItem.getOutputStream(),
355                             true);
356                 } catch (FileUploadIOException e) {
357                     throw (FileUploadException) e.getCause();
358                 } catch (IOException e) {
359                     throw new IOFileUploadException(
360                             "Processing of " + MULTIPART_FORM_DATA
361                             + " request failed. " + e.getMessage(), e);
362                 }
363                 items.add(fileItem);
364             }
365             return items;
366         } catch (FileUploadIOException e) {
367             throw (FileUploadException) e.getCause();
368         } catch (IOException e) {
369             throw new FileUploadException(e.getMessage(), e);
370         }
371     }
372 
373 
374     // ------------------------------------------------------ Protected methods
375 
376 
377     /**
378      * Retrieves the boundary from the <code>Content-type</code> header.
379      *
380      * @param contentType The value of the content type header from which to
381      *                    extract the boundary value.
382      *
383      * @return The boundary, as a byte array.
384      */
385     protected byte[] getBoundary(String contentType) {
386         ParameterParser parser = new ParameterParser();
387         parser.setLowerCaseNames(true);
388         // Parameter parser can handle null input
389         Map params = parser.parse(contentType, ';');
390         String boundaryStr = (String) params.get("boundary");
391 
392         if (boundaryStr == null) {
393             return null;
394         }
395         byte[] boundary;
396         try {
397             boundary = boundaryStr.getBytes("ISO-8859-1");
398         } catch (UnsupportedEncodingException e) {
399             boundary = boundaryStr.getBytes();
400         }
401         return boundary;
402     }
403 
404 
405     /**
406      * Retrieves the file name from the <code>Content-disposition</code>
407      * header.
408      *
409      * @param headers A <code>Map</code> containing the HTTP request headers.
410      *
411      * @return The file name for the current <code>encapsulation</code>.
412      */
413     protected String getFileName(Map /* String, String */ headers) {
414         String fileName = null;
415         String cd = getHeader(headers, CONTENT_DISPOSITION);
416         if (cd != null) {
417             String cdl = cd.toLowerCase();
418             if (cdl.startsWith(FORM_DATA) || cdl.startsWith(ATTACHMENT)) {
419                 ParameterParser parser = new ParameterParser();
420                 parser.setLowerCaseNames(true);
421                 // Parameter parser can handle null input
422                 Map params = parser.parse(cd, ';');
423                 if (params.containsKey("filename")) {
424                     fileName = (String) params.get("filename");
425                     if (fileName != null) {
426                         fileName = fileName.trim();
427                     } else {
428                         // Even if there is no value, the parameter is present,
429                         // so we return an empty file name rather than no file
430                         // name.
431                         fileName = "";
432                     }
433                 }
434             }
435         }
436         return fileName;
437     }
438 
439 
440     /**
441      * Retrieves the field name from the <code>Content-disposition</code>
442      * header.
443      *
444      * @param headers A <code>Map</code> containing the HTTP request headers.
445      *
446      * @return The field name for the current <code>encapsulation</code>.
447      */
448     protected String getFieldName(Map /* String, String */ headers) {
449         String fieldName = null;
450         String cd = getHeader(headers, CONTENT_DISPOSITION);
451         if (cd != null && cd.toLowerCase().startsWith(FORM_DATA)) {
452 
453             ParameterParser parser = new ParameterParser();
454             parser.setLowerCaseNames(true);
455             // Parameter parser can handle null input
456             Map params = parser.parse(cd, ';');
457             fieldName = (String) params.get("name");
458             if (fieldName != null) {
459                 fieldName = fieldName.trim();
460             }
461         }
462         return fieldName;
463     }
464 
465 
466     /**
467      * Creates a new {@link FileItem} instance.
468      *
469      * @param headers       A <code>Map</code> containing the HTTP request
470      *                      headers.
471      * @param isFormField   Whether or not this item is a form field, as
472      *                      opposed to a file.
473      *
474      * @return A newly created <code>FileItem</code> instance.
475      *
476      * @throws FileUploadException if an error occurs.
477      * @deprecated This method is no longer used in favour of
478      *   internally created instances of {@link FileItem}.
479      */
480     protected FileItem createItem(Map /* String, String */ headers,
481                                   boolean isFormField)
482         throws FileUploadException {
483         return getFileItemFactory().createItem(getFieldName(headers),
484                 getHeader(headers, CONTENT_TYPE),
485                 isFormField,
486                 getFileName(headers));
487     }
488 
489 
490     /**
491      * <p> Parses the <code>header-part</code> and returns as key/value
492      * pairs.
493      *
494      * <p> If there are multiple headers of the same names, the name
495      * will map to a comma-separated list containing the values.
496      *
497      * @param headerPart The <code>header-part</code> of the current
498      *                   <code>encapsulation</code>.
499      *
500      * @return A <code>Map</code> containing the parsed HTTP request headers.
501      */
502     protected Map /* String, String */ parseHeaders(String headerPart) {
503         final int len = headerPart.length();
504         Map headers = new HashMap();
505         int start = 0;
506         for (;;) {
507             int end = parseEndOfLine(headerPart, start);
508             if (start == end) {
509                 break;
510             }
511             String header = headerPart.substring(start, end);
512             start = end + 2;
513             while (start < len) {
514                 int nonWs = start;
515                 while (nonWs < len) {
516                     char c = headerPart.charAt(nonWs);
517                     if (c != ' '  &&  c != '\t') {
518                         break;
519                     }
520                     ++nonWs;
521                 }
522                 if (nonWs == start) {
523                     break;
524                 }
525                 // Continuation line found
526                 end = parseEndOfLine(headerPart, nonWs);
527                 header += " " + headerPart.substring(nonWs, end);
528                 start = end + 2;
529             }
530             parseHeaderLine(headers, header);
531         }
532         return headers;
533     }
534 
535     /**
536      * Skips bytes until the end of the current line.
537      * @param headerPart The headers, which are being parsed.
538      * @param end Index of the last byte, which has yet been
539      *   processed.
540      * @return Index of the \r\n sequence, which indicates
541      *   end of line.
542      */
543     private int parseEndOfLine(String headerPart, int end) {
544         int index = end;
545         for (;;) {
546             int offset = headerPart.indexOf('\r', index);
547             if (offset == -1  ||  offset + 1 >= headerPart.length()) {
548                 throw new IllegalStateException(
549                     "Expected headers to be terminated by an empty line.");
550             }
551             if (headerPart.charAt(offset + 1) == '\n') {
552                 return offset;
553             }
554             index = offset + 1;
555         }
556     }
557 
558     /**
559      * Reads the next header line.
560      * @param headers String with all headers.
561      * @param header Map where to store the current header.
562      */
563     private void parseHeaderLine(Map headers, String header) {
564         final int colonOffset = header.indexOf(':');
565         if (colonOffset == -1) {
566             // This header line is malformed, skip it.
567             return;
568         }
569         String headerName = header.substring(0, colonOffset)
570             .trim().toLowerCase();
571         String headerValue =
572             header.substring(header.indexOf(':') + 1).trim();
573         if (getHeader(headers, headerName) != null) {
574             // More that one heder of that name exists,
575             // append to the list.
576             headers.put(headerName,
577                     getHeader(headers, headerName) + ','
578                     + headerValue);
579         } else {
580             headers.put(headerName, headerValue);
581         }
582     }
583 
584     /**
585      * Returns the header with the specified name from the supplied map. The
586      * header lookup is case-insensitive.
587      *
588      * @param headers A <code>Map</code> containing the HTTP request headers.
589      * @param name    The name of the header to return.
590      *
591      * @return The value of specified header, or a comma-separated list if
592      *         there were multiple headers of that name.
593      */
594     protected final String getHeader(Map /* String, String */ headers,
595             String name) {
596         return (String) headers.get(name.toLowerCase());
597     }
598 
599     /**
600      * The iterator, which is returned by
601      * {@link FileUploadBase#getItemIterator(RequestContext)}.
602      */
603     private class FileItemIteratorImpl implements FileItemIterator {
604         /**
605          * Default implementation of {@link FileItemStream}.
606          */
607         private class FileItemStreamImpl implements FileItemStream {
608             /** The file items content type.
609              */
610             private final String contentType;
611             /** The file items field name.
612              */
613             private final String fieldName;
614             /** The file items file name.
615              */
616             private final String name;
617             /** Whether the file item is a form field.
618              */
619             private final boolean formField;
620             /** The file items input stream.
621              */
622             private final InputStream stream;
623             /** Whether the file item was already opened.
624              */
625             private boolean opened;
626 
627             /**
628              * CReates a new instance.
629              * @param pName The items file name, or null.
630              * @param pFieldName The items field name.
631              * @param pContentType The items content type, or null.
632              * @param pFormField Whether the item is a form field.
633              */
634             FileItemStreamImpl(String pName, String pFieldName,
635                     String pContentType, boolean pFormField) {
636                 name = pName;
637                 fieldName = pFieldName;
638                 contentType = pContentType;
639                 formField = pFormField;
640                 InputStream istream = multi.newInputStream();
641                 if (fileSizeMax != -1) {
642                     istream = new LimitedInputStream(istream, fileSizeMax) {
643                         protected void raiseError(long pSizeMax, long pCount)
644                                 throws IOException {
645                             FileUploadException e =
646                                 new FileSizeLimitExceededException(
647                                     "The field " + fieldName
648                                     + " exceeds its maximum permitted "
649                                     + " size of " + pSizeMax
650                                     + " characters.",
651                                     pCount, pSizeMax);
652                             throw new FileUploadIOException(e);
653                         }
654                     };
655                 }
656                 stream = istream;
657             }
658 
659             /**
660              * Returns the items content type, or null.
661              * @return Content type, if known, or null.
662              */
663             public String getContentType() {
664                 return contentType;
665             }
666 
667             /**
668              * Returns the items field name.
669              * @return Field name.
670              */
671             public String getFieldName() {
672                 return fieldName;
673             }
674 
675             /**
676              * Returns the items file name.
677              * @return File name, if known, or null.
678              */
679             public String getName() {
680                 return name;
681             }
682 
683             /**
684              * Returns, whether this is a form field.
685              * @return True, if the item is a form field,
686              *   otherwise false.
687              */
688             public boolean isFormField() {
689                 return formField;
690             }
691 
692             /**
693              * Returns an input stream, which may be used to
694              * read the items contents.
695              * @return Opened input stream.
696              * @throws IOException An I/O error occurred.
697              */
698             public InputStream openStream() throws IOException {
699                 if (opened) {
700                     throw new IllegalStateException(
701                             "The stream was already opened.");
702                 }
703                 if (((Closeable) stream).isClosed()) {
704                     throw new FileItemStream.ItemSkippedException();
705                 }
706                 return stream;
707             }
708 
709             /**
710              * Closes the file item.
711              * @throws IOException An I/O error occurred.
712              */
713             void close() throws IOException {
714                 stream.close();
715             }
716         }
717 
718         /**
719          * The multi part stream to process.
720          */
721         private final MultipartStream multi;
722         /**
723          * The notifier, which used for triggering the
724          * {@link ProgressListener}.
725          */
726         private final MultipartStream.ProgressNotifier notifier;
727         /**
728          * The boundary, which separates the various parts.
729          */
730         private final byte[] boundary;
731         /**
732          * The item, which we currently process.
733          */
734         private FileItemStreamImpl currentItem;
735         /**
736          * The current items field name.
737          */
738         private String currentFieldName;
739         /**
740          * Whether we are currently skipping the preamble.
741          */
742         private boolean skipPreamble;
743         /**
744          * Whether the current item may still be read.
745          */
746         private boolean itemValid;
747         /**
748          * Whether we have seen the end of the file.
749          */
750         private boolean eof;
751 
752         /**
753          * Creates a new instance.
754          * @param ctx The request context.
755          * @throws FileUploadException An error occurred while
756          *   parsing the request.
757          * @throws IOException An I/O error occurred.
758          */
759         FileItemIteratorImpl(RequestContext ctx)
760                 throws FileUploadException, IOException {
761             if (ctx == null) {
762                 throw new NullPointerException("ctx parameter");
763             }
764 
765             String contentType = ctx.getContentType();
766             if ((null == contentType)
767                     || (!contentType.toLowerCase().startsWith(MULTIPART))) {
768                 throw new InvalidContentTypeException(
769                         "the request doesn't contain a "
770                         + MULTIPART_FORM_DATA
771                         + " or "
772                         + MULTIPART_MIXED
773                         + " stream, content type header is "
774                         + contentType);
775             }
776 
777             InputStream input = ctx.getInputStream();
778 
779             if (sizeMax >= 0) {
780                 int requestSize = ctx.getContentLength();
781                 if (requestSize == -1) {
782                     input = new LimitedInputStream(input, sizeMax) {
783                         protected void raiseError(long pSizeMax, long pCount)
784                                 throws IOException {
785                             FileUploadException ex =
786                                 new SizeLimitExceededException(
787                                     "the request was rejected because"
788                                     + " its size (" + pCount
789                                     + ") exceeds the configured maximum"
790                                     + " (" + pSizeMax + ")",
791                                     pCount, pSizeMax);
792                             throw new FileUploadIOException(ex);
793                         }
794                     };
795                 } else {
796                     if (sizeMax >= 0 && requestSize > sizeMax) {
797                         throw new SizeLimitExceededException(
798                                 "the request was rejected because its size ("
799                                 + requestSize
800                                 + ") exceeds the configured maximum ("
801                                 + sizeMax + ")",
802                                 requestSize, sizeMax);
803                     }
804                 }
805             }
806 
807             String charEncoding = headerEncoding;
808             if (charEncoding == null) {
809                 charEncoding = ctx.getCharacterEncoding();
810             }
811 
812             boundary = getBoundary(contentType);
813             if (boundary == null) {
814                 throw new FileUploadException(
815                         "the request was rejected because "
816                         + "no multipart boundary was found");
817             }
818 
819             notifier = new MultipartStream.ProgressNotifier(listener,
820                     ctx.getContentLength());
821             multi = new MultipartStream(input, boundary, notifier);
822             multi.setHeaderEncoding(charEncoding);
823 
824             skipPreamble = true;
825             findNextItem();
826         }
827 
828         /**
829          * Called for finding the nex item, if any.
830          * @return True, if an next item was found, otherwise false.
831          * @throws IOException An I/O error occurred.
832          */
833         private boolean findNextItem() throws IOException {
834             if (eof) {
835                 return false;
836             }
837             if (currentItem != null) {
838                 currentItem.close();
839                 currentItem = null;
840             }
841             for (;;) {
842                 boolean nextPart;
843                 if (skipPreamble) {
844                     nextPart = multi.skipPreamble();
845                 } else {
846                     nextPart = multi.readBoundary();
847                 }
848                 if (!nextPart) {
849                     if (currentFieldName == null) {
850                         // Outer multipart terminated -> No more data
851                         eof = true;
852                         return false;
853                     }
854                     // Inner multipart terminated -> Return to parsing the outer
855                     multi.setBoundary(boundary);
856                     currentFieldName = null;
857                     continue;
858                 }
859                 Map headers = parseHeaders(multi.readHeaders());
860                 if (currentFieldName == null) {
861                     // We're parsing the outer multipart
862                     String fieldName = getFieldName(headers);
863                     if (fieldName != null) {
864                         String subContentType
865                             = getHeader(headers, CONTENT_TYPE);
866                         if (subContentType != null
867                                 &&  subContentType.toLowerCase()
868                                         .startsWith(MULTIPART_MIXED)) {
869                             currentFieldName = fieldName;
870                             // Multiple files associated with this field name
871                             byte[] subBoundary = getBoundary(subContentType);
872                             multi.setBoundary(subBoundary);
873                             skipPreamble = true;
874                             continue;
875                         }
876                         String fileName = getFileName(headers);
877                         currentItem = new FileItemStreamImpl(fileName,
878                                 fieldName, getHeader(headers, CONTENT_TYPE),
879                                 fileName == null);
880                         notifier.noteItem();
881                         itemValid = true;
882                         return true;
883                     }
884                 } else {
885                     String fileName = getFileName(headers);
886                     if (fileName != null) {
887                         currentItem = new FileItemStreamImpl(fileName,
888                                 currentFieldName,
889                                 getHeader(headers, CONTENT_TYPE),
890                                 false);
891                         notifier.noteItem();
892                         itemValid = true;
893                         return true;
894                     }
895                 }
896                 multi.discardBodyData();
897             }
898         }
899 
900         /**
901          * Returns, whether another instance of {@link FileItemStream}
902          * is available.
903          * @throws FileUploadException Parsing or processing the
904          *   file item failed.
905          * @throws IOException Reading the file item failed.
906          * @return True, if one or more additional file items
907          *   are available, otherwise false.
908          */
909         public boolean hasNext() throws FileUploadException, IOException {
910             if (eof) {
911                 return false;
912             }
913             if (itemValid) {
914                 return true;
915             }
916             return findNextItem();
917         }
918 
919         /**
920          * Returns the next available {@link FileItemStream}.
921          * @throws java.util.NoSuchElementException No more items are
922          *   available. Use {@link #hasNext()} to prevent this exception.
923          * @throws FileUploadException Parsing or processing the
924          *   file item failed.
925          * @throws IOException Reading the file item failed.
926          * @return FileItemStream instance, which provides
927          *   access to the next file item.
928          */
929         public FileItemStream next() throws FileUploadException, IOException {
930             if (eof  ||  (!itemValid && !hasNext())) {
931                 throw new NoSuchElementException();
932             }
933             itemValid = false;
934             return currentItem;
935         }
936     }
937 
938     /**
939      * This exception is thrown for hiding an inner
940      * {@link FileUploadException} in an {@link IOException}.
941      */
942     public static class FileUploadIOException extends IOException {
943         /** The exceptions UID, for serializing an instance.
944          */
945         private static final long serialVersionUID = -7047616958165584154L;
946         /** The exceptions cause; we overwrite the parent
947          * classes field, which is available since Java
948          * 1.4 only.
949          */
950         private final FileUploadException cause;
951 
952         /**
953          * Creates a <code>FileUploadIOException</code> with the
954          * given cause.
955          * @param pCause The exceptions cause, if any, or null.
956          */
957         public FileUploadIOException(FileUploadException pCause) {
958             // We're not doing super(pCause) cause of 1.3 compatibility.
959             cause = pCause;
960         }
961 
962         /**
963          * Returns the exceptions cause.
964          * @return The exceptions cause, if any, or null.
965          */
966         public Throwable getCause() {
967             return cause;
968         }
969     }
970 
971     /**
972      * Thrown to indicate that the request is not a multipart request.
973      */
974     public static class InvalidContentTypeException
975             extends FileUploadException {
976         /** The exceptions UID, for serializing an instance.
977          */
978         private static final long serialVersionUID = -9073026332015646668L;
979 
980         /**
981          * Constructs a <code>InvalidContentTypeException</code> with no
982          * detail message.
983          */
984         public InvalidContentTypeException() {
985             // Nothing to do.
986         }
987 
988         /**
989          * Constructs an <code>InvalidContentTypeException</code> with
990          * the specified detail message.
991          *
992          * @param message The detail message.
993          */
994         public InvalidContentTypeException(String message) {
995             super(message);
996         }
997     }
998 
999     /**
1000      * Thrown to indicate an IOException.
1001      */
1002     public static class IOFileUploadException extends FileUploadException {
1003         /** The exceptions UID, for serializing an instance.
1004          */
1005         private static final long serialVersionUID = 1749796615868477269L;
1006         /** The exceptions cause; we overwrite the parent
1007          * classes field, which is available since Java
1008          * 1.4 only.
1009          */
1010         private final IOException cause;
1011 
1012         /**
1013          * Creates a new instance with the given cause.
1014          * @param pMsg The detail message.
1015          * @param pException The exceptions cause.
1016          */
1017         public IOFileUploadException(String pMsg, IOException pException) {
1018             super(pMsg);
1019             cause = pException;
1020         }
1021 
1022         /**
1023          * Returns the exceptions cause.
1024          * @return The exceptions cause, if any, or null.
1025          */
1026         public Throwable getCause() {
1027             return cause;
1028         }
1029     }
1030 
1031     /** This exception is thrown, if a requests permitted size
1032      * is exceeded.
1033      */
1034     protected abstract static class SizeException extends FileUploadException {
1035         /**
1036          * The actual size of the request.
1037          */
1038         private final long actual;
1039 
1040         /**
1041          * The maximum permitted size of the request.
1042          */
1043         private final long permitted;
1044 
1045         /**
1046          * Creates a new instance.
1047          * @param message The detail message.
1048          * @param actual The actual number of bytes in the request.
1049          * @param permitted The requests size limit, in bytes.
1050          */
1051         protected SizeException(String message, long actual, long permitted) {
1052             super(message);
1053             this.actual = actual;
1054             this.permitted = permitted;
1055         }
1056 
1057         /**
1058          * Retrieves the actual size of the request.
1059          *
1060          * @return The actual size of the request.
1061          */
1062         public long getActualSize() {
1063             return actual;
1064         }
1065 
1066         /**
1067          * Retrieves the permitted size of the request.
1068          *
1069          * @return The permitted size of the request.
1070          */
1071         public long getPermittedSize() {
1072             return permitted;
1073         }
1074     }
1075 
1076     /**
1077      * Thrown to indicate that the request size is not specified. In other
1078      * words, it is thrown, if the content-length header is missing or
1079      * contains the value -1.
1080      * @deprecated As of commons-fileupload 1.2, the presence of a
1081      *   content-length header is no longer required.
1082      */
1083     public static class UnknownSizeException
1084         extends FileUploadException {
1085         /** The exceptions UID, for serializing an instance.
1086          */
1087         private static final long serialVersionUID = 7062279004812015273L;
1088 
1089         /**
1090          * Constructs a <code>UnknownSizeException</code> with no
1091          * detail message.
1092          */
1093         public UnknownSizeException() {
1094             super();
1095         }
1096 
1097         /**
1098          * Constructs an <code>UnknownSizeException</code> with
1099          * the specified detail message.
1100          *
1101          * @param message The detail message.
1102          */
1103         public UnknownSizeException(String message) {
1104             super(message);
1105         }
1106     }
1107 
1108     /**
1109      * Thrown to indicate that the request size exceeds the configured maximum.
1110      */
1111     public static class SizeLimitExceededException
1112             extends SizeException {
1113         /** The exceptions UID, for serializing an instance.
1114          */
1115         private static final long serialVersionUID = -2474893167098052828L;
1116 
1117         /**
1118          * @deprecated Replaced by
1119          * {@link #SizeLimitExceededException(String, long, long)}
1120          */
1121         public SizeLimitExceededException() {
1122             this(null, 0, 0);
1123         }
1124 
1125         /**
1126          * @deprecated Replaced by
1127          * {@link #SizeLimitExceededException(String, long, long)}
1128          * @param message The exceptions detail message.
1129          */
1130         public SizeLimitExceededException(String message) {
1131             this(message, 0, 0);
1132         }
1133 
1134         /**
1135          * Constructs a <code>SizeExceededException</code> with
1136          * the specified detail message, and actual and permitted sizes.
1137          *
1138          * @param message   The detail message.
1139          * @param actual    The actual request size.
1140          * @param permitted The maximum permitted request size.
1141          */
1142         public SizeLimitExceededException(String message, long actual,
1143                 long permitted) {
1144             super(message, actual, permitted);
1145         }
1146     }
1147 
1148     /**
1149      * Thrown to indicate that A files size exceeds the configured maximum.
1150      */
1151     public static class FileSizeLimitExceededException
1152             extends SizeException {
1153         /** The exceptions UID, for serializing an instance.
1154          */
1155         private static final long serialVersionUID = 8150776562029630058L;
1156 
1157         /**
1158          * Constructs a <code>SizeExceededException</code> with
1159          * the specified detail message, and actual and permitted sizes.
1160          *
1161          * @param message   The detail message.
1162          * @param actual    The actual request size.
1163          * @param permitted The maximum permitted request size.
1164          */
1165         public FileSizeLimitExceededException(String message, long actual,
1166                 long permitted) {
1167             super(message, actual, permitted);
1168         }
1169     }
1170 
1171     /**
1172      * Returns the progress listener.
1173      * @return The progress listener, if any, or null.
1174      */
1175     public ProgressListener getProgressListener() {
1176         return listener;
1177     }
1178 
1179     /**
1180      * Sets the progress listener.
1181      * @param pListener The progress listener, if any. Defaults to null.
1182      */
1183     public void setProgressListener(ProgressListener pListener) {
1184         listener = pListener;
1185     }
1186 }