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