1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one or more 3 * contributor license agreements. See the NOTICE file distributed with 4 * this work for additional information regarding copyright ownership. 5 * The ASF licenses this file to You under the Apache License, Version 2.0 6 * (the "License"); you may not use this file except in compliance with 7 * the License. You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 package org.apache.commons.fileupload; 18 19 import java.io.ByteArrayOutputStream; 20 import java.io.IOException; 21 import java.io.InputStream; 22 import java.io.OutputStream; 23 import java.io.UnsupportedEncodingException; 24 25 import org.apache.commons.fileupload.FileUploadBase.FileUploadIOException; 26 import org.apache.commons.fileupload.FileUploadBase.SizeLimitExceededException; 27 import org.apache.commons.fileupload.util.Closeable; 28 import org.apache.commons.fileupload.util.Streams; 29 30 /** 31 * Low level API for processing file uploads. 32 * 33 * <p> 34 * This class can be used to process data streams conforming to MIME 'multipart' format as defined in <a href="http://www.ietf.org/rfc/rfc1867.txt">RFC 35 * 1867</a>. Arbitrarily large amounts of data in the stream can be processed under constant memory usage. 36 * </p> 37 * 38 * <p> 39 * The format of the stream is defined in the following way: 40 * </p> 41 * 42 * <pre>{@code 43 * multipart-body := preamble 1*encapsulation close-delimiter epilogue 44 * encapsulation := delimiter body CRLF 45 * delimiter := "--" boundary CRLF 46 * close-delimiter := "--" boundary "--" 47 * preamble := <ignore> 48 * epilogue := <ignore> 49 * body := header-part CRLF body-part 50 * header-part := 1*header CRLF 51 * header := header-name ":" header-value 52 * header-name := <printable ASCII characters except ":"> 53 * header-value := <any ASCII characters except CR & LF> 54 * body-data := <arbitrary data> 55 * }</pre> 56 * 57 * <p> 58 * Note that body-data can contain another mulipart entity. There is limited support for single pass processing of such nested streams. The nested stream is 59 * <strong>required</strong> to have a boundary token of the same length as the parent stream (see {@link #setBoundary(byte[])}). 60 * </p> 61 * 62 * <p> 63 * Here is an example of usage of this class. 64 * </p> 65 * 66 * <pre>{@code 67 * try { 68 * MultipartStream multipartStream = new MultipartStream(input, boundary); 69 * boolean nextPart = multipartStream.skipPreamble(); 70 * OutputStream output; 71 * while (nextPart) { 72 * String header = multipartStream.readHeaders(); 73 * // process headers 74 * // create some output stream 75 * multipartStream.readBodyData(output); 76 * nextPart = multipartStream.readBoundary(); 77 * } 78 * } catch (MultipartStream.MalformedStreamException e) { 79 * // the stream failed to follow required syntax 80 * } catch (IOException e) { 81 * // a read or write error occurred 82 * } 83 * }</pre> 84 */ 85 public class MultipartStream { 86 87 /** 88 * Thrown upon attempt of setting an invalid boundary token. 89 */ 90 public static class IllegalBoundaryException extends IOException { 91 92 /** 93 * The UID to use when serializing this instance. 94 */ 95 private static final long serialVersionUID = -161533165102632918L; 96 97 /** 98 * Constructs an {@code IllegalBoundaryException} with no 99 * detail message. 100 */ 101 public IllegalBoundaryException() { 102 } 103 104 /** 105 * Constructs an {@code IllegalBoundaryException} with 106 * the specified detail message. 107 * 108 * @param message The detail message. 109 */ 110 public IllegalBoundaryException(final String message) { 111 super(message); 112 } 113 114 } 115 116 /** 117 * An {@link InputStream} for reading an items contents. 118 */ 119 public class ItemInputStream extends InputStream implements Closeable { 120 121 /** 122 * Offset when converting negative bytes to integers. 123 */ 124 private static final int BYTE_POSITIVE_OFFSET = 256; 125 126 /** 127 * The number of bytes, which have been read so far. 128 */ 129 private long total; 130 131 /** 132 * The number of bytes, which must be hold, because 133 * they might be a part of the boundary. 134 */ 135 private int pad; 136 137 /** 138 * The current offset in the buffer. 139 */ 140 private int pos; 141 142 /** 143 * Whether the stream is already closed. 144 */ 145 private boolean closed; 146 147 /** 148 * Creates a new instance. 149 */ 150 ItemInputStream() { 151 findSeparator(); 152 } 153 154 /** 155 * Returns the number of bytes, which are currently 156 * available, without blocking. 157 * 158 * @throws IOException An I/O error occurs. 159 * @return Number of bytes in the buffer. 160 */ 161 @Override 162 public int available() throws IOException { 163 if (pos == -1) { 164 return tail - head - pad; 165 } 166 return pos - head; 167 } 168 169 /** 170 * Closes the input stream. 171 * 172 * @throws IOException An I/O error occurred. 173 */ 174 @Override 175 public void close() throws IOException { 176 close(false); 177 } 178 179 /** 180 * Closes the input stream. 181 * 182 * @param closeUnderlying Whether to close the underlying stream (hard close) 183 * @throws IOException An I/O error occurred. 184 */ 185 public void close(final boolean closeUnderlying) throws IOException { 186 if (closed) { 187 return; 188 } 189 if (closeUnderlying) { 190 closed = true; 191 input.close(); 192 } else { 193 for (;;) { 194 int available = available(); 195 if (available == 0) { 196 available = makeAvailable(); 197 if (available == 0) { 198 break; 199 } 200 } 201 if (skip(available) != available) { 202 // TODO log or throw? 203 } 204 } 205 } 206 closed = true; 207 } 208 209 /** 210 * Called for finding the separator. 211 */ 212 private void findSeparator() { 213 pos = MultipartStream.this.findSeparator(); 214 if (pos == -1) { 215 if (tail - head > keepRegion) { 216 pad = keepRegion; 217 } else { 218 pad = tail - head; 219 } 220 } 221 } 222 223 /** 224 * Returns the number of bytes, which have been read 225 * by the stream. 226 * 227 * @return Number of bytes, which have been read so far. 228 */ 229 public long getBytesRead() { 230 return total; 231 } 232 233 /** 234 * Returns, whether the stream is closed. 235 * 236 * @return True, if the stream is closed, otherwise false. 237 */ 238 @Override 239 public boolean isClosed() { 240 return closed; 241 } 242 243 /** 244 * Attempts to read more data. 245 * 246 * @return Number of available bytes 247 * @throws IOException An I/O error occurred. 248 */ 249 private int makeAvailable() throws IOException { 250 if (pos != -1) { 251 return 0; 252 } 253 254 // Move the data to the beginning of the buffer. 255 total += tail - head - pad; 256 System.arraycopy(buffer, tail - pad, buffer, 0, pad); 257 258 // Refill buffer with new data. 259 head = 0; 260 tail = pad; 261 262 for (;;) { 263 final int bytesRead = input.read(buffer, tail, bufSize - tail); 264 if (bytesRead == -1) { 265 // The last pad amount is left in the buffer. 266 // Boundary can't be in there so signal an error 267 // condition. 268 final String msg = "Stream ended unexpectedly"; 269 throw new MalformedStreamException(msg); 270 } 271 if (notifier != null) { 272 notifier.noteBytesRead(bytesRead); 273 } 274 tail += bytesRead; 275 276 findSeparator(); 277 final int av = available(); 278 279 if (av > 0 || pos != -1) { 280 return av; 281 } 282 } 283 } 284 285 /** 286 * Returns the next byte in the stream. 287 * 288 * @return The next byte in the stream, as a non-negative 289 * integer, or -1 for EOF. 290 * @throws IOException An I/O error occurred. 291 */ 292 @Override 293 public int read() throws IOException { 294 if (closed) { 295 throw new FileItemStream.ItemSkippedException(); 296 } 297 if (available() == 0 && makeAvailable() == 0) { 298 return -1; 299 } 300 ++total; 301 final int b = buffer[head++]; 302 if (b >= 0) { 303 return b; 304 } 305 return b + BYTE_POSITIVE_OFFSET; 306 } 307 308 /** 309 * Reads bytes into the given buffer. 310 * 311 * @param b The destination buffer, where to write to. 312 * @param off Offset of the first byte in the buffer. 313 * @param len Maximum number of bytes to read. 314 * @return Number of bytes, which have been actually read, 315 * or -1 for EOF. 316 * @throws IOException An I/O error occurred. 317 */ 318 @Override 319 public int read(final byte[] b, final int off, final int len) throws IOException { 320 if (closed) { 321 throw new FileItemStream.ItemSkippedException(); 322 } 323 if (len == 0) { 324 return 0; 325 } 326 int res = available(); 327 if (res == 0) { 328 res = makeAvailable(); 329 if (res == 0) { 330 return -1; 331 } 332 } 333 res = Math.min(res, len); 334 System.arraycopy(buffer, head, b, off, res); 335 head += res; 336 total += res; 337 return res; 338 } 339 340 /** 341 * Skips the given number of bytes. 342 * 343 * @param bytes Number of bytes to skip. 344 * @return The number of bytes, which have actually been 345 * skipped. 346 * @throws IOException An I/O error occurred. 347 */ 348 @Override 349 public long skip(final long bytes) throws IOException { 350 if (closed) { 351 throw new FileItemStream.ItemSkippedException(); 352 } 353 int av = available(); 354 if (av == 0) { 355 av = makeAvailable(); 356 if (av == 0) { 357 return 0; 358 } 359 } 360 final long res = Math.min(av, bytes); 361 head += res; 362 return res; 363 } 364 365 } 366 367 /** 368 * Thrown to indicate that the input stream fails to follow the 369 * required syntax. 370 */ 371 public static class MalformedStreamException extends IOException { 372 373 /** 374 * The UID to use when serializing this instance. 375 */ 376 private static final long serialVersionUID = 6466926458059796677L; 377 378 /** 379 * Constructs a {@code MalformedStreamException} with no 380 * detail message. 381 */ 382 public MalformedStreamException() { 383 } 384 385 /** 386 * Constructs an {@code MalformedStreamException} with 387 * the specified detail message. 388 * 389 * @param message The detail message. 390 */ 391 public MalformedStreamException(final String message) { 392 super(message); 393 } 394 395 } 396 397 /** 398 * Internal class, which is used to invoke the 399 * {@link ProgressListener}. 400 */ 401 public static class ProgressNotifier { 402 403 /** 404 * The listener to invoke. 405 */ 406 private final ProgressListener listener; 407 408 /** 409 * Number of expected bytes, if known, or -1. 410 */ 411 private final long contentLength; 412 413 /** 414 * Number of bytes, which have been read so far. 415 */ 416 private long bytesRead; 417 418 /** 419 * Number of items, which have been read so far. 420 */ 421 private int items; 422 423 /** 424 * Creates a new instance with the given listener 425 * and content length. 426 * 427 * @param listener The listener to invoke. 428 * @param contentLength The expected content length. 429 */ 430 ProgressNotifier(final ProgressListener listener, final long contentLength) { 431 this.listener = listener; 432 this.contentLength = contentLength; 433 } 434 435 /** 436 * Called to indicate that bytes have been read. 437 * 438 * @param count Number of bytes, which have been read. 439 */ 440 void noteBytesRead(final int count) { 441 /* Indicates, that the given number of bytes have been read from 442 * the input stream. 443 */ 444 bytesRead += count; 445 notifyListener(); 446 } 447 448 /** 449 * Called to indicate, that a new file item has been detected. 450 */ 451 void noteItem() { 452 ++items; 453 notifyListener(); 454 } 455 456 /** 457 * Called for notifying the listener. 458 */ 459 private void notifyListener() { 460 if (listener != null) { 461 listener.update(bytesRead, contentLength, items); 462 } 463 } 464 465 } 466 467 /** 468 * The Carriage Return ASCII character value. 469 */ 470 public static final byte CR = 0x0D; 471 472 /** 473 * The Line Feed ASCII character value. 474 */ 475 public static final byte LF = 0x0A; 476 477 /** 478 * The dash (-) ASCII character value. 479 */ 480 public static final byte DASH = 0x2D; 481 482 /** 483 * The maximum length of {@code header-part} that will be 484 * processed (10 kilobytes = 10240 bytes.). 485 * 486 * @deprecated Unused. Replaced by {@link #getPartHeaderSizeMax()}. 487 */ 488 @Deprecated 489 public static final int HEADER_PART_SIZE_MAX = 10240; 490 491 /** 492 * The default length of the buffer used for processing a request. 493 */ 494 protected static final int DEFAULT_BUFSIZE = 4096; 495 496 /** 497 * A byte sequence that marks the end of {@code header-part} 498 * ({@code CRLFCRLF}). 499 */ 500 protected static final byte[] HEADER_SEPARATOR = {CR, LF, CR, LF}; 501 502 /** 503 * A byte sequence that follows a delimiter that will be 504 * followed by an encapsulation ({@code CRLF}). 505 */ 506 protected static final byte[] FIELD_SEPARATOR = {CR, LF}; 507 508 /** 509 * A byte sequence that follows a delimiter of the last 510 * encapsulation in the stream ({@code --}). 511 */ 512 protected static final byte[] STREAM_TERMINATOR = {DASH, DASH}; 513 514 /** 515 * A byte sequence that precedes a boundary ({@code CRLF--}). 516 */ 517 protected static final byte[] BOUNDARY_PREFIX = {CR, LF, DASH, DASH}; 518 519 /** 520 * Compares {@code count} first bytes in the arrays 521 * {@code a} and {@code b}. 522 * 523 * @param a The first array to compare. 524 * @param b The second array to compare. 525 * @param count How many bytes should be compared. 526 * @return {@code true} if {@code count} first bytes in arrays 527 * {@code a} and {@code b} are equal. 528 */ 529 public static boolean arrayequals(final byte[] a, 530 final byte[] b, 531 final int count) { 532 for (int i = 0; i < count; i++) { 533 if (a[i] != b[i]) { 534 return false; 535 } 536 } 537 return true; 538 } 539 540 /** 541 * The input stream from which data is read. 542 */ 543 private final InputStream input; 544 545 /** 546 * The length of the boundary token plus the leading {@code CRLF--}. 547 */ 548 private int boundaryLength; 549 550 /** 551 * The amount of data, in bytes, that must be kept in the buffer in order 552 * to detect delimiters reliably. 553 */ 554 private final int keepRegion; 555 556 /** 557 * The byte sequence that partitions the stream. 558 */ 559 private final byte[] boundary; 560 561 /** 562 * The table for Knuth-Morris-Pratt search algorithm. 563 */ 564 private final int[] boundaryTable; 565 566 /** 567 * The length of the buffer used for processing the request. 568 */ 569 private final int bufSize; 570 571 /** 572 * The buffer used for processing the request. 573 */ 574 private final byte[] buffer; 575 576 /** 577 * The index of first valid character in the buffer. 578 * 0 <= head < bufSize 579 */ 580 private int head; 581 582 /** 583 * The index of last valid character in the buffer + 1. 584 * 0 <= tail <= bufSize 585 */ 586 private int tail; 587 588 /** 589 * The content encoding to use when reading headers. 590 */ 591 private String headerEncoding; 592 593 /** 594 * The progress notifier, if any, or null. 595 */ 596 private final ProgressNotifier notifier; 597 598 /** 599 * The maximum permitted size of the headers provided with a single part in bytes. 600 */ 601 private int partHeaderSizeMax = FileUploadBase.DEFAULT_PART_HEADER_SIZE_MAX; 602 603 /** 604 * Creates a new instance. 605 * 606 * @deprecated 1.2.1 Use {@link #MultipartStream(InputStream, byte[], int, 607 * ProgressNotifier)} 608 */ 609 @Deprecated 610 public MultipartStream() { 611 this(null, null, null); 612 } 613 614 /** 615 * Constructs a {@code MultipartStream} with a default size buffer. 616 * 617 * @param input The {@code InputStream} to serve as a data source. 618 * @param boundary The token used for dividing the stream into 619 * {@code encapsulations}. 620 * 621 * @deprecated 1.2.1 Use {@link #MultipartStream(InputStream, byte[], int, 622 * ProgressNotifier)}. 623 */ 624 @Deprecated 625 public MultipartStream(final InputStream input, 626 final byte[] boundary) { 627 this(input, boundary, DEFAULT_BUFSIZE, null); 628 } 629 630 /** 631 * Constructs a {@code MultipartStream} with a custom size buffer and no progress notifier. 632 * 633 * <p> 634 * Note that the buffer must be at least big enough to contain the boundary string, plus 4 characters for CR/LF and double dash, plus at least one byte of 635 * data. Too small a buffer size setting will degrade performance. 636 * </p> 637 * 638 * @param input The {@code InputStream} to serve as a data source. 639 * @param boundary The token used for dividing the stream into {@code encapsulations}. 640 * @param bufSize The size of the buffer to be used, in bytes. 641 * @deprecated 1.2.1 Use {@link #MultipartStream(InputStream, byte[], int, ProgressNotifier)}. 642 */ 643 @Deprecated 644 public MultipartStream(final InputStream input, final byte[] boundary, final int bufSize) { 645 this(input, boundary, bufSize, null); 646 } 647 648 /** 649 * Constructs a {@code MultipartStream} with a custom size buffer. 650 * 651 * <p> 652 * Note that the buffer must be at least big enough to contain the boundary string, plus 4 characters for CR/LF and double dash, plus at least one byte of 653 * data. Too small a buffer size setting will degrade performance. 654 * </p> 655 * 656 * @param input The {@code InputStream} to serve as a data source. 657 * @param boundary The token used for dividing the stream into {@code encapsulations}. 658 * @param bufSize The size of the buffer to be used, in bytes. 659 * @param notifier The notifier, which is used for calling the progress listener, if any. 660 * 661 * @throws IllegalArgumentException If the buffer size is too small 662 * @since 1.3.1 663 */ 664 public MultipartStream(final InputStream input, final byte[] boundary, final int bufSize, final ProgressNotifier notifier) { 665 if (boundary == null) { 666 throw new IllegalArgumentException("boundary may not be null"); 667 } 668 // We prepend CR/LF to the boundary to chop trailing CR/LF from 669 // body-data tokens. 670 this.boundaryLength = boundary.length + BOUNDARY_PREFIX.length; 671 if (bufSize < this.boundaryLength + 1) { 672 throw new IllegalArgumentException("The buffer size specified for the MultipartStream is too small"); 673 } 674 this.input = input; 675 this.bufSize = Math.max(bufSize, boundaryLength * 2); 676 this.buffer = new byte[this.bufSize]; 677 this.notifier = notifier; 678 this.boundary = new byte[this.boundaryLength]; 679 this.boundaryTable = new int[this.boundaryLength + 1]; 680 this.keepRegion = this.boundary.length; 681 System.arraycopy(BOUNDARY_PREFIX, 0, this.boundary, 0, BOUNDARY_PREFIX.length); 682 System.arraycopy(boundary, 0, this.boundary, BOUNDARY_PREFIX.length, boundary.length); 683 computeBoundaryTable(); 684 head = 0; 685 tail = 0; 686 } 687 688 /** 689 * Constructs a {@code MultipartStream} with a default size buffer. 690 * 691 * @param input The {@code InputStream} to serve as a data source. 692 * @param boundary The token used for dividing the stream into 693 * {@code encapsulations}. 694 * @param notifier An object for calling the progress listener, if any. 695 * 696 * 697 * @see #MultipartStream(InputStream, byte[], int, ProgressNotifier) 698 */ 699 MultipartStream(final InputStream input, final byte[] boundary, final ProgressNotifier notifier) { 700 this(input, boundary, DEFAULT_BUFSIZE, notifier); 701 } 702 703 /** 704 * Compute the table used for Knuth-Morris-Pratt search algorithm. 705 */ 706 private void computeBoundaryTable() { 707 int position = 2; 708 int candidate = 0; 709 710 boundaryTable[0] = -1; 711 boundaryTable[1] = 0; 712 713 while (position <= boundaryLength) { 714 if (boundary[position - 1] == boundary[candidate]) { 715 boundaryTable[position] = candidate + 1; 716 candidate++; 717 position++; 718 } else if (candidate > 0) { 719 candidate = boundaryTable[candidate]; 720 } else { 721 boundaryTable[position] = 0; 722 position++; 723 } 724 } 725 } 726 727 /** 728 * Reads {@code body-data} from the current {@code encapsulation} and discards it. 729 * 730 * <p> 731 * Use this method to skip encapsulations you don't need or don't understand. 732 * </p> 733 * 734 * @return The amount of data discarded. 735 * @throws MalformedStreamException if the stream ends unexpectedly. 736 * @throws IOException if an i/o error occurs. 737 */ 738 public int discardBodyData() throws MalformedStreamException, IOException { 739 return readBodyData(null); 740 } 741 742 /** 743 * Searches for a byte of specified value in the {@code buffer}, 744 * starting at the specified {@code position}. 745 * 746 * @param value The value to find. 747 * @param pos The starting position for searching. 748 * @return The position of byte found, counting from beginning of the 749 * {@code buffer}, or {@code -1} if not found. 750 */ 751 protected int findByte(final byte value, 752 final int pos) { 753 for (int i = pos; i < tail; i++) { 754 if (buffer[i] == value) { 755 return i; 756 } 757 } 758 759 return -1; 760 } 761 762 /** 763 * Searches for the {@code boundary} in the {@code buffer} 764 * region delimited by {@code head} and {@code tail}. 765 * 766 * @return The position of the boundary found, counting from the 767 * beginning of the {@code buffer}, or {@code -1} if 768 * not found. 769 */ 770 protected int findSeparator() { 771 772 int bufferPos = head; 773 int tablePos = 0; 774 775 while (bufferPos < tail) { 776 while (tablePos >= 0 && buffer[bufferPos] != boundary[tablePos]) { 777 tablePos = boundaryTable[tablePos]; 778 } 779 bufferPos++; 780 tablePos++; 781 if (tablePos == boundaryLength) { 782 return bufferPos - boundaryLength; 783 } 784 } 785 return -1; 786 } 787 788 /** 789 * Gets the character encoding used when reading the headers of an 790 * individual part. When not specified, or {@code null}, the platform 791 * default encoding is used. 792 * 793 * @return The encoding used to read part headers. 794 */ 795 public String getHeaderEncoding() { 796 return headerEncoding; 797 } 798 799 /** 800 * Obtain the per part size limit for headers. 801 * 802 * @return The maximum size of the headers for a single part in bytes. 803 * 804 * @since 1.6.0 805 */ 806 public int getPartHeaderSizeMax() { 807 return partHeaderSizeMax; 808 } 809 810 /** 811 * Creates a new {@link ItemInputStream}. 812 * @return A new instance of {@link ItemInputStream}. 813 */ 814 ItemInputStream newInputStream() { 815 return new ItemInputStream(); 816 } 817 818 /** 819 * Reads {@code body-data} from the current {@code encapsulation} and writes its contents into the output {@code Stream}. 820 * 821 * <p> 822 * Arbitrary large amounts of data can be processed by this method using a constant size buffer. (see 823 * {@link #MultipartStream(InputStream,byte[],int, MultipartStream.ProgressNotifier) constructor}). 824 * </p> 825 * 826 * @param output The {@code Stream} to write data into. May be null, in which case this method is equivalent to {@link #discardBodyData()}. 827 * 828 * @return the amount of data written. 829 * @throws MalformedStreamException if the stream ends unexpectedly. 830 * @throws IOException if an i/o error occurs. 831 */ 832 public int readBodyData(final OutputStream output) 833 throws MalformedStreamException, IOException { 834 return (int) Streams.copy(newInputStream(), output, false); // Streams.copy closes the input stream 835 } 836 837 /** 838 * Skips a {@code boundary} token, and checks whether more 839 * {@code encapsulations} are contained in the stream. 840 * 841 * @return {@code true} if there are more encapsulations in 842 * this stream; {@code false} otherwise. 843 * 844 * @throws FileUploadIOException if the bytes read from the stream exceeded the size limits 845 * @throws MalformedStreamException if the stream ends unexpectedly or 846 * fails to follow required syntax. 847 */ 848 public boolean readBoundary() 849 throws FileUploadIOException, MalformedStreamException { 850 final byte[] marker = new byte[2]; 851 final boolean nextChunk; 852 853 head += boundaryLength; 854 try { 855 marker[0] = readByte(); 856 if (marker[0] == LF) { 857 // Work around IE5 Mac bug with input type=image. 858 // Because the boundary delimiter, not including the trailing 859 // CRLF, must not appear within any file (RFC 2046, section 860 // 5.1.1), we know the missing CR is due to a buggy browser 861 // rather than a file containing something similar to a 862 // boundary. 863 return true; 864 } 865 866 marker[1] = readByte(); 867 if (arrayequals(marker, STREAM_TERMINATOR, 2)) { 868 nextChunk = false; 869 } else if (arrayequals(marker, FIELD_SEPARATOR, 2)) { 870 nextChunk = true; 871 } else { 872 throw new MalformedStreamException( 873 "Unexpected characters follow a boundary"); 874 } 875 } catch (final FileUploadIOException e) { 876 // wraps a SizeException, re-throw as it will be unwrapped later 877 throw e; 878 } catch (final IOException e) { 879 throw new MalformedStreamException("Stream ended unexpectedly"); 880 } 881 return nextChunk; 882 } 883 884 /** 885 * Reads a byte from the {@code buffer}, and refills it as 886 * necessary. 887 * 888 * @return The next byte from the input stream. 889 * @throws IOException if there is no more data available. 890 */ 891 public byte readByte() throws IOException { 892 // Buffer depleted ? 893 if (head == tail) { 894 head = 0; 895 // Refill. 896 tail = input.read(buffer, head, bufSize); 897 if (tail == -1) { 898 // No more data available. 899 throw new IOException("No more data is available"); 900 } 901 if (notifier != null) { 902 notifier.noteBytesRead(tail); 903 } 904 } 905 return buffer[head++]; 906 } 907 908 /** 909 * Reads the {@code header-part} of the current {@code encapsulation}. 910 * <p> 911 * Headers are returned verbatim to the input stream, including the trailing {@code CRLF} marker. Parsing is left to the application. 912 * </p> 913 * 914 * @return The {@code header-part} of the current encapsulation. 915 * @throws FileUploadIOException if the bytes read from the stream exceeded the size limits. 916 * @throws MalformedStreamException if the stream ends unexpectedly. 917 */ 918 public String readHeaders() throws FileUploadIOException, MalformedStreamException { 919 int i = 0; 920 byte b; 921 // to support multi-byte characters 922 final ByteArrayOutputStream baos = new ByteArrayOutputStream(); 923 int size = 0; 924 while (i < HEADER_SEPARATOR.length) { 925 try { 926 b = readByte(); 927 } catch (final FileUploadIOException e) { 928 // wraps a SizeException, re-throw as it will be unwrapped later 929 throw e; 930 } catch (final IOException e) { 931 throw new MalformedStreamException("Stream ended unexpectedly"); 932 } 933 size++; 934 if (getPartHeaderSizeMax() != -1 && size > getPartHeaderSizeMax()) { 935 throw new FileUploadIOException(new SizeLimitExceededException( 936 String.format("Header section has more than %s bytes (maybe it is not properly terminated)", Integer.valueOf(getPartHeaderSizeMax())), 937 size, getPartHeaderSizeMax())); 938 } 939 if (b == HEADER_SEPARATOR[i]) { 940 i++; 941 } else { 942 i = 0; 943 } 944 baos.write(b); 945 } 946 String headers; 947 if (headerEncoding != null) { 948 try { 949 headers = baos.toString(headerEncoding); 950 } catch (final UnsupportedEncodingException e) { 951 // Fall back to platform default if specified encoding is not 952 // supported. 953 headers = baos.toString(); 954 } 955 } else { 956 headers = baos.toString(); 957 } 958 return headers; 959 } 960 961 /** 962 * Changes the boundary token used for partitioning the stream. 963 * 964 * <p> 965 * This method allows single pass processing of nested multipart streams. 966 * </p> 967 * <p> 968 * The boundary token of the nested stream is {@code required} to be of the same length as the boundary token in parent stream. 969 * </p> 970 * 971 * <p> 972 * Restoring the parent stream boundary token after processing of a nested stream is left to the application. 973 * </p> 974 * 975 * @param boundary The boundary to be used for parsing of the nested stream. 976 * 977 * @throws IllegalBoundaryException if the {@code boundary} has a different length than the one being currently parsed. 978 */ 979 public void setBoundary(final byte[] boundary) throws IllegalBoundaryException { 980 if (boundary.length != boundaryLength - BOUNDARY_PREFIX.length) { 981 throw new IllegalBoundaryException("The length of a boundary token cannot be changed"); 982 } 983 System.arraycopy(boundary, 0, this.boundary, BOUNDARY_PREFIX.length, boundary.length); 984 computeBoundaryTable(); 985 } 986 987 /** 988 * Specifies the character encoding to be used when reading the headers of 989 * individual parts. When not specified, or {@code null}, the platform 990 * default encoding is used. 991 * 992 * @param encoding The encoding used to read part headers. 993 */ 994 public void setHeaderEncoding(final String encoding) { 995 headerEncoding = encoding; 996 } 997 998 /** 999 * Sets the per part size limit for headers. 1000 * 1001 * @param partHeaderSizeMax The maximum size of the headers in bytes. 1002 * 1003 * @since 1.6.0 1004 */ 1005 public void setPartHeaderSizeMax(final int partHeaderSizeMax) { 1006 this.partHeaderSizeMax = partHeaderSizeMax; 1007 } 1008 1009 /** 1010 * Finds the beginning of the first {@code encapsulation}. 1011 * 1012 * @return {@code true} if an {@code encapsulation} was found in 1013 * the stream. 1014 * 1015 * @throws IOException if an i/o error occurs. 1016 */ 1017 public boolean skipPreamble() throws IOException { 1018 // First delimiter may be not preceded with a CRLF. 1019 System.arraycopy(boundary, 2, boundary, 0, boundary.length - 2); 1020 boundaryLength = boundary.length - 2; 1021 computeBoundaryTable(); 1022 try { 1023 // Discard all data up to the delimiter. 1024 discardBodyData(); 1025 1026 // Read boundary - if succeeded, the stream contains an 1027 // encapsulation. 1028 return readBoundary(); 1029 } catch (final MalformedStreamException e) { 1030 return false; 1031 } finally { 1032 // Restore delimiter. 1033 System.arraycopy(boundary, 0, boundary, 2, boundary.length - 2); 1034 boundaryLength = boundary.length; 1035 boundary[0] = CR; 1036 boundary[1] = LF; 1037 computeBoundaryTable(); 1038 } 1039 } 1040 1041 }