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