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 final ItemInputStream itemStream = multi.newInputStream(); 768 InputStream istream = itemStream; 769 if (fileSizeMax != -1) { 770 if (pContentLength != -1 771 && pContentLength > fileSizeMax) { 772 FileSizeLimitExceededException e = 773 new FileSizeLimitExceededException( 774 format("The field %s exceeds its maximum permitted size of %s bytes.", 775 fieldName, Long.valueOf(fileSizeMax)), 776 pContentLength, fileSizeMax); 777 e.setFileName(pName); 778 e.setFieldName(pFieldName); 779 throw new FileUploadIOException(e); 780 } 781 istream = new LimitedInputStream(istream, fileSizeMax) { 782 @Override 783 protected void raiseError(long pSizeMax, long pCount) 784 throws IOException { 785 itemStream.close(true); 786 FileSizeLimitExceededException e = 787 new FileSizeLimitExceededException( 788 format("The field %s exceeds its maximum permitted size of %s bytes.", 789 fieldName, Long.valueOf(pSizeMax)), 790 pCount, pSizeMax); 791 e.setFieldName(fieldName); 792 e.setFileName(name); 793 throw new FileUploadIOException(e); 794 } 795 }; 796 } 797 stream = istream; 798 } 799 800 /** 801 * Returns the items content type, or null. 802 * 803 * @return Content type, if known, or null. 804 */ 805 public String getContentType() { 806 return contentType; 807 } 808 809 /** 810 * Returns the items field name. 811 * 812 * @return Field name. 813 */ 814 public String getFieldName() { 815 return fieldName; 816 } 817 818 /** 819 * Returns the items file name. 820 * 821 * @return File name, if known, or null. 822 * @throws InvalidFileNameException The file name contains a NUL character, 823 * which might be an indicator of a security attack. If you intend to 824 * use the file name anyways, catch the exception and use 825 * InvalidFileNameException#getName(). 826 */ 827 public String getName() { 828 return Streams.checkFileName(name); 829 } 830 831 /** 832 * Returns, whether this is a form field. 833 * 834 * @return True, if the item is a form field, 835 * otherwise false. 836 */ 837 public boolean isFormField() { 838 return formField; 839 } 840 841 /** 842 * Returns an input stream, which may be used to 843 * read the items contents. 844 * 845 * @return Opened input stream. 846 * @throws IOException An I/O error occurred. 847 */ 848 public InputStream openStream() throws IOException { 849 if (opened) { 850 throw new IllegalStateException( 851 "The stream was already opened."); 852 } 853 if (((Closeable) stream).isClosed()) { 854 throw new FileItemStream.ItemSkippedException(); 855 } 856 return stream; 857 } 858 859 /** 860 * Closes the file item. 861 * 862 * @throws IOException An I/O error occurred. 863 */ 864 void close() throws IOException { 865 stream.close(); 866 } 867 868 /** 869 * Returns the file item headers. 870 * 871 * @return The items header object 872 */ 873 public FileItemHeaders getHeaders() { 874 return headers; 875 } 876 877 /** 878 * Sets the file item headers. 879 * 880 * @param pHeaders The items header object 881 */ 882 public void setHeaders(FileItemHeaders pHeaders) { 883 headers = pHeaders; 884 } 885 886 } 887 888 /** 889 * The multi part stream to process. 890 */ 891 private final MultipartStream multi; 892 893 /** 894 * The notifier, which used for triggering the 895 * {@link ProgressListener}. 896 */ 897 private final MultipartStream.ProgressNotifier notifier; 898 899 /** 900 * The boundary, which separates the various parts. 901 */ 902 private final byte[] boundary; 903 904 /** 905 * The item, which we currently process. 906 */ 907 private FileItemStreamImpl currentItem; 908 909 /** 910 * The current items field name. 911 */ 912 private String currentFieldName; 913 914 /** 915 * Whether we are currently skipping the preamble. 916 */ 917 private boolean skipPreamble; 918 919 /** 920 * Whether the current item may still be read. 921 */ 922 private boolean itemValid; 923 924 /** 925 * Whether we have seen the end of the file. 926 */ 927 private boolean eof; 928 929 /** 930 * Creates a new instance. 931 * 932 * @param ctx The request context. 933 * @throws FileUploadException An error occurred while 934 * parsing the request. 935 * @throws IOException An I/O error occurred. 936 */ 937 FileItemIteratorImpl(RequestContext ctx) 938 throws FileUploadException, IOException { 939 if (ctx == null) { 940 throw new NullPointerException("ctx parameter"); 941 } 942 943 String contentType = ctx.getContentType(); 944 if ((null == contentType) 945 || (!contentType.toLowerCase(Locale.ENGLISH).startsWith(MULTIPART))) { 946 throw new InvalidContentTypeException( 947 format("the request doesn't contain a %s or %s stream, content type header is %s", 948 MULTIPART_FORM_DATA, MULTIPART_MIXED, contentType)); 949 } 950 951 952 @SuppressWarnings("deprecation") // still has to be backward compatible 953 final int contentLengthInt = ctx.getContentLength(); 954 955 final long requestSize = UploadContext.class.isAssignableFrom(ctx.getClass()) 956 // Inline conditional is OK here CHECKSTYLE:OFF 957 ? ((UploadContext) ctx).contentLength() 958 : contentLengthInt; 959 // CHECKSTYLE:ON 960 961 InputStream input; // N.B. this is eventually closed in MultipartStream processing 962 if (sizeMax >= 0) { 963 if (requestSize != -1 && requestSize > sizeMax) { 964 throw new SizeLimitExceededException( 965 format("the request was rejected because its size (%s) exceeds the configured maximum (%s)", 966 Long.valueOf(requestSize), Long.valueOf(sizeMax)), 967 requestSize, sizeMax); 968 } 969 // N.B. this is eventually closed in MultipartStream processing 970 input = new LimitedInputStream(ctx.getInputStream(), sizeMax) { 971 @Override 972 protected void raiseError(long pSizeMax, long pCount) 973 throws IOException { 974 FileUploadException ex = new SizeLimitExceededException( 975 format("the request was rejected because its size (%s) exceeds the configured maximum (%s)", 976 Long.valueOf(pCount), Long.valueOf(pSizeMax)), 977 pCount, pSizeMax); 978 throw new FileUploadIOException(ex); 979 } 980 }; 981 } else { 982 input = ctx.getInputStream(); 983 } 984 985 String charEncoding = headerEncoding; 986 if (charEncoding == null) { 987 charEncoding = ctx.getCharacterEncoding(); 988 } 989 990 boundary = getBoundary(contentType); 991 if (boundary == null) { 992 IOUtils.closeQuietly(input); // avoid possible resource leak 993 throw new FileUploadException("the request was rejected because no multipart boundary was found"); 994 } 995 996 notifier = new MultipartStream.ProgressNotifier(listener, requestSize); 997 try { 998 multi = new MultipartStream(input, boundary, notifier); 999 } catch (IllegalArgumentException iae) { 1000 IOUtils.closeQuietly(input); // avoid possible resource leak 1001 throw new InvalidContentTypeException( 1002 format("The boundary specified in the %s header is too long", CONTENT_TYPE), iae); 1003 } 1004 multi.setHeaderEncoding(charEncoding); 1005 1006 skipPreamble = true; 1007 findNextItem(); 1008 } 1009 1010 /** 1011 * Called for finding the next item, if any. 1012 * 1013 * @return True, if an next item was found, otherwise false. 1014 * @throws IOException An I/O error occurred. 1015 */ 1016 private boolean findNextItem() throws IOException { 1017 if (eof) { 1018 return false; 1019 } 1020 if (currentItem != null) { 1021 currentItem.close(); 1022 currentItem = null; 1023 } 1024 for (;;) { 1025 boolean nextPart; 1026 if (skipPreamble) { 1027 nextPart = multi.skipPreamble(); 1028 } else { 1029 nextPart = multi.readBoundary(); 1030 } 1031 if (!nextPart) { 1032 if (currentFieldName == null) { 1033 // Outer multipart terminated -> No more data 1034 eof = true; 1035 return false; 1036 } 1037 // Inner multipart terminated -> Return to parsing the outer 1038 multi.setBoundary(boundary); 1039 currentFieldName = null; 1040 continue; 1041 } 1042 FileItemHeaders headers = getParsedHeaders(multi.readHeaders()); 1043 if (currentFieldName == null) { 1044 // We're parsing the outer multipart 1045 String fieldName = getFieldName(headers); 1046 if (fieldName != null) { 1047 String subContentType = headers.getHeader(CONTENT_TYPE); 1048 if (subContentType != null 1049 && subContentType.toLowerCase(Locale.ENGLISH) 1050 .startsWith(MULTIPART_MIXED)) { 1051 currentFieldName = fieldName; 1052 // Multiple files associated with this field name 1053 byte[] subBoundary = getBoundary(subContentType); 1054 multi.setBoundary(subBoundary); 1055 skipPreamble = true; 1056 continue; 1057 } 1058 String fileName = getFileName(headers); 1059 currentItem = new FileItemStreamImpl(fileName, 1060 fieldName, headers.getHeader(CONTENT_TYPE), 1061 fileName == null, getContentLength(headers)); 1062 currentItem.setHeaders(headers); 1063 notifier.noteItem(); 1064 itemValid = true; 1065 return true; 1066 } 1067 } else { 1068 String fileName = getFileName(headers); 1069 if (fileName != null) { 1070 currentItem = new FileItemStreamImpl(fileName, 1071 currentFieldName, 1072 headers.getHeader(CONTENT_TYPE), 1073 false, getContentLength(headers)); 1074 currentItem.setHeaders(headers); 1075 notifier.noteItem(); 1076 itemValid = true; 1077 return true; 1078 } 1079 } 1080 multi.discardBodyData(); 1081 } 1082 } 1083 1084 private long getContentLength(FileItemHeaders pHeaders) { 1085 try { 1086 return Long.parseLong(pHeaders.getHeader(CONTENT_LENGTH)); 1087 } catch (Exception e) { 1088 return -1; 1089 } 1090 } 1091 1092 /** 1093 * Returns, whether another instance of {@link FileItemStream} 1094 * is available. 1095 * 1096 * @throws FileUploadException Parsing or processing the 1097 * file item failed. 1098 * @throws IOException Reading the file item failed. 1099 * @return True, if one or more additional file items 1100 * are available, otherwise false. 1101 */ 1102 public boolean hasNext() throws FileUploadException, IOException { 1103 if (eof) { 1104 return false; 1105 } 1106 if (itemValid) { 1107 return true; 1108 } 1109 try { 1110 return findNextItem(); 1111 } catch (FileUploadIOException e) { 1112 // unwrap encapsulated SizeException 1113 throw (FileUploadException) e.getCause(); 1114 } 1115 } 1116 1117 /** 1118 * Returns the next available {@link FileItemStream}. 1119 * 1120 * @throws java.util.NoSuchElementException No more items are 1121 * available. Use {@link #hasNext()} to prevent this exception. 1122 * @throws FileUploadException Parsing or processing the 1123 * file item failed. 1124 * @throws IOException Reading the file item failed. 1125 * @return FileItemStream instance, which provides 1126 * access to the next file item. 1127 */ 1128 public FileItemStream next() throws FileUploadException, IOException { 1129 if (eof || (!itemValid && !hasNext())) { 1130 throw new NoSuchElementException(); 1131 } 1132 itemValid = false; 1133 return currentItem; 1134 } 1135 1136 } 1137 1138 /** 1139 * This exception is thrown for hiding an inner 1140 * {@link FileUploadException} in an {@link IOException}. 1141 */ 1142 public static class FileUploadIOException extends IOException { 1143 1144 /** 1145 * The exceptions UID, for serializing an instance. 1146 */ 1147 private static final long serialVersionUID = -7047616958165584154L; 1148 1149 /** 1150 * The exceptions cause; we overwrite the parent 1151 * classes field, which is available since Java 1152 * 1.4 only. 1153 */ 1154 private final FileUploadException cause; 1155 1156 /** 1157 * Creates a <code>FileUploadIOException</code> with the 1158 * given cause. 1159 * 1160 * @param pCause The exceptions cause, if any, or null. 1161 */ 1162 public FileUploadIOException(FileUploadException pCause) { 1163 // We're not doing super(pCause) cause of 1.3 compatibility. 1164 cause = pCause; 1165 } 1166 1167 /** 1168 * Returns the exceptions cause. 1169 * 1170 * @return The exceptions cause, if any, or null. 1171 */ 1172 @Override 1173 public Throwable getCause() { 1174 return cause; 1175 } 1176 1177 } 1178 1179 /** 1180 * Thrown to indicate that the request is not a multipart request. 1181 */ 1182 public static class InvalidContentTypeException 1183 extends FileUploadException { 1184 1185 /** 1186 * The exceptions UID, for serializing an instance. 1187 */ 1188 private static final long serialVersionUID = -9073026332015646668L; 1189 1190 /** 1191 * Constructs a <code>InvalidContentTypeException</code> with no 1192 * detail message. 1193 */ 1194 public InvalidContentTypeException() { 1195 super(); 1196 } 1197 1198 /** 1199 * Constructs an <code>InvalidContentTypeException</code> with 1200 * the specified detail message. 1201 * 1202 * @param message The detail message. 1203 */ 1204 public InvalidContentTypeException(String message) { 1205 super(message); 1206 } 1207 1208 /** 1209 * Constructs an <code>InvalidContentTypeException</code> with 1210 * the specified detail message and cause. 1211 * 1212 * @param msg The detail message. 1213 * @param cause the original cause 1214 * 1215 * @since 1.3.1 1216 */ 1217 public InvalidContentTypeException(String msg, Throwable cause) { 1218 super(msg, cause); 1219 } 1220 } 1221 1222 /** 1223 * Thrown to indicate an IOException. 1224 */ 1225 public static class IOFileUploadException extends FileUploadException { 1226 1227 /** 1228 * The exceptions UID, for serializing an instance. 1229 */ 1230 private static final long serialVersionUID = 1749796615868477269L; 1231 1232 /** 1233 * The exceptions cause; we overwrite the parent 1234 * classes field, which is available since Java 1235 * 1.4 only. 1236 */ 1237 private final IOException cause; 1238 1239 /** 1240 * Creates a new instance with the given cause. 1241 * 1242 * @param pMsg The detail message. 1243 * @param pException The exceptions cause. 1244 */ 1245 public IOFileUploadException(String pMsg, IOException pException) { 1246 super(pMsg); 1247 cause = pException; 1248 } 1249 1250 /** 1251 * Returns the exceptions cause. 1252 * 1253 * @return The exceptions cause, if any, or null. 1254 */ 1255 @Override 1256 public Throwable getCause() { 1257 return cause; 1258 } 1259 1260 } 1261 1262 /** 1263 * This exception is thrown, if a requests permitted size 1264 * is exceeded. 1265 */ 1266 protected abstract static class SizeException extends FileUploadException { 1267 1268 /** 1269 * Serial version UID, being used, if serialized. 1270 */ 1271 private static final long serialVersionUID = -8776225574705254126L; 1272 1273 /** 1274 * The actual size of the request. 1275 */ 1276 private final long actual; 1277 1278 /** 1279 * The maximum permitted size of the request. 1280 */ 1281 private final long permitted; 1282 1283 /** 1284 * Creates a new instance. 1285 * 1286 * @param message The detail message. 1287 * @param actual The actual number of bytes in the request. 1288 * @param permitted The requests size limit, in bytes. 1289 */ 1290 protected SizeException(String message, long actual, long permitted) { 1291 super(message); 1292 this.actual = actual; 1293 this.permitted = permitted; 1294 } 1295 1296 /** 1297 * Retrieves the actual size of the request. 1298 * 1299 * @return The actual size of the request. 1300 * @since 1.3 1301 */ 1302 public long getActualSize() { 1303 return actual; 1304 } 1305 1306 /** 1307 * Retrieves the permitted size of the request. 1308 * 1309 * @return The permitted size of the request. 1310 * @since 1.3 1311 */ 1312 public long getPermittedSize() { 1313 return permitted; 1314 } 1315 1316 } 1317 1318 /** 1319 * Thrown to indicate that the request size is not specified. In other 1320 * words, it is thrown, if the content-length header is missing or 1321 * contains the value -1. 1322 * 1323 * @deprecated 1.2 As of commons-fileupload 1.2, the presence of a 1324 * content-length header is no longer required. 1325 */ 1326 @Deprecated 1327 public static class UnknownSizeException 1328 extends FileUploadException { 1329 1330 /** 1331 * The exceptions UID, for serializing an instance. 1332 */ 1333 private static final long serialVersionUID = 7062279004812015273L; 1334 1335 /** 1336 * Constructs a <code>UnknownSizeException</code> with no 1337 * detail message. 1338 */ 1339 public UnknownSizeException() { 1340 super(); 1341 } 1342 1343 /** 1344 * Constructs an <code>UnknownSizeException</code> with 1345 * the specified detail message. 1346 * 1347 * @param message The detail message. 1348 */ 1349 public UnknownSizeException(String message) { 1350 super(message); 1351 } 1352 1353 } 1354 1355 /** 1356 * Thrown to indicate that the request size exceeds the configured maximum. 1357 */ 1358 public static class SizeLimitExceededException 1359 extends SizeException { 1360 1361 /** 1362 * The exceptions UID, for serializing an instance. 1363 */ 1364 private static final long serialVersionUID = -2474893167098052828L; 1365 1366 /** 1367 * @deprecated 1.2 Replaced by 1368 * {@link #SizeLimitExceededException(String, long, long)} 1369 */ 1370 @Deprecated 1371 public SizeLimitExceededException() { 1372 this(null, 0, 0); 1373 } 1374 1375 /** 1376 * @deprecated 1.2 Replaced by 1377 * {@link #SizeLimitExceededException(String, long, long)} 1378 * @param message The exceptions detail message. 1379 */ 1380 @Deprecated 1381 public SizeLimitExceededException(String message) { 1382 this(message, 0, 0); 1383 } 1384 1385 /** 1386 * Constructs a <code>SizeExceededException</code> with 1387 * the specified detail message, and actual and permitted sizes. 1388 * 1389 * @param message The detail message. 1390 * @param actual The actual request size. 1391 * @param permitted The maximum permitted request size. 1392 */ 1393 public SizeLimitExceededException(String message, long actual, 1394 long permitted) { 1395 super(message, actual, permitted); 1396 } 1397 1398 } 1399 1400 /** 1401 * Thrown to indicate that A files size exceeds the configured maximum. 1402 */ 1403 public static class FileSizeLimitExceededException 1404 extends SizeException { 1405 1406 /** 1407 * The exceptions UID, for serializing an instance. 1408 */ 1409 private static final long serialVersionUID = 8150776562029630058L; 1410 1411 /** 1412 * File name of the item, which caused the exception. 1413 */ 1414 private String fileName; 1415 1416 /** 1417 * Field name of the item, which caused the exception. 1418 */ 1419 private String fieldName; 1420 1421 /** 1422 * Constructs a <code>SizeExceededException</code> with 1423 * the specified detail message, and actual and permitted sizes. 1424 * 1425 * @param message The detail message. 1426 * @param actual The actual request size. 1427 * @param permitted The maximum permitted request size. 1428 */ 1429 public FileSizeLimitExceededException(String message, long actual, 1430 long permitted) { 1431 super(message, actual, permitted); 1432 } 1433 1434 /** 1435 * Returns the file name of the item, which caused the 1436 * exception. 1437 * 1438 * @return File name, if known, or null. 1439 */ 1440 public String getFileName() { 1441 return fileName; 1442 } 1443 1444 /** 1445 * Sets the file name of the item, which caused the 1446 * exception. 1447 * 1448 * @param pFileName the file name of the item, which caused the exception. 1449 */ 1450 public void setFileName(String pFileName) { 1451 fileName = pFileName; 1452 } 1453 1454 /** 1455 * Returns the field name of the item, which caused the 1456 * exception. 1457 * 1458 * @return Field name, if known, or null. 1459 */ 1460 public String getFieldName() { 1461 return fieldName; 1462 } 1463 1464 /** 1465 * Sets the field name of the item, which caused the 1466 * exception. 1467 * 1468 * @param pFieldName the field name of the item, 1469 * which caused the exception. 1470 */ 1471 public void setFieldName(String pFieldName) { 1472 fieldName = pFieldName; 1473 } 1474 1475 } 1476 1477 /** 1478 * Returns the progress listener. 1479 * 1480 * @return The progress listener, if any, or null. 1481 */ 1482 public ProgressListener getProgressListener() { 1483 return listener; 1484 } 1485 1486 /** 1487 * Sets the progress listener. 1488 * 1489 * @param pListener The progress listener, if any. Defaults to null. 1490 */ 1491 public void setProgressListener(ProgressListener pListener) { 1492 listener = pListener; 1493 } 1494 1495}