1/*
2 * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/impl/io/ChunkedInputStream.java $
3 * $Revision: 569843 $
4 * $Date: 2007-08-26 10:05:40 -0700 (Sun, 26 Aug 2007) $
5 *
6 * ====================================================================
7 * Licensed to the Apache Software Foundation (ASF) under one
8 * or more contributor license agreements.  See the NOTICE file
9 * distributed with this work for additional information
10 * regarding copyright ownership.  The ASF licenses this file
11 * to you under the Apache License, Version 2.0 (the
12 * "License"); you may not use this file except in compliance
13 * with the License.  You may obtain a copy of the License at
14 *
15 *   http://www.apache.org/licenses/LICENSE-2.0
16 *
17 * Unless required by applicable law or agreed to in writing,
18 * software distributed under the License is distributed on an
19 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
20 * KIND, either express or implied.  See the License for the
21 * specific language governing permissions and limitations
22 * under the License.
23 * ====================================================================
24 *
25 * This software consists of voluntary contributions made by many
26 * individuals on behalf of the Apache Software Foundation.  For more
27 * information on the Apache Software Foundation, please see
28 * <http://www.apache.org/>.
29 *
30 */
31
32package org.apache.http.impl.io;
33
34import java.io.IOException;
35import java.io.InputStream;
36
37import org.apache.http.Header;
38import org.apache.http.HttpException;
39import org.apache.http.MalformedChunkCodingException;
40import org.apache.http.io.SessionInputBuffer;
41import org.apache.http.protocol.HTTP;
42import org.apache.http.util.CharArrayBuffer;
43import org.apache.http.util.ExceptionUtils;
44
45/**
46 * Implements chunked transfer coding.
47 * See <a href="http://www.w3.org/Protocols/rfc2616/rfc2616.txt">RFC 2616</a>,
48 * <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.6">section 3.6.1</a>.
49 * It transparently coalesces chunks of a HTTP stream that uses chunked
50 * transfer coding. After the stream is read to the end, it provides access
51 * to the trailers, if any.
52 * <p>
53 * Note that this class NEVER closes the underlying stream, even when close
54 * gets called.  Instead, it will read until the "end" of its chunking on
55 * close, which allows for the seamless execution of subsequent HTTP 1.1
56 * requests, while not requiring the client to remember to read the entire
57 * contents of the response.
58 * </p>
59 *
60 * @author Ortwin Glueck
61 * @author Sean C. Sullivan
62 * @author Martin Elwin
63 * @author Eric Johnson
64 * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a>
65 * @author Michael Becke
66 * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a>
67 *
68 * @since 4.0
69 *
70 *
71 * @deprecated Please use {@link java.net.URL#openConnection} instead.
72 *     Please visit <a href="http://android-developers.blogspot.com/2011/09/androids-http-clients.html">this webpage</a>
73 *     for further details.
74 */
75@Deprecated
76public class ChunkedInputStream extends InputStream {
77
78    /** The session input buffer */
79    private SessionInputBuffer in;
80
81    private final CharArrayBuffer buffer;
82
83    /** The chunk size */
84    private int chunkSize;
85
86    /** The current position within the current chunk */
87    private int pos;
88
89    /** True if we'are at the beginning of stream */
90    private boolean bof = true;
91
92    /** True if we've reached the end of stream */
93    private boolean eof = false;
94
95    /** True if this stream is closed */
96    private boolean closed = false;
97
98    private Header[] footers = new Header[] {};
99
100    public ChunkedInputStream(final SessionInputBuffer in) {
101        super();
102        if (in == null) {
103            throw new IllegalArgumentException("Session input buffer may not be null");
104        }
105        this.in = in;
106        this.pos = 0;
107        this.buffer = new CharArrayBuffer(16);
108    }
109
110    /**
111     * <p> Returns all the data in a chunked stream in coalesced form. A chunk
112     * is followed by a CRLF. The method returns -1 as soon as a chunksize of 0
113     * is detected.</p>
114     *
115     * <p> Trailer headers are read automcatically at the end of the stream and
116     * can be obtained with the getResponseFooters() method.</p>
117     *
118     * @return -1 of the end of the stream has been reached or the next data
119     * byte
120     * @throws IOException If an IO problem occurs
121     */
122    public int read() throws IOException {
123        if (this.closed) {
124            throw new IOException("Attempted read from closed stream.");
125        }
126        if (this.eof) {
127            return -1;
128        }
129        if (this.pos >= this.chunkSize) {
130            nextChunk();
131            if (this.eof) {
132                return -1;
133            }
134        }
135        pos++;
136        return in.read();
137    }
138
139    /**
140     * Read some bytes from the stream.
141     * @param b The byte array that will hold the contents from the stream.
142     * @param off The offset into the byte array at which bytes will start to be
143     * placed.
144     * @param len the maximum number of bytes that can be returned.
145     * @return The number of bytes returned or -1 if the end of stream has been
146     * reached.
147     * @see java.io.InputStream#read(byte[], int, int)
148     * @throws IOException if an IO problem occurs.
149     */
150    public int read (byte[] b, int off, int len) throws IOException {
151
152        if (closed) {
153            throw new IOException("Attempted read from closed stream.");
154        }
155
156        if (eof) {
157            return -1;
158        }
159        if (pos >= chunkSize) {
160            nextChunk();
161            if (eof) {
162                return -1;
163            }
164        }
165        len = Math.min(len, chunkSize - pos);
166        int count = in.read(b, off, len);
167        pos += count;
168        return count;
169    }
170
171    /**
172     * Read some bytes from the stream.
173     * @param b The byte array that will hold the contents from the stream.
174     * @return The number of bytes returned or -1 if the end of stream has been
175     * reached.
176     * @see java.io.InputStream#read(byte[])
177     * @throws IOException if an IO problem occurs.
178     */
179    public int read (byte[] b) throws IOException {
180        return read(b, 0, b.length);
181    }
182
183    /**
184     * Read the next chunk.
185     * @throws IOException If an IO error occurs.
186     */
187    private void nextChunk() throws IOException {
188        chunkSize = getChunkSize();
189        if (chunkSize < 0) {
190            throw new MalformedChunkCodingException("Negative chunk size");
191        }
192        bof = false;
193        pos = 0;
194        if (chunkSize == 0) {
195            eof = true;
196            parseTrailerHeaders();
197        }
198    }
199
200    /**
201     * Expects the stream to start with a chunksize in hex with optional
202     * comments after a semicolon. The line must end with a CRLF: "a3; some
203     * comment\r\n" Positions the stream at the start of the next line.
204     *
205     * @param in The new input stream.
206     * @param required <tt>true<tt/> if a valid chunk must be present,
207     *                 <tt>false<tt/> otherwise.
208     *
209     * @return the chunk size as integer
210     *
211     * @throws IOException when the chunk size could not be parsed
212     */
213    private int getChunkSize() throws IOException {
214        // skip CRLF
215        if (!bof) {
216            int cr = in.read();
217            int lf = in.read();
218            if ((cr != HTTP.CR) || (lf != HTTP.LF)) {
219                throw new MalformedChunkCodingException(
220                    "CRLF expected at end of chunk");
221            }
222        }
223        //parse data
224        this.buffer.clear();
225        int i = this.in.readLine(this.buffer);
226        if (i == -1) {
227            throw new MalformedChunkCodingException(
228                    "Chunked stream ended unexpectedly");
229        }
230        int separator = this.buffer.indexOf(';');
231        if (separator < 0) {
232            separator = this.buffer.length();
233        }
234        try {
235            return Integer.parseInt(this.buffer.substringTrimmed(0, separator), 16);
236        } catch (NumberFormatException e) {
237            throw new MalformedChunkCodingException("Bad chunk header");
238        }
239    }
240
241    /**
242     * Reads and stores the Trailer headers.
243     * @throws IOException If an IO problem occurs
244     */
245    private void parseTrailerHeaders() throws IOException {
246        try {
247            this.footers = AbstractMessageParser.parseHeaders
248                (in, -1, -1, null);
249        } catch (HttpException e) {
250            IOException ioe = new MalformedChunkCodingException("Invalid footer: "
251                    + e.getMessage());
252            ExceptionUtils.initCause(ioe, e);
253            throw ioe;
254        }
255    }
256
257    /**
258     * Upon close, this reads the remainder of the chunked message,
259     * leaving the underlying socket at a position to start reading the
260     * next response without scanning.
261     * @throws IOException If an IO problem occurs.
262     */
263    public void close() throws IOException {
264        if (!closed) {
265            try {
266                if (!eof) {
267                    exhaustInputStream(this);
268                }
269            } finally {
270                eof = true;
271                closed = true;
272            }
273        }
274    }
275
276    public Header[] getFooters() {
277        return (Header[])this.footers.clone();
278    }
279
280    /**
281     * Exhaust an input stream, reading until EOF has been encountered.
282     *
283     * <p>Note that this function is intended as a non-public utility.
284     * This is a little weird, but it seemed silly to make a utility
285     * class for this one function, so instead it is just static and
286     * shared that way.</p>
287     *
288     * @param inStream The {@link InputStream} to exhaust.
289     * @throws IOException If an IO problem occurs
290     */
291    static void exhaustInputStream(final InputStream inStream) throws IOException {
292        // read and discard the remainder of the message
293        byte buffer[] = new byte[1024];
294        while (inStream.read(buffer) >= 0) {
295            ;
296        }
297    }
298
299}
300