1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.commons.fileupload;
18
19 import java.io.IOException;
20 import java.io.InputStream;
21 import java.io.UnsupportedEncodingException;
22 import java.util.ArrayList;
23 import java.util.HashMap;
24 import java.util.List;
25 import java.util.Map;
26 import java.util.NoSuchElementException;
27
28 import javax.servlet.http.HttpServletRequest;
29
30 import org.apache.commons.fileupload.servlet.ServletFileUpload;
31 import org.apache.commons.fileupload.servlet.ServletRequestContext;
32 import org.apache.commons.fileupload.util.Closeable;
33 import org.apache.commons.fileupload.util.LimitedInputStream;
34 import org.apache.commons.fileupload.util.Streams;
35
36
37 /**
38 * <p>High level API for processing file uploads.</p>
39 *
40 * <p>This class handles multiple files per single HTML widget, sent using
41 * <code>multipart/mixed</code> encoding type, as specified by
42 * <a href="http://www.ietf.org/rfc/rfc1867.txt">RFC 1867</a>. Use {@link
43 * #parseRequest(HttpServletRequest)} to acquire a list of {@link
44 * org.apache.commons.fileupload.FileItem}s associated with a given HTML
45 * widget.</p>
46 *
47 * <p>How the data for individual parts is stored is determined by the factory
48 * used to create them; a given part may be in memory, on disk, or somewhere
49 * else.</p>
50 *
51 * @author <a href="mailto:Rafal.Krzewski@e-point.pl">Rafal Krzewski</a>
52 * @author <a href="mailto:dlr@collab.net">Daniel Rall</a>
53 * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a>
54 * @author <a href="mailto:jmcnally@collab.net">John McNally</a>
55 * @author <a href="mailto:martinc@apache.org">Martin Cooper</a>
56 * @author Sean C. Sullivan
57 *
58 * @version $Id: FileUploadBase.java 502350 2007-02-01 20:42:48Z jochen $
59 */
60 public abstract class FileUploadBase {
61
62
63
64
65 /**
66 * <p>Utility method that determines whether the request contains multipart
67 * content.</p>
68 *
69 * <p><strong>NOTE:</strong>This method will be moved to the
70 * <code>ServletFileUpload</code> class after the FileUpload 1.1 release.
71 * Unfortunately, since this method is static, it is not possible to
72 * provide its replacement until this method is removed.</p>
73 *
74 * @param ctx The request context to be evaluated. Must be non-null.
75 *
76 * @return <code>true</code> if the request is multipart;
77 * <code>false</code> otherwise.
78 */
79 public static final boolean isMultipartContent(RequestContext ctx) {
80 String contentType = ctx.getContentType();
81 if (contentType == null) {
82 return false;
83 }
84 if (contentType.toLowerCase().startsWith(MULTIPART)) {
85 return true;
86 }
87 return false;
88 }
89
90
91 /**
92 * Utility method that determines whether the request contains multipart
93 * content.
94 *
95 * @param req The servlet request to be evaluated. Must be non-null.
96 *
97 * @return <code>true</code> if the request is multipart;
98 * <code>false</code> otherwise.
99 *
100 * @deprecated Use the method on <code>ServletFileUpload</code> instead.
101 */
102 public static boolean isMultipartContent(HttpServletRequest req) {
103 return ServletFileUpload.isMultipartContent(req);
104 }
105
106
107
108
109
110 /**
111 * HTTP content type header name.
112 */
113 public static final String CONTENT_TYPE = "Content-type";
114
115
116 /**
117 * HTTP content disposition header name.
118 */
119 public static final String CONTENT_DISPOSITION = "Content-disposition";
120
121
122 /**
123 * Content-disposition value for form data.
124 */
125 public static final String FORM_DATA = "form-data";
126
127
128 /**
129 * Content-disposition value for file attachment.
130 */
131 public static final String ATTACHMENT = "attachment";
132
133
134 /**
135 * Part of HTTP content type header.
136 */
137 public static final String MULTIPART = "multipart/";
138
139
140 /**
141 * HTTP content type header for multipart forms.
142 */
143 public static final String MULTIPART_FORM_DATA = "multipart/form-data";
144
145
146 /**
147 * HTTP content type header for multiple uploads.
148 */
149 public static final String MULTIPART_MIXED = "multipart/mixed";
150
151
152 /**
153 * The maximum length of a single header line that will be parsed
154 * (1024 bytes).
155 * @deprecated This constant is no longer used. As of commons-fileupload
156 * 1.2, the only applicable limit is the total size of a parts headers,
157 * {@link MultipartStream#HEADER_PART_SIZE_MAX}.
158 */
159 public static final int MAX_HEADER_SIZE = 1024;
160
161
162
163
164
165 /**
166 * The maximum size permitted for the complete request, as opposed to
167 * {@link #fileSizeMax}. A value of -1 indicates no maximum.
168 */
169 private long sizeMax = -1;
170
171 /**
172 * The maximum size permitted for a single uploaded file, as opposed
173 * to {@link #sizeMax}. A value of -1 indicates no maximum.
174 */
175 private long fileSizeMax = -1;
176
177 /**
178 * The content encoding to use when reading part headers.
179 */
180 private String headerEncoding;
181
182 /**
183 * The progress listener.
184 */
185 private ProgressListener listener;
186
187
188
189
190 /**
191 * Returns the factory class used when creating file items.
192 *
193 * @return The factory class for new file items.
194 */
195 public abstract FileItemFactory getFileItemFactory();
196
197
198 /**
199 * Sets the factory class to use when creating file items.
200 *
201 * @param factory The factory class for new file items.
202 */
203 public abstract void setFileItemFactory(FileItemFactory factory);
204
205
206 /**
207 * Returns the maximum allowed size of a complete request, as opposed
208 * to {@link #getFileSizeMax()}.
209 *
210 * @return The maximum allowed size, in bytes. The default value of
211 * -1 indicates, that there is no limit.
212 *
213 * @see #setSizeMax(long)
214 *
215 */
216 public long getSizeMax() {
217 return sizeMax;
218 }
219
220
221 /**
222 * Sets the maximum allowed size of a complete request, as opposed
223 * to {@link #setFileSizeMax(long)}.
224 *
225 * @param sizeMax The maximum allowed size, in bytes. The default value of
226 * -1 indicates, that there is no limit.
227 *
228 * @see #getSizeMax()
229 *
230 */
231 public void setSizeMax(long sizeMax) {
232 this.sizeMax = sizeMax;
233 }
234
235 /**
236 * Returns the maximum allowed size of a single uploaded file,
237 * as opposed to {@link #getSizeMax()}.
238 *
239 * @see #setFileSizeMax(long)
240 * @return Maximum size of a single uploaded file.
241 */
242 public long getFileSizeMax() {
243 return fileSizeMax;
244 }
245
246 /**
247 * Sets the maximum allowed size of a single uploaded file,
248 * as opposed to {@link #getSizeMax()}.
249 *
250 * @see #getFileSizeMax()
251 * @param fileSizeMax Maximum size of a single uploaded file.
252 */
253 public void setFileSizeMax(long fileSizeMax) {
254 this.fileSizeMax = fileSizeMax;
255 }
256
257 /**
258 * Retrieves the character encoding used when reading the headers of an
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 * @return The encoding used to read part headers.
264 */
265 public String getHeaderEncoding() {
266 return headerEncoding;
267 }
268
269
270 /**
271 * Specifies the character encoding to be used when reading the headers of
272 * individual part. When not specified, or <code>null</code>, the request
273 * encoding is used. If that is also not specified, or <code>null</code>,
274 * the platform default encoding is used.
275 *
276 * @param encoding The encoding used to read part headers.
277 */
278 public void setHeaderEncoding(String encoding) {
279 headerEncoding = encoding;
280 }
281
282
283
284
285
286 /**
287 * Processes an <a href="http://www.ietf.org/rfc/rfc1867.txt">RFC 1867</a>
288 * compliant <code>multipart/form-data</code> stream.
289 *
290 * @param req The servlet request to be parsed.
291 *
292 * @return A list of <code>FileItem</code> instances parsed from the
293 * request, in the order that they were transmitted.
294 *
295 * @throws FileUploadException if there are problems reading/parsing
296 * the request or storing files.
297 *
298 * @deprecated Use the method in <code>ServletFileUpload</code> instead.
299 */
300 public List
301 throws FileUploadException {
302 return parseRequest(new ServletRequestContext(req));
303 }
304
305 /**
306 * Processes an <a href="http://www.ietf.org/rfc/rfc1867.txt">RFC 1867</a>
307 * compliant <code>multipart/form-data</code> stream.
308 *
309 * @param ctx The context for the request to be parsed.
310 *
311 * @return An iterator to instances of <code>FileItemStream</code>
312 * parsed from the request, in the order that they were
313 * transmitted.
314 *
315 * @throws FileUploadException if there are problems reading/parsing
316 * the request or storing files.
317 * @throws IOException An I/O error occurred. This may be a network
318 * error while communicating with the client or a problem while
319 * storing the uploaded content.
320 */
321 public FileItemIterator getItemIterator(RequestContext ctx)
322 throws FileUploadException, IOException {
323 return new FileItemIteratorImpl(ctx);
324 }
325
326 /**
327 * Processes an <a href="http://www.ietf.org/rfc/rfc1867.txt">RFC 1867</a>
328 * compliant <code>multipart/form-data</code> stream.
329 *
330 * @param ctx The context for the request to be parsed.
331 *
332 * @return A list of <code>FileItem</code> instances parsed from the
333 * request, in the order that they were transmitted.
334 *
335 * @throws FileUploadException if there are problems reading/parsing
336 * the request or storing files.
337 */
338 public List
339 throws FileUploadException {
340 try {
341 FileItemIterator iter = getItemIterator(ctx);
342 List items = new ArrayList();
343 FileItemFactory fac = getFileItemFactory();
344 if (fac == null) {
345 throw new NullPointerException(
346 "No FileItemFactory has been set.");
347 }
348 while (iter.hasNext()) {
349 FileItemStream item = iter.next();
350 FileItem fileItem = fac.createItem(item.getFieldName(),
351 item.getContentType(), item.isFormField(),
352 item.getName());
353 try {
354 Streams.copy(item.openStream(), fileItem.getOutputStream(),
355 true);
356 } catch (FileUploadIOException e) {
357 throw (FileUploadException) e.getCause();
358 } catch (IOException e) {
359 throw new IOFileUploadException(
360 "Processing of " + MULTIPART_FORM_DATA
361 + " request failed. " + e.getMessage(), e);
362 }
363 items.add(fileItem);
364 }
365 return items;
366 } catch (FileUploadIOException e) {
367 throw (FileUploadException) e.getCause();
368 } catch (IOException e) {
369 throw new FileUploadException(e.getMessage(), e);
370 }
371 }
372
373
374
375
376
377 /**
378 * Retrieves the boundary from the <code>Content-type</code> header.
379 *
380 * @param contentType The value of the content type header from which to
381 * extract the boundary value.
382 *
383 * @return The boundary, as a byte array.
384 */
385 protected byte[] getBoundary(String contentType) {
386 ParameterParser parser = new ParameterParser();
387 parser.setLowerCaseNames(true);
388
389 Map params = parser.parse(contentType, ';');
390 String boundaryStr = (String) params.get("boundary");
391
392 if (boundaryStr == null) {
393 return null;
394 }
395 byte[] boundary;
396 try {
397 boundary = boundaryStr.getBytes("ISO-8859-1");
398 } catch (UnsupportedEncodingException e) {
399 boundary = boundaryStr.getBytes();
400 }
401 return boundary;
402 }
403
404
405 /**
406 * Retrieves the file name from the <code>Content-disposition</code>
407 * header.
408 *
409 * @param headers A <code>Map</code> containing the HTTP request headers.
410 *
411 * @return The file name for the current <code>encapsulation</code>.
412 */
413 protected String getFileName(Map
414 String fileName = null;
415 String cd = getHeader(headers, CONTENT_DISPOSITION);
416 if (cd != null) {
417 String cdl = cd.toLowerCase();
418 if (cdl.startsWith(FORM_DATA) || cdl.startsWith(ATTACHMENT)) {
419 ParameterParser parser = new ParameterParser();
420 parser.setLowerCaseNames(true);
421
422 Map params = parser.parse(cd, ';');
423 if (params.containsKey("filename")) {
424 fileName = (String) params.get("filename");
425 if (fileName != null) {
426 fileName = fileName.trim();
427 } else {
428
429
430
431 fileName = "";
432 }
433 }
434 }
435 }
436 return fileName;
437 }
438
439
440 /**
441 * Retrieves the field name from the <code>Content-disposition</code>
442 * header.
443 *
444 * @param headers A <code>Map</code> containing the HTTP request headers.
445 *
446 * @return The field name for the current <code>encapsulation</code>.
447 */
448 protected String getFieldName(Map
449 String fieldName = null;
450 String cd = getHeader(headers, CONTENT_DISPOSITION);
451 if (cd != null && cd.toLowerCase().startsWith(FORM_DATA)) {
452
453 ParameterParser parser = new ParameterParser();
454 parser.setLowerCaseNames(true);
455
456 Map params = parser.parse(cd, ';');
457 fieldName = (String) params.get("name");
458 if (fieldName != null) {
459 fieldName = fieldName.trim();
460 }
461 }
462 return fieldName;
463 }
464
465
466 /**
467 * Creates a new {@link FileItem} instance.
468 *
469 * @param headers A <code>Map</code> containing the HTTP request
470 * headers.
471 * @param isFormField Whether or not this item is a form field, as
472 * opposed to a file.
473 *
474 * @return A newly created <code>FileItem</code> instance.
475 *
476 * @throws FileUploadException if an error occurs.
477 * @deprecated This method is no longer used in favour of
478 * internally created instances of {@link FileItem}.
479 */
480 protected FileItem createItem(Map
481 boolean isFormField)
482 throws FileUploadException {
483 return getFileItemFactory().createItem(getFieldName(headers),
484 getHeader(headers, CONTENT_TYPE),
485 isFormField,
486 getFileName(headers));
487 }
488
489
490 /**
491 * <p> Parses the <code>header-part</code> and returns as key/value
492 * pairs.
493 *
494 * <p> If there are multiple headers of the same names, the name
495 * will map to a comma-separated list containing the values.
496 *
497 * @param headerPart The <code>header-part</code> of the current
498 * <code>encapsulation</code>.
499 *
500 * @return A <code>Map</code> containing the parsed HTTP request headers.
501 */
502 protected Map
503 final int len = headerPart.length();
504 Map headers = new HashMap();
505 int start = 0;
506 for (;;) {
507 int end = parseEndOfLine(headerPart, start);
508 if (start == end) {
509 break;
510 }
511 String header = headerPart.substring(start, end);
512 start = end + 2;
513 while (start < len) {
514 int nonWs = start;
515 while (nonWs < len) {
516 char c = headerPart.charAt(nonWs);
517 if (c != ' ' && c != '\t') {
518 break;
519 }
520 ++nonWs;
521 }
522 if (nonWs == start) {
523 break;
524 }
525
526 end = parseEndOfLine(headerPart, nonWs);
527 header += " " + headerPart.substring(nonWs, end);
528 start = end + 2;
529 }
530 parseHeaderLine(headers, header);
531 }
532 return headers;
533 }
534
535 /**
536 * Skips bytes until the end of the current line.
537 * @param headerPart The headers, which are being parsed.
538 * @param end Index of the last byte, which has yet been
539 * processed.
540 * @return Index of the \r\n sequence, which indicates
541 * end of line.
542 */
543 private int parseEndOfLine(String headerPart, int end) {
544 int index = end;
545 for (;;) {
546 int offset = headerPart.indexOf('\r', index);
547 if (offset == -1 || offset + 1 >= headerPart.length()) {
548 throw new IllegalStateException(
549 "Expected headers to be terminated by an empty line.");
550 }
551 if (headerPart.charAt(offset + 1) == '\n') {
552 return offset;
553 }
554 index = offset + 1;
555 }
556 }
557
558 /**
559 * Reads the next header line.
560 * @param headers String with all headers.
561 * @param header Map where to store the current header.
562 */
563 private void parseHeaderLine(Map headers, String header) {
564 final int colonOffset = header.indexOf(':');
565 if (colonOffset == -1) {
566
567 return;
568 }
569 String headerName = header.substring(0, colonOffset)
570 .trim().toLowerCase();
571 String headerValue =
572 header.substring(header.indexOf(':') + 1).trim();
573 if (getHeader(headers, headerName) != null) {
574
575
576 headers.put(headerName,
577 getHeader(headers, headerName) + ','
578 + headerValue);
579 } else {
580 headers.put(headerName, headerValue);
581 }
582 }
583
584 /**
585 * Returns the header with the specified name from the supplied map. The
586 * header lookup is case-insensitive.
587 *
588 * @param headers A <code>Map</code> containing the HTTP request headers.
589 * @param name The name of the header to return.
590 *
591 * @return The value of specified header, or a comma-separated list if
592 * there were multiple headers of that name.
593 */
594 protected final String getHeader(Map
595 String name) {
596 return (String) headers.get(name.toLowerCase());
597 }
598
599 /**
600 * The iterator, which is returned by
601 * {@link FileUploadBase#getItemIterator(RequestContext)}.
602 */
603 private class FileItemIteratorImpl implements FileItemIterator {
604 /**
605 * Default implementation of {@link FileItemStream}.
606 */
607 private class FileItemStreamImpl implements FileItemStream {
608 /** The file items content type.
609 */
610 private final String contentType;
611 /** The file items field name.
612 */
613 private final String fieldName;
614 /** The file items file name.
615 */
616 private final String name;
617 /** Whether the file item is a form field.
618 */
619 private final boolean formField;
620 /** The file items input stream.
621 */
622 private final InputStream stream;
623 /** Whether the file item was already opened.
624 */
625 private boolean opened;
626
627 /**
628 * CReates a new instance.
629 * @param pName The items file name, or null.
630 * @param pFieldName The items field name.
631 * @param pContentType The items content type, or null.
632 * @param pFormField Whether the item is a form field.
633 */
634 FileItemStreamImpl(String pName, String pFieldName,
635 String pContentType, boolean pFormField) {
636 name = pName;
637 fieldName = pFieldName;
638 contentType = pContentType;
639 formField = pFormField;
640 InputStream istream = multi.newInputStream();
641 if (fileSizeMax != -1) {
642 istream = new LimitedInputStream(istream, fileSizeMax) {
643 protected void raiseError(long pSizeMax, long pCount)
644 throws IOException {
645 FileUploadException e =
646 new FileSizeLimitExceededException(
647 "The field " + fieldName
648 + " exceeds its maximum permitted "
649 + " size of " + pSizeMax
650 + " characters.",
651 pCount, pSizeMax);
652 throw new FileUploadIOException(e);
653 }
654 };
655 }
656 stream = istream;
657 }
658
659 /**
660 * Returns the items content type, or null.
661 * @return Content type, if known, or null.
662 */
663 public String getContentType() {
664 return contentType;
665 }
666
667 /**
668 * Returns the items field name.
669 * @return Field name.
670 */
671 public String getFieldName() {
672 return fieldName;
673 }
674
675 /**
676 * Returns the items file name.
677 * @return File name, if known, or null.
678 */
679 public String getName() {
680 return name;
681 }
682
683 /**
684 * Returns, whether this is a form field.
685 * @return True, if the item is a form field,
686 * otherwise false.
687 */
688 public boolean isFormField() {
689 return formField;
690 }
691
692 /**
693 * Returns an input stream, which may be used to
694 * read the items contents.
695 * @return Opened input stream.
696 * @throws IOException An I/O error occurred.
697 */
698 public InputStream openStream() throws IOException {
699 if (opened) {
700 throw new IllegalStateException(
701 "The stream was already opened.");
702 }
703 if (((Closeable) stream).isClosed()) {
704 throw new FileItemStream.ItemSkippedException();
705 }
706 return stream;
707 }
708
709 /**
710 * Closes the file item.
711 * @throws IOException An I/O error occurred.
712 */
713 void close() throws IOException {
714 stream.close();
715 }
716 }
717
718 /**
719 * The multi part stream to process.
720 */
721 private final MultipartStream multi;
722 /**
723 * The notifier, which used for triggering the
724 * {@link ProgressListener}.
725 */
726 private final MultipartStream.ProgressNotifier notifier;
727 /**
728 * The boundary, which separates the various parts.
729 */
730 private final byte[] boundary;
731 /**
732 * The item, which we currently process.
733 */
734 private FileItemStreamImpl currentItem;
735 /**
736 * The current items field name.
737 */
738 private String currentFieldName;
739 /**
740 * Whether we are currently skipping the preamble.
741 */
742 private boolean skipPreamble;
743 /**
744 * Whether the current item may still be read.
745 */
746 private boolean itemValid;
747 /**
748 * Whether we have seen the end of the file.
749 */
750 private boolean eof;
751
752 /**
753 * Creates a new instance.
754 * @param ctx The request context.
755 * @throws FileUploadException An error occurred while
756 * parsing the request.
757 * @throws IOException An I/O error occurred.
758 */
759 FileItemIteratorImpl(RequestContext ctx)
760 throws FileUploadException, IOException {
761 if (ctx == null) {
762 throw new NullPointerException("ctx parameter");
763 }
764
765 String contentType = ctx.getContentType();
766 if ((null == contentType)
767 || (!contentType.toLowerCase().startsWith(MULTIPART))) {
768 throw new InvalidContentTypeException(
769 "the request doesn't contain a "
770 + MULTIPART_FORM_DATA
771 + " or "
772 + MULTIPART_MIXED
773 + " stream, content type header is "
774 + contentType);
775 }
776
777 InputStream input = ctx.getInputStream();
778
779 if (sizeMax >= 0) {
780 int requestSize = ctx.getContentLength();
781 if (requestSize == -1) {
782 input = new LimitedInputStream(input, sizeMax) {
783 protected void raiseError(long pSizeMax, long pCount)
784 throws IOException {
785 FileUploadException ex =
786 new SizeLimitExceededException(
787 "the request was rejected because"
788 + " its size (" + pCount
789 + ") exceeds the configured maximum"
790 + " (" + pSizeMax + ")",
791 pCount, pSizeMax);
792 throw new FileUploadIOException(ex);
793 }
794 };
795 } else {
796 if (sizeMax >= 0 && requestSize > sizeMax) {
797 throw new SizeLimitExceededException(
798 "the request was rejected because its size ("
799 + requestSize
800 + ") exceeds the configured maximum ("
801 + sizeMax + ")",
802 requestSize, sizeMax);
803 }
804 }
805 }
806
807 String charEncoding = headerEncoding;
808 if (charEncoding == null) {
809 charEncoding = ctx.getCharacterEncoding();
810 }
811
812 boundary = getBoundary(contentType);
813 if (boundary == null) {
814 throw new FileUploadException(
815 "the request was rejected because "
816 + "no multipart boundary was found");
817 }
818
819 notifier = new MultipartStream.ProgressNotifier(listener,
820 ctx.getContentLength());
821 multi = new MultipartStream(input, boundary, notifier);
822 multi.setHeaderEncoding(charEncoding);
823
824 skipPreamble = true;
825 findNextItem();
826 }
827
828 /**
829 * Called for finding the nex item, if any.
830 * @return True, if an next item was found, otherwise false.
831 * @throws IOException An I/O error occurred.
832 */
833 private boolean findNextItem() throws IOException {
834 if (eof) {
835 return false;
836 }
837 if (currentItem != null) {
838 currentItem.close();
839 currentItem = null;
840 }
841 for (;;) {
842 boolean nextPart;
843 if (skipPreamble) {
844 nextPart = multi.skipPreamble();
845 } else {
846 nextPart = multi.readBoundary();
847 }
848 if (!nextPart) {
849 if (currentFieldName == null) {
850
851 eof = true;
852 return false;
853 }
854
855 multi.setBoundary(boundary);
856 currentFieldName = null;
857 continue;
858 }
859 Map headers = parseHeaders(multi.readHeaders());
860 if (currentFieldName == null) {
861
862 String fieldName = getFieldName(headers);
863 if (fieldName != null) {
864 String subContentType
865 = getHeader(headers, CONTENT_TYPE);
866 if (subContentType != null
867 && subContentType.toLowerCase()
868 .startsWith(MULTIPART_MIXED)) {
869 currentFieldName = fieldName;
870
871 byte[] subBoundary = getBoundary(subContentType);
872 multi.setBoundary(subBoundary);
873 skipPreamble = true;
874 continue;
875 }
876 String fileName = getFileName(headers);
877 currentItem = new FileItemStreamImpl(fileName,
878 fieldName, getHeader(headers, CONTENT_TYPE),
879 fileName == null);
880 notifier.noteItem();
881 itemValid = true;
882 return true;
883 }
884 } else {
885 String fileName = getFileName(headers);
886 if (fileName != null) {
887 currentItem = new FileItemStreamImpl(fileName,
888 currentFieldName,
889 getHeader(headers, CONTENT_TYPE),
890 false);
891 notifier.noteItem();
892 itemValid = true;
893 return true;
894 }
895 }
896 multi.discardBodyData();
897 }
898 }
899
900 /**
901 * Returns, whether another instance of {@link FileItemStream}
902 * is available.
903 * @throws FileUploadException Parsing or processing the
904 * file item failed.
905 * @throws IOException Reading the file item failed.
906 * @return True, if one or more additional file items
907 * are available, otherwise false.
908 */
909 public boolean hasNext() throws FileUploadException, IOException {
910 if (eof) {
911 return false;
912 }
913 if (itemValid) {
914 return true;
915 }
916 return findNextItem();
917 }
918
919 /**
920 * Returns the next available {@link FileItemStream}.
921 * @throws java.util.NoSuchElementException No more items are
922 * available. Use {@link #hasNext()} to prevent this exception.
923 * @throws FileUploadException Parsing or processing the
924 * file item failed.
925 * @throws IOException Reading the file item failed.
926 * @return FileItemStream instance, which provides
927 * access to the next file item.
928 */
929 public FileItemStream next() throws FileUploadException, IOException {
930 if (eof || (!itemValid && !hasNext())) {
931 throw new NoSuchElementException();
932 }
933 itemValid = false;
934 return currentItem;
935 }
936 }
937
938 /**
939 * This exception is thrown for hiding an inner
940 * {@link FileUploadException} in an {@link IOException}.
941 */
942 public static class FileUploadIOException extends IOException {
943 /** The exceptions UID, for serializing an instance.
944 */
945 private static final long serialVersionUID = -7047616958165584154L;
946 /** The exceptions cause; we overwrite the parent
947 * classes field, which is available since Java
948 * 1.4 only.
949 */
950 private final FileUploadException cause;
951
952 /**
953 * Creates a <code>FileUploadIOException</code> with the
954 * given cause.
955 * @param pCause The exceptions cause, if any, or null.
956 */
957 public FileUploadIOException(FileUploadException pCause) {
958
959 cause = pCause;
960 }
961
962 /**
963 * Returns the exceptions cause.
964 * @return The exceptions cause, if any, or null.
965 */
966 public Throwable getCause() {
967 return cause;
968 }
969 }
970
971 /**
972 * Thrown to indicate that the request is not a multipart request.
973 */
974 public static class InvalidContentTypeException
975 extends FileUploadException {
976 /** The exceptions UID, for serializing an instance.
977 */
978 private static final long serialVersionUID = -9073026332015646668L;
979
980 /**
981 * Constructs a <code>InvalidContentTypeException</code> with no
982 * detail message.
983 */
984 public InvalidContentTypeException() {
985
986 }
987
988 /**
989 * Constructs an <code>InvalidContentTypeException</code> with
990 * the specified detail message.
991 *
992 * @param message The detail message.
993 */
994 public InvalidContentTypeException(String message) {
995 super(message);
996 }
997 }
998
999 /**
1000 * Thrown to indicate an IOException.
1001 */
1002 public static class IOFileUploadException extends FileUploadException {
1003 /** The exceptions UID, for serializing an instance.
1004 */
1005 private static final long serialVersionUID = 1749796615868477269L;
1006 /** The exceptions cause; we overwrite the parent
1007 * classes field, which is available since Java
1008 * 1.4 only.
1009 */
1010 private final IOException cause;
1011
1012 /**
1013 * Creates a new instance with the given cause.
1014 * @param pMsg The detail message.
1015 * @param pException The exceptions cause.
1016 */
1017 public IOFileUploadException(String pMsg, IOException pException) {
1018 super(pMsg);
1019 cause = pException;
1020 }
1021
1022 /**
1023 * Returns the exceptions cause.
1024 * @return The exceptions cause, if any, or null.
1025 */
1026 public Throwable getCause() {
1027 return cause;
1028 }
1029 }
1030
1031 /** This exception is thrown, if a requests permitted size
1032 * is exceeded.
1033 */
1034 protected abstract static class SizeException extends FileUploadException {
1035 /**
1036 * The actual size of the request.
1037 */
1038 private final long actual;
1039
1040 /**
1041 * The maximum permitted size of the request.
1042 */
1043 private final long permitted;
1044
1045 /**
1046 * Creates a new instance.
1047 * @param message The detail message.
1048 * @param actual The actual number of bytes in the request.
1049 * @param permitted The requests size limit, in bytes.
1050 */
1051 protected SizeException(String message, long actual, long permitted) {
1052 super(message);
1053 this.actual = actual;
1054 this.permitted = permitted;
1055 }
1056
1057 /**
1058 * Retrieves the actual size of the request.
1059 *
1060 * @return The actual size of the request.
1061 */
1062 public long getActualSize() {
1063 return actual;
1064 }
1065
1066 /**
1067 * Retrieves the permitted size of the request.
1068 *
1069 * @return The permitted size of the request.
1070 */
1071 public long getPermittedSize() {
1072 return permitted;
1073 }
1074 }
1075
1076 /**
1077 * Thrown to indicate that the request size is not specified. In other
1078 * words, it is thrown, if the content-length header is missing or
1079 * contains the value -1.
1080 * @deprecated As of commons-fileupload 1.2, the presence of a
1081 * content-length header is no longer required.
1082 */
1083 public static class UnknownSizeException
1084 extends FileUploadException {
1085 /** The exceptions UID, for serializing an instance.
1086 */
1087 private static final long serialVersionUID = 7062279004812015273L;
1088
1089 /**
1090 * Constructs a <code>UnknownSizeException</code> with no
1091 * detail message.
1092 */
1093 public UnknownSizeException() {
1094 super();
1095 }
1096
1097 /**
1098 * Constructs an <code>UnknownSizeException</code> with
1099 * the specified detail message.
1100 *
1101 * @param message The detail message.
1102 */
1103 public UnknownSizeException(String message) {
1104 super(message);
1105 }
1106 }
1107
1108 /**
1109 * Thrown to indicate that the request size exceeds the configured maximum.
1110 */
1111 public static class SizeLimitExceededException
1112 extends SizeException {
1113 /** The exceptions UID, for serializing an instance.
1114 */
1115 private static final long serialVersionUID = -2474893167098052828L;
1116
1117 /**
1118 * @deprecated Replaced by
1119 * {@link #SizeLimitExceededException(String, long, long)}
1120 */
1121 public SizeLimitExceededException() {
1122 this(null, 0, 0);
1123 }
1124
1125 /**
1126 * @deprecated Replaced by
1127 * {@link #SizeLimitExceededException(String, long, long)}
1128 * @param message The exceptions detail message.
1129 */
1130 public SizeLimitExceededException(String message) {
1131 this(message, 0, 0);
1132 }
1133
1134 /**
1135 * Constructs a <code>SizeExceededException</code> with
1136 * the specified detail message, and actual and permitted sizes.
1137 *
1138 * @param message The detail message.
1139 * @param actual The actual request size.
1140 * @param permitted The maximum permitted request size.
1141 */
1142 public SizeLimitExceededException(String message, long actual,
1143 long permitted) {
1144 super(message, actual, permitted);
1145 }
1146 }
1147
1148 /**
1149 * Thrown to indicate that A files size exceeds the configured maximum.
1150 */
1151 public static class FileSizeLimitExceededException
1152 extends SizeException {
1153 /** The exceptions UID, for serializing an instance.
1154 */
1155 private static final long serialVersionUID = 8150776562029630058L;
1156
1157 /**
1158 * Constructs a <code>SizeExceededException</code> with
1159 * the specified detail message, and actual and permitted sizes.
1160 *
1161 * @param message The detail message.
1162 * @param actual The actual request size.
1163 * @param permitted The maximum permitted request size.
1164 */
1165 public FileSizeLimitExceededException(String message, long actual,
1166 long permitted) {
1167 super(message, actual, permitted);
1168 }
1169 }
1170
1171 /**
1172 * Returns the progress listener.
1173 * @return The progress listener, if any, or null.
1174 */
1175 public ProgressListener getProgressListener() {
1176 return listener;
1177 }
1178
1179 /**
1180 * Sets the progress listener.
1181 * @param pListener The progress listener, if any. Defaults to null.
1182 */
1183 public void setProgressListener(ProgressListener pListener) {
1184 listener = pListener;
1185 }
1186 }