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 607869 2008-01-01 16:42:17Z 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         try {
349             FileItemIterator iter = getItemIterator(ctx);
350             List items = new ArrayList();
351             FileItemFactory fac = getFileItemFactory();
352             if (fac == null) {
353                 throw new NullPointerException(
354                     "No FileItemFactory has been set.");
355             }
356             while (iter.hasNext()) {
357                 FileItemStream item = iter.next();
358                 FileItem fileItem = fac.createItem(item.getFieldName(),
359                         item.getContentType(), item.isFormField(),
360                         item.getName());
361                 try {
362                     Streams.copy(item.openStream(), fileItem.getOutputStream(),
363                             true);
364                 } catch (FileUploadIOException e) {
365                     throw (FileUploadException) e.getCause();
366                 } catch (IOException e) {
367                     throw new IOFileUploadException(
368                             "Processing of " + MULTIPART_FORM_DATA
369                             + " request failed. " + e.getMessage(), e);
370                 }
371                 if (fileItem instanceof FileItemHeadersSupport) {
372                     final FileItemHeaders fih = item.getHeaders();
373                     ((FileItemHeadersSupport) fileItem).setHeaders(fih);
374                 }
375                 items.add(fileItem);
376             }
377             return items;
378         } catch (FileUploadIOException e) {
379             throw (FileUploadException) e.getCause();
380         } catch (IOException e) {
381             throw new FileUploadException(e.getMessage(), e);
382         }
383     }
384 
385 
386     // ------------------------------------------------------ Protected methods
387 
388 
389     /**
390      * Retrieves the boundary from the <code>Content-type</code> header.
391      *
392      * @param contentType The value of the content type header from which to
393      *                    extract the boundary value.
394      *
395      * @return The boundary, as a byte array.
396      */
397     protected byte[] getBoundary(String contentType) {
398         ParameterParser parser = new ParameterParser();
399         parser.setLowerCaseNames(true);
400         // Parameter parser can handle null input
401         Map params = parser.parse(contentType, new char[] {';', ','});
402         String boundaryStr = (String) params.get("boundary");
403 
404         if (boundaryStr == null) {
405             return null;
406         }
407         byte[] boundary;
408         try {
409             boundary = boundaryStr.getBytes("ISO-8859-1");
410         } catch (UnsupportedEncodingException e) {
411             boundary = boundaryStr.getBytes();
412         }
413         return boundary;
414     }
415 
416 
417     /**
418      * Retrieves the file name from the <code>Content-disposition</code>
419      * header.
420      *
421      * @param headers A <code>Map</code> containing the HTTP request headers.
422      *
423      * @return The file name for the current <code>encapsulation</code>.
424      * @deprecated Use {@link #getFileName(FileItemHeaders)}.
425      */
426     protected String getFileName(Map /* String, String */ headers) {
427         return getFileName(getHeader(headers, CONTENT_DISPOSITION));
428     }
429 
430     /**
431      * Retrieves the file name from the <code>Content-disposition</code>
432      * header.
433      *
434      * @param headers The HTTP headers object.
435      *
436      * @return The file name for the current <code>encapsulation</code>.
437      */
438     protected String getFileName(FileItemHeaders headers) {
439         return getFileName(headers.getHeader(CONTENT_DISPOSITION));
440     }
441 
442     /**
443      * Returns the given content-disposition headers file name.
444      * @param pContentDisposition The content-disposition headers value.
445      * @return The file name
446      */
447     private String getFileName(String pContentDisposition) {
448         String fileName = null;
449         if (pContentDisposition != null) {
450             String cdl = pContentDisposition.toLowerCase();
451             if (cdl.startsWith(FORM_DATA) || cdl.startsWith(ATTACHMENT)) {
452                 ParameterParser parser = new ParameterParser();
453                 parser.setLowerCaseNames(true);
454                 // Parameter parser can handle null input
455                 Map params = parser.parse(pContentDisposition, ';');
456                 if (params.containsKey("filename")) {
457                     fileName = (String) params.get("filename");
458                     if (fileName != null) {
459                         fileName = fileName.trim();
460                     } else {
461                         // Even if there is no value, the parameter is present,
462                         // so we return an empty file name rather than no file
463                         // name.
464                         fileName = "";
465                     }
466                 }
467             }
468         }
469         return fileName;
470     }
471 
472 
473     /**
474      * Retrieves the field name from the <code>Content-disposition</code>
475      * header.
476      *
477      * @param headers A <code>Map</code> containing the HTTP request headers.
478      *
479      * @return The field name for the current <code>encapsulation</code>.
480      */
481     protected String getFieldName(FileItemHeaders headers) {
482         return getFieldName(headers.getHeader(CONTENT_DISPOSITION));
483     }
484 
485     /**
486      * Returns the field name, which is given by the content-disposition
487      * header.
488      * @param pContentDisposition The content-dispositions header value.
489      * @return The field jake
490      */
491     private String getFieldName(String pContentDisposition) {
492         String fieldName = null;
493         if (pContentDisposition != null
494                 && pContentDisposition.toLowerCase().startsWith(FORM_DATA)) {
495             ParameterParser parser = new ParameterParser();
496             parser.setLowerCaseNames(true);
497             // Parameter parser can handle null input
498             Map params = parser.parse(pContentDisposition, ';');
499             fieldName = (String) params.get("name");
500             if (fieldName != null) {
501                 fieldName = fieldName.trim();
502             }
503         }
504         return fieldName;
505     }
506 
507     /**
508      * Retrieves the field name from the <code>Content-disposition</code>
509      * header.
510      *
511      * @param headers A <code>Map</code> containing the HTTP request headers.
512      *
513      * @return The field name for the current <code>encapsulation</code>.
514      * @deprecated Use {@link #getFieldName(FileItemHeaders)}.
515      */
516     protected String getFieldName(Map /* String, String */ headers) {
517         return getFieldName(getHeader(headers, CONTENT_DISPOSITION));
518     }
519 
520 
521     /**
522      * Creates a new {@link FileItem} instance.
523      *
524      * @param headers       A <code>Map</code> containing the HTTP request
525      *                      headers.
526      * @param isFormField   Whether or not this item is a form field, as
527      *                      opposed to a file.
528      *
529      * @return A newly created <code>FileItem</code> instance.
530      *
531      * @throws FileUploadException if an error occurs.
532      * @deprecated This method is no longer used in favour of
533      *   internally created instances of {@link FileItem}.
534      */
535     protected FileItem createItem(Map /* String, String */ headers,
536                                   boolean isFormField)
537         throws FileUploadException {
538         return getFileItemFactory().createItem(getFieldName(headers),
539                 getHeader(headers, CONTENT_TYPE),
540                 isFormField,
541                 getFileName(headers));
542     }
543 
544     /**
545      * <p> Parses the <code>header-part</code> and returns as key/value
546      * pairs.
547      *
548      * <p> If there are multiple headers of the same names, the name
549      * will map to a comma-separated list containing the values.
550      *
551      * @param headerPart The <code>header-part</code> of the current
552      *                   <code>encapsulation</code>.
553      *
554      * @return A <code>Map</code> containing the parsed HTTP request headers.
555      */
556     protected FileItemHeaders getParsedHeaders(String headerPart) {
557         final int len = headerPart.length();
558         FileItemHeadersImpl headers = newFileItemHeaders();
559         int start = 0;
560         for (;;) {
561             int end = parseEndOfLine(headerPart, start);
562             if (start == end) {
563                 break;
564             }
565             String header = headerPart.substring(start, end);
566             start = end + 2;
567             while (start < len) {
568                 int nonWs = start;
569                 while (nonWs < len) {
570                     char c = headerPart.charAt(nonWs);
571                     if (c != ' '  &&  c != '\t') {
572                         break;
573                     }
574                     ++nonWs;
575                 }
576                 if (nonWs == start) {
577                     break;
578                 }
579                 // Continuation line found
580                 end = parseEndOfLine(headerPart, nonWs);
581                 header += " " + headerPart.substring(nonWs, end);
582                 start = end + 2;
583             }
584             parseHeaderLine(headers, header);
585         }
586         return headers;
587     }
588 
589     /**
590      * Creates a new instance of {@link FileItemHeaders}.
591      * @return The new instance.
592      */
593     protected FileItemHeadersImpl newFileItemHeaders() {
594         return new FileItemHeadersImpl();
595     }
596 
597     /**
598      * <p> Parses the <code>header-part</code> and returns as key/value
599      * pairs.
600      *
601      * <p> If there are multiple headers of the same names, the name
602      * will map to a comma-separated list containing the values.
603      *
604      * @param headerPart The <code>header-part</code> of the current
605      *                   <code>encapsulation</code>.
606      *
607      * @return A <code>Map</code> containing the parsed HTTP request headers.
608      * @deprecated Use {@link #getParsedHeaders(String)}
609      */
610     protected Map /* String, String */ parseHeaders(String headerPart) {
611         FileItemHeaders headers = getParsedHeaders(headerPart);
612         Map result = new HashMap();
613         for (Iterator iter = headers.getHeaderNames();  iter.hasNext();) {
614             String headerName = (String) iter.next();
615             Iterator iter2 = headers.getHeaders(headerName);
616             String headerValue = (String) iter2.next();
617             while (iter2.hasNext()) {
618                 headerValue += "," + iter2.next();
619             }
620             result.put(headerName, headerValue);
621         }
622         return result;
623     }
624 
625     /**
626      * Skips bytes until the end of the current line.
627      * @param headerPart The headers, which are being parsed.
628      * @param end Index of the last byte, which has yet been
629      *   processed.
630      * @return Index of the \r\n sequence, which indicates
631      *   end of line.
632      */
633     private int parseEndOfLine(String headerPart, int end) {
634         int index = end;
635         for (;;) {
636             int offset = headerPart.indexOf('\r', index);
637             if (offset == -1  ||  offset + 1 >= headerPart.length()) {
638                 throw new IllegalStateException(
639                     "Expected headers to be terminated by an empty line.");
640             }
641             if (headerPart.charAt(offset + 1) == '\n') {
642                 return offset;
643             }
644             index = offset + 1;
645         }
646     }
647 
648     /**
649      * Reads the next header line.
650      * @param headers String with all headers.
651      * @param header Map where to store the current header.
652      */
653     private void parseHeaderLine(FileItemHeadersImpl headers, String header) {
654         final int colonOffset = header.indexOf(':');
655         if (colonOffset == -1) {
656             // This header line is malformed, skip it.
657             return;
658         }
659         String headerName = header.substring(0, colonOffset).trim();
660         String headerValue =
661             header.substring(header.indexOf(':') + 1).trim();
662         headers.addHeader(headerName, headerValue);
663     }
664 
665     /**
666      * Returns the header with the specified name from the supplied map. The
667      * header lookup is case-insensitive.
668      *
669      * @param headers A <code>Map</code> containing the HTTP request headers.
670      * @param name    The name of the header to return.
671      *
672      * @return The value of specified header, or a comma-separated list if
673      *         there were multiple headers of that name.
674      * @deprecated Use {@link FileItemHeaders#getHeader(String)}.
675      */
676     protected final String getHeader(Map /* String, String */ headers,
677             String name) {
678         return (String) headers.get(name.toLowerCase());
679     }
680 
681     /**
682      * The iterator, which is returned by
683      * {@link FileUploadBase#getItemIterator(RequestContext)}.
684      */
685     private class FileItemIteratorImpl implements FileItemIterator {
686         /**
687          * Default implementation of {@link FileItemStream}.
688          */
689         private class FileItemStreamImpl implements FileItemStream {
690             /** The file items content type.
691              */
692             private final String contentType;
693             /** The file items field name.
694              */
695             private final String fieldName;
696             /** The file items file name.
697              */
698             private final String name;
699             /** Whether the file item is a form field.
700              */
701             private final boolean formField;
702             /** The file items input stream.
703              */
704             private final InputStream stream;
705             /** Whether the file item was already opened.
706              */
707             private boolean opened;
708             /** The headers, if any.
709              */
710             private FileItemHeaders headers;
711 
712             /**
713              * Creates a new instance.
714              * @param pName The items file name, or null.
715              * @param pFieldName The items field name.
716              * @param pContentType The items content type, or null.
717              * @param pFormField Whether the item is a form field.
718              * @param pContentLength The items content length, if known, or -1
719              * @throws IOException Creating the file item failed.
720              */
721             FileItemStreamImpl(String pName, String pFieldName,
722                     String pContentType, boolean pFormField,
723                     long pContentLength) throws IOException {
724                 name = pName;
725                 fieldName = pFieldName;
726                 contentType = pContentType;
727                 formField = pFormField;
728                 final ItemInputStream itemStream = multi.newInputStream();
729                 InputStream istream = itemStream;
730                 if (fileSizeMax != -1) {
731                     if (pContentLength != -1
732                             &&  pContentLength > fileSizeMax) {
733                         FileUploadException e =
734                             new FileSizeLimitExceededException(
735                                 "The field " + fieldName
736                                 + " exceeds its maximum permitted "
737                                 + " size of " + fileSizeMax
738                                 + " characters.",
739                                 pContentLength, fileSizeMax);
740                         throw new FileUploadIOException(e);
741                     }
742                     istream = new LimitedInputStream(istream, fileSizeMax) {
743                         protected void raiseError(long pSizeMax, long pCount)
744                                 throws IOException {
745                             itemStream.close(true);
746                             FileUploadException e =
747                                 new FileSizeLimitExceededException(
748                                     "The field " + fieldName
749                                     + " exceeds its maximum permitted "
750                                     + " size of " + pSizeMax
751                                     + " characters.",
752                                     pCount, pSizeMax);
753                             throw new FileUploadIOException(e);
754                         }
755                     };
756                 }
757                 stream = istream;
758             }
759 
760             /**
761              * Returns the items content type, or null.
762              * @return Content type, if known, or null.
763              */
764             public String getContentType() {
765                 return contentType;
766             }
767 
768             /**
769              * Returns the items field name.
770              * @return Field name.
771              */
772             public String getFieldName() {
773                 return fieldName;
774             }
775 
776             /**
777              * Returns the items file name.
778              * @return File name, if known, or null.
779              */
780             public String getName() {
781                 return name;
782             }
783 
784             /**
785              * Returns, whether this is a form field.
786              * @return True, if the item is a form field,
787              *   otherwise false.
788              */
789             public boolean isFormField() {
790                 return formField;
791             }
792 
793             /**
794              * Returns an input stream, which may be used to
795              * read the items contents.
796              * @return Opened input stream.
797              * @throws IOException An I/O error occurred.
798              */
799             public InputStream openStream() throws IOException {
800                 if (opened) {
801                     throw new IllegalStateException(
802                             "The stream was already opened.");
803                 }
804                 if (((Closeable) stream).isClosed()) {
805                     throw new FileItemStream.ItemSkippedException();
806                 }
807                 return stream;
808             }
809 
810             /**
811              * Closes the file item.
812              * @throws IOException An I/O error occurred.
813              */
814             void close() throws IOException {
815                 stream.close();
816             }
817 
818             /**
819              * Returns the file item headers.
820              * @return The items header object
821              */
822             public FileItemHeaders getHeaders() {
823                 return headers;
824             }
825 
826             /**
827              * Sets the file item headers.
828              * @param pHeaders The items header object
829              */
830             public void setHeaders(FileItemHeaders pHeaders) {
831                 headers = pHeaders;
832             }
833         }
834 
835         /**
836          * The multi part stream to process.
837          */
838         private final MultipartStream multi;
839         /**
840          * The notifier, which used for triggering the
841          * {@link ProgressListener}.
842          */
843         private final MultipartStream.ProgressNotifier notifier;
844         /**
845          * The boundary, which separates the various parts.
846          */
847         private final byte[] boundary;
848         /**
849          * The item, which we currently process.
850          */
851         private FileItemStreamImpl currentItem;
852         /**
853          * The current items field name.
854          */
855         private String currentFieldName;
856         /**
857          * Whether we are currently skipping the preamble.
858          */
859         private boolean skipPreamble;
860         /**
861          * Whether the current item may still be read.
862          */
863         private boolean itemValid;
864         /**
865          * Whether we have seen the end of the file.
866          */
867         private boolean eof;
868 
869         /**
870          * Creates a new instance.
871          * @param ctx The request context.
872          * @throws FileUploadException An error occurred while
873          *   parsing the request.
874          * @throws IOException An I/O error occurred.
875          */
876         FileItemIteratorImpl(RequestContext ctx)
877                 throws FileUploadException, IOException {
878             if (ctx == null) {
879                 throw new NullPointerException("ctx parameter");
880             }
881 
882             String contentType = ctx.getContentType();
883             if ((null == contentType)
884                     || (!contentType.toLowerCase().startsWith(MULTIPART))) {
885                 throw new InvalidContentTypeException(
886                         "the request doesn't contain a "
887                         + MULTIPART_FORM_DATA
888                         + " or "
889                         + MULTIPART_MIXED
890                         + " stream, content type header is "
891                         + contentType);
892             }
893 
894             InputStream input = ctx.getInputStream();
895 
896             if (sizeMax >= 0) {
897                 int requestSize = ctx.getContentLength();
898                 if (requestSize == -1) {
899                     input = new LimitedInputStream(input, sizeMax) {
900                         protected void raiseError(long pSizeMax, long pCount)
901                                 throws IOException {
902                             FileUploadException ex =
903                                 new SizeLimitExceededException(
904                                     "the request was rejected because"
905                                     + " its size (" + pCount
906                                     + ") exceeds the configured maximum"
907                                     + " (" + pSizeMax + ")",
908                                     pCount, pSizeMax);
909                             throw new FileUploadIOException(ex);
910                         }
911                     };
912                 } else {
913                     if (sizeMax >= 0 && requestSize > sizeMax) {
914                         throw new SizeLimitExceededException(
915                                 "the request was rejected because its size ("
916                                 + requestSize
917                                 + ") exceeds the configured maximum ("
918                                 + sizeMax + ")",
919                                 requestSize, sizeMax);
920                     }
921                 }
922             }
923 
924             String charEncoding = headerEncoding;
925             if (charEncoding == null) {
926                 charEncoding = ctx.getCharacterEncoding();
927             }
928 
929             boundary = getBoundary(contentType);
930             if (boundary == null) {
931                 throw new FileUploadException(
932                         "the request was rejected because "
933                         + "no multipart boundary was found");
934             }
935 
936             notifier = new MultipartStream.ProgressNotifier(listener,
937                     ctx.getContentLength());
938             multi = new MultipartStream(input, boundary, notifier);
939             multi.setHeaderEncoding(charEncoding);
940 
941             skipPreamble = true;
942             findNextItem();
943         }
944 
945         /**
946          * Called for finding the nex item, if any.
947          * @return True, if an next item was found, otherwise false.
948          * @throws IOException An I/O error occurred.
949          */
950         private boolean findNextItem() throws IOException {
951             if (eof) {
952                 return false;
953             }
954             if (currentItem != null) {
955                 currentItem.close();
956                 currentItem = null;
957             }
958             for (;;) {
959                 boolean nextPart;
960                 if (skipPreamble) {
961                     nextPart = multi.skipPreamble();
962                 } else {
963                     nextPart = multi.readBoundary();
964                 }
965                 if (!nextPart) {
966                     if (currentFieldName == null) {
967                         // Outer multipart terminated -> No more data
968                         eof = true;
969                         return false;
970                     }
971                     // Inner multipart terminated -> Return to parsing the outer
972                     multi.setBoundary(boundary);
973                     currentFieldName = null;
974                     continue;
975                 }
976                 FileItemHeaders headers = getParsedHeaders(multi.readHeaders());
977                 if (currentFieldName == null) {
978                     // We're parsing the outer multipart
979                     String fieldName = getFieldName(headers);
980                     if (fieldName != null) {
981                         String subContentType = headers.getHeader(CONTENT_TYPE);
982                         if (subContentType != null
983                                 &&  subContentType.toLowerCase()
984                                         .startsWith(MULTIPART_MIXED)) {
985                             currentFieldName = fieldName;
986                             // Multiple files associated with this field name
987                             byte[] subBoundary = getBoundary(subContentType);
988                             multi.setBoundary(subBoundary);
989                             skipPreamble = true;
990                             continue;
991                         }
992                         String fileName = getFileName(headers);
993                         currentItem = new FileItemStreamImpl(fileName,
994                                 fieldName, headers.getHeader(CONTENT_TYPE),
995                                 fileName == null, getContentLength(headers));
996                         notifier.noteItem();
997                         itemValid = true;
998                         return true;
999                     }
1000                 } else {
1001                     String fileName = getFileName(headers);
1002                     if (fileName != null) {
1003                         currentItem = new FileItemStreamImpl(fileName,
1004                                 currentFieldName,
1005                                 headers.getHeader(CONTENT_TYPE),
1006                                 false, getContentLength(headers));
1007                         notifier.noteItem();
1008                         itemValid = true;
1009                         return true;
1010                     }
1011                 }
1012                 multi.discardBodyData();
1013             }
1014         }
1015 
1016         private long getContentLength(FileItemHeaders pHeaders) {
1017             try {
1018                 return Long.parseLong(pHeaders.getHeader(CONTENT_LENGTH));
1019             } catch (Exception e) {
1020                 return -1;
1021             }
1022         }
1023 
1024         /**
1025          * Returns, whether another instance of {@link FileItemStream}
1026          * is available.
1027          * @throws FileUploadException Parsing or processing the
1028          *   file item failed.
1029          * @throws IOException Reading the file item failed.
1030          * @return True, if one or more additional file items
1031          *   are available, otherwise false.
1032          */
1033         public boolean hasNext() throws FileUploadException, IOException {
1034             if (eof) {
1035                 return false;
1036             }
1037             if (itemValid) {
1038                 return true;
1039             }
1040             return findNextItem();
1041         }
1042 
1043         /**
1044          * Returns the next available {@link FileItemStream}.
1045          * @throws java.util.NoSuchElementException No more items are
1046          *   available. Use {@link #hasNext()} to prevent this exception.
1047          * @throws FileUploadException Parsing or processing the
1048          *   file item failed.
1049          * @throws IOException Reading the file item failed.
1050          * @return FileItemStream instance, which provides
1051          *   access to the next file item.
1052          */
1053         public FileItemStream next() throws FileUploadException, IOException {
1054             if (eof  ||  (!itemValid && !hasNext())) {
1055                 throw new NoSuchElementException();
1056             }
1057             itemValid = false;
1058             return currentItem;
1059         }
1060     }
1061 
1062     /**
1063      * This exception is thrown for hiding an inner
1064      * {@link FileUploadException} in an {@link IOException}.
1065      */
1066     public static class FileUploadIOException extends IOException {
1067         /** The exceptions UID, for serializing an instance.
1068          */
1069         private static final long serialVersionUID = -7047616958165584154L;
1070         /** The exceptions cause; we overwrite the parent
1071          * classes field, which is available since Java
1072          * 1.4 only.
1073          */
1074         private final FileUploadException cause;
1075 
1076         /**
1077          * Creates a <code>FileUploadIOException</code> with the
1078          * given cause.
1079          * @param pCause The exceptions cause, if any, or null.
1080          */
1081         public FileUploadIOException(FileUploadException pCause) {
1082             // We're not doing super(pCause) cause of 1.3 compatibility.
1083             cause = pCause;
1084         }
1085 
1086         /**
1087          * Returns the exceptions cause.
1088          * @return The exceptions cause, if any, or null.
1089          */
1090         public Throwable getCause() {
1091             return cause;
1092         }
1093     }
1094 
1095     /**
1096      * Thrown to indicate that the request is not a multipart request.
1097      */
1098     public static class InvalidContentTypeException
1099             extends FileUploadException {
1100         /** The exceptions UID, for serializing an instance.
1101          */
1102         private static final long serialVersionUID = -9073026332015646668L;
1103 
1104         /**
1105          * Constructs a <code>InvalidContentTypeException</code> with no
1106          * detail message.
1107          */
1108         public InvalidContentTypeException() {
1109             // Nothing to do.
1110         }
1111 
1112         /**
1113          * Constructs an <code>InvalidContentTypeException</code> with
1114          * the specified detail message.
1115          *
1116          * @param message The detail message.
1117          */
1118         public InvalidContentTypeException(String message) {
1119             super(message);
1120         }
1121     }
1122 
1123     /**
1124      * Thrown to indicate an IOException.
1125      */
1126     public static class IOFileUploadException extends FileUploadException {
1127         /** The exceptions UID, for serializing an instance.
1128          */
1129         private static final long serialVersionUID = 1749796615868477269L;
1130         /** The exceptions cause; we overwrite the parent
1131          * classes field, which is available since Java
1132          * 1.4 only.
1133          */
1134         private final IOException cause;
1135 
1136         /**
1137          * Creates a new instance with the given cause.
1138          * @param pMsg The detail message.
1139          * @param pException The exceptions cause.
1140          */
1141         public IOFileUploadException(String pMsg, IOException pException) {
1142             super(pMsg);
1143             cause = pException;
1144         }
1145 
1146         /**
1147          * Returns the exceptions cause.
1148          * @return The exceptions cause, if any, or null.
1149          */
1150         public Throwable getCause() {
1151             return cause;
1152         }
1153     }
1154 
1155     /** This exception is thrown, if a requests permitted size
1156      * is exceeded.
1157      */
1158     protected abstract static class SizeException extends FileUploadException {
1159         /**
1160          * The actual size of the request.
1161          */
1162         private final long actual;
1163 
1164         /**
1165          * The maximum permitted size of the request.
1166          */
1167         private final long permitted;
1168 
1169         /**
1170          * Creates a new instance.
1171          * @param message The detail message.
1172          * @param actual The actual number of bytes in the request.
1173          * @param permitted The requests size limit, in bytes.
1174          */
1175         protected SizeException(String message, long actual, long permitted) {
1176             super(message);
1177             this.actual = actual;
1178             this.permitted = permitted;
1179         }
1180 
1181         /**
1182          * Retrieves the actual size of the request.
1183          *
1184          * @return The actual size of the request.
1185          */
1186         public long getActualSize() {
1187             return actual;
1188         }
1189 
1190         /**
1191          * Retrieves the permitted size of the request.
1192          *
1193          * @return The permitted size of the request.
1194          */
1195         public long getPermittedSize() {
1196             return permitted;
1197         }
1198     }
1199 
1200     /**
1201      * Thrown to indicate that the request size is not specified. In other
1202      * words, it is thrown, if the content-length header is missing or
1203      * contains the value -1.
1204      * @deprecated As of commons-fileupload 1.2, the presence of a
1205      *   content-length header is no longer required.
1206      */
1207     public static class UnknownSizeException
1208         extends FileUploadException {
1209         /** The exceptions UID, for serializing an instance.
1210          */
1211         private static final long serialVersionUID = 7062279004812015273L;
1212 
1213         /**
1214          * Constructs a <code>UnknownSizeException</code> with no
1215          * detail message.
1216          */
1217         public UnknownSizeException() {
1218             super();
1219         }
1220 
1221         /**
1222          * Constructs an <code>UnknownSizeException</code> with
1223          * the specified detail message.
1224          *
1225          * @param message The detail message.
1226          */
1227         public UnknownSizeException(String message) {
1228             super(message);
1229         }
1230     }
1231 
1232     /**
1233      * Thrown to indicate that the request size exceeds the configured maximum.
1234      */
1235     public static class SizeLimitExceededException
1236             extends SizeException {
1237         /** The exceptions UID, for serializing an instance.
1238          */
1239         private static final long serialVersionUID = -2474893167098052828L;
1240 
1241         /**
1242          * @deprecated Replaced by
1243          * {@link #SizeLimitExceededException(String, long, long)}
1244          */
1245         public SizeLimitExceededException() {
1246             this(null, 0, 0);
1247         }
1248 
1249         /**
1250          * @deprecated Replaced by
1251          * {@link #SizeLimitExceededException(String, long, long)}
1252          * @param message The exceptions detail message.
1253          */
1254         public SizeLimitExceededException(String message) {
1255             this(message, 0, 0);
1256         }
1257 
1258         /**
1259          * Constructs a <code>SizeExceededException</code> with
1260          * the specified detail message, and actual and permitted sizes.
1261          *
1262          * @param message   The detail message.
1263          * @param actual    The actual request size.
1264          * @param permitted The maximum permitted request size.
1265          */
1266         public SizeLimitExceededException(String message, long actual,
1267                 long permitted) {
1268             super(message, actual, permitted);
1269         }
1270     }
1271 
1272     /**
1273      * Thrown to indicate that A files size exceeds the configured maximum.
1274      */
1275     public static class FileSizeLimitExceededException
1276             extends SizeException {
1277         /** The exceptions UID, for serializing an instance.
1278          */
1279         private static final long serialVersionUID = 8150776562029630058L;
1280 
1281         /**
1282          * Constructs a <code>SizeExceededException</code> with
1283          * the specified detail message, and actual and permitted sizes.
1284          *
1285          * @param message   The detail message.
1286          * @param actual    The actual request size.
1287          * @param permitted The maximum permitted request size.
1288          */
1289         public FileSizeLimitExceededException(String message, long actual,
1290                 long permitted) {
1291             super(message, actual, permitted);
1292         }
1293     }
1294 
1295     /**
1296      * Returns the progress listener.
1297      * @return The progress listener, if any, or null.
1298      */
1299     public ProgressListener getProgressListener() {
1300         return listener;
1301     }
1302 
1303     /**
1304      * Sets the progress listener.
1305      * @param pListener The progress listener, if any. Defaults to null.
1306      */
1307     public void setProgressListener(ProgressListener pListener) {
1308         listener = pListener;
1309     }
1310 }