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