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