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