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