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