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