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 607869 2008-01-01 16:42:17Z 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 try { 349 FileItemIterator iter = getItemIterator(ctx); 350 List items = new ArrayList(); 351 FileItemFactory fac = getFileItemFactory(); 352 if (fac == null) { 353 throw new NullPointerException( 354 "No FileItemFactory has been set."); 355 } 356 while (iter.hasNext()) { 357 FileItemStream item = iter.next(); 358 FileItem fileItem = fac.createItem(item.getFieldName(), 359 item.getContentType(), item.isFormField(), 360 item.getName()); 361 try { 362 Streams.copy(item.openStream(), fileItem.getOutputStream(), 363 true); 364 } catch (FileUploadIOException e) { 365 throw (FileUploadException) e.getCause(); 366 } catch (IOException e) { 367 throw new IOFileUploadException( 368 "Processing of " + MULTIPART_FORM_DATA 369 + " request failed. " + e.getMessage(), e); 370 } 371 if (fileItem instanceof FileItemHeadersSupport) { 372 final FileItemHeaders fih = item.getHeaders(); 373 ((FileItemHeadersSupport) fileItem).setHeaders(fih); 374 } 375 items.add(fileItem); 376 } 377 return items; 378 } catch (FileUploadIOException e) { 379 throw (FileUploadException) e.getCause(); 380 } catch (IOException e) { 381 throw new FileUploadException(e.getMessage(), e); 382 } 383 } 384 385 386 // ------------------------------------------------------ Protected methods 387 388 389 /** 390 * Retrieves the boundary from the <code>Content-type</code> header. 391 * 392 * @param contentType The value of the content type header from which to 393 * extract the boundary value. 394 * 395 * @return The boundary, as a byte array. 396 */ 397 protected byte[] getBoundary(String contentType) { 398 ParameterParser parser = new ParameterParser(); 399 parser.setLowerCaseNames(true); 400 // Parameter parser can handle null input 401 Map params = parser.parse(contentType, new char[] {';', ','}); 402 String boundaryStr = (String) params.get("boundary"); 403 404 if (boundaryStr == null) { 405 return null; 406 } 407 byte[] boundary; 408 try { 409 boundary = boundaryStr.getBytes("ISO-8859-1"); 410 } catch (UnsupportedEncodingException e) { 411 boundary = boundaryStr.getBytes(); 412 } 413 return boundary; 414 } 415 416 417 /** 418 * Retrieves the file name from the <code>Content-disposition</code> 419 * header. 420 * 421 * @param headers A <code>Map</code> containing the HTTP request headers. 422 * 423 * @return The file name for the current <code>encapsulation</code>. 424 * @deprecated Use {@link #getFileName(FileItemHeaders)}. 425 */ 426 protected String getFileName(Map /* String, String */ headers) { 427 return getFileName(getHeader(headers, CONTENT_DISPOSITION)); 428 } 429 430 /** 431 * Retrieves the file name from the <code>Content-disposition</code> 432 * header. 433 * 434 * @param headers The HTTP headers object. 435 * 436 * @return The file name for the current <code>encapsulation</code>. 437 */ 438 protected String getFileName(FileItemHeaders headers) { 439 return getFileName(headers.getHeader(CONTENT_DISPOSITION)); 440 } 441 442 /** 443 * Returns the given content-disposition headers file name. 444 * @param pContentDisposition The content-disposition headers value. 445 * @return The file name 446 */ 447 private String getFileName(String pContentDisposition) { 448 String fileName = null; 449 if (pContentDisposition != null) { 450 String cdl = pContentDisposition.toLowerCase(); 451 if (cdl.startsWith(FORM_DATA) || cdl.startsWith(ATTACHMENT)) { 452 ParameterParser parser = new ParameterParser(); 453 parser.setLowerCaseNames(true); 454 // Parameter parser can handle null input 455 Map params = parser.parse(pContentDisposition, ';'); 456 if (params.containsKey("filename")) { 457 fileName = (String) params.get("filename"); 458 if (fileName != null) { 459 fileName = fileName.trim(); 460 } else { 461 // Even if there is no value, the parameter is present, 462 // so we return an empty file name rather than no file 463 // name. 464 fileName = ""; 465 } 466 } 467 } 468 } 469 return fileName; 470 } 471 472 473 /** 474 * Retrieves the field name from the <code>Content-disposition</code> 475 * header. 476 * 477 * @param headers A <code>Map</code> containing the HTTP request headers. 478 * 479 * @return The field name for the current <code>encapsulation</code>. 480 */ 481 protected String getFieldName(FileItemHeaders headers) { 482 return getFieldName(headers.getHeader(CONTENT_DISPOSITION)); 483 } 484 485 /** 486 * Returns the field name, which is given by the content-disposition 487 * header. 488 * @param pContentDisposition The content-dispositions header value. 489 * @return The field jake 490 */ 491 private String getFieldName(String pContentDisposition) { 492 String fieldName = null; 493 if (pContentDisposition != null 494 && pContentDisposition.toLowerCase().startsWith(FORM_DATA)) { 495 ParameterParser parser = new ParameterParser(); 496 parser.setLowerCaseNames(true); 497 // Parameter parser can handle null input 498 Map params = parser.parse(pContentDisposition, ';'); 499 fieldName = (String) params.get("name"); 500 if (fieldName != null) { 501 fieldName = fieldName.trim(); 502 } 503 } 504 return fieldName; 505 } 506 507 /** 508 * Retrieves the field name from the <code>Content-disposition</code> 509 * header. 510 * 511 * @param headers A <code>Map</code> containing the HTTP request headers. 512 * 513 * @return The field name for the current <code>encapsulation</code>. 514 * @deprecated Use {@link #getFieldName(FileItemHeaders)}. 515 */ 516 protected String getFieldName(Map /* String, String */ headers) { 517 return getFieldName(getHeader(headers, CONTENT_DISPOSITION)); 518 } 519 520 521 /** 522 * Creates a new {@link FileItem} instance. 523 * 524 * @param headers A <code>Map</code> containing the HTTP request 525 * headers. 526 * @param isFormField Whether or not this item is a form field, as 527 * opposed to a file. 528 * 529 * @return A newly created <code>FileItem</code> instance. 530 * 531 * @throws FileUploadException if an error occurs. 532 * @deprecated This method is no longer used in favour of 533 * internally created instances of {@link FileItem}. 534 */ 535 protected FileItem createItem(Map /* String, String */ headers, 536 boolean isFormField) 537 throws FileUploadException { 538 return getFileItemFactory().createItem(getFieldName(headers), 539 getHeader(headers, CONTENT_TYPE), 540 isFormField, 541 getFileName(headers)); 542 } 543 544 /** 545 * <p> Parses the <code>header-part</code> and returns as key/value 546 * pairs. 547 * 548 * <p> If there are multiple headers of the same names, the name 549 * will map to a comma-separated list containing the values. 550 * 551 * @param headerPart The <code>header-part</code> of the current 552 * <code>encapsulation</code>. 553 * 554 * @return A <code>Map</code> containing the parsed HTTP request headers. 555 */ 556 protected FileItemHeaders getParsedHeaders(String headerPart) { 557 final int len = headerPart.length(); 558 FileItemHeadersImpl headers = newFileItemHeaders(); 559 int start = 0; 560 for (;;) { 561 int end = parseEndOfLine(headerPart, start); 562 if (start == end) { 563 break; 564 } 565 String header = headerPart.substring(start, end); 566 start = end + 2; 567 while (start < len) { 568 int nonWs = start; 569 while (nonWs < len) { 570 char c = headerPart.charAt(nonWs); 571 if (c != ' ' && c != '\t') { 572 break; 573 } 574 ++nonWs; 575 } 576 if (nonWs == start) { 577 break; 578 } 579 // Continuation line found 580 end = parseEndOfLine(headerPart, nonWs); 581 header += " " + headerPart.substring(nonWs, end); 582 start = end + 2; 583 } 584 parseHeaderLine(headers, header); 585 } 586 return headers; 587 } 588 589 /** 590 * Creates a new instance of {@link FileItemHeaders}. 591 * @return The new instance. 592 */ 593 protected FileItemHeadersImpl newFileItemHeaders() { 594 return new FileItemHeadersImpl(); 595 } 596 597 /** 598 * <p> Parses the <code>header-part</code> and returns as key/value 599 * pairs. 600 * 601 * <p> If there are multiple headers of the same names, the name 602 * will map to a comma-separated list containing the values. 603 * 604 * @param headerPart The <code>header-part</code> of the current 605 * <code>encapsulation</code>. 606 * 607 * @return A <code>Map</code> containing the parsed HTTP request headers. 608 * @deprecated Use {@link #getParsedHeaders(String)} 609 */ 610 protected Map /* String, String */ parseHeaders(String headerPart) { 611 FileItemHeaders headers = getParsedHeaders(headerPart); 612 Map result = new HashMap(); 613 for (Iterator iter = headers.getHeaderNames(); iter.hasNext();) { 614 String headerName = (String) iter.next(); 615 Iterator iter2 = headers.getHeaders(headerName); 616 String headerValue = (String) iter2.next(); 617 while (iter2.hasNext()) { 618 headerValue += "," + iter2.next(); 619 } 620 result.put(headerName, headerValue); 621 } 622 return result; 623 } 624 625 /** 626 * Skips bytes until the end of the current line. 627 * @param headerPart The headers, which are being parsed. 628 * @param end Index of the last byte, which has yet been 629 * processed. 630 * @return Index of the \r\n sequence, which indicates 631 * end of line. 632 */ 633 private int parseEndOfLine(String headerPart, int end) { 634 int index = end; 635 for (;;) { 636 int offset = headerPart.indexOf('\r', index); 637 if (offset == -1 || offset + 1 >= headerPart.length()) { 638 throw new IllegalStateException( 639 "Expected headers to be terminated by an empty line."); 640 } 641 if (headerPart.charAt(offset + 1) == '\n') { 642 return offset; 643 } 644 index = offset + 1; 645 } 646 } 647 648 /** 649 * Reads the next header line. 650 * @param headers String with all headers. 651 * @param header Map where to store the current header. 652 */ 653 private void parseHeaderLine(FileItemHeadersImpl headers, String header) { 654 final int colonOffset = header.indexOf(':'); 655 if (colonOffset == -1) { 656 // This header line is malformed, skip it. 657 return; 658 } 659 String headerName = header.substring(0, colonOffset).trim(); 660 String headerValue = 661 header.substring(header.indexOf(':') + 1).trim(); 662 headers.addHeader(headerName, headerValue); 663 } 664 665 /** 666 * Returns the header with the specified name from the supplied map. The 667 * header lookup is case-insensitive. 668 * 669 * @param headers A <code>Map</code> containing the HTTP request headers. 670 * @param name The name of the header to return. 671 * 672 * @return The value of specified header, or a comma-separated list if 673 * there were multiple headers of that name. 674 * @deprecated Use {@link FileItemHeaders#getHeader(String)}. 675 */ 676 protected final String getHeader(Map /* String, String */ headers, 677 String name) { 678 return (String) headers.get(name.toLowerCase()); 679 } 680 681 /** 682 * The iterator, which is returned by 683 * {@link FileUploadBase#getItemIterator(RequestContext)}. 684 */ 685 private class FileItemIteratorImpl implements FileItemIterator { 686 /** 687 * Default implementation of {@link FileItemStream}. 688 */ 689 private class FileItemStreamImpl implements FileItemStream { 690 /** The file items content type. 691 */ 692 private final String contentType; 693 /** The file items field name. 694 */ 695 private final String fieldName; 696 /** The file items file name. 697 */ 698 private final String name; 699 /** Whether the file item is a form field. 700 */ 701 private final boolean formField; 702 /** The file items input stream. 703 */ 704 private final InputStream stream; 705 /** Whether the file item was already opened. 706 */ 707 private boolean opened; 708 /** The headers, if any. 709 */ 710 private FileItemHeaders headers; 711 712 /** 713 * Creates a new instance. 714 * @param pName The items file name, or null. 715 * @param pFieldName The items field name. 716 * @param pContentType The items content type, or null. 717 * @param pFormField Whether the item is a form field. 718 * @param pContentLength The items content length, if known, or -1 719 * @throws IOException Creating the file item failed. 720 */ 721 FileItemStreamImpl(String pName, String pFieldName, 722 String pContentType, boolean pFormField, 723 long pContentLength) throws IOException { 724 name = pName; 725 fieldName = pFieldName; 726 contentType = pContentType; 727 formField = pFormField; 728 final ItemInputStream itemStream = multi.newInputStream(); 729 InputStream istream = itemStream; 730 if (fileSizeMax != -1) { 731 if (pContentLength != -1 732 && pContentLength > fileSizeMax) { 733 FileUploadException e = 734 new FileSizeLimitExceededException( 735 "The field " + fieldName 736 + " exceeds its maximum permitted " 737 + " size of " + fileSizeMax 738 + " characters.", 739 pContentLength, fileSizeMax); 740 throw new FileUploadIOException(e); 741 } 742 istream = new LimitedInputStream(istream, fileSizeMax) { 743 protected void raiseError(long pSizeMax, long pCount) 744 throws IOException { 745 itemStream.close(true); 746 FileUploadException e = 747 new FileSizeLimitExceededException( 748 "The field " + fieldName 749 + " exceeds its maximum permitted " 750 + " size of " + pSizeMax 751 + " characters.", 752 pCount, pSizeMax); 753 throw new FileUploadIOException(e); 754 } 755 }; 756 } 757 stream = istream; 758 } 759 760 /** 761 * Returns the items content type, or null. 762 * @return Content type, if known, or null. 763 */ 764 public String getContentType() { 765 return contentType; 766 } 767 768 /** 769 * Returns the items field name. 770 * @return Field name. 771 */ 772 public String getFieldName() { 773 return fieldName; 774 } 775 776 /** 777 * Returns the items file name. 778 * @return File name, if known, or null. 779 */ 780 public String getName() { 781 return name; 782 } 783 784 /** 785 * Returns, whether this is a form field. 786 * @return True, if the item is a form field, 787 * otherwise false. 788 */ 789 public boolean isFormField() { 790 return formField; 791 } 792 793 /** 794 * Returns an input stream, which may be used to 795 * read the items contents. 796 * @return Opened input stream. 797 * @throws IOException An I/O error occurred. 798 */ 799 public InputStream openStream() throws IOException { 800 if (opened) { 801 throw new IllegalStateException( 802 "The stream was already opened."); 803 } 804 if (((Closeable) stream).isClosed()) { 805 throw new FileItemStream.ItemSkippedException(); 806 } 807 return stream; 808 } 809 810 /** 811 * Closes the file item. 812 * @throws IOException An I/O error occurred. 813 */ 814 void close() throws IOException { 815 stream.close(); 816 } 817 818 /** 819 * Returns the file item headers. 820 * @return The items header object 821 */ 822 public FileItemHeaders getHeaders() { 823 return headers; 824 } 825 826 /** 827 * Sets the file item headers. 828 * @param pHeaders The items header object 829 */ 830 public void setHeaders(FileItemHeaders pHeaders) { 831 headers = pHeaders; 832 } 833 } 834 835 /** 836 * The multi part stream to process. 837 */ 838 private final MultipartStream multi; 839 /** 840 * The notifier, which used for triggering the 841 * {@link ProgressListener}. 842 */ 843 private final MultipartStream.ProgressNotifier notifier; 844 /** 845 * The boundary, which separates the various parts. 846 */ 847 private final byte[] boundary; 848 /** 849 * The item, which we currently process. 850 */ 851 private FileItemStreamImpl currentItem; 852 /** 853 * The current items field name. 854 */ 855 private String currentFieldName; 856 /** 857 * Whether we are currently skipping the preamble. 858 */ 859 private boolean skipPreamble; 860 /** 861 * Whether the current item may still be read. 862 */ 863 private boolean itemValid; 864 /** 865 * Whether we have seen the end of the file. 866 */ 867 private boolean eof; 868 869 /** 870 * Creates a new instance. 871 * @param ctx The request context. 872 * @throws FileUploadException An error occurred while 873 * parsing the request. 874 * @throws IOException An I/O error occurred. 875 */ 876 FileItemIteratorImpl(RequestContext ctx) 877 throws FileUploadException, IOException { 878 if (ctx == null) { 879 throw new NullPointerException("ctx parameter"); 880 } 881 882 String contentType = ctx.getContentType(); 883 if ((null == contentType) 884 || (!contentType.toLowerCase().startsWith(MULTIPART))) { 885 throw new InvalidContentTypeException( 886 "the request doesn't contain a " 887 + MULTIPART_FORM_DATA 888 + " or " 889 + MULTIPART_MIXED 890 + " stream, content type header is " 891 + contentType); 892 } 893 894 InputStream input = ctx.getInputStream(); 895 896 if (sizeMax >= 0) { 897 int requestSize = ctx.getContentLength(); 898 if (requestSize == -1) { 899 input = new LimitedInputStream(input, sizeMax) { 900 protected void raiseError(long pSizeMax, long pCount) 901 throws IOException { 902 FileUploadException ex = 903 new SizeLimitExceededException( 904 "the request was rejected because" 905 + " its size (" + pCount 906 + ") exceeds the configured maximum" 907 + " (" + pSizeMax + ")", 908 pCount, pSizeMax); 909 throw new FileUploadIOException(ex); 910 } 911 }; 912 } else { 913 if (sizeMax >= 0 && requestSize > sizeMax) { 914 throw new SizeLimitExceededException( 915 "the request was rejected because its size (" 916 + requestSize 917 + ") exceeds the configured maximum (" 918 + sizeMax + ")", 919 requestSize, sizeMax); 920 } 921 } 922 } 923 924 String charEncoding = headerEncoding; 925 if (charEncoding == null) { 926 charEncoding = ctx.getCharacterEncoding(); 927 } 928 929 boundary = getBoundary(contentType); 930 if (boundary == null) { 931 throw new FileUploadException( 932 "the request was rejected because " 933 + "no multipart boundary was found"); 934 } 935 936 notifier = new MultipartStream.ProgressNotifier(listener, 937 ctx.getContentLength()); 938 multi = new MultipartStream(input, boundary, notifier); 939 multi.setHeaderEncoding(charEncoding); 940 941 skipPreamble = true; 942 findNextItem(); 943 } 944 945 /** 946 * Called for finding the nex item, if any. 947 * @return True, if an next item was found, otherwise false. 948 * @throws IOException An I/O error occurred. 949 */ 950 private boolean findNextItem() throws IOException { 951 if (eof) { 952 return false; 953 } 954 if (currentItem != null) { 955 currentItem.close(); 956 currentItem = null; 957 } 958 for (;;) { 959 boolean nextPart; 960 if (skipPreamble) { 961 nextPart = multi.skipPreamble(); 962 } else { 963 nextPart = multi.readBoundary(); 964 } 965 if (!nextPart) { 966 if (currentFieldName == null) { 967 // Outer multipart terminated -> No more data 968 eof = true; 969 return false; 970 } 971 // Inner multipart terminated -> Return to parsing the outer 972 multi.setBoundary(boundary); 973 currentFieldName = null; 974 continue; 975 } 976 FileItemHeaders headers = getParsedHeaders(multi.readHeaders()); 977 if (currentFieldName == null) { 978 // We're parsing the outer multipart 979 String fieldName = getFieldName(headers); 980 if (fieldName != null) { 981 String subContentType = headers.getHeader(CONTENT_TYPE); 982 if (subContentType != null 983 && subContentType.toLowerCase() 984 .startsWith(MULTIPART_MIXED)) { 985 currentFieldName = fieldName; 986 // Multiple files associated with this field name 987 byte[] subBoundary = getBoundary(subContentType); 988 multi.setBoundary(subBoundary); 989 skipPreamble = true; 990 continue; 991 } 992 String fileName = getFileName(headers); 993 currentItem = new FileItemStreamImpl(fileName, 994 fieldName, headers.getHeader(CONTENT_TYPE), 995 fileName == null, getContentLength(headers)); 996 notifier.noteItem(); 997 itemValid = true; 998 return true; 999 } 1000 } else { 1001 String fileName = getFileName(headers); 1002 if (fileName != null) { 1003 currentItem = new FileItemStreamImpl(fileName, 1004 currentFieldName, 1005 headers.getHeader(CONTENT_TYPE), 1006 false, getContentLength(headers)); 1007 notifier.noteItem(); 1008 itemValid = true; 1009 return true; 1010 } 1011 } 1012 multi.discardBodyData(); 1013 } 1014 } 1015 1016 private long getContentLength(FileItemHeaders pHeaders) { 1017 try { 1018 return Long.parseLong(pHeaders.getHeader(CONTENT_LENGTH)); 1019 } catch (Exception e) { 1020 return -1; 1021 } 1022 } 1023 1024 /** 1025 * Returns, whether another instance of {@link FileItemStream} 1026 * is available. 1027 * @throws FileUploadException Parsing or processing the 1028 * file item failed. 1029 * @throws IOException Reading the file item failed. 1030 * @return True, if one or more additional file items 1031 * are available, otherwise false. 1032 */ 1033 public boolean hasNext() throws FileUploadException, IOException { 1034 if (eof) { 1035 return false; 1036 } 1037 if (itemValid) { 1038 return true; 1039 } 1040 return findNextItem(); 1041 } 1042 1043 /** 1044 * Returns the next available {@link FileItemStream}. 1045 * @throws java.util.NoSuchElementException No more items are 1046 * available. Use {@link #hasNext()} to prevent this exception. 1047 * @throws FileUploadException Parsing or processing the 1048 * file item failed. 1049 * @throws IOException Reading the file item failed. 1050 * @return FileItemStream instance, which provides 1051 * access to the next file item. 1052 */ 1053 public FileItemStream next() throws FileUploadException, IOException { 1054 if (eof || (!itemValid && !hasNext())) { 1055 throw new NoSuchElementException(); 1056 } 1057 itemValid = false; 1058 return currentItem; 1059 } 1060 } 1061 1062 /** 1063 * This exception is thrown for hiding an inner 1064 * {@link FileUploadException} in an {@link IOException}. 1065 */ 1066 public static class FileUploadIOException extends IOException { 1067 /** The exceptions UID, for serializing an instance. 1068 */ 1069 private static final long serialVersionUID = -7047616958165584154L; 1070 /** The exceptions cause; we overwrite the parent 1071 * classes field, which is available since Java 1072 * 1.4 only. 1073 */ 1074 private final FileUploadException cause; 1075 1076 /** 1077 * Creates a <code>FileUploadIOException</code> with the 1078 * given cause. 1079 * @param pCause The exceptions cause, if any, or null. 1080 */ 1081 public FileUploadIOException(FileUploadException pCause) { 1082 // We're not doing super(pCause) cause of 1.3 compatibility. 1083 cause = pCause; 1084 } 1085 1086 /** 1087 * Returns the exceptions cause. 1088 * @return The exceptions cause, if any, or null. 1089 */ 1090 public Throwable getCause() { 1091 return cause; 1092 } 1093 } 1094 1095 /** 1096 * Thrown to indicate that the request is not a multipart request. 1097 */ 1098 public static class InvalidContentTypeException 1099 extends FileUploadException { 1100 /** The exceptions UID, for serializing an instance. 1101 */ 1102 private static final long serialVersionUID = -9073026332015646668L; 1103 1104 /** 1105 * Constructs a <code>InvalidContentTypeException</code> with no 1106 * detail message. 1107 */ 1108 public InvalidContentTypeException() { 1109 // Nothing to do. 1110 } 1111 1112 /** 1113 * Constructs an <code>InvalidContentTypeException</code> with 1114 * the specified detail message. 1115 * 1116 * @param message The detail message. 1117 */ 1118 public InvalidContentTypeException(String message) { 1119 super(message); 1120 } 1121 } 1122 1123 /** 1124 * Thrown to indicate an IOException. 1125 */ 1126 public static class IOFileUploadException extends FileUploadException { 1127 /** The exceptions UID, for serializing an instance. 1128 */ 1129 private static final long serialVersionUID = 1749796615868477269L; 1130 /** The exceptions cause; we overwrite the parent 1131 * classes field, which is available since Java 1132 * 1.4 only. 1133 */ 1134 private final IOException cause; 1135 1136 /** 1137 * Creates a new instance with the given cause. 1138 * @param pMsg The detail message. 1139 * @param pException The exceptions cause. 1140 */ 1141 public IOFileUploadException(String pMsg, IOException pException) { 1142 super(pMsg); 1143 cause = pException; 1144 } 1145 1146 /** 1147 * Returns the exceptions cause. 1148 * @return The exceptions cause, if any, or null. 1149 */ 1150 public Throwable getCause() { 1151 return cause; 1152 } 1153 } 1154 1155 /** This exception is thrown, if a requests permitted size 1156 * is exceeded. 1157 */ 1158 protected abstract static class SizeException extends FileUploadException { 1159 /** 1160 * The actual size of the request. 1161 */ 1162 private final long actual; 1163 1164 /** 1165 * The maximum permitted size of the request. 1166 */ 1167 private final long permitted; 1168 1169 /** 1170 * Creates a new instance. 1171 * @param message The detail message. 1172 * @param actual The actual number of bytes in the request. 1173 * @param permitted The requests size limit, in bytes. 1174 */ 1175 protected SizeException(String message, long actual, long permitted) { 1176 super(message); 1177 this.actual = actual; 1178 this.permitted = permitted; 1179 } 1180 1181 /** 1182 * Retrieves the actual size of the request. 1183 * 1184 * @return The actual size of the request. 1185 */ 1186 public long getActualSize() { 1187 return actual; 1188 } 1189 1190 /** 1191 * Retrieves the permitted size of the request. 1192 * 1193 * @return The permitted size of the request. 1194 */ 1195 public long getPermittedSize() { 1196 return permitted; 1197 } 1198 } 1199 1200 /** 1201 * Thrown to indicate that the request size is not specified. In other 1202 * words, it is thrown, if the content-length header is missing or 1203 * contains the value -1. 1204 * @deprecated As of commons-fileupload 1.2, the presence of a 1205 * content-length header is no longer required. 1206 */ 1207 public static class UnknownSizeException 1208 extends FileUploadException { 1209 /** The exceptions UID, for serializing an instance. 1210 */ 1211 private static final long serialVersionUID = 7062279004812015273L; 1212 1213 /** 1214 * Constructs a <code>UnknownSizeException</code> with no 1215 * detail message. 1216 */ 1217 public UnknownSizeException() { 1218 super(); 1219 } 1220 1221 /** 1222 * Constructs an <code>UnknownSizeException</code> with 1223 * the specified detail message. 1224 * 1225 * @param message The detail message. 1226 */ 1227 public UnknownSizeException(String message) { 1228 super(message); 1229 } 1230 } 1231 1232 /** 1233 * Thrown to indicate that the request size exceeds the configured maximum. 1234 */ 1235 public static class SizeLimitExceededException 1236 extends SizeException { 1237 /** The exceptions UID, for serializing an instance. 1238 */ 1239 private static final long serialVersionUID = -2474893167098052828L; 1240 1241 /** 1242 * @deprecated Replaced by 1243 * {@link #SizeLimitExceededException(String, long, long)} 1244 */ 1245 public SizeLimitExceededException() { 1246 this(null, 0, 0); 1247 } 1248 1249 /** 1250 * @deprecated Replaced by 1251 * {@link #SizeLimitExceededException(String, long, long)} 1252 * @param message The exceptions detail message. 1253 */ 1254 public SizeLimitExceededException(String message) { 1255 this(message, 0, 0); 1256 } 1257 1258 /** 1259 * Constructs a <code>SizeExceededException</code> with 1260 * the specified detail message, and actual and permitted sizes. 1261 * 1262 * @param message The detail message. 1263 * @param actual The actual request size. 1264 * @param permitted The maximum permitted request size. 1265 */ 1266 public SizeLimitExceededException(String message, long actual, 1267 long permitted) { 1268 super(message, actual, permitted); 1269 } 1270 } 1271 1272 /** 1273 * Thrown to indicate that A files size exceeds the configured maximum. 1274 */ 1275 public static class FileSizeLimitExceededException 1276 extends SizeException { 1277 /** The exceptions UID, for serializing an instance. 1278 */ 1279 private static final long serialVersionUID = 8150776562029630058L; 1280 1281 /** 1282 * Constructs a <code>SizeExceededException</code> with 1283 * the specified detail message, and actual and permitted sizes. 1284 * 1285 * @param message The detail message. 1286 * @param actual The actual request size. 1287 * @param permitted The maximum permitted request size. 1288 */ 1289 public FileSizeLimitExceededException(String message, long actual, 1290 long permitted) { 1291 super(message, actual, permitted); 1292 } 1293 } 1294 1295 /** 1296 * Returns the progress listener. 1297 * @return The progress listener, if any, or null. 1298 */ 1299 public ProgressListener getProgressListener() { 1300 return listener; 1301 } 1302 1303 /** 1304 * Sets the progress listener. 1305 * @param pListener The progress listener, if any. Defaults to null. 1306 */ 1307 public void setProgressListener(ProgressListener pListener) { 1308 listener = pListener; 1309 } 1310 }