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