/* * This file is part of the OWASP Proxy, a free intercepting proxy library. * Copyright (C) 2008-2010 Rogan Dawes <rogan@dawes.za.net> * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to: * The Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ package org.owasp.proxy.io; import java.io.FilterInputStream; import java.io.IOException; import java.io.InputStream; import org.owasp.proxy.util.CircularByteBuffer; /** * This class processes streams encoded according to RFC2616 "chunked" specification. * * This class will either return decoded chunks, or will return the original stream, but return EOF when the last chunk * and trailer have been read (if raw == true). * * @author Rogan Dawes */ public class ChunkedInputStream extends FilterInputStream { private boolean raw = false; private boolean eof = false; private byte[] b = new byte[1024]; private CircularByteBuffer buffer = new CircularByteBuffer(1024); public ChunkedInputStream(InputStream in) throws IOException { super(in); readChunk(); } /** * Running with raw = true returns the individual chunks, but still shows eof when the last chunk has been read, * rather than continuing into the next message * * @param in * @param raw * @throws IOException */ public ChunkedInputStream(InputStream in, boolean raw) throws IOException { super(in); this.raw = raw; readChunk(); } private int addRaw(int i) { if (raw && i != -1) { buffer.add((byte) i); } return i; } private void readChunk() throws IOException { String line = readLine(); try { int semi = line.indexOf(';'); if (semi > -1) line = line.substring(0, semi); int size = Integer.parseInt(line.trim(), 16); int got, read = 0; while (read < size && (got = in.read(b, 0, Math.min(b.length, size - read))) > 0) { buffer.add(b, 0, got); read = read + got; } if (read < size) throw new IOException( "Unexpected end of stream reading a chunk of " + size); if (size > 0) { // read the trailing line feed after the chunk body, // but before the next chunk size if (!"".equals(line = readLine())) throw new IOException( "Unexpected characters reading the trailing CRLF : " + line); } else { discardTrailer(); eof = true; } } catch (NumberFormatException nfe) { IOException ioe = new IOException("Error parsing chunk size from '" + line); ioe.initCause(nfe); throw ioe; } } private boolean isEmptyBuffer() { return buffer == null || buffer.length() == 0; } public int read() throws IOException { if (eof && isEmptyBuffer()) return -1; int i = buffer.remove(); while (!eof && isEmptyBuffer()) readChunk(); return i; } public int read(byte[] b, int off, int len) throws IOException { if (eof && isEmptyBuffer()) return -1; int got = buffer.remove(b, off, len); while (!eof && isEmptyBuffer()) readChunk(); return got; } public int available() throws IOException { return buffer == null ? 0 : buffer.length(); } public boolean markSupported() { return false; } private String readLine() throws IOException { StringBuilder line = new StringBuilder(); int i = addRaw(in.read()); while (i > -1 && i != '\r' && i != '\n') { line = line.append((char) (i & 0xFF)); i = addRaw(in.read()); } if (i == '\n') { throw new IOException("Unexpected LF, was expecting a CR first"); } else if (i == '\r') { i = addRaw(in.read()); if (i != '\n') throw new IOException("Unexpected character " + Integer.toHexString(i) + ", was expecting 0x0A"); } return line.toString(); } /** * This actually discards the trailer, since it is available for use via the raw content, if desired * * @throws IOException */ private void discardTrailer() throws IOException { while (!"".equals(readLine())) ; } /* * (non-Javadoc) * * @see java.io.FilterInputStream#close() */ @Override public void close() throws IOException { // do nothing } }