View Javadoc

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