1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28 package org.apache.http.impl.io;
29
30 import java.io.IOException;
31 import java.io.InputStream;
32
33 import org.apache.http.ConnectionClosedException;
34 import org.apache.http.Header;
35 import org.apache.http.HttpException;
36 import org.apache.http.MalformedChunkCodingException;
37 import org.apache.http.TruncatedChunkException;
38 import org.apache.http.config.MessageConstraints;
39 import org.apache.http.io.BufferInfo;
40 import org.apache.http.io.SessionInputBuffer;
41 import org.apache.http.util.Args;
42 import org.apache.http.util.CharArrayBuffer;
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60 public class ChunkedInputStream extends InputStream {
61
62 private static final int CHUNK_LEN = 1;
63 private static final int CHUNK_DATA = 2;
64 private static final int CHUNK_CRLF = 3;
65 private static final int CHUNK_INVALID = Integer.MAX_VALUE;
66
67 private static final int BUFFER_SIZE = 2048;
68
69
70 private final SessionInputBuffer in;
71 private final CharArrayBuffer buffer;
72 private final MessageConstraints constraints;
73
74 private int state;
75
76
77 private long chunkSize;
78
79
80 private long pos;
81
82
83 private boolean eof = false;
84
85
86 private boolean closed = false;
87
88 private HeaderHeader">Header[] footers = new Header[] {};
89
90
91
92
93
94
95
96
97
98
99 public ChunkedInputStream(final SessionInputBuffer in, final MessageConstraints constraints) {
100 super();
101 this.in = Args.notNull(in, "Session input buffer");
102 this.pos = 0L;
103 this.buffer = new CharArrayBuffer(16);
104 this.constraints = constraints != null ? constraints : MessageConstraints.DEFAULT;
105 this.state = CHUNK_LEN;
106 }
107
108
109
110
111
112
113 public ChunkedInputStream(final SessionInputBuffer in) {
114 this(in, null);
115 }
116
117 @Override
118 public int available() throws IOException {
119 if (this.in instanceof BufferInfo) {
120 final int len = ((BufferInfo) this.in).length();
121 return (int) Math.min(len, this.chunkSize - this.pos);
122 }
123 return 0;
124 }
125
126
127
128
129
130
131
132
133
134
135
136
137
138 @Override
139 public int read() throws IOException {
140 if (this.closed) {
141 throw new IOException("Attempted read from closed stream.");
142 }
143 if (this.eof) {
144 return -1;
145 }
146 if (state != CHUNK_DATA) {
147 nextChunk();
148 if (this.eof) {
149 return -1;
150 }
151 }
152 final int b = in.read();
153 if (b != -1) {
154 pos++;
155 if (pos >= chunkSize) {
156 state = CHUNK_CRLF;
157 }
158 }
159 return b;
160 }
161
162
163
164
165
166
167
168
169
170
171
172 @Override
173 public int read (final byte[] b, final int off, final int len) throws IOException {
174
175 if (closed) {
176 throw new IOException("Attempted read from closed stream.");
177 }
178
179 if (eof) {
180 return -1;
181 }
182 if (state != CHUNK_DATA) {
183 nextChunk();
184 if (eof) {
185 return -1;
186 }
187 }
188 final int readLen = in.read(b, off, (int) Math.min(len, chunkSize - pos));
189 if (readLen != -1) {
190 pos += readLen;
191 if (pos >= chunkSize) {
192 state = CHUNK_CRLF;
193 }
194 return readLen;
195 }
196 eof = true;
197 throw new TruncatedChunkException("Truncated chunk (expected size: %,d; actual size: %,d)",
198 chunkSize, pos);
199 }
200
201
202
203
204
205
206
207
208 @Override
209 public int read (final byte[] b) throws IOException {
210 return read(b, 0, b.length);
211 }
212
213
214
215
216
217 private void nextChunk() throws IOException {
218 if (state == CHUNK_INVALID) {
219 throw new MalformedChunkCodingException("Corrupt data stream");
220 }
221 try {
222 chunkSize = getChunkSize();
223 if (chunkSize < 0L) {
224 throw new MalformedChunkCodingException("Negative chunk size");
225 }
226 state = CHUNK_DATA;
227 pos = 0L;
228 if (chunkSize == 0L) {
229 eof = true;
230 parseTrailerHeaders();
231 }
232 } catch (final MalformedChunkCodingException ex) {
233 state = CHUNK_INVALID;
234 throw ex;
235 }
236 }
237
238
239
240
241
242
243 private long getChunkSize() throws IOException {
244 final int st = this.state;
245 switch (st) {
246 case CHUNK_CRLF:
247 this.buffer.clear();
248 final int bytesRead1 = this.in.readLine(this.buffer);
249 if (bytesRead1 == -1) {
250 throw new MalformedChunkCodingException(
251 "CRLF expected at end of chunk");
252 }
253 if (!this.buffer.isEmpty()) {
254 throw new MalformedChunkCodingException(
255 "Unexpected content at the end of chunk");
256 }
257 state = CHUNK_LEN;
258
259 case CHUNK_LEN:
260 this.buffer.clear();
261 final int bytesRead2 = this.in.readLine(this.buffer);
262 if (bytesRead2 == -1) {
263 throw new ConnectionClosedException(
264 "Premature end of chunk coded message body: closing chunk expected");
265 }
266 int separator = this.buffer.indexOf(';');
267 if (separator < 0) {
268 separator = this.buffer.length();
269 }
270 final String s = this.buffer.substringTrimmed(0, separator);
271 try {
272 return Long.parseLong(s, 16);
273 } catch (final NumberFormatException e) {
274 throw new MalformedChunkCodingException("Bad chunk header: " + s);
275 }
276 default:
277 throw new IllegalStateException("Inconsistent codec state");
278 }
279 }
280
281
282
283
284
285 private void parseTrailerHeaders() throws IOException {
286 try {
287 this.footers = AbstractMessageParser.parseHeaders(in,
288 constraints.getMaxHeaderCount(),
289 constraints.getMaxLineLength(),
290 null);
291 } catch (final HttpException ex) {
292 final IOException ioe = new MalformedChunkCodingException("Invalid footer: "
293 + ex.getMessage());
294 ioe.initCause(ex);
295 throw ioe;
296 }
297 }
298
299
300
301
302
303
304
305 @Override
306 public void close() throws IOException {
307 if (!closed) {
308 try {
309 if (!eof && state != CHUNK_INVALID) {
310
311 final byte buff[] = new byte[BUFFER_SIZE];
312 while (read(buff) >= 0) {
313 }
314 }
315 } finally {
316 eof = true;
317 closed = true;
318 }
319 }
320 }
321
322 public Header[] getFooters() {
323 return this.footers.clone();
324 }
325
326 }