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 static java.lang.String.format;
020    
021    import java.io.IOException;
022    import java.io.InputStream;
023    import java.io.UnsupportedEncodingException;
024    import java.util.ArrayList;
025    import java.util.HashMap;
026    import java.util.Iterator;
027    import java.util.List;
028    import java.util.Locale;
029    import java.util.Map;
030    import java.util.NoSuchElementException;
031    
032    import javax.servlet.http.HttpServletRequest;
033    
034    import org.apache.commons.fileupload.MultipartStream.ItemInputStream;
035    import org.apache.commons.fileupload.servlet.ServletFileUpload;
036    import org.apache.commons.fileupload.servlet.ServletRequestContext;
037    import org.apache.commons.fileupload.util.Closeable;
038    import org.apache.commons.fileupload.util.FileItemHeadersImpl;
039    import org.apache.commons.fileupload.util.LimitedInputStream;
040    import 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 1458220 2013-03-19 10:56:17Z markt $
057     */
058    public 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, 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, 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_FORM_DATA, 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                                   requestSize, 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                                   pCount, 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                multi = new MultipartStream(input, boundary, notifier);
995                multi.setHeaderEncoding(charEncoding);
996    
997                skipPreamble = true;
998                findNextItem();
999            }
1000    
1001            /**
1002             * Called for finding the next item, if any.
1003             *
1004             * @return True, if an next item was found, otherwise false.
1005             * @throws IOException An I/O error occurred.
1006             */
1007            private boolean findNextItem() throws IOException {
1008                if (eof) {
1009                    return false;
1010                }
1011                if (currentItem != null) {
1012                    currentItem.close();
1013                    currentItem = null;
1014                }
1015                for (;;) {
1016                    boolean nextPart;
1017                    if (skipPreamble) {
1018                        nextPart = multi.skipPreamble();
1019                    } else {
1020                        nextPart = multi.readBoundary();
1021                    }
1022                    if (!nextPart) {
1023                        if (currentFieldName == null) {
1024                            // Outer multipart terminated -> No more data
1025                            eof = true;
1026                            return false;
1027                        }
1028                        // Inner multipart terminated -> Return to parsing the outer
1029                        multi.setBoundary(boundary);
1030                        currentFieldName = null;
1031                        continue;
1032                    }
1033                    FileItemHeaders headers = getParsedHeaders(multi.readHeaders());
1034                    if (currentFieldName == null) {
1035                        // We're parsing the outer multipart
1036                        String fieldName = getFieldName(headers);
1037                        if (fieldName != null) {
1038                            String subContentType = headers.getHeader(CONTENT_TYPE);
1039                            if (subContentType != null
1040                                    &&  subContentType.toLowerCase(Locale.ENGLISH)
1041                                            .startsWith(MULTIPART_MIXED)) {
1042                                currentFieldName = fieldName;
1043                                // Multiple files associated with this field name
1044                                byte[] subBoundary = getBoundary(subContentType);
1045                                multi.setBoundary(subBoundary);
1046                                skipPreamble = true;
1047                                continue;
1048                            }
1049                            String fileName = getFileName(headers);
1050                            currentItem = new FileItemStreamImpl(fileName,
1051                                    fieldName, headers.getHeader(CONTENT_TYPE),
1052                                    fileName == null, getContentLength(headers));
1053                            currentItem.setHeaders(headers);
1054                            notifier.noteItem();
1055                            itemValid = true;
1056                            return true;
1057                        }
1058                    } else {
1059                        String fileName = getFileName(headers);
1060                        if (fileName != null) {
1061                            currentItem = new FileItemStreamImpl(fileName,
1062                                    currentFieldName,
1063                                    headers.getHeader(CONTENT_TYPE),
1064                                    false, getContentLength(headers));
1065                            currentItem.setHeaders(headers);
1066                            notifier.noteItem();
1067                            itemValid = true;
1068                            return true;
1069                        }
1070                    }
1071                    multi.discardBodyData();
1072                }
1073            }
1074    
1075            private long getContentLength(FileItemHeaders pHeaders) {
1076                try {
1077                    return Long.parseLong(pHeaders.getHeader(CONTENT_LENGTH));
1078                } catch (Exception e) {
1079                    return -1;
1080                }
1081            }
1082    
1083            /**
1084             * Returns, whether another instance of {@link FileItemStream}
1085             * is available.
1086             *
1087             * @throws FileUploadException Parsing or processing the
1088             *   file item failed.
1089             * @throws IOException Reading the file item failed.
1090             * @return True, if one or more additional file items
1091             *   are available, otherwise false.
1092             */
1093            public boolean hasNext() throws FileUploadException, IOException {
1094                if (eof) {
1095                    return false;
1096                }
1097                if (itemValid) {
1098                    return true;
1099                }
1100                try {
1101                    return findNextItem();
1102                } catch (FileUploadIOException e) {
1103                    // unwrap encapsulated SizeException
1104                    throw (FileUploadException) e.getCause();
1105                }
1106            }
1107    
1108            /**
1109             * Returns the next available {@link FileItemStream}.
1110             *
1111             * @throws java.util.NoSuchElementException No more items are
1112             *   available. Use {@link #hasNext()} to prevent this exception.
1113             * @throws FileUploadException Parsing or processing the
1114             *   file item failed.
1115             * @throws IOException Reading the file item failed.
1116             * @return FileItemStream instance, which provides
1117             *   access to the next file item.
1118             */
1119            public FileItemStream next() throws FileUploadException, IOException {
1120                if (eof  ||  (!itemValid && !hasNext())) {
1121                    throw new NoSuchElementException();
1122                }
1123                itemValid = false;
1124                return currentItem;
1125            }
1126    
1127        }
1128    
1129        /**
1130         * This exception is thrown for hiding an inner
1131         * {@link FileUploadException} in an {@link IOException}.
1132         */
1133        public static class FileUploadIOException extends IOException {
1134    
1135            /**
1136             * The exceptions UID, for serializing an instance.
1137             */
1138            private static final long serialVersionUID = -7047616958165584154L;
1139    
1140            /**
1141             * The exceptions cause; we overwrite the parent
1142             * classes field, which is available since Java
1143             * 1.4 only.
1144             */
1145            private final FileUploadException cause;
1146    
1147            /**
1148             * Creates a <code>FileUploadIOException</code> with the
1149             * given cause.
1150             *
1151             * @param pCause The exceptions cause, if any, or null.
1152             */
1153            public FileUploadIOException(FileUploadException pCause) {
1154                // We're not doing super(pCause) cause of 1.3 compatibility.
1155                cause = pCause;
1156            }
1157    
1158            /**
1159             * Returns the exceptions cause.
1160             *
1161             * @return The exceptions cause, if any, or null.
1162             */
1163            @Override
1164            public Throwable getCause() {
1165                return cause;
1166            }
1167    
1168        }
1169    
1170        /**
1171         * Thrown to indicate that the request is not a multipart request.
1172         */
1173        public static class InvalidContentTypeException
1174                extends FileUploadException {
1175    
1176            /**
1177             * The exceptions UID, for serializing an instance.
1178             */
1179            private static final long serialVersionUID = -9073026332015646668L;
1180    
1181            /**
1182             * Constructs a <code>InvalidContentTypeException</code> with no
1183             * detail message.
1184             */
1185            public InvalidContentTypeException() {
1186                // Nothing to do.
1187            }
1188    
1189            /**
1190             * Constructs an <code>InvalidContentTypeException</code> with
1191             * the specified detail message.
1192             *
1193             * @param message The detail message.
1194             */
1195            public InvalidContentTypeException(String message) {
1196                super(message);
1197            }
1198    
1199        }
1200    
1201        /**
1202         * Thrown to indicate an IOException.
1203         */
1204        public static class IOFileUploadException extends FileUploadException {
1205    
1206            /**
1207             * The exceptions UID, for serializing an instance.
1208             */
1209            private static final long serialVersionUID = 1749796615868477269L;
1210    
1211            /**
1212             * The exceptions cause; we overwrite the parent
1213             * classes field, which is available since Java
1214             * 1.4 only.
1215             */
1216            private final IOException cause;
1217    
1218            /**
1219             * Creates a new instance with the given cause.
1220             *
1221             * @param pMsg The detail message.
1222             * @param pException The exceptions cause.
1223             */
1224            public IOFileUploadException(String pMsg, IOException pException) {
1225                super(pMsg);
1226                cause = pException;
1227            }
1228    
1229            /**
1230             * Returns the exceptions cause.
1231             *
1232             * @return The exceptions cause, if any, or null.
1233             */
1234            @Override
1235            public Throwable getCause() {
1236                return cause;
1237            }
1238    
1239        }
1240    
1241        /**
1242         * This exception is thrown, if a requests permitted size
1243         * is exceeded.
1244         */
1245        protected abstract static class SizeException extends FileUploadException {
1246    
1247            /**
1248             * Serial version UID, being used, if serialized.
1249             */
1250            private static final long serialVersionUID = -8776225574705254126L;
1251    
1252            /**
1253             * The actual size of the request.
1254             */
1255            private final long actual;
1256    
1257            /**
1258             * The maximum permitted size of the request.
1259             */
1260            private final long permitted;
1261    
1262            /**
1263             * Creates a new instance.
1264             *
1265             * @param message The detail message.
1266             * @param actual The actual number of bytes in the request.
1267             * @param permitted The requests size limit, in bytes.
1268             */
1269            protected SizeException(String message, long actual, long permitted) {
1270                super(message);
1271                this.actual = actual;
1272                this.permitted = permitted;
1273            }
1274    
1275            /**
1276             * Retrieves the actual size of the request.
1277             *
1278             * @return The actual size of the request.
1279             * @since 1.3
1280             */
1281            public long getActualSize() {
1282                return actual;
1283            }
1284    
1285            /**
1286             * Retrieves the permitted size of the request.
1287             *
1288             * @return The permitted size of the request.
1289             * @since 1.3
1290             */
1291            public long getPermittedSize() {
1292                return permitted;
1293            }
1294    
1295        }
1296    
1297        /**
1298         * Thrown to indicate that the request size is not specified. In other
1299         * words, it is thrown, if the content-length header is missing or
1300         * contains the value -1.
1301         *
1302         * @deprecated 1.2 As of commons-fileupload 1.2, the presence of a
1303         *   content-length header is no longer required.
1304         */
1305        @Deprecated
1306        public static class UnknownSizeException
1307            extends FileUploadException {
1308    
1309            /**
1310             * The exceptions UID, for serializing an instance.
1311             */
1312            private static final long serialVersionUID = 7062279004812015273L;
1313    
1314            /**
1315             * Constructs a <code>UnknownSizeException</code> with no
1316             * detail message.
1317             */
1318            public UnknownSizeException() {
1319                super();
1320            }
1321    
1322            /**
1323             * Constructs an <code>UnknownSizeException</code> with
1324             * the specified detail message.
1325             *
1326             * @param message The detail message.
1327             */
1328            public UnknownSizeException(String message) {
1329                super(message);
1330            }
1331    
1332        }
1333    
1334        /**
1335         * Thrown to indicate that the request size exceeds the configured maximum.
1336         */
1337        public static class SizeLimitExceededException
1338                extends SizeException {
1339    
1340            /**
1341             * The exceptions UID, for serializing an instance.
1342             */
1343            private static final long serialVersionUID = -2474893167098052828L;
1344    
1345            /**
1346             * @deprecated 1.2 Replaced by
1347             * {@link #SizeLimitExceededException(String, long, long)}
1348             */
1349            @Deprecated
1350            public SizeLimitExceededException() {
1351                this(null, 0, 0);
1352            }
1353    
1354            /**
1355             * @deprecated 1.2 Replaced by
1356             * {@link #SizeLimitExceededException(String, long, long)}
1357             * @param message The exceptions detail message.
1358             */
1359            @Deprecated
1360            public SizeLimitExceededException(String message) {
1361                this(message, 0, 0);
1362            }
1363    
1364            /**
1365             * Constructs a <code>SizeExceededException</code> with
1366             * the specified detail message, and actual and permitted sizes.
1367             *
1368             * @param message   The detail message.
1369             * @param actual    The actual request size.
1370             * @param permitted The maximum permitted request size.
1371             */
1372            public SizeLimitExceededException(String message, long actual,
1373                    long permitted) {
1374                super(message, actual, permitted);
1375            }
1376    
1377        }
1378    
1379        /**
1380         * Thrown to indicate that A files size exceeds the configured maximum.
1381         */
1382        public static class FileSizeLimitExceededException
1383                extends SizeException {
1384    
1385            /**
1386             * The exceptions UID, for serializing an instance.
1387             */
1388            private static final long serialVersionUID = 8150776562029630058L;
1389    
1390            /**
1391             * File name of the item, which caused the exception.
1392             */
1393            private String fileName;
1394    
1395            /**
1396             * Field name of the item, which caused the exception.
1397             */
1398            private String fieldName;
1399    
1400            /**
1401             * Constructs a <code>SizeExceededException</code> with
1402             * the specified detail message, and actual and permitted sizes.
1403             *
1404             * @param message   The detail message.
1405             * @param actual    The actual request size.
1406             * @param permitted The maximum permitted request size.
1407             */
1408            public FileSizeLimitExceededException(String message, long actual,
1409                    long permitted) {
1410                super(message, actual, permitted);
1411            }
1412    
1413            /**
1414             * Returns the file name of the item, which caused the
1415             * exception.
1416             *
1417             * @return File name, if known, or null.
1418             */
1419            public String getFileName() {
1420                return fileName;
1421            }
1422    
1423            /**
1424             * Sets the file name of the item, which caused the
1425             * exception.
1426             *
1427             * @param pFileName the file name of the item, which caused the exception.
1428             */
1429            public void setFileName(String pFileName) {
1430                fileName = pFileName;
1431            }
1432    
1433            /**
1434             * Returns the field name of the item, which caused the
1435             * exception.
1436             *
1437             * @return Field name, if known, or null.
1438             */
1439            public String getFieldName() {
1440                return fieldName;
1441            }
1442    
1443            /**
1444             * Sets the field name of the item, which caused the
1445             * exception.
1446             *
1447             * @param pFieldName the field name of the item,
1448             *        which caused the exception.
1449             */
1450            public void setFieldName(String pFieldName) {
1451                fieldName = pFieldName;
1452            }
1453    
1454        }
1455    
1456        /**
1457         * Returns the progress listener.
1458         *
1459         * @return The progress listener, if any, or null.
1460         */
1461        public ProgressListener getProgressListener() {
1462            return listener;
1463        }
1464    
1465        /**
1466         * Sets the progress listener.
1467         *
1468         * @param pListener The progress listener, if any. Defaults to null.
1469         */
1470        public void setProgressListener(ProgressListener pListener) {
1471            listener = pListener;
1472        }
1473    
1474    }