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