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