001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.commons.fileupload; 018 019import static java.lang.String.format; 020 021import java.io.IOException; 022import java.io.InputStream; 023import java.nio.charset.StandardCharsets; 024import java.util.ArrayList; 025import java.util.HashMap; 026import java.util.Iterator; 027import java.util.List; 028import java.util.Locale; 029import java.util.Map; 030import java.util.NoSuchElementException; 031import java.util.Objects; 032 033import javax.servlet.http.HttpServletRequest; 034 035import org.apache.commons.fileupload.MultipartStream.ItemInputStream; 036import org.apache.commons.fileupload.servlet.ServletFileUpload; 037import org.apache.commons.fileupload.servlet.ServletRequestContext; 038import org.apache.commons.fileupload.util.Closeable; 039import org.apache.commons.fileupload.util.FileItemHeadersImpl; 040import org.apache.commons.fileupload.util.LimitedInputStream; 041import org.apache.commons.fileupload.util.Streams; 042import org.apache.commons.io.IOUtils; 043 044/** 045 * High level API for processing file uploads. 046 * 047 * <p> 048 * This class handles multiple files per single HTML widget, sent using {@code multipart/mixed} encoding type, as specified by 049 * <a href="http://www.ietf.org/rfc/rfc1867.txt">RFC 1867</a>. Use {@link #parseRequest(RequestContext)} to acquire a list of 050 * {@link org.apache.commons.fileupload.FileItem}s associated with a given HTML widget. 051 * </p> 052 * 053 * <p> 054 * 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. 055 * </p> 056 */ 057public abstract class FileUploadBase { 058 059 /** 060 * The iterator, which is returned by 061 * {@link FileUploadBase#getItemIterator(RequestContext)}. 062 */ 063 private class FileItemIteratorImpl implements FileItemIterator { 064 065 /** 066 * Default implementation of {@link FileItemStream}. 067 */ 068 private final class FileItemStreamImpl implements FileItemStream { 069 070 /** 071 * The file items content type. 072 */ 073 private final String contentType; 074 075 /** 076 * The file items field name. 077 */ 078 private final String fieldName; 079 080 /** 081 * The file items file name. 082 */ 083 private final String name; 084 085 /** 086 * Whether the file item is a form field. 087 */ 088 private final boolean formField; 089 090 /** 091 * The file items input stream. 092 */ 093 private final InputStream inputStream; 094 095 /** 096 * The headers, if any. 097 */ 098 private FileItemHeaders headers; 099 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}