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.hc.core5.http.impl.io;
29
30 import java.io.ByteArrayInputStream;
31 import java.io.ByteArrayOutputStream;
32 import java.io.IOException;
33 import java.io.InterruptedIOException;
34 import java.nio.charset.StandardCharsets;
35 import java.util.Arrays;
36
37 import org.apache.hc.core5.http.ConnectionClosedException;
38 import org.apache.hc.core5.http.Header;
39 import org.apache.hc.core5.http.MalformedChunkCodingException;
40 import org.apache.hc.core5.http.MessageConstraintException;
41 import org.apache.hc.core5.http.StreamClosedException;
42 import org.apache.hc.core5.http.io.SessionInputBuffer;
43 import org.apache.hc.core5.http.io.SessionOutputBuffer;
44 import org.apache.hc.core5.http.message.BasicHeader;
45 import org.junit.jupiter.api.Assertions;
46 import org.junit.jupiter.api.Test;
47
48 public class TestChunkCoding {
49
50 private final static String CHUNKED_INPUT
51 = "10;key=\"value\"\r\n1234567890123456\r\n5\r\n12345\r\n0\r\nFooter1: abcde\r\nFooter2: fghij\r\n";
52
53 private final static String CHUNKED_RESULT
54 = "123456789012345612345";
55
56 @Test
57 public void testChunkedInputStreamLargeBuffer() throws IOException {
58 final SessionInputBuffer inBuffer = new SessionInputBufferImpl(16);
59 final ByteArrayInputStream inputStream = new ByteArrayInputStream(CHUNKED_INPUT.getBytes(StandardCharsets.US_ASCII));
60 final ChunkedInputStream in = new ChunkedInputStream(inBuffer, inputStream);
61 final byte[] buffer = new byte[300];
62 final ByteArrayOutputStream out = new ByteArrayOutputStream();
63 int len;
64 while ((len = in.read(buffer)) > 0) {
65 out.write(buffer, 0, len);
66 }
67 Assertions.assertEquals(-1, in.read(buffer));
68 Assertions.assertEquals(-1, in.read(buffer));
69
70 in.close();
71
72 final String result = new String(out.toByteArray(), StandardCharsets.US_ASCII);
73 Assertions.assertEquals(result, CHUNKED_RESULT);
74
75 final Header[] footers = in.getFooters();
76 Assertions.assertNotNull(footers);
77 Assertions.assertEquals(2, footers.length);
78 Assertions.assertEquals("Footer1", footers[0].getName());
79 Assertions.assertEquals("abcde", footers[0].getValue());
80 Assertions.assertEquals("Footer2", footers[1].getName());
81 Assertions.assertEquals("fghij", footers[1].getValue());
82 }
83
84
85 @Test
86 public void testChunkedInputStreamSmallBuffer() throws IOException {
87 final SessionInputBuffer inBuffer = new SessionInputBufferImpl(16);
88 final ByteArrayInputStream inputStream = new ByteArrayInputStream(CHUNKED_INPUT.getBytes(StandardCharsets.US_ASCII));
89 final ChunkedInputStream in = new ChunkedInputStream(inBuffer, inputStream);
90
91 final byte[] buffer = new byte[7];
92 final ByteArrayOutputStream out = new ByteArrayOutputStream();
93 int len;
94 while ((len = in.read(buffer)) > 0) {
95 out.write(buffer, 0, len);
96 }
97 Assertions.assertEquals(-1, in.read(buffer));
98 Assertions.assertEquals(-1, in.read(buffer));
99
100 in.close();
101
102 final Header[] footers = in.getFooters();
103 Assertions.assertNotNull(footers);
104 Assertions.assertEquals(2, footers.length);
105 Assertions.assertEquals("Footer1", footers[0].getName());
106 Assertions.assertEquals("abcde", footers[0].getValue());
107 Assertions.assertEquals("Footer2", footers[1].getName());
108 Assertions.assertEquals("fghij", footers[1].getValue());
109 }
110
111
112 @Test
113 public void testChunkedInputStreamOneByteRead() throws IOException {
114 final String s = "5\r\n01234\r\n5\r\n56789\r\n0\r\n";
115 final SessionInputBuffer inBuffer = new SessionInputBufferImpl(16);
116 final ByteArrayInputStream inputStream = new ByteArrayInputStream(s.getBytes(StandardCharsets.US_ASCII));
117 final ChunkedInputStream in = new ChunkedInputStream(inBuffer, inputStream);
118 int ch;
119 int i = '0';
120 while ((ch = in.read()) != -1) {
121 Assertions.assertEquals(i, ch);
122 i++;
123 }
124 Assertions.assertEquals(-1, in.read());
125 Assertions.assertEquals(-1, in.read());
126
127 in.close();
128 }
129
130 @Test
131 public void testAvailable() throws IOException {
132 final String s = "5\r\n12345\r\n0\r\n";
133 final SessionInputBuffer inBuffer = new SessionInputBufferImpl(16);
134 final ByteArrayInputStream inputStream = new ByteArrayInputStream(s.getBytes(StandardCharsets.US_ASCII));
135 final ChunkedInputStream in = new ChunkedInputStream(inBuffer, inputStream);
136 Assertions.assertEquals(0, in.available());
137 in.read();
138 Assertions.assertEquals(4, in.available());
139 in.close();
140 }
141
142 @Test
143 public void testChunkedInputStreamClose() throws IOException {
144 final String s = "5\r\n01234\r\n5\r\n56789\r\n0\r\n";
145 final SessionInputBuffer inBuffer = new SessionInputBufferImpl(16);
146 final ByteArrayInputStream inputStream = new ByteArrayInputStream(s.getBytes(StandardCharsets.US_ASCII));
147 final ChunkedInputStream in = new ChunkedInputStream(inBuffer, inputStream);
148 in.close();
149 in.close();
150 Assertions.assertThrows(StreamClosedException.class, () -> in.read());
151 final byte[] tmp = new byte[10];
152 Assertions.assertThrows(StreamClosedException.class, () -> in.read(tmp));
153 Assertions.assertThrows(StreamClosedException.class, () -> in.read(tmp, 0, tmp.length));
154 }
155
156
157 @Test
158 public void testChunkedInputStreamNoClosingChunk() throws IOException {
159 final String s = "5\r\n01234\r\n";
160 final SessionInputBuffer inBuffer = new SessionInputBufferImpl(16);
161 final ByteArrayInputStream inputStream = new ByteArrayInputStream(s.getBytes(StandardCharsets.US_ASCII));
162 final ChunkedInputStream in = new ChunkedInputStream(inBuffer, inputStream);
163 final byte[] tmp = new byte[5];
164 Assertions.assertEquals(5, in.read(tmp));
165 Assertions.assertThrows(ConnectionClosedException.class, () -> in.read());
166 Assertions.assertThrows(ConnectionClosedException.class, () -> in.close());
167 }
168
169
170 @Test
171 public void testCorruptChunkedInputStreamTruncatedCRLF() throws IOException {
172 final String s = "5\r\n01234";
173 final SessionInputBuffer inBuffer = new SessionInputBufferImpl(16);
174 final ByteArrayInputStream inputStream = new ByteArrayInputStream(s.getBytes(StandardCharsets.US_ASCII));
175 final ChunkedInputStream in = new ChunkedInputStream(inBuffer, inputStream);
176 final byte[] tmp = new byte[5];
177 Assertions.assertEquals(5, in.read(tmp));
178 Assertions.assertThrows(MalformedChunkCodingException.class, () -> in.read());
179 in.close();
180 }
181
182
183 @Test
184 public void testCorruptChunkedInputStreamMissingCRLF() throws IOException {
185 final String s = "5\r\n012345\r\n56789\r\n0\r\n";
186 final SessionInputBuffer inBuffer = new SessionInputBufferImpl(16);
187 final ByteArrayInputStream inputStream = new ByteArrayInputStream(s.getBytes(StandardCharsets.US_ASCII));
188 final ChunkedInputStream in = new ChunkedInputStream(inBuffer, inputStream);
189 final byte[] buffer = new byte[300];
190 final ByteArrayOutputStream out = new ByteArrayOutputStream();
191 Assertions.assertThrows(MalformedChunkCodingException.class, () -> {
192 int len;
193 while ((len = in.read(buffer)) > 0) {
194 out.write(buffer, 0, len);
195 }
196 });
197 in.close();
198 }
199
200
201 @Test
202 public void testCorruptChunkedInputStreamMissingLF() throws IOException {
203 final String s = "5\r01234\r\n5\r\n56789\r\n0\r\n";
204 final SessionInputBuffer inBuffer = new SessionInputBufferImpl(16);
205 final ByteArrayInputStream inputStream = new ByteArrayInputStream(s.getBytes(StandardCharsets.US_ASCII));
206 final ChunkedInputStream in = new ChunkedInputStream(inBuffer, inputStream);
207 Assertions.assertThrows(MalformedChunkCodingException.class, in::read);
208 in.close();
209 }
210
211
212 @Test
213 public void testCorruptChunkedInputStreamInvalidSize() throws IOException {
214 final String s = "whatever\r\n01234\r\n5\r\n56789\r\n0\r\n";
215 final SessionInputBuffer inBuffer = new SessionInputBufferImpl(16);
216 final ByteArrayInputStream inputStream = new ByteArrayInputStream(s.getBytes(StandardCharsets.US_ASCII));
217 final ChunkedInputStream in = new ChunkedInputStream(inBuffer, inputStream);
218 Assertions.assertThrows(MalformedChunkCodingException.class, in::read);
219 in.close();
220 }
221
222
223 @Test
224 public void testCorruptChunkedInputStreamNegativeSize() throws IOException {
225 final String s = "-5\r\n01234\r\n5\r\n56789\r\n0\r\n";
226 final SessionInputBuffer inBuffer = new SessionInputBufferImpl(16);
227 final ByteArrayInputStream inputStream = new ByteArrayInputStream(s.getBytes(StandardCharsets.US_ASCII));
228 final ChunkedInputStream in = new ChunkedInputStream(inBuffer, inputStream);
229 Assertions.assertThrows(MalformedChunkCodingException.class, in::read);
230 in.close();
231 }
232
233
234 @Test
235 public void testCorruptChunkedInputStreamTruncatedChunk() throws IOException {
236 final String s = "3\r\n12";
237 final SessionInputBuffer inBuffer = new SessionInputBufferImpl(16);
238 final ByteArrayInputStream inputStream = new ByteArrayInputStream(s.getBytes(StandardCharsets.US_ASCII));
239 final ChunkedInputStream in = new ChunkedInputStream(inBuffer, inputStream);
240 final byte[] buffer = new byte[300];
241 Assertions.assertEquals(2, in.read(buffer));
242 Assertions.assertThrows(MalformedChunkCodingException.class, () -> in.read(buffer));
243 in.close();
244 }
245
246
247 @Test
248 public void testCorruptChunkedInputStreamInvalidFooter() throws IOException {
249 final String s = "1\r\n0\r\n0\r\nstuff\r\n";
250 final SessionInputBuffer inBuffer = new SessionInputBufferImpl(16);
251 final ByteArrayInputStream inputStream = new ByteArrayInputStream(s.getBytes(StandardCharsets.US_ASCII));
252 final ChunkedInputStream in = new ChunkedInputStream(inBuffer, inputStream);
253 in.read();
254 Assertions.assertThrows(MalformedChunkCodingException.class, in::read);
255 in.close();
256 }
257
258 @Test
259 public void testCorruptChunkedInputStreamClose() throws IOException {
260 final String s = "whatever\r\n01234\r\n5\r\n56789\r\n0\r\n";
261 final SessionInputBuffer inBuffer = new SessionInputBufferImpl(16);
262 final ByteArrayInputStream inputStream = new ByteArrayInputStream(s.getBytes(StandardCharsets.US_ASCII));
263 try (final ChunkedInputStream in = new ChunkedInputStream(inBuffer, inputStream)) {
264 Assertions.assertThrows(MalformedChunkCodingException.class, in::read);
265 }
266 }
267
268 @Test
269 public void testEmptyChunkedInputStream() throws IOException {
270 final String s = "0\r\n";
271 final SessionInputBuffer inBuffer = new SessionInputBufferImpl(16);
272 final ByteArrayInputStream inputStream = new ByteArrayInputStream(s.getBytes(StandardCharsets.US_ASCII));
273 final ChunkedInputStream in = new ChunkedInputStream(inBuffer, inputStream);
274 final byte[] buffer = new byte[300];
275 final ByteArrayOutputStream out = new ByteArrayOutputStream();
276 int len;
277 while ((len = in.read(buffer)) > 0) {
278 out.write(buffer, 0, len);
279 }
280 Assertions.assertEquals(0, out.size());
281 in.close();
282 }
283
284 @Test
285 public void testTooLongChunkHeader() throws IOException {
286 final String s = "5; and some very looooong commend\r\n12345\r\n0\r\n";
287 final SessionInputBuffer inBuffer1 = new SessionInputBufferImpl(16);
288 final byte[] buffer = new byte[300];
289 final ByteArrayInputStream inputStream1 = new ByteArrayInputStream(s.getBytes(StandardCharsets.US_ASCII));
290 try (final ChunkedInputStream in1 = new ChunkedInputStream(inBuffer1, inputStream1)) {
291 Assertions.assertEquals(5, in1.read(buffer));
292 }
293
294 final SessionInputBuffer inBuffer2 = new SessionInputBufferImpl(16, 10);
295 final ByteArrayInputStream inputStream2 = new ByteArrayInputStream(s.getBytes(StandardCharsets.US_ASCII));
296 final ChunkedInputStream in2 = new ChunkedInputStream(inBuffer2, inputStream2);
297 Assertions.assertThrows(MessageConstraintException.class, () -> in2.read(buffer));
298 }
299
300 @Test
301 public void testChunkedOutputStreamClose() throws IOException {
302 final SessionOutputBuffer outbuffer = new SessionOutputBufferImpl(16);
303 final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
304 final ChunkedOutputStream out = new ChunkedOutputStream(outbuffer, outputStream, 2048);
305 out.close();
306 out.close();
307 Assertions.assertThrows(IOException.class, () -> out.write(new byte[] {1,2,3}));
308 Assertions.assertThrows(IOException.class, () -> out.write(1));
309 }
310
311 @Test
312 public void testChunkedConsistence() throws IOException {
313 final String input = "76126;27823abcd;:q38a-\nkjc\rk%1ad\tkh/asdui\r\njkh+?\\suweb";
314 final SessionOutputBuffer outbuffer = new SessionOutputBufferImpl(16);
315 final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
316 final ChunkedOutputStream out = new ChunkedOutputStream(outbuffer, outputStream, 2048);
317 out.write(input.getBytes(StandardCharsets.US_ASCII));
318 out.flush();
319 out.close();
320 out.close();
321 final SessionInputBuffer inBuffer = new SessionInputBufferImpl(16);
322 final ByteArrayInputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray());
323 final ChunkedInputStream in = new ChunkedInputStream(inBuffer, inputStream);
324
325 final byte[] d = new byte[10];
326 final ByteArrayOutputStream result = new ByteArrayOutputStream();
327 int len = 0;
328 while ((len = in.read(d)) > 0) {
329 result.write(d, 0, len);
330 }
331
332 final String output = new String(result.toByteArray(), StandardCharsets.US_ASCII);
333 Assertions.assertEquals(input, output);
334 in.close();
335 }
336
337 @Test
338 public void testChunkedOutputStreamWithTrailers() throws IOException {
339 final SessionOutputBuffer outbuffer = new SessionOutputBufferImpl(16);
340 final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
341 final ChunkedOutputStream out = new ChunkedOutputStream(outbuffer, outputStream, 2, () -> Arrays.asList(
342 new BasicHeader("E", ""),
343 new BasicHeader("Y", "Z"))
344 );
345 out.write('x');
346 out.finish();
347 out.close();
348
349 final String content = new String(outputStream.toByteArray(), StandardCharsets.US_ASCII);
350 Assertions.assertEquals("1\r\nx\r\n0\r\nE: \r\nY: Z\r\n\r\n", content);
351 }
352
353 @Test
354 public void testChunkedOutputStream() throws IOException {
355 final SessionOutputBuffer outbuffer = new SessionOutputBufferImpl(16);
356 final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
357 final ChunkedOutputStream out = new ChunkedOutputStream(outbuffer, outputStream, 2);
358 out.write('1');
359 out.write('2');
360 out.write('3');
361 out.write('4');
362 out.finish();
363 out.close();
364
365 final String content = new String(outputStream.toByteArray(), StandardCharsets.US_ASCII);
366 Assertions.assertEquals("2\r\n12\r\n2\r\n34\r\n0\r\n\r\n", content);
367 }
368
369 @Test
370 public void testChunkedOutputStreamLargeChunk() throws IOException {
371 final SessionOutputBuffer outbuffer = new SessionOutputBufferImpl(16);
372 final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
373 final ChunkedOutputStream out = new ChunkedOutputStream(outbuffer, outputStream, 2);
374 out.write(new byte[] {'1', '2', '3', '4'});
375 out.finish();
376 out.close();
377
378 final String content = new String(outputStream.toByteArray(), StandardCharsets.US_ASCII);
379 Assertions.assertEquals("4\r\n1234\r\n0\r\n\r\n", content);
380 }
381
382 @Test
383 public void testChunkedOutputStreamSmallChunk() throws IOException {
384 final SessionOutputBuffer outbuffer = new SessionOutputBufferImpl(16);
385 final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
386 final ChunkedOutputStream out = new ChunkedOutputStream(outbuffer, outputStream, 2);
387 out.write('1');
388 out.finish();
389 out.close();
390
391 final String content = new String(outputStream.toByteArray(), StandardCharsets.US_ASCII);
392 Assertions.assertEquals("1\r\n1\r\n0\r\n\r\n", content);
393 }
394
395 @Test
396 public void testResumeOnSocketTimeoutInData() throws IOException {
397 final String s = "5\r\n01234\r\n5\r\n5\0006789\r\na\r\n0123\000456789\r\n0\r\n";
398 final SessionInputBuffer inBuffer = new SessionInputBufferImpl(16);
399 final TimeoutByteArrayInputStream inputStream = new TimeoutByteArrayInputStream(s.getBytes(StandardCharsets.US_ASCII));
400 final ChunkedInputStream in = new ChunkedInputStream(inBuffer, inputStream);
401
402 final byte[] tmp = new byte[3];
403
404 int bytesRead = 0;
405 int timeouts = 0;
406
407 int i = 0;
408 while (i != -1) {
409 try {
410 i = in.read(tmp);
411 if (i > 0) {
412 bytesRead += i;
413 }
414 } catch (final InterruptedIOException ex) {
415 timeouts++;
416 }
417 }
418 Assertions.assertEquals(20, bytesRead);
419 Assertions.assertEquals(2, timeouts);
420 in.close();
421 }
422
423 @Test
424 public void testResumeOnSocketTimeoutInChunk() throws IOException {
425 final String s = "5\000\r\000\n\00001234\r\n\0005\r\n56789\r\na\r\n0123456789\r\n\0000\r\n";
426 final SessionInputBuffer inBuffer = new SessionInputBufferImpl(16);
427 final TimeoutByteArrayInputStream inputStream = new TimeoutByteArrayInputStream(s.getBytes(StandardCharsets.US_ASCII));
428 final ChunkedInputStream in = new ChunkedInputStream(inBuffer, inputStream);
429
430 final byte[] tmp = new byte[3];
431
432 int bytesRead = 0;
433 int timeouts = 0;
434
435 int i = 0;
436 while (i != -1) {
437 try {
438 i = in.read(tmp);
439 if (i > 0) {
440 bytesRead += i;
441 }
442 } catch (final InterruptedIOException ex) {
443 timeouts++;
444 }
445 }
446 Assertions.assertEquals(20, bytesRead);
447 Assertions.assertEquals(5, timeouts);
448 in.close();
449 }
450
451
452 @Test
453 public void testHugeChunk() throws IOException {
454
455 final SessionInputBuffer inBuffer = new SessionInputBufferImpl(16);
456 final ByteArrayInputStream inputStream = new ByteArrayInputStream("1234567890abcdef\r\n01234567".getBytes(
457 StandardCharsets.US_ASCII));
458 final ChunkedInputStream in = new ChunkedInputStream(inBuffer, inputStream);
459
460 final ByteArrayOutputStream out = new ByteArrayOutputStream();
461 for (int i = 0; i < 8; ++i) {
462 out.write(in.read());
463 }
464
465 final String result = new String(out.toByteArray(), StandardCharsets.US_ASCII);
466 Assertions.assertEquals("01234567", result);
467 }
468
469 }
470