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