mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/io/ChunkedInputStream.java

Wed, 31 Dec 2014 07:22:50 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 07:22:50 +0100
branch
TOR_BUG_3246
changeset 4
fc2d59ddac77
permissions
-rw-r--r--

Correct previous dual key logic pending first delivery installment.

     1 /*
     2  * ====================================================================
     3  * Licensed to the Apache Software Foundation (ASF) under one
     4  * or more contributor license agreements.  See the NOTICE file
     5  * distributed with this work for additional information
     6  * regarding copyright ownership.  The ASF licenses this file
     7  * to you under the Apache License, Version 2.0 (the
     8  * "License"); you may not use this file except in compliance
     9  * with the License.  You may obtain a copy of the License at
    10  *
    11  *   http://www.apache.org/licenses/LICENSE-2.0
    12  *
    13  * Unless required by applicable law or agreed to in writing,
    14  * software distributed under the License is distributed on an
    15  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
    16  * KIND, either express or implied.  See the License for the
    17  * specific language governing permissions and limitations
    18  * under the License.
    19  * ====================================================================
    20  *
    21  * This software consists of voluntary contributions made by many
    22  * individuals on behalf of the Apache Software Foundation.  For more
    23  * information on the Apache Software Foundation, please see
    24  * <http://www.apache.org/>.
    25  *
    26  */
    28 package ch.boye.httpclientandroidlib.impl.io;
    30 import java.io.IOException;
    31 import java.io.InputStream;
    33 import ch.boye.httpclientandroidlib.Header;
    34 import ch.boye.httpclientandroidlib.HttpException;
    35 import ch.boye.httpclientandroidlib.MalformedChunkCodingException;
    36 import ch.boye.httpclientandroidlib.TruncatedChunkException;
    37 import ch.boye.httpclientandroidlib.io.BufferInfo;
    38 import ch.boye.httpclientandroidlib.io.SessionInputBuffer;
    39 import ch.boye.httpclientandroidlib.util.CharArrayBuffer;
    40 import ch.boye.httpclientandroidlib.util.ExceptionUtils;
    42 /**
    43  * Implements chunked transfer coding. The content is received in small chunks.
    44  * Entities transferred using this input stream can be of unlimited length.
    45  * After the stream is read to the end, it provides access to the trailers,
    46  * if any.
    47  * <p>
    48  * Note that this class NEVER closes the underlying stream, even when close
    49  * gets called.  Instead, it will read until the "end" of its chunking on
    50  * close, which allows for the seamless execution of subsequent HTTP 1.1
    51  * requests, while not requiring the client to remember to read the entire
    52  * contents of the response.
    53  *
    54  *
    55  * @since 4.0
    56  *
    57  */
    58 public class ChunkedInputStream extends InputStream {
    60     private static final int CHUNK_LEN               = 1;
    61     private static final int CHUNK_DATA              = 2;
    62     private static final int CHUNK_CRLF              = 3;
    64     private static final int BUFFER_SIZE = 2048;
    66     /** The session input buffer */
    67     private final SessionInputBuffer in;
    69     private final CharArrayBuffer buffer;
    71     private int state;
    73     /** The chunk size */
    74     private int chunkSize;
    76     /** The current position within the current chunk */
    77     private int pos;
    79     /** True if we've reached the end of stream */
    80     private boolean eof = false;
    82     /** True if this stream is closed */
    83     private boolean closed = false;
    85     private Header[] footers = new Header[] {};
    87     /**
    88      * Wraps session input stream and reads chunk coded input.
    89      *
    90      * @param in The session input buffer
    91      */
    92     public ChunkedInputStream(final SessionInputBuffer in) {
    93         super();
    94         if (in == null) {
    95             throw new IllegalArgumentException("Session input buffer may not be null");
    96         }
    97         this.in = in;
    98         this.pos = 0;
    99         this.buffer = new CharArrayBuffer(16);
   100         this.state = CHUNK_LEN;
   101     }
   103     public int available() throws IOException {
   104         if (this.in instanceof BufferInfo) {
   105             int len = ((BufferInfo) this.in).length();
   106             return Math.min(len, this.chunkSize - this.pos);
   107         } else {
   108             return 0;
   109         }
   110     }
   112     /**
   113      * <p> Returns all the data in a chunked stream in coalesced form. A chunk
   114      * is followed by a CRLF. The method returns -1 as soon as a chunksize of 0
   115      * is detected.</p>
   116      *
   117      * <p> Trailer headers are read automatically at the end of the stream and
   118      * can be obtained with the getResponseFooters() method.</p>
   119      *
   120      * @return -1 of the end of the stream has been reached or the next data
   121      * byte
   122      * @throws IOException in case of an I/O error
   123      */
   124     public int read() throws IOException {
   125         if (this.closed) {
   126             throw new IOException("Attempted read from closed stream.");
   127         }
   128         if (this.eof) {
   129             return -1;
   130         }
   131         if (state != CHUNK_DATA) {
   132             nextChunk();
   133             if (this.eof) {
   134                 return -1;
   135             }
   136         }
   137         int b = in.read();
   138         if (b != -1) {
   139             pos++;
   140             if (pos >= chunkSize) {
   141                 state = CHUNK_CRLF;
   142             }
   143         }
   144         return b;
   145     }
   147     /**
   148      * Read some bytes from the stream.
   149      * @param b The byte array that will hold the contents from the stream.
   150      * @param off The offset into the byte array at which bytes will start to be
   151      * placed.
   152      * @param len the maximum number of bytes that can be returned.
   153      * @return The number of bytes returned or -1 if the end of stream has been
   154      * reached.
   155      * @throws IOException in case of an I/O error
   156      */
   157     public int read (byte[] b, int off, int len) throws IOException {
   159         if (closed) {
   160             throw new IOException("Attempted read from closed stream.");
   161         }
   163         if (eof) {
   164             return -1;
   165         }
   166         if (state != CHUNK_DATA) {
   167             nextChunk();
   168             if (eof) {
   169                 return -1;
   170             }
   171         }
   172         len = Math.min(len, chunkSize - pos);
   173         int bytesRead = in.read(b, off, len);
   174         if (bytesRead != -1) {
   175             pos += bytesRead;
   176             if (pos >= chunkSize) {
   177                 state = CHUNK_CRLF;
   178             }
   179             return bytesRead;
   180         } else {
   181             eof = true;
   182             throw new TruncatedChunkException("Truncated chunk "
   183                     + "( expected size: " + chunkSize
   184                     + "; actual size: " + pos + ")");
   185         }
   186     }
   188     /**
   189      * Read some bytes from the stream.
   190      * @param b The byte array that will hold the contents from the stream.
   191      * @return The number of bytes returned or -1 if the end of stream has been
   192      * reached.
   193      * @throws IOException in case of an I/O error
   194      */
   195     public int read (byte[] b) throws IOException {
   196         return read(b, 0, b.length);
   197     }
   199     /**
   200      * Read the next chunk.
   201      * @throws IOException in case of an I/O error
   202      */
   203     private void nextChunk() throws IOException {
   204         chunkSize = getChunkSize();
   205         if (chunkSize < 0) {
   206             throw new MalformedChunkCodingException("Negative chunk size");
   207         }
   208         state = CHUNK_DATA;
   209         pos = 0;
   210         if (chunkSize == 0) {
   211             eof = true;
   212             parseTrailerHeaders();
   213         }
   214     }
   216     /**
   217      * Expects the stream to start with a chunksize in hex with optional
   218      * comments after a semicolon. The line must end with a CRLF: "a3; some
   219      * comment\r\n" Positions the stream at the start of the next line.
   220      *
   221      * @param in The new input stream.
   222      * @param required <tt>true<tt/> if a valid chunk must be present,
   223      *                 <tt>false<tt/> otherwise.
   224      *
   225      * @return the chunk size as integer
   226      *
   227      * @throws IOException when the chunk size could not be parsed
   228      */
   229     private int getChunkSize() throws IOException {
   230         int st = this.state;
   231         switch (st) {
   232         case CHUNK_CRLF:
   233             this.buffer.clear();
   234             int i = this.in.readLine(this.buffer);
   235             if (i == -1) {
   236                 return 0;
   237             }
   238             if (!this.buffer.isEmpty()) {
   239                 throw new MalformedChunkCodingException(
   240                     "Unexpected content at the end of chunk");
   241             }
   242             state = CHUNK_LEN;
   243             //$FALL-THROUGH$
   244         case CHUNK_LEN:
   245             this.buffer.clear();
   246             i = this.in.readLine(this.buffer);
   247             if (i == -1) {
   248                 return 0;
   249             }
   250             int separator = this.buffer.indexOf(';');
   251             if (separator < 0) {
   252                 separator = this.buffer.length();
   253             }
   254             try {
   255                 return Integer.parseInt(this.buffer.substringTrimmed(0, separator), 16);
   256             } catch (NumberFormatException e) {
   257                 throw new MalformedChunkCodingException("Bad chunk header");
   258             }
   259         default:
   260             throw new IllegalStateException("Inconsistent codec state");
   261         }
   262     }
   264     /**
   265      * Reads and stores the Trailer headers.
   266      * @throws IOException in case of an I/O error
   267      */
   268     private void parseTrailerHeaders() throws IOException {
   269         try {
   270             this.footers = AbstractMessageParser.parseHeaders
   271                 (in, -1, -1, null);
   272         } catch (HttpException e) {
   273             IOException ioe = new MalformedChunkCodingException("Invalid footer: "
   274                     + e.getMessage());
   275             ExceptionUtils.initCause(ioe, e);
   276             throw ioe;
   277         }
   278     }
   280     /**
   281      * Upon close, this reads the remainder of the chunked message,
   282      * leaving the underlying socket at a position to start reading the
   283      * next response without scanning.
   284      * @throws IOException in case of an I/O error
   285      */
   286     public void close() throws IOException {
   287         if (!closed) {
   288             try {
   289                 if (!eof) {
   290                     // read and discard the remainder of the message
   291                     byte buffer[] = new byte[BUFFER_SIZE];
   292                     while (read(buffer) >= 0) {
   293                     }
   294                 }
   295             } finally {
   296                 eof = true;
   297                 closed = true;
   298             }
   299         }
   300     }
   302     public Header[] getFooters() {
   303         return (Header[])this.footers.clone();
   304     }
   306 }

mercurial