~ubuntu-branches/ubuntu/lucid/commons-httpclient/lucid

« back to all changes in this revision

Viewing changes to src/java/org/apache/commons/httpclient/ChunkedInputStream.java

  • Committer: Bazaar Package Importer
  • Author(s): Barry Hawkins
  • Date: 2005-11-25 13:12:23 UTC
  • Revision ID: james.westby@ubuntu.com-20051125131223-2g7eyo21pqgrohpo
Tags: upstream-2.0.2
ImportĀ upstreamĀ versionĀ 2.0.2

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
2
 * $Header: /home/cvs/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/ChunkedInputStream.java,v 1.16.2.2 2004/02/22 18:21:13 olegk Exp $
 
3
 * $Revision: 1.16.2.2 $
 
4
 * $Date: 2004/02/22 18:21:13 $
 
5
 *
 
6
 * ====================================================================
 
7
 *
 
8
 *  Copyright 2002-2004 The Apache Software Foundation
 
9
 *
 
10
 *  Licensed under the Apache License, Version 2.0 (the "License");
 
11
 *  you may not use this file except in compliance with the License.
 
12
 *  You may obtain a copy of the License at
 
13
 *
 
14
 *      http://www.apache.org/licenses/LICENSE-2.0
 
15
 *
 
16
 *  Unless required by applicable law or agreed to in writing, software
 
17
 *  distributed under the License is distributed on an "AS IS" BASIS,
 
18
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 
19
 *  See the License for the specific language governing permissions and
 
20
 *  limitations under the License.
 
21
 * ====================================================================
 
22
 *
 
23
 * This software consists of voluntary contributions made by many
 
24
 * individuals on behalf of the Apache Software Foundation.  For more
 
25
 * information on the Apache Software Foundation, please see
 
26
 * <http://www.apache.org/>.
 
27
 *
 
28
 * [Additional notices, if required by prior licensing conditions]
 
29
 *
 
30
 */
 
31
 
 
32
package org.apache.commons.httpclient;
 
33
 
 
34
import java.io.ByteArrayOutputStream;
 
35
import java.io.IOException;
 
36
import java.io.InputStream;
 
37
 
 
38
 
 
39
/**
 
40
 * <p>Transparently coalesces chunks of a HTTP stream that uses
 
41
 * Transfer-Encoding chunked.</p>
 
42
 *
 
43
 * <p>Note that this class NEVER closes the underlying stream, even when close
 
44
 * gets called.  Instead, it will read until the "end" of its chunking on close,
 
45
 * which allows for the seamless invocation of subsequent HTTP 1.1 calls, while
 
46
 * not requiring the client to remember to read the entire contents of the
 
47
 * response.</p>
 
48
 *
 
49
 * @see ResponseInputStream
 
50
 *
 
51
 * @author Ortwin Glļæ½ck
 
52
 * @author Sean C. Sullivan
 
53
 * @author Martin Elwin
 
54
 * @author Eric Johnson
 
55
 * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a>
 
56
 * @author Michael Becke
 
57
 * @author <a href="mailto:oleg@ural.ru">Oleg Kalnichevski</a>
 
58
 *
 
59
 * @since 2.0
 
60
 *
 
61
 */
 
