001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.commons.fileupload2.core; 018 019import java.io.ByteArrayOutputStream; 020import java.io.IOException; 021import java.io.InputStream; 022import java.io.OutputStream; 023import java.io.UnsupportedEncodingException; 024import java.nio.charset.Charset; 025 026import org.apache.commons.fileupload2.core.FileItemInput.ItemSkippedException; 027import org.apache.commons.io.Charsets; 028import org.apache.commons.io.IOUtils; 029import org.apache.commons.io.build.AbstractOrigin; 030import org.apache.commons.io.build.AbstractStreamBuilder; 031import org.apache.commons.io.output.NullOutputStream; 032 033/** 034 * Low-level API for processing file uploads. 035 * 036 * <p> 037 * 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 038 * 1867</a>. Arbitrarily large amounts of data in the stream can be processed under constant memory usage. 039 * </p> 040 * <p> 041 * The format of the stream is defined in the following way: 042 * </p> 043 * <code> 044 * multipart-body := preamble 1*encapsulation close-delimiter epilogue<br> 045 * encapsulation := delimiter body CRLF<br> 046 * delimiter := "--" boundary CRLF<br> 047 * close-delimiter := "--" boundary "--"<br> 048 * preamble := <ignore><br> 049 * epilogue := <ignore><br> 050 * body := header-part CRLF body-part<br> 051 * header-part := 1*header CRLF<br> 052 * header := header-name ":" header-value<br> 053 * header-name := <printable ascii characters except ":"><br> 054 * header-value := <any ascii characters except CR & LF><br> 055 * body-data := <arbitrary data><br> 056 * </code> 057 * 058 * <p> 059 * 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 060 * <strong>required</strong> to have a boundary token of the same length as the parent stream (see {@link #setBoundary(byte[])}). 061 * </p> 062 * <p> 063 * Here is an example of usage of this class: 064 * </p> 065 * 066 * <pre> 067 * try { 068 * MultipartInput multipartStream = new MultipartInput(input, boundary); 069 * boolean nextPart = multipartStream.skipPreamble(); 070 * OutputStream output; 071 * while (nextPart) { 072 * String header = multipartStream.readHeaders(); 073 * // process headers 074 * // create some output stream 075 * multipartStream.readBodyData(output); 076 * nextPart = multipartStream.readBoundary(); 077 * } 078 * } catch (MultipartInput.MalformedStreamException e) { 079 * // the stream failed to follow required syntax 080 * } catch (IOException e) { 081 * // a read or write error occurred 082 * } 083 * </pre> 084 */ 085public final class MultipartInput { 086 087 /** 088 * Builds a new {@link MultipartInput} instance. 089 * <p> 090 * For example: 091 * </p> 092 * 093 * <pre>{@code 094 * MultipartInput factory = MultipartInput.builder().setPath(path).setBufferSize(DEFAULT_THRESHOLD).get(); 095 * } 096 * </pre> 097 */ 098 public static class Builder extends AbstractStreamBuilder<MultipartInput, Builder> { 099 100 /** 101 * Boundary. 102 */ 103 private byte[] boundary; 104 105 /** 106 * Progress notifier. 107 */ 108 private ProgressNotifier progressNotifier; 109 110 public Builder() { 111 setBufferSizeDefault(DEFAULT_BUFSIZE); 112 } 113 114 /** 115 * Constructs a new instance. 116 * <p> 117 * This builder uses the InputStream, buffer size, boundary and progress notifier aspects. 118 * </p> 119 * <p> 120 * You must provide an origin that can be converted to a Reader by this builder, otherwise, this call will throw an 121 * {@link UnsupportedOperationException}. 122 * </p> 123 * 124 * @return a new instance. 125 * @throws IOException if an I/O error occurs. 126 * @throws UnsupportedOperationException if the origin cannot provide a Path. 127 * @see AbstractOrigin#getReader(Charset) 128 */ 129 @Override 130 public MultipartInput get() throws IOException { 131 return new MultipartInput(getInputStream(), boundary, getBufferSize(), progressNotifier); 132 } 133 134 /** 135 * Sets the boundary. 136 * 137 * @param boundary the boundary. 138 * @return this 139 */ 140 public Builder setBoundary(final byte[] boundary) { 141 this.boundary = boundary; 142 return this; 143 } 144 145 /** 146 * Sets the progress notifier. 147 * 148 * @param progressNotifier progress notifier.. 149 * @return this 150 */ 151 public Builder setProgressNotifier(final ProgressNotifier progressNotifier) { 152 this.progressNotifier = progressNotifier; 153 return this; 154 } 155 156 } 157 158 /** 159 * Signals an attempt to set an invalid boundary token. 160 */ 161 public static class FileUploadBoundaryException extends FileUploadException { 162 163 /** 164 * The UID to use when serializing this instance. 165 */ 166 private static final long serialVersionUID = 2; 167 168 /** 169 * Constructs an instance with the specified detail message. 170 * 171 * @param message The detail message (which is saved for later retrieval by the {@link #getMessage()} method) 172 */ 173 public FileUploadBoundaryException(final String message) { 174 super(message); 175 } 176 177 } 178 179 /** 180 * An {@link InputStream} for reading an items contents. 181 */ 182 public class ItemInputStream extends InputStream { 183 184 /** 185 * Offset when converting negative bytes to integers. 186 */ 187 private static final int BYTE_POSITIVE_OFFSET = 256; 188 189 /** 190 * The number of bytes, which have been read so far. 191 */ 192 private long total; 193 194 /** 195 * The number of bytes, which must be hold, because they might be a part of the boundary. 196 */ 197 private int pad; 198 199 /** 200 * The current offset in the buffer. 201 */ 202 private int pos; 203 204 /** 205 * Whether the stream is already closed. 206 */ 207 private boolean closed; 208 209 /** 210 * Creates a new instance. 211 */ 212 ItemInputStream() { 213 findSeparator(); 214 } 215 216 /** 217 * Returns the number of bytes, which are currently available, without blocking. 218 * 219 * @throws IOException An I/O error occurs. 220 * @return Number of bytes in the buffer. 221 */ 222 @Override 223 public int available() throws IOException { 224 if (pos == -1) { 225 return tail - head - pad; 226 } 227 return pos - head; 228 } 229 230 private void checkOpen() throws ItemSkippedException { 231 if (closed) { 232 throw new FileItemInput.ItemSkippedException("checkOpen()"); 233 } 234 } 235 236 /** 237 * Closes the input stream. 238 * 239 * @throws IOException An I/O error occurred. 240 */ 241 @Override 242 public void close() throws IOException { 243 close(false); 244 } 245 246 /** 247 * Closes the input stream. 248 * 249 * @param closeUnderlying Whether to close the underlying stream (hard close) 250 * @throws IOException An I/O error occurred. 251 */ 252 public void close(final boolean closeUnderlying) throws IOException { 253 if (closed) { 254 return; 255 } 256 if (closeUnderlying) { 257 closed = true; 258 input.close(); 259 } else { 260 for (;;) { 261 int av = available(); 262 if (av == 0) { 263 av = makeAvailable(); 264 if (av == 0) { 265 break; 266 } 267 } 268 skip(av); 269 } 270 } 271 closed = true; 272 } 273 274 /** 275 * Called for finding the separator. 276 */ 277 private void findSeparator() { 278 pos = MultipartInput.this.findSeparator(); 279 if (pos == -1) { 280 if (tail - head > keepRegion) { 281 pad = keepRegion; 282 } else { 283 pad = tail - head; 284 } 285 } 286 } 287 288 /** 289 * Gets the number of bytes, which have been read by the stream. 290 * 291 * @return Number of bytes, which have been read so far. 292 */ 293 public long getBytesRead() { 294 return total; 295 } 296 297 public boolean isClosed() { 298 return closed; 299 } 300 301 /** 302 * Attempts to read more data. 303 * 304 * @return Number of available bytes 305 * @throws IOException An I/O error occurred. 306 */ 307 private int makeAvailable() throws IOException { 308 if (pos != -1) { 309 return 0; 310 } 311 312 // Move the data to the beginning of the buffer. 313 total += tail - head - pad; 314 System.arraycopy(buffer, tail - pad, buffer, 0, pad); 315 316 // Refill buffer with new data. 317 head = 0; 318 tail = pad; 319 320 for (;;) { 321 final int bytesRead = input.read(buffer, tail, bufSize - tail); 322 if (bytesRead == -1) { 323 // The last pad amount is left in the buffer. 324 // Boundary can't be in there so signal an error 325 // condition. 326 final String msg = "Stream ended unexpectedly"; 327 throw new MalformedStreamException(msg); 328 } 329 if (notifier != null) { 330 notifier.noteBytesRead(bytesRead); 331 } 332 tail += bytesRead; 333 334 findSeparator(); 335 final int av = available(); 336 337 if (av > 0 || pos != -1) { 338 return av; 339 } 340 } 341 } 342 343 /** 344 * Reads the next byte in the stream. 345 * 346 * @return The next byte in the stream, as a non-negative integer, or -1 for EOF. 347 * @throws IOException An I/O error occurred. 348 */ 349 @Override 350 public int read() throws IOException { 351 checkOpen(); 352 if (available() == 0 && makeAvailable() == 0) { 353 return -1; 354 } 355 ++total; 356 final int b = buffer[head++]; 357 if (b >= 0) { 358 return b; 359 } 360 return b + BYTE_POSITIVE_OFFSET; 361 } 362 363 /** 364 * Reads bytes into the given buffer. 365 * 366 * @param b The destination buffer, where to write to. 367 * @param off Offset of the first byte in the buffer. 368 * @param len Maximum number of bytes to read. 369 * @return Number of bytes, which have been actually read, or -1 for EOF. 370 * @throws IOException An I/O error occurred. 371 */ 372 @Override 373 public int read(final byte[] b, final int off, final int len) throws IOException { 374 checkOpen(); 375 if (len == 0) { 376 return 0; 377 } 378 int res = available(); 379 if (res == 0) { 380 res = makeAvailable(); 381 if (res == 0) { 382 return -1; 383 } 384 } 385 res = Math.min(res, len); 386 System.arraycopy(buffer, head, b, off, res); 387 head += res; 388 total += res; 389 return res; 390 } 391 392 /** 393 * Skips the given number of bytes. 394 * 395 * @param bytes Number of bytes to skip. 396 * @return The number of bytes, which have actually been skipped. 397 * @throws IOException An I/O error occurred. 398 */ 399 @Override 400 public long skip(final long bytes) throws IOException { 401 checkOpen(); 402 int av = available(); 403 if (av == 0) { 404 av = makeAvailable(); 405 if (av == 0) { 406 return 0; 407 } 408 } 409 final long res = Math.min(av, bytes); 410 head += res; 411 return res; 412 } 413 414 } 415 416 /** 417 * Signals that the input stream fails to follow the required syntax. 418 */ 419 public static class MalformedStreamException extends FileUploadException { 420 421 /** 422 * The UID to use when serializing this instance. 423 */ 424 private static final long serialVersionUID = 2; 425 426 /** 427 * Constructs an {@code MalformedStreamException} with the specified detail message. 428 * 429 * @param message The detail message. 430 */ 431 public MalformedStreamException(final String message) { 432 super(message); 433 } 434 435 /** 436 * Constructs an {@code MalformedStreamException} with the specified detail message. 437 * 438 * @param message The detail message. 439 * @param cause The cause (which is saved for later retrieval by the {@link #getCause()} method). (A null value is permitted, and indicates that the 440 * cause is nonexistent or unknown.) 441 */ 442 public MalformedStreamException(final String message, final Throwable cause) { 443 super(message, cause); 444 } 445 446 } 447 448 /** 449 * Internal class, which is used to invoke the {@link ProgressListener}. 450 */ 451 public static class ProgressNotifier { 452 453 /** 454 * The listener to invoke. 455 */ 456 private final ProgressListener progressListener; 457 458 /** 459 * Number of expected bytes, if known, or -1. 460 */ 461 private final long contentLength; 462 463 /** 464 * Number of bytes, which have been read so far. 465 */ 466 private long bytesRead; 467 468 /** 469 * Number of items, which have been read so far. 470 */ 471 private int items; 472 473 /** 474 * Creates a new instance with the given listener and content length. 475 * 476 * @param progressListener The listener to invoke. 477 * @param contentLength The expected content length. 478 */ 479 public ProgressNotifier(final ProgressListener progressListener, final long contentLength) { 480 this.progressListener = progressListener != null ? progressListener : ProgressListener.NOP; 481 this.contentLength = contentLength; 482 } 483 484 /** 485 * Called to indicate that bytes have been read. 486 * 487 * @param byteCount Number of bytes, which have been read. 488 */ 489 void noteBytesRead(final int byteCount) { 490 // 491 // Indicates, that the given number of bytes have been read from the input stream. 492 // 493 bytesRead += byteCount; 494 notifyListener(); 495 } 496 497 /** 498 * Called to indicate, that a new file item has been detected. 499 */ 500 public void noteItem() { 501 ++items; 502 notifyListener(); 503 } 504 505 /** 506 * Called for notifying the listener. 507 */ 508 private void notifyListener() { 509 progressListener.update(bytesRead, contentLength, items); 510 } 511 512 } 513 514 /** 515 * The Carriage Return ASCII character value. 516 */ 517 public static final byte CR = 0x0D; 518 519 /** 520 * The Line Feed ASCII character value. 521 */ 522 public static final byte LF = 0x0A; 523 524 /** 525 * The dash (-) ASCII character value. 526 */ 527 public static final byte DASH = 0x2D; 528 529 /** 530 * The maximum length of {@code header-part} that will be processed (10 kilobytes = 10240 bytes.). 531 */ 532 public static final int HEADER_PART_SIZE_MAX = 10_240; 533 534 /** 535 * The default length of the buffer used for processing a request. 536 */ 537 static final int DEFAULT_BUFSIZE = 4096; 538 539 /** 540 * A byte sequence that marks the end of {@code header-part} ({@code CRLFCRLF}). 541 */ 542 static final byte[] HEADER_SEPARATOR = { CR, LF, CR, LF }; 543 544 /** 545 * A byte sequence that that follows a delimiter that will be followed by an encapsulation ({@code CRLF}). 546 */ 547 static final byte[] FIELD_SEPARATOR = { CR, LF }; 548 549 /** 550 * A byte sequence that that follows a delimiter of the last encapsulation in the stream ({@code --}). 551 */ 552 static final byte[] STREAM_TERMINATOR = { DASH, DASH }; 553 554 /** 555 * A byte sequence that precedes a boundary ({@code CRLF--}). 556 */ 557 static final byte[] BOUNDARY_PREFIX = { CR, LF, DASH, DASH }; 558 559 /** 560 * Compares {@code count} first bytes in the arrays {@code a} and {@code b}. 561 * 562 * @param a The first array to compare. 563 * @param b The second array to compare. 564 * @param count How many bytes should be compared. 565 * @return {@code true} if {@code count} first bytes in arrays {@code a} and {@code b} are equal. 566 */ 567 static boolean arrayEquals(final byte[] a, final byte[] b, final int count) { 568 for (int i = 0; i < count; i++) { 569 if (a[i] != b[i]) { 570 return false; 571 } 572 } 573 return true; 574 } 575 576 /** 577 * Constructs a new {@link Builder}. 578 * 579 * @return a new {@link Builder}. 580 */ 581 public static Builder builder() { 582 return new Builder(); 583 } 584 585 /** 586 * The input stream from which data is read. 587 */ 588 private final InputStream input; 589 590 /** 591 * The length of the boundary token plus the leading {@code CRLF--}. 592 */ 593 private int boundaryLength; 594 595 /** 596 * The amount of data, in bytes, that must be kept in the buffer in order to detect delimiters reliably. 597 */ 598 private final int keepRegion; 599 600 /** 601 * The byte sequence that partitions the stream. 602 */ 603 private final byte[] boundary; 604 605 /** 606 * The table for Knuth-Morris-Pratt search algorithm. 607 */ 608 private final int[] boundaryTable; 609 610 /** 611 * The length of the buffer used for processing the request. 612 */ 613 private final int bufSize; 614 615 /** 616 * The buffer used for processing the request. 617 */ 618 private final byte[] buffer; 619 620 /** 621 * The index of first valid character in the buffer. <br> 622 * 0 <= head < bufSize 623 */ 624 private int head; 625 626 /** 627 * The index of last valid character in the buffer + 1. <br> 628 * 0 <= tail <= bufSize 629 */ 630 private int tail; 631 632 /** 633 * The content encoding to use when reading headers. 634 */ 635 private Charset headerCharset; 636 637 /** 638 * The progress notifier, if any, or null. 639 */ 640 private final ProgressNotifier notifier; 641 642 /** 643 * Constructs a {@code MultipartInput} with a custom size buffer. 644 * <p> 645 * 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 646 * data. Too small a buffer size setting will degrade performance. 647 * </p> 648 * 649 * @param input The {@code InputStream} to serve as a data source. 650 * @param boundary The token used for dividing the stream into {@code encapsulations}. 651 * @param bufferSize The size of the buffer to be used, in bytes. 652 * @param notifier The notifier, which is used for calling the progress listener, if any. 653 * @throws IllegalArgumentException If the buffer size is too small. 654 */ 655 private MultipartInput(final InputStream input, final byte[] boundary, final int bufferSize, final ProgressNotifier notifier) { 656 if (boundary == null) { 657 throw new IllegalArgumentException("boundary may not be null"); 658 } 659 // We prepend CR/LF to the boundary to chop trailing CR/LF from 660 // body-data tokens. 661 this.boundaryLength = boundary.length + BOUNDARY_PREFIX.length; 662 if (bufferSize < this.boundaryLength + 1) { 663 throw new IllegalArgumentException("The buffer size specified for the MultipartInput is too small"); 664 } 665 666 this.input = input; 667 this.bufSize = Math.max(bufferSize, boundaryLength * 2); 668 this.buffer = new byte[this.bufSize]; 669 this.notifier = notifier; 670 671 this.boundary = new byte[this.boundaryLength]; 672 this.boundaryTable = new int[this.boundaryLength + 1]; 673 this.keepRegion = this.boundary.length; 674 675 System.arraycopy(BOUNDARY_PREFIX, 0, this.boundary, 0, BOUNDARY_PREFIX.length); 676 System.arraycopy(boundary, 0, this.boundary, BOUNDARY_PREFIX.length, boundary.length); 677 computeBoundaryTable(); 678 679 head = 0; 680 tail = 0; 681 } 682 683 /** 684 * Computes the table used for Knuth-Morris-Pratt search algorithm. 685 */ 686 private void computeBoundaryTable() { 687 int position = 2; 688 int candidate = 0; 689 690 boundaryTable[0] = -1; 691 boundaryTable[1] = 0; 692 693 while (position <= boundaryLength) { 694 if (boundary[position - 1] == boundary[candidate]) { 695 boundaryTable[position] = candidate + 1; 696 candidate++; 697 position++; 698 } else if (candidate > 0) { 699 candidate = boundaryTable[candidate]; 700 } else { 701 boundaryTable[position] = 0; 702 position++; 703 } 704 } 705 } 706 707 /** 708 * Reads {@code body-data} from the current {@code encapsulation} and discards it. 709 * <p> 710 * Use this method to skip encapsulations you don't need or don't understand. 711 * </p> 712 * 713 * @return The amount of data discarded. 714 * @throws MalformedStreamException if the stream ends unexpectedly. 715 * @throws IOException if an i/o error occurs. 716 */ 717 public long discardBodyData() throws MalformedStreamException, IOException { 718 return readBodyData(NullOutputStream.INSTANCE); 719 } 720 721 /** 722 * Searches for a byte of specified value in the {@code buffer}, starting at the specified {@code position}. 723 * 724 * @param value The value to find. 725 * @param pos The starting position for searching. 726 * 727 * @return The position of byte found, counting from beginning of the {@code buffer}, or {@code -1} if not found. 728 */ 729 protected int findByte(final byte value, final int pos) { 730 for (int i = pos; i < tail; i++) { 731 if (buffer[i] == value) { 732 return i; 733 } 734 } 735 736 return -1; 737 } 738 739 /** 740 * Searches for the {@code boundary} in the {@code buffer} region delimited by {@code head} and {@code tail}. 741 * 742 * @return The position of the boundary found, counting from the beginning of the {@code buffer}, or {@code -1} if not found. 743 */ 744 protected int findSeparator() { 745 int bufferPos = this.head; 746 int tablePos = 0; 747 while (bufferPos < this.tail) { 748 while (tablePos >= 0 && buffer[bufferPos] != boundary[tablePos]) { 749 tablePos = boundaryTable[tablePos]; 750 } 751 bufferPos++; 752 tablePos++; 753 if (tablePos == boundaryLength) { 754 return bufferPos - boundaryLength; 755 } 756 } 757 return -1; 758 } 759 760 /** 761 * Gets the character encoding used when reading the headers of an individual part. When not specified, or {@code null}, the platform default encoding is 762 * used. 763 * 764 * @return The encoding used to read part headers. 765 */ 766 public Charset getHeaderCharset() { 767 return headerCharset; 768 } 769 770 /** 771 * Creates a new {@link ItemInputStream}. 772 * 773 * @return A new instance of {@link ItemInputStream}. 774 */ 775 public ItemInputStream newInputStream() { 776 return new ItemInputStream(); 777 } 778 779 /** 780 * Reads {@code body-data} from the current {@code encapsulation} and writes its contents into the output {@code Stream}. 781 * <p> 782 * Arbitrary large amounts of data can be processed by this method using a constant size buffer. (see {@link MultipartInput#builder()}). 783 * </p> 784 * 785 * @param output The {@code Stream} to write data into. May be null, in which case this method is equivalent to {@link #discardBodyData()}. 786 * @return the amount of data written. 787 * @throws MalformedStreamException if the stream ends unexpectedly. 788 * @throws IOException if an i/o error occurs. 789 */ 790 public long readBodyData(final OutputStream output) throws MalformedStreamException, IOException { 791 try (ItemInputStream inputStream = newInputStream()) { 792 return IOUtils.copyLarge(inputStream, output); 793 } 794 } 795 796 /** 797 * Skips a {@code boundary} token, and checks whether more {@code encapsulations} are contained in the stream. 798 * 799 * @return {@code true} if there are more encapsulations in this stream; {@code false} otherwise. 800 * @throws FileUploadSizeException if the bytes read from the stream exceeded the size limits 801 * @throws MalformedStreamException if the stream ends unexpectedly or fails to follow required syntax. 802 */ 803 public boolean readBoundary() throws FileUploadSizeException, MalformedStreamException { 804 final byte[] marker = new byte[2]; 805 final boolean nextChunk; 806 head += boundaryLength; 807 try { 808 marker[0] = readByte(); 809 if (marker[0] == LF) { 810 // Work around IE5 Mac bug with input type=image. 811 // Because the boundary delimiter, not including the trailing 812 // CRLF, must not appear within any file (RFC 2046, section 813 // 5.1.1), we know the missing CR is due to a buggy browser 814 // rather than a file containing something similar to a 815 // boundary. 816 return true; 817 } 818 819 marker[1] = readByte(); 820 if (arrayEquals(marker, STREAM_TERMINATOR, 2)) { 821 nextChunk = false; 822 } else if (arrayEquals(marker, FIELD_SEPARATOR, 2)) { 823 nextChunk = true; 824 } else { 825 throw new MalformedStreamException("Unexpected characters follow a boundary"); 826 } 827 } catch (final FileUploadSizeException e) { 828 throw e; 829 } catch (final IOException e) { 830 throw new MalformedStreamException("Stream ended unexpectedly", e); 831 } 832 return nextChunk; 833 } 834 835 /** 836 * Reads a byte from the {@code buffer}, and refills it as necessary. 837 * 838 * @return The next byte from the input stream. 839 * @throws IOException if there is no more data available. 840 */ 841 public byte readByte() throws IOException { 842 // Buffer depleted ? 843 if (head == tail) { 844 head = 0; 845 // Refill. 846 tail = input.read(buffer, head, bufSize); 847 if (tail == -1) { 848 // No more data available. 849 throw new IOException("No more data is available"); 850 } 851 if (notifier != null) { 852 notifier.noteBytesRead(tail); 853 } 854 } 855 return buffer[head++]; 856 } 857 858 /** 859 * Reads the {@code header-part} of the current {@code encapsulation}. 860 * <p> 861 * Headers are returned verbatim to the input stream, including the trailing {@code CRLF} marker. Parsing is left to the application. 862 * </p> 863 * 864 * @return The {@code header-part} of the current encapsulation. 865 * @throws FileUploadSizeException if the bytes read from the stream exceeded the size limits. 866 * @throws MalformedStreamException if the stream ends unexpectedly. 867 */ 868 public String readHeaders() throws FileUploadSizeException, MalformedStreamException { 869 int i = 0; 870 byte b; 871 // to support multi-byte characters 872 final ByteArrayOutputStream baos = new ByteArrayOutputStream(); 873 int size = 0; 874 while (i < HEADER_SEPARATOR.length) { 875 try { 876 b = readByte(); 877 } catch (final FileUploadSizeException e) { 878 // wraps a FileUploadSizeException, re-throw as it will be unwrapped later 879 throw e; 880 } catch (final IOException e) { 881 throw new MalformedStreamException("Stream ended unexpectedly", e); 882 } 883 if (++size > HEADER_PART_SIZE_MAX) { 884 throw new MalformedStreamException( 885 String.format("Header section has more than %s bytes (maybe it is not properly terminated)", HEADER_PART_SIZE_MAX)); 886 } 887 if (b == HEADER_SEPARATOR[i]) { 888 i++; 889 } else { 890 i = 0; 891 } 892 baos.write(b); 893 } 894 895 try { 896 return baos.toString(Charsets.toCharset(headerCharset, Charset.defaultCharset()).name()); 897 } catch (final UnsupportedEncodingException e) { 898 // not possible 899 throw new IllegalStateException(e); 900 } 901 } 902 903 /** 904 * Changes the boundary token used for partitioning the stream. 905 * <p> 906 * This method allows single pass processing of nested multipart streams. 907 * </p> 908 * <p> 909 * The boundary token of the nested stream is {@code required} to be of the same length as the boundary token in parent stream. 910 * </p> 911 * <p> 912 * Restoring the parent stream boundary token after processing of a nested stream is left to the application. 913 * </p> 914 * 915 * @param boundary The boundary to be used for parsing of the nested stream. 916 * @throws FileUploadBoundaryException if the {@code boundary} has a different length than the one being currently parsed. 917 */ 918 public void setBoundary(final byte[] boundary) throws FileUploadBoundaryException { 919 if (boundary.length != boundaryLength - BOUNDARY_PREFIX.length) { 920 throw new FileUploadBoundaryException("The length of a boundary token cannot be changed"); 921 } 922 System.arraycopy(boundary, 0, this.boundary, BOUNDARY_PREFIX.length, boundary.length); 923 computeBoundaryTable(); 924 } 925 926 /** 927 * Sets the character encoding to be used when reading the headers of individual parts. When not specified, or {@code null}, the platform default encoding 928 * is used. 929 * 930 * @param headerCharset The encoding used to read part headers. 931 */ 932 public void setHeaderCharset(final Charset headerCharset) { 933 this.headerCharset = headerCharset; 934 } 935 936 /** 937 * Finds the beginning of the first {@code encapsulation}. 938 * 939 * @return {@code true} if an {@code encapsulation} was found in the stream. 940 * @throws IOException if an i/o error occurs. 941 */ 942 public boolean skipPreamble() throws IOException { 943 // First delimiter may be not preceded with a CRLF. 944 System.arraycopy(boundary, 2, boundary, 0, boundary.length - 2); 945 boundaryLength = boundary.length - 2; 946 computeBoundaryTable(); 947 try { 948 // Discard all data up to the delimiter. 949 discardBodyData(); 950 951 // Read boundary - if succeeded, the stream contains an 952 // encapsulation. 953 return readBoundary(); 954 } catch (final MalformedStreamException e) { 955 return false; 956 } finally { 957 // Restore delimiter. 958 System.arraycopy(boundary, 0, boundary, 2, boundary.length - 2); 959 boundaryLength = boundary.length; 960 boundary[0] = CR; 961 boundary[1] = LF; 962 computeBoundaryTable(); 963 } 964 } 965 966}