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