// ======================================================================== // $Id: HttpInputStream.java,v 1.13 2005/08/23 20:02:26 gregwilkins Exp $ // Copyright 1996-2004 Mort Bay Consulting Pty. Ltd. // ------------------------------------------------------------------------ // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // http://www.apache.org/licenses/LICENSE-2.0 // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // ======================================================================== package net.lightbody.bmp.proxy.jetty.http; import net.lightbody.bmp.proxy.jetty.log.LogFactory; import net.lightbody.bmp.proxy.jetty.util.LineInput; import net.lightbody.bmp.proxy.jetty.util.StringUtil; import org.apache.commons.logging.Log; import java.io.*; /* ------------------------------------------------------------ */ /** HTTP Chunking InputStream. * This FilterInputStream acts as a BufferedInputStream until * setChunking(true) is called. Once chunking is * enabled, the raw stream is chunk decoded as per RFC2616. * * The "8859-1" encoding is used on underlying LineInput instance for * line based reads from the raw stream. * * This class is not synchronized and should be synchronized * explicitly if an instance is used by multiple threads. * * @see net.lightbody.bmp.proxy.jetty.util.LineInput * @version $Id: HttpInputStream.java,v 1.13 2005/08/23 20:02:26 gregwilkins Exp $ * @author Greg Wilkins (gregw) */ public class HttpInputStream extends FilterInputStream { private static Log log = LogFactory.getLog(HttpInputStream.class); /* ------------------------------------------------------------ */ private static ClosedStream __closedStream=new ClosedStream(); /* ------------------------------------------------------------ */ private ChunkingInputStream _deChunker; private LineInput _realIn; private boolean _chunking; private OutputStream _expectContinues; /* ------------------------------------------------------------ */ /** Constructor. */ public HttpInputStream( InputStream in) { this(in,4096); } /* ------------------------------------------------------------ */ /** Constructor. */ public HttpInputStream(InputStream in, int bufferSize) { super(null); try { _realIn= new LineInput(in,bufferSize,StringUtil.__ISO_8859_1); } catch(UnsupportedEncodingException e) { log.fatal(e); System.exit(1); } this.in=_realIn; } /* ------------------------------------------------------------ */ /** * @param expectContinues The expectContinues to set. */ public OutputStream getExpectContinues() { return _expectContinues; } /* ------------------------------------------------------------ */ /** * @param expectContinues The expectContinues to set. */ public void setExpectContinues(OutputStream expectContinues) { _expectContinues = expectContinues; } /* ------------------------------------------------------------ */ /* * @see java.io.InputStream#read() */ public int read() throws IOException { if (_expectContinues!=null) expectContinues(); return super.read(); } /* ------------------------------------------------------------ */ /* * @see java.io.InputStream#read(byte[], int, int) */ public int read(byte[] b, int off, int len) throws IOException { if (_expectContinues!=null) expectContinues(); return super.read(b, off, len); } /* ------------------------------------------------------------ */ /* * @see java.io.InputStream#read(byte[]) */ public int read(byte[] b) throws IOException { if (_expectContinues!=null) expectContinues(); return super.read(b); } /* ------------------------------------------------------------ */ /* * @see java.io.InputStream#skip(long) */ public long skip(long n) throws IOException { if (_expectContinues!=null) expectContinues(); return super.skip(n); } /* ------------------------------------------------------------ */ private void expectContinues() throws IOException { try { if (available()<=0) { _expectContinues.write(HttpResponse.__Continue); _expectContinues.flush(); } } finally { _expectContinues=null; } } /* ------------------------------------------------------------ */ /** Get the raw stream. * A stream without filters or chunking is returned. This stream * may still be buffered and uprocessed bytes may be in the buffer. * @return Raw InputStream. */ public InputStream getInputStream() { return _realIn; } /* ------------------------------------------------------------ */ /** Get Filter InputStream. * Get the current top of the InputStream filter stack * @return InputStream. */ public InputStream getFilterStream() { return in; } /* ------------------------------------------------------------ */ /** Set Filter InputStream. * Set input filter stream, which should be constructed to wrap * the stream returned from get FilterStream. */ public void setFilterStream(InputStream filter) { in=filter; } /* ------------------------------------------------------------ */ /** Get chunking mode */ public boolean isChunking() { return _chunking; } /* ------------------------------------------------------------ */ /** Set chunking mode. * Chunking can only be turned off with a call to resetStream(). * @exception IllegalStateException Checking cannot be set if * a content length has been set. */ public void setChunking() throws IllegalStateException { if (_realIn.getByteLimit()>=0) throw new IllegalStateException("Has Content-Length"); if (_deChunker==null) _deChunker=new ChunkingInputStream(_realIn); in=_deChunker; _chunking=true; _deChunker._trailer=null; } /* ------------------------------------------------------------ */ /** Reset the stream. * Turn chunking off and disable all filters. * @exception IllegalStateException The stream cannot be reset if * there is some unread chunked input or a content length greater * than zero remaining. */ public void resetStream() throws IllegalStateException { if ((_deChunker!=null && _deChunker._chunkSize>0) || _realIn.getByteLimit()>0) throw new IllegalStateException("Unread input"); if(log.isTraceEnabled())log.trace("resetStream()"); in=_realIn; if (_deChunker!=null) _deChunker.resetStream(); _chunking=false; _realIn.setByteLimit(-1); } /* ------------------------------------------------------------ */ public void close() throws IOException { in=__closedStream; } /* ------------------------------------------------------------ */ /** Set the content length. * Only this number of bytes can be read before EOF is returned. * @param len length. */ public void setContentLength(int len) { if (_chunking && len>=0 && getExpectContinues()==null) throw new IllegalStateException("Chunking"); _realIn.setByteLimit(len); } /* ------------------------------------------------------------ */ void unsafeSetContentLength(int len) { _realIn.setByteLimit(len); } /* ------------------------------------------------------------ */ /** Get the content length. * @return Number of bytes until EOF is returned or -1 for no limit. */ public int getContentLength() { return _realIn.getByteLimit(); } /* ------------------------------------------------------------ */ public HttpFields getTrailer() { return _deChunker._trailer; } /* ------------------------------------------------------------ */ public void destroy() { if (_realIn!=null) _realIn.destroy(); _realIn=null; _deChunker=null; _expectContinues=null; } /* ------------------------------------------------------------ */ /** A closed input stream. */ private static class ClosedStream extends InputStream { /* ------------------------------------------------------------ */ public int read() throws IOException { return -1; } } }