All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.github.stylesmile.file.MultipartInputStream Maven / Gradle / Ivy

package io.github.stylesmile.file;

import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;

import static io.github.stylesmile.server.JdkHTTPServer.CRLF;

/**
 * The {@code MultipartInputStream} decodes an InputStream whose data has
 * a "multipart/*" content type (see RFC 2046), providing the underlying
 * data of its various parts.
 * 

* The {@code InputStream} methods (e.g. {@link #read}) relate only to * the current part, and the {@link #nextPart} method advances to the * beginning of the next part. */ public class MultipartInputStream extends FilterInputStream { protected final byte[] boundary; // including leading CRLF-- protected final byte[] buf = new byte[4096]; protected int head, tail; // indices of current part's data in buf protected int end; // last index of input data read into buf protected int len; // length of found boundary protected int state; // initial, started data, start boundary, EOS, last boundary, epilogue /** * Constructs a MultipartInputStream with the given underlying stream. * * @param in the underlying multipart stream * @param boundary the multipart boundary * @throws NullPointerException if the given stream or boundary is null * @throws IllegalArgumentException if the given boundary's size is not * between 1 and 70 */ public MultipartInputStream(InputStream in, byte[] boundary) { super(in); int len = boundary.length; if (len == 0 || len > 70) throw new IllegalArgumentException("invalid boundary length"); this.boundary = new byte[len + 4]; // CRLF--boundary System.arraycopy(CRLF, 0, this.boundary, 0, 2); this.boundary[2] = this.boundary[3] = '-'; System.arraycopy(boundary, 0, this.boundary, 4, len); } @Override public int read() throws IOException { if (!fill()) return -1; return buf[head++] & 0xFF; } @Override public int read(byte[] b, int off, int len) throws IOException { if (!fill()) return -1; len = Math.min(tail - head, len); System.arraycopy(buf, head, b, off, len); // throws IOOBE as necessary head += len; return len; } @Override public long skip(long len) throws IOException { if (len <= 0 || !fill()) return 0; len = Math.min(tail - head, len); head += len; return len; } @Override public int available() throws IOException { return tail - head; } @Override public boolean markSupported() { return false; } /** * Advances the stream position to the beginning of the next part. * Data read before calling this method for the first time is the preamble, * and data read after this method returns false is the epilogue. * * @return true if successful, or false if there are no more parts * @throws IOException if an error occurs */ public boolean nextPart() throws IOException { while (skip(buf.length) != 0) ; // skip current part (until boundary) head = tail += len; // the next part starts right after boundary state |= 1; // started data (after first boundary) if (state >= 8) { // found last boundary state |= 0x10; // now beyond last boundary (epilogue) return false; } findBoundary(); // update indices return true; } /** * Fills the buffer with more data from the underlying stream. * * @return true if there is available data for the current part, * or false if the current part's end has been reached * @throws IOException if an error occurs or the input format is invalid */ protected boolean fill() throws IOException { // check if we already have more available data if (head != tail) // remember that if we continue, head == tail below return true; // if there's no more room, shift extra unread data to beginning of buffer if (tail > buf.length - 256) { // max boundary + whitespace supported size System.arraycopy(buf, tail, buf, 0, end -= tail); head = tail = 0; } // read more data and look for boundary (or potential partial boundary) int read; do { read = super.read(buf, end, buf.length - end); if (read < 0) state |= 4; // end of stream (EOS) else end += read; findBoundary(); // updates tail and length to next potential boundary // if we found a partial boundary with no data before it, we must // continue reading to determine if there is more data or not } while (read > 0 && tail == head && len == 0); // update and validate state if (tail != 0) // anything but a boundary right at the beginning state |= 1; // started data (preamble or after boundary) if (state < 8 && len > 0) state |= 2; // found start boundary if ((state & 6) == 4 // EOS but no start boundary found || len == 0 && ((state & 0xFC) == 4 // EOS but no last and no more boundaries || read == 0 && tail == head)) // boundary longer than buffer throw new IOException("missing boundary"); if (state >= 0x10) // in epilogue tail = end; // ignore boundaries, return everything return tail > head; // available data in current part } /** * Finds the first (potential) boundary within the buffer's remaining data. * Updates tail, length and state fields accordingly. * * @throws IOException if an error occurs or the input format is invalid */ protected void findBoundary() throws IOException { // see RFC2046#5.1.1 for boundary syntax len = 0; int off = tail - ((state & 1) != 0 || buf[0] != '-' ? 0 : 2); // skip initial CRLF? for (int end = this.end; tail < end; tail++, off = tail) { int j = tail; // end of potential boundary // try to match boundary value (leading CRLF is optional at first boundary) while (j < end && j - off < boundary.length && buf[j] == boundary[j - off]) j++; // return potential partial boundary which is cut off at end of current data if (j + 1 >= end) // at least two more chars needed for full boundary (CRLF or --) return; // if we found the boundary value, expand selection to include full line if (j - off == boundary.length) { // check if last boundary of entire multipart if (buf[j] == '-' && buf[j + 1] == '-') { j += 2; state |= 8; // found last boundary that ends multipart } // allow linear whitespace after boundary while (j < end && (buf[j] == ' ' || buf[j] == '\t')) j++; // check for CRLF (required, except in last boundary with no epilogue) if (j + 1 < end && buf[j] == '\r' && buf[j + 1] == '\n') // found CRLF len = j - tail + 2; // including optional whitespace and CRLF else if (j + 1 < end || (state & 4) != 0 && j + 1 == end) // should have found or never will throw new IOException("boundary must end with CRLF"); else if ((state & 4) != 0) // last boundary with no CRLF at end of data is valid len = j - tail; return; } } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy