View Javadoc

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