    001/*
    002 * Licensed to the Apache Software Foundation (ASF) under one or more
    003 * contributor license agreements.  See the NOTICE file distributed with
    004 * this work for additional information regarding copyright ownership.
    005 * The ASF licenses this file to You under the Apache License, Version 2.0
    006 * (the "License"); you may not use this file except in compliance with
    007 * the License.  You may obtain a copy of the License at
    008 *
    009 *      http://www.apache.org/licenses/LICENSE-2.0
    010 *
    011 * Unless required by applicable law or agreed to in writing, software
    012 * distributed under the License is distributed on an "AS IS" BASIS,
    013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    014 * See the License for the specific language governing permissions and
    015 * limitations under the License.
    016 */
    017package org.apache.commons.fileupload;
    018
    019import static java.lang.String.format;
    020
    021import java.io.IOException;
    022import java.io.InputStream;
    023import java.io.UnsupportedEncodingException;
    024import java.util.ArrayList;
    025import java.util.HashMap;
    026import java.util.Iterator;
    027import java.util.List;
    028import java.util.Locale;
    029import java.util.Map;
    030import java.util.NoSuchElementException;
    031
    032import javax.servlet.http.HttpServletRequest;
    033
    034import org.apache.commons.fileupload.MultipartStream.ItemInputStream;
    035import org.apache.commons.fileupload.servlet.ServletFileUpload;
    036import org.apache.commons.fileupload.servlet.ServletRequestContext;
    037import org.apache.commons.fileupload.util.Closeable;
    038import org.apache.commons.fileupload.util.FileItemHeadersImpl;
    039import org.apache.commons.fileupload.util.LimitedInputStream;
    040import org.apache.commons.fileupload.util.Streams;
    041import org.apache.commons.io.IOUtils;
    042
    043/**
    044 * <p>High level API for processing file uploads.</p>
    045 *
    046 * <p>This class handles multiple files per single HTML widget, sent using
    047 * <code>multipart/mixed</code> encoding type, as specified by
    048 * <a href="http://www.ietf.org/rfc/rfc1867.txt">RFC 1867</a>.  Use {@link
    049 * #parseRequest(RequestContext)} to acquire a list of {@link
    050 * org.apache.commons.fileupload.FileItem}s associated with a given HTML
    051 * widget.</p>
    052 *
    053 * <p>How the data for individual parts is stored is determined by the factory
    054 * used to create them; a given part may be in memory, on disk, or somewhere
    055 * else.</p>
    056 */
    057public abstract class FileUploadBase {
    058
    059    // ---------------------------------------------------------- Class methods
    060
    061    /**
    062     * <p>Utility method that determines whether the request contains multipart
    063     * content.</p>
    064     *
    065     * <p><strong>NOTE:</strong>This method will be moved to the
    066     * <code>ServletFileUpload</code> class after the FileUpload 1.1 release.
    067     * Unfortunately, since this method is static, it is not possible to
    068     * provide its replacement until this method is removed.</p>
    069     *
    070     * @param ctx The request context to be evaluated. Must be non-null.
    071     *
    072     * @return <code>true</code> if the request is multipart;
    073     *         <code>false</code> otherwise.
    074     */
    075    public static final boolean isMultipartContent(RequestContext ctx) {
    076        String contentType = ctx.getContentType();
    077        if (contentType == null) {
    078            return false;
    079        }
    080        if (contentType.toLowerCase(Locale.ENGLISH).startsWith(MULTIPART)) {
    081            return true;
    082        }
    083        return false;
    084    }
    085
    086    /**
    087     * Utility method that determines whether the request contains multipart
    088     * content.
    089     *
    090     * @param req The servlet request to be evaluated. Must be non-null.
    091     *
    092     * @return <code>true</code> if the request is multipart;
    093     *         <code>false</code> otherwise.
    094     *
    095     * @deprecated 1.1 Use the method on <code>ServletFileUpload</code> instead.
    096     */
    097    @Deprecated
    098    public static boolean isMultipartContent(HttpServletRequest req) {
    099        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}