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 
32 package org.apache.http.impl.io;
33 
34 import java.io.IOException;
35 import java.io.InputStream;
36 
37 import org.apache.http.Header;
38 import org.apache.http.HttpException;
39 import org.apache.http.MalformedChunkCodingException;
40 import org.apache.http.io.SessionInputBuffer;
41 import org.apache.http.protocol.HTTP;
42 import org.apache.http.util.CharArrayBuffer;
43 import 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
76 public 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 
ChunkedInputStream(final SessionInputBuffer in)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      */
read()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      */
read(byte[] b, int off, int len)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      */
read(byte[] b)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      */
nextChunk()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      */
getChunkSize()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      */
parseTrailerHeaders()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      */
close()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 
getFooters()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      */
exhaustInputStream(final InputStream inStream)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