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 static java.lang.String.format;
20
21 import java.io.IOException;
22 import java.io.InputStream;
23 import java.io.UnsupportedEncodingException;
24 import java.util.ArrayList;
25 import java.util.HashMap;
26 import java.util.Iterator;
27 import java.util.List;
28 import java.util.Locale;
29 import java.util.Map;
30 import java.util.NoSuchElementException;
31
32 import javax.servlet.http.HttpServletRequest;
33
34 import org.apache.commons.fileupload.MultipartStream.ItemInputStream;
35 import org.apache.commons.fileupload.servlet.ServletFileUpload;
36 import org.apache.commons.fileupload.servlet.ServletRequestContext;
37 import org.apache.commons.fileupload.util.Closeable;
38 import org.apache.commons.fileupload.util.FileItemHeadersImpl;
39 import org.apache.commons.fileupload.util.LimitedInputStream;
40 import org.apache.commons.fileupload.util.Streams;
41
42 /**
43 * <p>High level API for processing file uploads.</p>
44 *
45 * <p>This class handles multiple files per single HTML widget, sent using
46 * <code>multipart/mixed</code> encoding type, as specified by
47 * <a href="http://www.ietf.org/rfc/rfc1867.txt">RFC 1867</a>. Use {@link
48 * #parseRequest(RequestContext)} to acquire a list of {@link
49 * org.apache.commons.fileupload.FileItem}s associated with a given HTML
50 * widget.</p>
51 *
52 * <p>How the data for individual parts is stored is determined by the factory
53 * used to create them; a given part may be in memory, on disk, or somewhere
54 * else.</p>
55 *
56 * @version $Id: FileUploadBase.java 1458220 2013-03-19 10:56:17Z markt $
57 */
58 public abstract class FileUploadBase {
59
60 // ---------------------------------------------------------- Class methods
61
62 /**
63 * <p>Utility method that determines whether the request contains multipart
64 * content.</p>
65 *
66 * <p><strong>NOTE:</strong>This method will be moved to the
67 * <code>ServletFileUpload</code> class after the FileUpload 1.1 release.
68 * Unfortunately, since this method is static, it is not possible to
69 * provide its replacement until this method is removed.</p>
70 *
71 * @param ctx The request context to be evaluated. Must be non-null.
72 *
73 * @return <code>true</code> if the request is multipart;
74 * <code>false</code> otherwise.
75 */
76 public static final boolean isMultipartContent(RequestContext ctx) {
77 String contentType = ctx.getContentType();
78 if (contentType == null) {
79 return false;
80 }
81 if (contentType.toLowerCase(Locale.ENGLISH).startsWith(MULTIPART)) {
82 return true;
83 }
84 return false;
85 }
86
87 /**
88 * Utility method that determines whether the request contains multipart
89 * content.
90 *
91 * @param req The servlet request to be evaluated. Must be non-null.
92 *
93 * @return <code>true</code> if the request is multipart;
94 * <code>false</code> otherwise.
95 *
96 * @deprecated 1.1 Use the method on <code>ServletFileUpload</code> instead.
97 */
98 @Deprecated
99 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 }