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