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 var 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 var 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 var 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 var 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 var 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 var available = available(); 403 if (available == 0) { 404 available = makeAvailable(); 405 if (available == 0) { 406 return 0; 407 } 408 } 409 // Fix "Implicit narrowing conversion in compound assignment" 410 // https://github.com/apache/commons-fileupload/security/code-scanning/118 411 // Math.min always returns an int because available is an int. 412 final var res = Math.toIntExact(Math.min(available, bytes)); 413 head += res; 414 return res; 415 } 416 417 } 418 419 /** 420 * Signals that the input stream fails to follow the required syntax. 421 */ 422 public static class MalformedStreamException extends FileUploadException { 423 424 /** 425 * The UID to use when serializing this instance. 426 */ 427 private static final long serialVersionUID = 2; 428 429 /** 430 * Constructs an {@code MalformedStreamException} with the specified detail message. 431 * 432 * @param message The detail message. 433 */ 434 public MalformedStreamException(final String message) { 435 super(message); 436 } 437 438 /** 439 * Constructs an {@code MalformedStreamException} with the specified detail message. 440 * 441 * @param message The detail message. 442 * @param cause The cause (which is saved for later retrieval by the {@link #getCause()} method). (A null value is permitted, and indicates that the 443 * cause is nonexistent or unknown.) 444 */ 445 public MalformedStreamException(final String message, final Throwable cause) { 446 super(message, cause); 447 } 448 449 } 450 451 /** 452 * Internal class, which is used to invoke the {@link ProgressListener}. 453 */ 454 public static class ProgressNotifier { 455 456 /** 457 * The listener to invoke. 458 */ 459 private final ProgressListener progressListener; 460 461 /** 462 * Number of expected bytes, if known, or -1. 463 */ 464 private final long contentLength; 465 466 /** 467 * Number of bytes, which have been read so far. 468 */ 469 private long bytesRead; 470 471 /** 472 * Number of items, which have been read so far. 473 */ 474 private int items; 475 476 /** 477 * Creates a new instance with the given listener and content length. 478 * 479 * @param progressListener The listener to invoke. 480 * @param contentLength The expected content length. 481 */ 482 public ProgressNotifier(final ProgressListener progressListener, final long contentLength) { 483 this.progressListener = progressListener != null ? progressListener : ProgressListener.NOP; 484 this.contentLength = contentLength; 485 } 486 487 /** 488 * Called to indicate that bytes have been read. 489 * 490 * @param byteCount Number of bytes, which have been read. 491 */ 492 void noteBytesRead(final int byteCount) { 493 // 494 // Indicates, that the given number of bytes have been read from the input stream. 495 // 496 bytesRead += byteCount; 497 notifyListener(); 498 } 499 500 /** 501 * Called to indicate, that a new file item has been detected. 502 */ 503 public void noteItem() { 504 ++items; 505 notifyListener(); 506 } 507 508 /** 509 * Called for notifying the listener. 510 */ 511 private void notifyListener() { 512 progressListener.update(bytesRead, contentLength, items); 513 } 514 515 } 516 517 /** 518 * The Carriage Return ASCII character value. 519 */ 520 public static final byte CR = 0x0D; 521 522 /** 523 * The Line Feed ASCII character value. 524 */ 525 public static final byte LF = 0x0A; 526 527 /** 528 * The dash (-) ASCII character value. 529 */ 530 public static final byte DASH = 0x2D; 531 532 /** 533 * The maximum length of {@code header-part} that will be processed (10 kilobytes = 10240 bytes.). 534 */ 535 public static final int HEADER_PART_SIZE_MAX = 10_240; 536 537 /** 538 * The default length of the buffer used for processing a request. 539 */ 540 static final int DEFAULT_BUFSIZE = 4096; 541 542 /** 543 * A byte sequence that marks the end of {@code header-part} ({@code CRLFCRLF}). 544 */ 545 static final byte[] HEADER_SEPARATOR = { CR, LF, CR, LF }; 546 547 /** 548 * A byte sequence that that follows a delimiter that will be followed by an encapsulation ({@code CRLF}). 549 */ 550 static final byte[] FIELD_SEPARATOR = { CR, LF }; 551 552 /** 553 * A byte sequence that that follows a delimiter of the last encapsulation in the stream ({@code --}). 554 */ 555 static final byte[] STREAM_TERMINATOR = { DASH, DASH }; 556 557 /** 558 * A byte sequence that precedes a boundary ({@code CRLF--}). 559 */ 560 static final byte[] BOUNDARY_PREFIX = { CR, LF, DASH, DASH }; 561 562 /** 563 * Compares {@code count} first bytes in the arrays {@code a} and {@code b}. 564 * 565 * @param a The first array to compare. 566 * @param b The second array to compare. 567 * @param count How many bytes should be compared. 568 * @return {@code true} if {@code count} first bytes in arrays {@code a} and {@code b} are equal. 569 */ 570 static boolean arrayEquals(final byte[] a, final byte[] b, final int count) { 571 for (var i = 0; i < count; i++) { 572 if (a[i] != b[i]) { 573 return false; 574 } 575 } 576 return true; 577 } 578 579 /** 580 * Constructs a new {@link Builder}. 581 * 582 * @return a new {@link Builder}. 583 */ 584 public static Builder builder() { 585 return new Builder(); 586 } 587 588 /** 589 * The input stream from which data is read. 590 */ 591 private final InputStream input; 592 593 /** 594 * The length of the boundary token plus the leading {@code CRLF--}. 595 */ 596 private int boundaryLength; 597 598 /** 599 * The amount of data, in bytes, that must be kept in the buffer in order to detect delimiters reliably. 600 */ 601 private final int keepRegion; 602 603 /** 604 * The byte sequence that partitions the stream. 605 */ 606 private final byte[] boundary; 607 608 /** 609 * The table for Knuth-Morris-Pratt search algorithm. 610 */ 611 private final int[] boundaryTable; 612 613 /** 614 * The length of the buffer used for processing the request. 615 */ 616 private final int bufSize; 617 618 /** 619 * The buffer used for processing the request. 620 */ 621 private final byte[] buffer; 622 623 /** 624 * The index of first valid character in the buffer. <br> 625 * 0 <= head < bufSize 626 */ 627 private int head; 628 629 /** 630 * The index of last valid character in the buffer + 1. <br> 631 * 0 <= tail <= bufSize 632 */ 633 private int tail; 634 635 /** 636 * The content encoding to use when reading headers. 637 */ 638 private Charset headerCharset; 639 640 /** 641 * The progress notifier, if any, or null. 642 */ 643 private final ProgressNotifier notifier; 644 645 /** 646 * Constructs a {@code MultipartInput} with a custom size buffer. 647 * <p> 648 * 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 649 * data. Too small a buffer size setting will degrade performance. 650 * </p> 651 * 652 * @param input The {@code InputStream} to serve as a data source. 653 * @param boundary The token used for dividing the stream into {@code encapsulations}. 654 * @param bufferSize The size of the buffer to be used, in bytes. 655 * @param notifier The notifier, which is used for calling the progress listener, if any. 656 * @throws IllegalArgumentException If the buffer size is too small. 657 */ 658 private MultipartInput(final InputStream input, final byte[] boundary, final int bufferSize, final ProgressNotifier notifier) { 659 if (boundary == null) { 660 throw new IllegalArgumentException("boundary may not be null"); 661 } 662 // We prepend CR/LF to the boundary to chop trailing CR/LF from 663 // body-data tokens. 664 this.boundaryLength = boundary.length + BOUNDARY_PREFIX.length; 665 if (bufferSize < this.boundaryLength + 1) { 666 throw new IllegalArgumentException("The buffer size specified for the MultipartInput is too small"); 667 } 668 669 this.input = input; 670 this.bufSize = Math.max(bufferSize, boundaryLength * 2); 671 this.buffer = new byte[this.bufSize]; 672 this.notifier = notifier; 673 674 this.boundary = new byte[this.boundaryLength]; 675 this.boundaryTable = new int[this.boundaryLength + 1]; 676 this.keepRegion = this.boundary.length; 677 678 System.arraycopy(BOUNDARY_PREFIX, 0, this.boundary, 0, BOUNDARY_PREFIX.length); 679 System.arraycopy(boundary, 0, this.boundary, BOUNDARY_PREFIX.length, boundary.length); 680 computeBoundaryTable(); 681 682 head = 0; 683 tail = 0; 684 } 685 686 /** 687 * Computes the table used for Knuth-Morris-Pratt search algorithm. 688 */ 689 private void computeBoundaryTable() { 690 var position = 2; 691 var candidate = 0; 692 693 boundaryTable[0] = -1; 694 boundaryTable[1] = 0; 695 696 while (position <= boundaryLength) { 697 if (boundary[position - 1] == boundary[candidate]) { 698 boundaryTable[position] = candidate + 1; 699 candidate++; 700 position++; 701 } else if (candidate > 0) { 702 candidate = boundaryTable[candidate]; 703 } else { 704 boundaryTable[position] = 0; 705 position++; 706 } 707 } 708 } 709 710 /** 711 * Reads {@code body-data} from the current {@code encapsulation} and discards it. 712 * <p> 713 * Use this method to skip encapsulations you don't need or don't understand. 714 * </p> 715 * 716 * @return The amount of data discarded. 717 * @throws MalformedStreamException if the stream ends unexpectedly. 718 * @throws IOException if an i/o error occurs. 719 */ 720 public long discardBodyData() throws MalformedStreamException, IOException { 721 return readBodyData(NullOutputStream.INSTANCE); 722 } 723 724 /** 725 * Searches for a byte of specified value in the {@code buffer}, starting at the specified {@code position}. 726 * 727 * @param value The value to find. 728 * @param pos The starting position for searching. 729 * 730 * @return The position of byte found, counting from beginning of the {@code buffer}, or {@code -1} if not found. 731 */ 732 protected int findByte(final byte value, final int pos) { 733 for (var i = pos; i < tail; i++) { 734 if (buffer[i] == value) { 735 return i; 736 } 737 } 738 739 return -1; 740 } 741 742 /** 743 * Searches for the {@code boundary} in the {@code buffer} region delimited by {@code head} and {@code tail}. 744 * 745 * @return The position of the boundary found, counting from the beginning of the {@code buffer}, or {@code -1} if not found. 746 */ 747 protected int findSeparator() { 748 var bufferPos = this.head; 749 var tablePos = 0; 750 while (bufferPos < this.tail) { 751 while (tablePos >= 0 && buffer[bufferPos] != boundary[tablePos]) { 752 tablePos = boundaryTable[tablePos]; 753 } 754 bufferPos++; 755 tablePos++; 756 if (tablePos == boundaryLength) { 757 return bufferPos - boundaryLength; 758 } 759 } 760 return -1; 761 } 762 763 /** 764 * Gets the character encoding used when reading the headers of an individual part. When not specified, or {@code null}, the platform default encoding is 765 * used. 766 * 767 * @return The encoding used to read part headers. 768 */ 769 public Charset getHeaderCharset() { 770 return headerCharset; 771 } 772 773 /** 774 * Creates a new {@link ItemInputStream}. 775 * 776 * @return A new instance of {@link ItemInputStream}. 777 */ 778 public ItemInputStream newInputStream() { 779 return new ItemInputStream(); 780 } 781 782 /** 783 * Reads {@code body-data} from the current {@code encapsulation} and writes its contents into the output {@code Stream}. 784 * <p> 785 * Arbitrary large amounts of data can be processed by this method using a constant size buffer. (see {@link MultipartInput#builder()}). 786 * </p> 787 * 788 * @param output The {@code Stream} to write data into. May be null, in which case this method is equivalent to {@link #discardBodyData()}. 789 * @return the amount of data written. 790 * @throws MalformedStreamException if the stream ends unexpectedly. 791 * @throws IOException if an i/o error occurs. 792 */ 793 public long readBodyData(final OutputStream output) throws MalformedStreamException, IOException { 794 try (var inputStream = newInputStream()) { 795 return IOUtils.copyLarge(inputStream, output); 796 } 797 } 798 799 /** 800 * Skips a {@code boundary} token, and checks whether more {@code encapsulations} are contained in the stream. 801 * 802 * @return {@code true} if there are more encapsulations in this stream; {@code false} otherwise. 803 * @throws FileUploadSizeException if the bytes read from the stream exceeded the size limits 804 * @throws MalformedStreamException if the stream ends unexpectedly or fails to follow required syntax. 805 */ 806 public boolean readBoundary() throws FileUploadSizeException, MalformedStreamException { 807 final var marker = new byte[2]; 808 final boolean nextChunk; 809 head += boundaryLength; 810 try { 811 marker[0] = readByte(); 812 if (marker[0] == LF) { 813 // Work around IE5 Mac bug with input type=image. 814 // Because the boundary delimiter, not including the trailing 815 // CRLF, must not appear within any file (RFC 2046, section 816 // 5.1.1), we know the missing CR is due to a buggy browser 817 // rather than a file containing something similar to a 818 // boundary. 819 return true; 820 } 821 822 marker[1] = readByte(); 823 if (arrayEquals(marker, STREAM_TERMINATOR, 2)) { 824 nextChunk = false; 825 } else if (arrayEquals(marker, FIELD_SEPARATOR, 2)) { 826 nextChunk = true; 827 } else { 828 throw new MalformedStreamException("Unexpected characters follow a boundary"); 829 } 830 } catch (final FileUploadSizeException e) { 831 throw e; 832 } catch (final IOException e) { 833 throw new MalformedStreamException("Stream ended unexpectedly", e); 834 } 835 return nextChunk; 836 } 837 838 /** 839 * Reads a byte from the {@code buffer}, and refills it as necessary. 840 * 841 * @return The next byte from the input stream. 842 * @throws IOException if there is no more data available. 843 */ 844 public byte readByte() throws IOException { 845 // Buffer depleted ? 846 if (head == tail) { 847 head = 0; 848 // Refill. 849 tail = input.read(buffer, head, bufSize); 850 if (tail == -1) { 851 // No more data available. 852 throw new IOException("No more data is available"); 853 } 854 if (notifier != null) { 855 notifier.noteBytesRead(tail); 856 } 857 } 858 return buffer[head++]; 859 } 860 861 /** 862 * Reads the {@code header-part} of the current {@code encapsulation}. 863 * <p> 864 * Headers are returned verbatim to the input stream, including the trailing {@code CRLF} marker. Parsing is left to the application. 865 * </p> 866 * 867 * @return The {@code header-part} of the current encapsulation. 868 * @throws FileUploadSizeException if the bytes read from the stream exceeded the size limits. 869 * @throws MalformedStreamException if the stream ends unexpectedly. 870 */ 871 public String readHeaders() throws FileUploadSizeException, MalformedStreamException { 872 var i = 0; 873 byte b; 874 // to support multi-byte characters 875 final var baos = new ByteArrayOutputStream(); 876 var size = 0; 877 while (i < HEADER_SEPARATOR.length) { 878 try { 879 b = readByte(); 880 } catch (final FileUploadSizeException e) { 881 // wraps a FileUploadSizeException, re-throw as it will be unwrapped later 882 throw e; 883 } catch (final IOException e) { 884 throw new MalformedStreamException("Stream ended unexpectedly", e); 885 } 886 if (++size > HEADER_PART_SIZE_MAX) { 887 throw new MalformedStreamException( 888 String.format("Header section has more than %s bytes (maybe it is not properly terminated)", HEADER_PART_SIZE_MAX)); 889 } 890 if (b == HEADER_SEPARATOR[i]) { 891 i++; 892 } else { 893 i = 0; 894 } 895 baos.write(b); 896 } 897 898 try { 899 return baos.toString(Charsets.toCharset(headerCharset, Charset.defaultCharset()).name()); 900 } catch (final UnsupportedEncodingException e) { 901 // not possible 902 throw new IllegalStateException(e); 903 } 904 } 905 906 /** 907 * Changes the boundary token used for partitioning the stream. 908 * <p> 909 * This method allows single pass processing of nested multipart streams. 910 * </p> 911 * <p> 912 * The boundary token of the nested stream is {@code required} to be of the same length as the boundary token in parent stream. 913 * </p> 914 * <p> 915 * Restoring the parent stream boundary token after processing of a nested stream is left to the application. 916 * </p> 917 * 918 * @param boundary The boundary to be used for parsing of the nested stream. 919 * @throws FileUploadBoundaryException if the {@code boundary} has a different length than the one being currently parsed. 920 */ 921 public void setBoundary(final byte[] boundary) throws FileUploadBoundaryException { 922 if (boundary.length != boundaryLength - BOUNDARY_PREFIX.length) { 923 throw new FileUploadBoundaryException("The length of a boundary token cannot be changed"); 924 } 925 System.arraycopy(boundary, 0, this.boundary, BOUNDARY_PREFIX.length, boundary.length); 926 computeBoundaryTable(); 927 } 928 929 /** 930 * Sets the character encoding to be used when reading the headers of individual parts. When not specified, or {@code null}, the platform default encoding 931 * is used. 932 * 933 * @param headerCharset The encoding used to read part headers. 934 */ 935 public void setHeaderCharset(final Charset headerCharset) { 936 this.headerCharset = headerCharset; 937 } 938 939 /** 940 * Finds the beginning of the first {@code encapsulation}. 941 * 942 * @return {@code true} if an {@code encapsulation} was found in the stream. 943 * @throws IOException if an i/o error occurs. 944 */ 945 public boolean skipPreamble() throws IOException { 946 // First delimiter may be not preceded with a CRLF. 947 System.arraycopy(boundary, 2, boundary, 0, boundary.length - 2); 948 boundaryLength = boundary.length - 2; 949 computeBoundaryTable(); 950 try { 951 // Discard all data up to the delimiter. 952 discardBodyData(); 953 954 // Read boundary - if succeeded, the stream contains an 955 // encapsulation. 956 return readBoundary(); 957 } catch (final MalformedStreamException e) { 958 return false; 959 } finally { 960 // Restore delimiter. 961 System.arraycopy(boundary, 0, boundary, 2, boundary.length - 2); 962 boundaryLength = boundary.length; 963 boundary[0] = CR; 964 boundary[1] = LF; 965 computeBoundaryTable(); 966 } 967 } 968 969}