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 public class ChunkedInputStream extends InputStream { 72 73 /** The session input buffer */ 74 private SessionInputBuffer in; 75 76 private final CharArrayBuffer buffer; 77 78 /** The chunk size */ 79 private int chunkSize; 80 81 /** The current position within the current chunk */ 82 private int pos; 83 84 /** True if we'are at the beginning of stream */ 85 private boolean bof = true; 86 87 /** True if we've reached the end of stream */ 88 private boolean eof = false; 89 90 /** True if this stream is closed */ 91 private boolean closed = false; 92 93 private Header[] footers = new Header[] {}; 94 ChunkedInputStream(final SessionInputBuffer in)95 public ChunkedInputStream(final SessionInputBuffer in) { 96 super(); 97 if (in == null) { 98 throw new IllegalArgumentException("Session input buffer may not be null"); 99 } 100 this.in = in; 101 this.pos = 0; 102 this.buffer = new CharArrayBuffer(16); 103 } 104 105 /** 106 * <p> Returns all the data in a chunked stream in coalesced form. A chunk 107 * is followed by a CRLF. The method returns -1 as soon as a chunksize of 0 108 * is detected.</p> 109 * 110 * <p> Trailer headers are read automcatically at the end of the stream and 111 * can be obtained with the getResponseFooters() method.</p> 112 * 113 * @return -1 of the end of the stream has been reached or the next data 114 * byte 115 * @throws IOException If an IO problem occurs 116 */ read()117 public int read() throws IOException { 118 if (this.closed) { 119 throw new IOException("Attempted read from closed stream."); 120 } 121 if (this.eof) { 122 return -1; 123 } 124 if (this.pos >= this.chunkSize) { 125 nextChunk(); 126 if (this.eof) { 127 return -1; 128 } 129 } 130 pos++; 131 return in.read(); 132 } 133 134 /** 135 * Read some bytes from the stream. 136 * @param b The byte array that will hold the contents from the stream. 137 * @param off The offset into the byte array at which bytes will start to be 138 * placed. 139 * @param len the maximum number of bytes that can be returned. 140 * @return The number of bytes returned or -1 if the end of stream has been 141 * reached. 142 * @see java.io.InputStream#read(byte[], int, int) 143 * @throws IOException if an IO problem occurs. 144 */ read(byte[] b, int off, int len)145 public int read (byte[] b, int off, int len) throws IOException { 146 147 if (closed) { 148 throw new IOException("Attempted read from closed stream."); 149 } 150 151 if (eof) { 152 return -1; 153 } 154 if (pos >= chunkSize) { 155 nextChunk(); 156 if (eof) { 157 return -1; 158 } 159 } 160 len = Math.min(len, chunkSize - pos); 161 int count = in.read(b, off, len); 162 pos += count; 163 return count; 164 } 165 166 /** 167 * Read some bytes from the stream. 168 * @param b The byte array that will hold the contents from the stream. 169 * @return The number of bytes returned or -1 if the end of stream has been 170 * reached. 171 * @see java.io.InputStream#read(byte[]) 172 * @throws IOException if an IO problem occurs. 173 */ read(byte[] b)174 public int read (byte[] b) throws IOException { 175 return read(b, 0, b.length); 176 } 177 178 /** 179 * Read the next chunk. 180 * @throws IOException If an IO error occurs. 181 */ nextChunk()182 private void nextChunk() throws IOException { 183 chunkSize = getChunkSize(); 184 if (chunkSize < 0) { 185 throw new MalformedChunkCodingException("Negative chunk size"); 186 } 187 bof = false; 188 pos = 0; 189 if (chunkSize == 0) { 190 eof = true; 191 parseTrailerHeaders(); 192 } 193 } 194 195 /** 196 * Expects the stream to start with a chunksize in hex with optional 197 * comments after a semicolon. The line must end with a CRLF: "a3; some 198 * comment\r\n" Positions the stream at the start of the next line. 199 * 200 * @param in The new input stream. 201 * @param required <tt>true<tt/> if a valid chunk must be present, 202 * <tt>false<tt/> otherwise. 203 * 204 * @return the chunk size as integer 205 * 206 * @throws IOException when the chunk size could not be parsed 207 */ getChunkSize()208 private int getChunkSize() throws IOException { 209 // skip CRLF 210 if (!bof) { 211 int cr = in.read(); 212 int lf = in.read(); 213 if ((cr != HTTP.CR) || (lf != HTTP.LF)) { 214 throw new MalformedChunkCodingException( 215 "CRLF expected at end of chunk"); 216 } 217 } 218 //parse data 219 this.buffer.clear(); 220 int i = this.in.readLine(this.buffer); 221 if (i == -1) { 222 throw new MalformedChunkCodingException( 223 "Chunked stream ended unexpectedly"); 224 } 225 int separator = this.buffer.indexOf(';'); 226 if (separator < 0) { 227 separator = this.buffer.length(); 228 } 229 try { 230 return Integer.parseInt(this.buffer.substringTrimmed(0, separator), 16); 231 } catch (NumberFormatException e) { 232 throw new MalformedChunkCodingException("Bad chunk header"); 233 } 234 } 235 236 /** 237 * Reads and stores the Trailer headers. 238 * @throws IOException If an IO problem occurs 239 */ parseTrailerHeaders()240 private void parseTrailerHeaders() throws IOException { 241 try { 242 this.footers = AbstractMessageParser.parseHeaders 243 (in, -1, -1, null); 244 } catch (HttpException e) { 245 IOException ioe = new MalformedChunkCodingException("Invalid footer: " 246 + e.getMessage()); 247 ExceptionUtils.initCause(ioe, e); 248 throw ioe; 249 } 250 } 251 252 /** 253 * Upon close, this reads the remainder of the chunked message, 254 * leaving the underlying socket at a position to start reading the 255 * next response without scanning. 256 * @throws IOException If an IO problem occurs. 257 */ close()258 public void close() throws IOException { 259 if (!closed) { 260 try { 261 if (!eof) { 262 exhaustInputStream(this); 263 } 264 } finally { 265 eof = true; 266 closed = true; 267 } 268 } 269 } 270 getFooters()271 public Header[] getFooters() { 272 return (Header[])this.footers.clone(); 273 } 274 275 /** 276 * Exhaust an input stream, reading until EOF has been encountered. 277 * 278 * <p>Note that this function is intended as a non-public utility. 279 * This is a little weird, but it seemed silly to make a utility 280 * class for this one function, so instead it is just static and 281 * shared that way.</p> 282 * 283 * @param inStream The {@link InputStream} to exhaust. 284 * @throws IOException If an IO problem occurs 285 */ exhaustInputStream(final InputStream inStream)286 static void exhaustInputStream(final InputStream inStream) throws IOException { 287 // read and discard the remainder of the message 288 byte buffer[] = new byte[1024]; 289 while (inStream.read(buffer) >= 0) { 290 ; 291 } 292 } 293 294 } 295