62
 
 
63
public class ChunkedInputStream extends InputStream {
 
64
    /** The inputstream that we're wrapping */
 
65
    private InputStream in;
 
66
 
 
67
    /** The chunk size */
 
68
    private int chunkSize;
 
69
 
 
70
    /** The current position within the current chunk */
 
71
    private int pos;
 
72
 
 
73
    /** True if we'are at the beginning of stream */
 
74
    private boolean bof = true;
 
75
 
 
76
    /** True if we've reached the end of stream */
 
77
    private boolean eof = false;
 
78
 
 
79
    /** True if this stream is closed */
 
80
    private boolean closed = false;
 
81
 
 
82
    /** The method that this stream came from */
 
83
    private HttpMethod method;
 
84
 
 
85
    /**
 
86
     *
 
87
     *
 
88
     * @param in must be non-null
 
89
     * @param method must be non-null
 
90
     *
 
91
     * @throws IOException If an IO error occurs
 
92
     */
 
93
    public ChunkedInputStream(
 
94
        final InputStream in, final HttpMethod method) throws IOException {
 
95
            
 
96
      if (in == null) {
 
97
        throw new IllegalArgumentException("InputStream parameter may not be null");
 
98
      }
 
99
      if (method == null) {
 
100
        throw new IllegalArgumentException("HttpMethod parameter may not be null");
 
101
      }
 
102
        this.in = in;
 
103
        this.method = method;
 
104
        this.pos = 0;
 
105
    }
 
106
 
 
107
    /**
 
108
     * <p> Returns all the data in a chunked stream in coalesced form. A chunk
 
109
     * is followed by a CRLF. The method returns -1 as soon as a chunksize of 0
 
110
     * is detected.</p>
 
111
     * 
 
112
     * <p> Trailer headers are read automcatically at the end of the stream and
 
113
     * can be obtained with the getResponseFooters() method.</p>
 
114
     *
 
115
     * @return -1 of the end of the stream has been reached or the next data
 
116
     * byte
 
117
     * @throws IOException If an IO problem occurs
 
118
     * 
 
119
     * @see HttpMethod#getResponseFooters()
 
120
     */
 
121
    public int read() throws IOException {
 
122
 
 
123
        if (closed) {
 
124
            throw new IOException("Attempted read from closed stream.");
 
125
        }
 
126
        if (eof) {
 
127
            return -1;
 
128
        } 
 
129
        if (pos >= chunkSize) {
 
130
            nextChunk();
 
131
            if (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
     */
 
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
     */
 
179
    public int read (byte[] b) throws IOException {
 
180
        return read(b, 0, b.length);
 
181
    }
 
182
 
 
183
    /**
 
184
     * Read the CRLF terminator.
 
185
     * @throws IOException If an IO error occurs.
 
186
     */
 
187
    private void readCRLF() throws IOException {
 
188
        int cr = in.read();
 
189
        int lf = in.read();
 
190
        if ((cr != '\r') || (lf != '\n')) { 
 
191
            throw new IOException(
 
192
                "CRLF expected at end of chunk: " + cr + "/" + lf);
 
193
        }
 
194
    }
 
195
 
 
196
 
 
197
    /**
 
198
     * Read the next chunk.
 
199
     * @throws IOException If an IO error occurs.
 
200
     */
 
201
    private void nextChunk() throws IOException {
 
202
        if (!bof) {
 
203
            readCRLF();
 
204
        }
 
205
        chunkSize = getChunkSizeFromInputStream(in);
 
206
        bof = false;
 
207
        pos = 0;
 
208
        if (chunkSize == 0) {
 
209
            eof = true;
 
210
            parseTrailerHeaders();
 
211
        }
 
212
    }
 
213
 
 
214
    /**
 
215
     * Expects the stream to start with a chunksize in hex with optional
 
216
     * comments after a semicolon. The line must end with a CRLF: "a3; some
 
217
     * comment\r\n" Positions the stream at the start of the next line.
 
218
     *
 
219
     * @param in The new input stream.
 
220
     * 
 
221
     * @return the chunk size as integer
 
222
     * 
 
223
     * @throws IOException when the chunk size could not be parsed
 
224
     */
 
225
    private static int getChunkSizeFromInputStream(final InputStream in) 
 
226
      throws IOException {
 
227
            
 
228
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
 
229
        // States: 0=normal, 1=\r was scanned, 2=inside quoted string, -1=end
 
230
        int state = 0; 
 
231
        while (state != -1) {
 
232
        int b = in.read();
 
233
            if (b == -1) { 
 
234
                throw new IOException("chunked stream ended unexpectedly");
 
235
            }
 
236
            switch (state) {
 
237
                case 0: 
 
238
                    switch (b) {
 
239
                        case '\r':
 
240
                            state = 1;
 
241
                            break;
 
242
                        case '\"':
 
243
                            state = 2;
 
244
                            /* fall through */
 
245
                        default:
 
246
                            baos.write(b);
 
247
                    }
 
248
                    break;
 
249
 
 
250
                case 1:
 
251
                    if (b == '\n') {
 
252
                        state = -1;
 
253
                    } else {
 
254
                        // this was not CRLF
 
255
                        throw new IOException("Protocol violation: Unexpected"
 
256
                            + " single newline character in chunk size");
 
257
                    }
 
258
                    break;
 
259
 
 
260
                case 2:
 
261
                    switch (b) {
 
262
                        case '\\':
 
263
                            b = in.read();
 
264
                            baos.write(b);
 
265
                            break;
 
266
                        case '\"':
 
267
                            state = 0;
 
268
                            /* fall through */
 
269
                        default:
 
270
                            baos.write(b);
 
271
                    }
 
272
                    break;
 
273
                default: throw new RuntimeException("assertion failed");
 
274
            }
 
275
        }
 
276
 
 
277
        //parse data
 
278
        String dataString = HttpConstants.getString(baos.toByteArray());
 
279
        int separator = dataString.indexOf(';');
 
280
        dataString = (separator > 0)
 
281
            ? dataString.substring(0, separator).trim()
 
282
            : dataString.trim();
 
283
 
 
284
        int result;
 
285
        try {
 
286
            result = Integer.parseInt(dataString.trim(), 16);
 
287
        } catch (NumberFormatException e) {
 
288
            throw new IOException ("Bad chunk size: " + dataString);
 
289
        }
 
290
        return result;
 
291
    }
 
292
 
 
293
    /**
 
294
     * Reads and stores the Trailer headers.
 
295
     * @throws IOException If an IO problem occurs
 
296
     */
 
297
    private void parseTrailerHeaders() throws IOException {
 
298
        Header[] footers = HttpParser.parseHeaders(in);
 
299
        
 
300
        for (int i = 0; i < footers.length; i++) {
 
301
            method.addResponseFooter(footers[i]);
 
302
        }
 
303
    }
 
304
 
 
305
    /**
 
306
     * Upon close, this reads the remainder of the chunked message,
 
307
     * leaving the underlying socket at a position to start reading the
 
308
     * next response without scanning.
 
309
     * @throws IOException If an IO problem occurs.
 
310
     */
 
311
    public void close() throws IOException {
 
312
        if (!closed) {
 
313
            try {
 
314
                if (!eof) {
 
315
                    exhaustInputStream(this);
 
316
                }
 
317
            } finally {
 
318
                eof = true;
 
319
                closed = true;
 
320
            }
 
321
        }
 
322
    }
 
323
 
 
324
    /**
 
325
     * Exhaust an input stream, reading until EOF has been encountered.
 
326
     *
 
327
     * <p>Note that this function is intended as a non-public utility.
 
328
     * This is a little weird, but it seemed silly to make a utility
 
329
     * class for this one function, so instead it is just static and
 
330
     * shared that way.</p>
 
331
     *
 
332
     * @param inStream The {@link InputStream} to exhaust.
 
333
     * @throws IOException If an IO problem occurs
 
334
     */
 
335
    static void exhaustInputStream(InputStream inStream) throws IOException {
 
336
        // read and discard the remainder of the message
 
337
        byte buffer[] = new byte[1024];
 
338
        while (inStream.read(buffer) >= 0) {
 
339
            ;
 
340
        }
 
341
    }
 
342
}