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}