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