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