1/*
2 * Copyright (C) 2010 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package libcore.net.http;
18
19import java.io.IOException;
20import java.io.InputStream;
21import java.net.CacheRequest;
22import java.util.Arrays;
23import libcore.io.Streams;
24
25/**
26 * An HTTP body with alternating chunk sizes and chunk bodies.
27 */
28final class ChunkedInputStream extends AbstractHttpInputStream {
29    private static final int MIN_LAST_CHUNK_LENGTH = "\r\n0\r\n\r\n".length();
30    private static final int NO_CHUNK_YET = -1;
31    private int bytesRemainingInChunk = NO_CHUNK_YET;
32    private boolean hasMoreChunks = true;
33
34    ChunkedInputStream(InputStream is, CacheRequest cacheRequest,
35            HttpEngine httpEngine) throws IOException {
36        super(is, httpEngine, cacheRequest);
37    }
38
39    @Override public int read(byte[] buffer, int offset, int count) throws IOException {
40        Arrays.checkOffsetAndCount(buffer.length, offset, count);
41        checkNotClosed();
42
43        if (!hasMoreChunks) {
44            return -1;
45        }
46        if (bytesRemainingInChunk == 0 || bytesRemainingInChunk == NO_CHUNK_YET) {
47            readChunkSize();
48            if (!hasMoreChunks) {
49                return -1;
50            }
51        }
52        int read = in.read(buffer, offset, Math.min(count, bytesRemainingInChunk));
53        if (read == -1) {
54            unexpectedEndOfInput(); // the server didn't supply the promised chunk length
55            throw new IOException("unexpected end of stream");
56        }
57        bytesRemainingInChunk -= read;
58        cacheWrite(buffer, offset, read);
59
60        /*
61         * If we're at the end of a chunk and the next chunk size is readable,
62         * read it! Reading the last chunk causes the underlying connection to
63         * be recycled and we want to do that as early as possible. Otherwise
64         * self-delimiting streams like gzip will never be recycled.
65         * http://code.google.com/p/android/issues/detail?id=7059
66         */
67        if (bytesRemainingInChunk == 0 && in.available() >= MIN_LAST_CHUNK_LENGTH) {
68            readChunkSize();
69        }
70
71        return read;
72    }
73
74    private void readChunkSize() throws IOException {
75        // read the suffix of the previous chunk
76        if (bytesRemainingInChunk != NO_CHUNK_YET) {
77            Streams.readAsciiLine(in);
78        }
79        String chunkSizeString = Streams.readAsciiLine(in);
80        int index = chunkSizeString.indexOf(";");
81        if (index != -1) {
82            chunkSizeString = chunkSizeString.substring(0, index);
83        }
84        try {
85            bytesRemainingInChunk = Integer.parseInt(chunkSizeString.trim(), 16);
86        } catch (NumberFormatException e) {
87            throw new IOException("Expected a hex chunk size, but was " + chunkSizeString);
88        }
89        if (bytesRemainingInChunk == 0) {
90            hasMoreChunks = false;
91            httpEngine.readTrailers();
92            endOfInput(true);
93        }
94    }
95
96    @Override public int available() throws IOException {
97        checkNotClosed();
98        if (!hasMoreChunks || bytesRemainingInChunk == NO_CHUNK_YET) {
99            return 0;
100        }
101        return Math.min(in.available(), bytesRemainingInChunk);
102    }
103
104    @Override public void close() throws IOException {
105        if (closed) {
106            return;
107        }
108
109        closed = true;
110        if (hasMoreChunks) {
111            unexpectedEndOfInput();
112        }
113    }
114}
115