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