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