001: package net.matuschek.util;
002:
003: import java.io.FilterInputStream;
004: import java.io.IOException;
005: import java.io.InputStream;
006: import java.util.Enumeration;
007: import java.util.Vector;
008:
009: // ChunkedInputStream - an InputStream that implements HTTP/1.1 chunking
010: //
011: // Copyright (C) 1996,1998 by Jef Poskanzer <jef@acme.com>. All rights reserved.
012: //
013: // Redistribution and use in source and binary forms, with or without
014: // modification, are permitted provided that the following conditions
015: // are met:
016: // 1. Redistributions of source code must retain the above copyright
017: // notice, this list of conditions and the following disclaimer.
018: // 2. Redistributions in binary form must reproduce the above copyright
019: // notice, this list of conditions and the following disclaimer in the
020: // documentation and/or other materials provided with the distribution.
021: //
022: // THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
023: // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
024: // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
025: // ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
026: // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
027: // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
028: // OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
029: // HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
030: // LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
031: // OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
032: // SUCH DAMAGE.
033: //
034: // Visit the ACME Labs Java page for up-to-date versions of this and other
035: // fine Java utilities: http://www.acme.com/java/
036:
037: /**
038: * Modifications done by Daniel Matuschek (daniel@matuschek.net)
039: * - modified JavaDoc documentation
040: * - adapted to Java 1.2, removed deprecated DataInputStream.readLine() method
041: * - replaced DataInputStream by InputStream (there was no need for a
042: * DatainputStream, not idea why this was used in the original version)
043: * - fixed a bug (there is an CRLF after every the data block)
044: */
045:
046: /**
047: * An InputStream that implements HTTP/1.1 chunking.
048: * <P>
049: * This class lets a Servlet read its request data as an HTTP/1.1 chunked
050: * stream. Chunked streams are a way to send arbitrary-length data without
051: * having to know beforehand how much you're going to send. They are
052: * introduced by a "Transfer-Encoding: chunked" header, so if such a header
053: * appears in an HTTP request you should use this class to read any data.
054: * <P>
055: * Sample usage:
056: * <BLOCKQUOTE><PRE><CODE>
057: * InputStream in = req.getInputStream();
058: * if ( "chunked".equals( req.getHeader( "Transfer-Encoding" ) ) )
059: * in = new ChunkedInputStream( in );
060: * </CODE></PRE></BLOCKQUOTE>
061: * <P>
062: * Because it would be impolite to make the authors of every Servlet include
063: * the above code, this is general done at the server level so that it
064: * happens automatically. Servlet authors will generally not create
065: * ChunkedInputStreams. This is in contrast with ChunkedOutputStream,
066: * which Servlets have to call themselves if they want to use it.
067: * <P>
068: * <A HREF="/resources/classes/Acme/Serve/servlet/http/ChunkedInputStream.java">Fetch the software.</A><BR>
069: * <A HREF="/resources/classes/Acme.tar.gz">Fetch the entire Acme package.</A>
070: *
071: * @author Jef Poskanzer
072: * @author Daniel Matuschek
073: * @version $Id: ChunkedInputStream.java,v 1.6 2002/05/31 14:45:56 matuschd Exp $
074: */
075: public class ChunkedInputStream extends FilterInputStream {
076:
077: private int contentLength;
078: private byte[] b1 = new byte[1];
079:
080: /** number of bytes available in the current chunk */
081: private int chunkCount = 0;
082:
083: private Vector<String> footerNames = null;
084: private Vector<String> footerValues = null;
085:
086: /**
087: * Make a ChunkedInputStream.
088: */
089: public ChunkedInputStream(InputStream in) {
090: super (in);
091: contentLength = 0;
092: }
093:
094: /**
095: * The FilterInputStream implementation of the single-byte read()
096: * method just reads directly from the underlying stream. We want
097: * to go through our own read-block method, so we have to override.
098: * Seems like FilterInputStream really ought to do this itself.
099: */
100: public int read() throws IOException {
101: if (read(b1, 0, 1) == -1) {
102: return -1;
103: }
104:
105: return b1[0];
106: }
107:
108: /**
109: * Reads into an array of bytes.
110: * @param b the buffer into which the data is read
111: * @param off the start offset of the data
112: * @param len the maximum number of bytes read
113: * @return the actual number of bytes read, or -1 on EOF
114: * @exception IOException if an I/O error has occurred
115: */
116: public int read(byte[] b, int off, int len) throws IOException {
117: if (chunkCount == 0) {
118: startChunk();
119: if (chunkCount == 0) {
120: return -1;
121: }
122: }
123: int toRead = Math.min(chunkCount, len);
124: int r = in.read(b, off, toRead);
125:
126: if (r != -1) {
127: chunkCount -= r;
128: }
129: return r;
130: }
131:
132: /**
133: * Reads the start of a chunk.
134: */
135: private void startChunk() throws IOException {
136: String line = readLine();
137: if (line.equals("")) {
138: line = readLine();
139: }
140:
141: try {
142: chunkCount = Integer.parseInt(line.trim(), 16);
143: } catch (NumberFormatException e) {
144: throw new IOException("malformed chunk (" + line + ")");
145: }
146: contentLength += chunkCount;
147: if (chunkCount == 0) {
148: readFooters();
149: }
150:
151: }
152:
153: /**
154: * Reads any footers.
155: */
156: private void readFooters() throws IOException {
157: footerNames = new Vector<String>();
158: footerValues = new Vector<String>();
159: String line;
160: while (true) {
161: line = readLine();
162: if (line.length() == 0)
163: break;
164: int colon = line.indexOf(':');
165: if (colon != -1) {
166: String name = line.substring(0, colon).toLowerCase();
167: String value = line.substring(colon + 1).trim();
168: footerNames.addElement(name.toLowerCase());
169: footerValues.addElement(value);
170: }
171: }
172: }
173:
174: /**
175: * Returns the value of a footer field, or null if not known.
176: * Footers come at the end of a chunked stream, so trying to
177: * retrieve them before the stream has given an EOF will return
178: * only nulls.
179: * @param name the footer field name
180: */
181: public String getFooter(String name) {
182: if (!isDone())
183: return null;
184: int i = footerNames.indexOf(name.toLowerCase());
185: if (i == -1)
186: return null;
187: return (String) footerValues.elementAt(i);
188: }
189:
190: /**
191: * Returns an Enumeration of the footer names.
192: */
193: public Enumeration getFooters() {
194: if (!isDone())
195: return null;
196: return footerNames.elements();
197: }
198:
199: /**
200: * Returns the size of the request entity data, or -1 if not known.
201: */
202: public int getContentLength() {
203: if (!isDone()) {
204: return -1;
205: }
206: return contentLength;
207: }
208:
209: /**
210: * Tells whether the stream has gotten to its end yet. Remembering
211: * whether you've gotten an EOF works fine too, but this is a convenient
212: * predicate. java.io.InputStream should probably have its own isEof()
213: * predicate.
214: */
215: public boolean isDone() {
216: return footerNames != null;
217: }
218:
219: /**
220: * ChunkedInputStream used DataInputStream.readLine() before. This method
221: * is deprecated, therefore we will it replace by our own method.
222: * Because the chunk lines only use 7bit ASCII, we can use the
223: * system default encoding
224: * The data lines itself will not be read using this readLine method
225: * but by a block read
226: */
227: protected String readLine() throws IOException {
228: final byte CR = 13;
229: final byte LF = 10;
230:
231: ByteBuffer buff = new ByteBuffer();
232: byte b = 0;
233:
234: int i = 0;
235: do {
236: b = (byte) this .in.read();
237: if (b != LF) {
238: buff.append(b);
239: }
240: i++;
241: } while ((b != LF));
242:
243: // according to the RFC there must be a CR before the LF, but some
244: // web servers don't do this :-(
245: byte[] byteBuff = buff.getContent();
246:
247: if (byteBuff.length == 0) {
248: return "";
249: }
250:
251: if (byteBuff[byteBuff.length - 1] != CR) {
252: return new String(byteBuff);
253: } else {
254: return new String(byteBuff, 0, byteBuff.length - 1);
255: }
256: }
257:
258: }
|