// Copyright (c) 2003-present, Jodd Team (http://jodd.org) // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // 1. Redistributions of source code must retain the above copyright notice, // this list of conditions and the following disclaimer. // // 2. Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE // POSSIBILITY OF SUCH DAMAGE. package jodd.upload; import jodd.io.FastByteArrayOutputStream; import java.io.BufferedInputStream; import java.io.InputStream; import java.io.IOException; import java.io.OutputStream; /** * Extended input stream based on buffered requests input stream. * It provides some more functions that might be useful when working * with uploaded fies. */ public class MultipartRequestInputStream extends BufferedInputStream { public MultipartRequestInputStream(InputStream in) { super(in); } /** * Reads expected byte. Throws exception on streams end. */ public byte readByte() throws IOException { int i = super.read(); if (i == -1) { throw new IOException("End of HTTP request stream reached"); } return (byte) i; } /** * Skips specified number of bytes. */ public void skipBytes(int i) throws IOException { long len = super.skip(i); if (len != i) { throw new IOException("Failed to skip data in HTTP request"); } } // ---------------------------------------------------------------- boundary protected byte[] boundary; /** * Reads boundary from the input stream. */ public byte[] readBoundary() throws IOException { FastByteArrayOutputStream boundaryOutput = new FastByteArrayOutputStream(); byte b; // skip optional whitespaces while ((b = readByte()) <= ' ') { } boundaryOutput.write(b); // now read boundary chars while ((b = readByte()) != '\r') { boundaryOutput.write(b); } if (boundaryOutput.size() == 0) { throw new IOException("Problems with parsing request: invalid boundary"); } skipBytes(1); boundary = new byte[boundaryOutput.size() + 2]; System.arraycopy(boundaryOutput.toByteArray(), 0, boundary, 2, boundary.length - 2); boundary[0] = '\r'; boundary[1] = '\n'; return boundary; } // ---------------------------------------------------------------- data header protected FileUploadHeader lastHeader; public FileUploadHeader getLastHeader() { return lastHeader; } /** * Reads data header from the input stream. When there is no more * headers (i.e. end of stream reached), returns <code>null</code> */ public FileUploadHeader readDataHeader(String encoding) throws IOException { String dataHeader = readDataHeaderString(encoding); if (dataHeader != null) { lastHeader = new FileUploadHeader(dataHeader); } else { lastHeader = null; } return lastHeader; } protected String readDataHeaderString(String encoding) throws IOException { FastByteArrayOutputStream data = new FastByteArrayOutputStream(); byte b; while (true) { // end marker byte on offset +0 and +2 must be 13 if ((b = readByte()) != '\r') { data.write(b); continue; } mark(4); skipBytes(1); int i = read(); if (i == -1) { // reached end of stream return null; } if (i == '\r') { reset(); break; } reset(); data.write(b); } skipBytes(3); if (encoding != null) { return data.toString(encoding); } else { return data.toString(); } } // ---------------------------------------------------------------- copy /** * Copies bytes from this stream to some output until boundary is * reached. Returns number of copied bytes. It will throw an exception * for any irregular behaviour. */ public int copyAll(OutputStream out) throws IOException { int count = 0; while (true) { byte b = readByte(); if (isBoundary(b)) { break; } out.write(b); count++; } return count; } /** * Copies max or less number of bytes to output stream. Useful for determining * if uploaded file is larger then expected. */ public int copyMax(OutputStream out, int maxBytes) throws IOException { int count = 0; while (true) { byte b = readByte(); if (isBoundary(b)) { break; } out.write(b); count++; if (count == maxBytes) { return count; } } return count; } /** * Skips to the boundary and returns total number of bytes skipped. */ public int skipToBoundary() throws IOException { int count = 0; while (true) { byte b = readByte(); count++; if (isBoundary(b)) { break; } } return count; } /** * Checks if the current byte (i.e. one that was read last) represents * the very first byte of the boundary. */ public boolean isBoundary(byte b) throws IOException { int boundaryLen = boundary.length; mark(boundaryLen + 1); int bpos = 0; while (b == boundary[bpos]) { b = readByte(); bpos++; if (bpos == boundaryLen) { return true; // boundary found! } } reset(); return false; } }