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 if (fileSizeMax != -1) { // Check if limit is already exceeded
768 if (pContentLength != -1
769 && pContentLength > fileSizeMax) {
770 FileSizeLimitExceededException e =
771 new FileSizeLimitExceededException(
772 format("The field %s exceeds its maximum permitted size of %s bytes.",
773 fieldName, Long.valueOf(fileSizeMax)),
774 pContentLength, fileSizeMax);
775 e.setFileName(pName);
776 e.setFieldName(pFieldName);
777 throw new FileUploadIOException(e);
778 }
779 }
780 // OK to construct stream now
781 final ItemInputStream itemStream = multi.newInputStream();
782 InputStream istream = itemStream;
783 if (fileSizeMax != -1) {
784 istream = new LimitedInputStream(istream, fileSizeMax) {
785 @Override
786 protected void raiseError(long pSizeMax, long pCount)
787 throws IOException {
788 itemStream.close(true);
789 FileSizeLimitExceededException e =
790 new FileSizeLimitExceededException(
791 format("The field %s exceeds its maximum permitted size of %s bytes.",
792 fieldName, Long.valueOf(pSizeMax)),
793 pCount, pSizeMax);
794 e.setFieldName(fieldName);
795 e.setFileName(name);
796 throw new FileUploadIOException(e);
797 }
798 };
799 }
800 stream = istream;
801 }
802
803 /**
804 * Returns the items content type, or null.
805 *
806 * @return Content type, if known, or null.
807 */
808 @Override
809 public String getContentType() {
810 return contentType;
811 }
812
813 /**
814 * Returns the items field name.
815 *
816 * @return Field name.
817 */
818 @Override
819 public String getFieldName() {
820 return fieldName;
821 }
822
823 /**
824 * Returns the items file name.
825 *
826 * @return File name, if known, or null.
827 * @throws InvalidFileNameException The file name contains a NUL character,
828 * which might be an indicator of a security attack. If you intend to
829 * use the file name anyways, catch the exception and use
830 * InvalidFileNameException#getName().
831 */
832 @Override
833 public String getName() {
834 return Streams.checkFileName(name);
835 }
836
837 /**
838 * Returns, whether this is a form field.
839 *
840 * @return True, if the item is a form field,
841 * otherwise false.
842 */
843 @Override
844 public boolean isFormField() {
845 return formField;
846 }
847
848 /**
849 * Returns an input stream, which may be used to
850 * read the items contents.
851 *
852 * @return Opened input stream.
853 * @throws IOException An I/O error occurred.
854 */
855 @Override
856 public InputStream openStream() throws IOException {
857 if (opened) {
858 throw new IllegalStateException(
859 "The stream was already opened.");
860 }
861 if (((Closeable) stream).isClosed()) {
862 throw new FileItemStream.ItemSkippedException();
863 }
864 return stream;
865 }
866
867 /**
868 * Closes the file item.
869 *
870 * @throws IOException An I/O error occurred.
871 */
872 void close() throws IOException {
873 stream.close();
874 }
875
876 /**
877 * Returns the file item headers.
878 *
879 * @return The items header object
880 */
881 @Override
882 public FileItemHeaders getHeaders() {
883 return headers;
884 }
885
886 /**
887 * Sets the file item headers.
888 *
889 * @param pHeaders The items header object
890 */
891 @Override
892 public void setHeaders(FileItemHeaders pHeaders) {
893 headers = pHeaders;
894 }
895
896 }
897
898 /**
899 * The multi part stream to process.
900 */
901 private final MultipartStream multi;
902
903 /**
904 * The notifier, which used for triggering the
905 * {@link ProgressListener}.
906 */
907 private final MultipartStream.ProgressNotifier notifier;
908
909 /**
910 * The boundary, which separates the various parts.
911 */
912 private final byte[] boundary;
913
914 /**
915 * The item, which we currently process.
916 */
917 private FileItemStreamImpl currentItem;
918
919 /**
920 * The current items field name.
921 */
922 private String currentFieldName;
923
924 /**
925 * Whether we are currently skipping the preamble.
926 */
927 private boolean skipPreamble;
928
929 /**
930 * Whether the current item may still be read.
931 */
932 private boolean itemValid;
933
934 /**
935 * Whether we have seen the end of the file.
936 */
937 private boolean eof;
938
939 /**
940 * Creates a new instance.
941 *
942 * @param ctx The request context.
943 * @throws FileUploadException An error occurred while
944 * parsing the request.
945 * @throws IOException An I/O error occurred.
946 */
947 FileItemIteratorImpl(RequestContext ctx)
948 throws FileUploadException, IOException {
949 if (ctx == null) {
950 throw new NullPointerException("ctx parameter");
951 }
952
953 String contentType = ctx.getContentType();
954 if ((null == contentType)
955 || (!contentType.toLowerCase(Locale.ENGLISH).startsWith(MULTIPART))) {
956 throw new InvalidContentTypeException(
957 format("the request doesn't contain a %s or %s stream, content type header is %s",
958 MULTIPART_FORM_DATA, MULTIPART_MIXED, contentType));
959 }
960
961
962 @SuppressWarnings("deprecation") // still has to be backward compatible
963 final int contentLengthInt = ctx.getContentLength();
964
965 final long requestSize = UploadContext.class.isAssignableFrom(ctx.getClass())
966 // Inline conditional is OK here CHECKSTYLE:OFF
967 ? ((UploadContext) ctx).contentLength()
968 : contentLengthInt;
969 // CHECKSTYLE:ON
970
971 InputStream input; // N.B. this is eventually closed in MultipartStream processing
972 if (sizeMax >= 0) {
973 if (requestSize != -1 && requestSize > sizeMax) {
974 throw new SizeLimitExceededException(
975 format("the request was rejected because its size (%s) exceeds the configured maximum (%s)",
976 Long.valueOf(requestSize), Long.valueOf(sizeMax)),
977 requestSize, sizeMax);
978 }
979 // N.B. this is eventually closed in MultipartStream processing
980 input = new LimitedInputStream(ctx.getInputStream(), sizeMax) {
981 @Override
982 protected void raiseError(long pSizeMax, long pCount)
983 throws IOException {
984 FileUploadException ex = new SizeLimitExceededException(
985 format("the request was rejected because its size (%s) exceeds the configured maximum (%s)",
986 Long.valueOf(pCount), Long.valueOf(pSizeMax)),
987 pCount, pSizeMax);
988 throw new FileUploadIOException(ex);
989 }
990 };
991 } else {
992 input = ctx.getInputStream();
993 }
994
995 String charEncoding = headerEncoding;
996 if (charEncoding == null) {
997 charEncoding = ctx.getCharacterEncoding();
998 }
999
1000 boundary = getBoundary(contentType);
1001 if (boundary == null) {
1002 IOUtils.closeQuietly(input); // avoid possible resource leak
1003 throw new FileUploadException("the request was rejected because no multipart boundary was found");
1004 }
1005
1006 notifier = new MultipartStream.ProgressNotifier(listener, requestSize);
1007 try {
1008 multi = new MultipartStream(input, boundary, notifier);
1009 } catch (IllegalArgumentException iae) {
1010 IOUtils.closeQuietly(input); // avoid possible resource leak
1011 throw new InvalidContentTypeException(
1012 format("The boundary specified in the %s header is too long", CONTENT_TYPE), iae);
1013 }
1014 multi.setHeaderEncoding(charEncoding);
1015
1016 skipPreamble = true;
1017 findNextItem();
1018 }
1019
1020 /**
1021 * Called for finding the next item, if any.
1022 *
1023 * @return True, if an next item was found, otherwise false.
1024 * @throws IOException An I/O error occurred.
1025 */
1026 private boolean findNextItem() throws IOException {
1027 if (eof) {
1028 return false;
1029 }
1030 if (currentItem != null) {
1031 currentItem.close();
1032 currentItem = null;
1033 }
1034 for (;;) {
1035 boolean nextPart;
1036 if (skipPreamble) {
1037 nextPart = multi.skipPreamble();
1038 } else {
1039 nextPart = multi.readBoundary();
1040 }
1041 if (!nextPart) {
1042 if (currentFieldName == null) {
1043 // Outer multipart terminated -> No more data
1044 eof = true;
1045 return false;
1046 }
1047 // Inner multipart terminated -> Return to parsing the outer
1048 multi.setBoundary(boundary);
1049 currentFieldName = null;
1050 continue;
1051 }
1052 FileItemHeaders headers = getParsedHeaders(multi.readHeaders());
1053 if (currentFieldName == null) {
1054 // We're parsing the outer multipart
1055 String fieldName = getFieldName(headers);
1056 if (fieldName != null) {
1057 String subContentType = headers.getHeader(CONTENT_TYPE);
1058 if (subContentType != null
1059 && subContentType.toLowerCase(Locale.ENGLISH)
1060 .startsWith(MULTIPART_MIXED)) {
1061 currentFieldName = fieldName;
1062 // Multiple files associated with this field name
1063 byte[] subBoundary = getBoundary(subContentType);
1064 multi.setBoundary(subBoundary);
1065 skipPreamble = true;
1066 continue;
1067 }
1068 String fileName = getFileName(headers);
1069 currentItem = new FileItemStreamImpl(fileName,
1070 fieldName, headers.getHeader(CONTENT_TYPE),
1071 fileName == null, getContentLength(headers));
1072 currentItem.setHeaders(headers);
1073 notifier.noteItem();
1074 itemValid = true;
1075 return true;
1076 }
1077 } else {
1078 String fileName = getFileName(headers);
1079 if (fileName != null) {
1080 currentItem = new FileItemStreamImpl(fileName,
1081 currentFieldName,
1082 headers.getHeader(CONTENT_TYPE),
1083 false, getContentLength(headers));
1084 currentItem.setHeaders(headers);
1085 notifier.noteItem();
1086 itemValid = true;
1087 return true;
1088 }
1089 }
1090 multi.discardBodyData();
1091 }
1092 }
1093
1094 private long getContentLength(FileItemHeaders pHeaders) {
1095 try {
1096 return Long.parseLong(pHeaders.getHeader(CONTENT_LENGTH));
1097 } catch (Exception e) {
1098 return -1;
1099 }
1100 }
1101
1102 /**
1103 * Returns, whether another instance of {@link FileItemStream}
1104 * is available.
1105 *
1106 * @throws FileUploadException Parsing or processing the
1107 * file item failed.
1108 * @throws IOException Reading the file item failed.
1109 * @return True, if one or more additional file items
1110 * are available, otherwise false.
1111 */
1112 @Override
1113 public boolean hasNext() throws FileUploadException, IOException {
1114 if (eof) {
1115 return false;
1116 }
1117 if (itemValid) {
1118 return true;
1119 }
1120 try {
1121 return findNextItem();
1122 } catch (FileUploadIOException e) {
1123 // unwrap encapsulated SizeException
1124 throw (FileUploadException) e.getCause();
1125 }
1126 }
1127
1128 /**
1129 * Returns the next available {@link FileItemStream}.
1130 *
1131 * @throws java.util.NoSuchElementException No more items are
1132 * available. Use {@link #hasNext()} to prevent this exception.
1133 * @throws FileUploadException Parsing or processing the
1134 * file item failed.
1135 * @throws IOException Reading the file item failed.
1136 * @return FileItemStream instance, which provides
1137 * access to the next file item.
1138 */
1139 @Override
1140 public FileItemStream next() throws FileUploadException, IOException {
1141 if (eof || (!itemValid && !hasNext())) {
1142 throw new NoSuchElementException();
1143 }
1144 itemValid = false;
1145 return currentItem;
1146 }
1147
1148 }
1149
1150 /**
1151 * This exception is thrown for hiding an inner
1152 * {@link FileUploadException} in an {@link IOException}.
1153 */
1154 public static class FileUploadIOException extends IOException {
1155
1156 /**
1157 * The exceptions UID, for serializing an instance.
1158 */
1159 private static final long serialVersionUID = -7047616958165584154L;
1160
1161 /**
1162 * The exceptions cause; we overwrite the parent
1163 * classes field, which is available since Java
1164 * 1.4 only.
1165 */
1166 private final FileUploadException cause;
1167
1168 /**
1169 * Creates a <code>FileUploadIOException</code> with the
1170 * given cause.
1171 *
1172 * @param pCause The exceptions cause, if any, or null.
1173 */
1174 public FileUploadIOException(FileUploadException pCause) {
1175 // We're not doing super(pCause) cause of 1.3 compatibility.
1176 cause = pCause;
1177 }
1178
1179 /**
1180 * Returns the exceptions cause.
1181 *
1182 * @return The exceptions cause, if any, or null.
1183 */
1184 @Override
1185 public Throwable getCause() {
1186 return cause;
1187 }
1188
1189 }
1190
1191 /**
1192 * Thrown to indicate that the request is not a multipart request.
1193 */
1194 public static class InvalidContentTypeException
1195 extends FileUploadException {
1196
1197 /**
1198 * The exceptions UID, for serializing an instance.
1199 */
1200 private static final long serialVersionUID = -9073026332015646668L;
1201
1202 /**
1203 * Constructs a <code>InvalidContentTypeException</code> with no
1204 * detail message.
1205 */
1206 public InvalidContentTypeException() {
1207 super();
1208 }
1209
1210 /**
1211 * Constructs an <code>InvalidContentTypeException</code> with
1212 * the specified detail message.
1213 *
1214 * @param message The detail message.
1215 */
1216 public InvalidContentTypeException(String message) {
1217 super(message);
1218 }
1219
1220 /**
1221 * Constructs an <code>InvalidContentTypeException</code> with
1222 * the specified detail message and cause.
1223 *
1224 * @param msg The detail message.
1225 * @param cause the original cause
1226 *
1227 * @since 1.3.1
1228 */
1229 public InvalidContentTypeException(String msg, Throwable cause) {
1230 super(msg, cause);
1231 }
1232 }
1233
1234 /**
1235 * Thrown to indicate an IOException.
1236 */
1237 public static class IOFileUploadException extends FileUploadException {
1238
1239 /**
1240 * The exceptions UID, for serializing an instance.
1241 */
1242 private static final long serialVersionUID = 1749796615868477269L;
1243
1244 /**
1245 * The exceptions cause; we overwrite the parent
1246 * classes field, which is available since Java
1247 * 1.4 only.
1248 */
1249 private final IOException cause;
1250
1251 /**
1252 * Creates a new instance with the given cause.
1253 *
1254 * @param pMsg The detail message.
1255 * @param pException The exceptions cause.
1256 */
1257 public IOFileUploadException(String pMsg, IOException pException) {
1258 super(pMsg);
1259 cause = pException;
1260 }
1261
1262 /**
1263 * Returns the exceptions cause.
1264 *
1265 * @return The exceptions cause, if any, or null.
1266 */
1267 @Override
1268 public Throwable getCause() {
1269 return cause;
1270 }
1271
1272 }
1273
1274 /**
1275 * This exception is thrown, if a requests permitted size
1276 * is exceeded.
1277 */
1278 protected abstract static class SizeException extends FileUploadException {
1279
1280 /**
1281 * Serial version UID, being used, if serialized.
1282 */
1283 private static final long serialVersionUID = -8776225574705254126L;
1284
1285 /**
1286 * The actual size of the request.
1287 */
1288 private final long actual;
1289
1290 /**
1291 * The maximum permitted size of the request.
1292 */
1293 private final long permitted;
1294
1295 /**
1296 * Creates a new instance.
1297 *
1298 * @param message The detail message.
1299 * @param actual The actual number of bytes in the request.
1300 * @param permitted The requests size limit, in bytes.
1301 */
1302 protected SizeException(String message, long actual, long permitted) {
1303 super(message);
1304 this.actual = actual;
1305 this.permitted = permitted;
1306 }
1307
1308 /**
1309 * Retrieves the actual size of the request.
1310 *
1311 * @return The actual size of the request.
1312 * @since 1.3
1313 */
1314 public long getActualSize() {
1315 return actual;
1316 }
1317
1318 /**
1319 * Retrieves the permitted size of the request.
1320 *
1321 * @return The permitted size of the request.
1322 * @since 1.3
1323 */
1324 public long getPermittedSize() {
1325 return permitted;
1326 }
1327
1328 }
1329
1330 /**
1331 * Thrown to indicate that the request size is not specified. In other
1332 * words, it is thrown, if the content-length header is missing or
1333 * contains the value -1.
1334 *
1335 * @deprecated 1.2 As of commons-fileupload 1.2, the presence of a
1336 * content-length header is no longer required.
1337 */
1338 @Deprecated
1339 public static class UnknownSizeException
1340 extends FileUploadException {
1341
1342 /**
1343 * The exceptions UID, for serializing an instance.
1344 */
1345 private static final long serialVersionUID = 7062279004812015273L;
1346
1347 /**
1348 * Constructs a <code>UnknownSizeException</code> with no
1349 * detail message.
1350 */
1351 public UnknownSizeException() {
1352 super();
1353 }
1354
1355 /**
1356 * Constructs an <code>UnknownSizeException</code> with
1357 * the specified detail message.
1358 *
1359 * @param message The detail message.
1360 */
1361 public UnknownSizeException(String message) {
1362 super(message);
1363 }
1364
1365 }
1366
1367 /**
1368 * Thrown to indicate that the request size exceeds the configured maximum.
1369 */
1370 public static class SizeLimitExceededException
1371 extends SizeException {
1372
1373 /**
1374 * The exceptions UID, for serializing an instance.
1375 */
1376 private static final long serialVersionUID = -2474893167098052828L;
1377
1378 /**
1379 * @deprecated 1.2 Replaced by
1380 * {@link #SizeLimitExceededException(String, long, long)}
1381 */
1382 @Deprecated
1383 public SizeLimitExceededException() {
1384 this(null, 0, 0);
1385 }
1386
1387 /**
1388 * @deprecated 1.2 Replaced by
1389 * {@link #SizeLimitExceededException(String, long, long)}
1390 * @param message The exceptions detail message.
1391 */
1392 @Deprecated
1393 public SizeLimitExceededException(String message) {
1394 this(message, 0, 0);
1395 }
1396
1397 /**
1398 * Constructs a <code>SizeExceededException</code> with
1399 * the specified detail message, and actual and permitted sizes.
1400 *
1401 * @param message The detail message.
1402 * @param actual The actual request size.
1403 * @param permitted The maximum permitted request size.
1404 */
1405 public SizeLimitExceededException(String message, long actual,
1406 long permitted) {
1407 super(message, actual, permitted);
1408 }
1409
1410 }
1411
1412 /**
1413 * Thrown to indicate that A files size exceeds the configured maximum.
1414 */
1415 public static class FileSizeLimitExceededException
1416 extends SizeException {
1417
1418 /**
1419 * The exceptions UID, for serializing an instance.
1420 */
1421 private static final long serialVersionUID = 8150776562029630058L;
1422
1423 /**
1424 * File name of the item, which caused the exception.
1425 */
1426 private String fileName;
1427
1428 /**
1429 * Field name of the item, which caused the exception.
1430 */
1431 private String fieldName;
1432
1433 /**
1434 * Constructs a <code>SizeExceededException</code> with
1435 * the specified detail message, and actual and permitted sizes.
1436 *
1437 * @param message The detail message.
1438 * @param actual The actual request size.
1439 * @param permitted The maximum permitted request size.
1440 */
1441 public FileSizeLimitExceededException(String message, long actual,
1442 long permitted) {
1443 super(message, actual, permitted);
1444 }
1445
1446 /**
1447 * Returns the file name of the item, which caused the
1448 * exception.
1449 *
1450 * @return File name, if known, or null.
1451 */
1452 public String getFileName() {
1453 return fileName;
1454 }
1455
1456 /**
1457 * Sets the file name of the item, which caused the
1458 * exception.
1459 *
1460 * @param pFileName the file name of the item, which caused the exception.
1461 */
1462 public void setFileName(String pFileName) {
1463 fileName = pFileName;
1464 }
1465
1466 /**
1467 * Returns the field name of the item, which caused the
1468 * exception.
1469 *
1470 * @return Field name, if known, or null.
1471 */
1472 public String getFieldName() {
1473 return fieldName;
1474 }
1475
1476 /**
1477 * Sets the field name of the item, which caused the
1478 * exception.
1479 *
1480 * @param pFieldName the field name of the item,
1481 * which caused the exception.
1482 */
1483 public void setFieldName(String pFieldName) {
1484 fieldName = pFieldName;
1485 }
1486
1487 }
1488
1489 /**
1490 * Returns the progress listener.
1491 *
1492 * @return The progress listener, if any, or null.
1493 */
1494 public ProgressListener getProgressListener() {
1495 return listener;
1496 }
1497
1498 /**
1499 * Sets the progress listener.
1500 *
1501 * @param pListener The progress listener, if any. Defaults to null.
1502 */
1503 public void setProgressListener(ProgressListener pListener) {
1504 listener = pListener;
1505 }
1506
1507 }