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.ISO_8859_1));
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.ISO_8859_1);
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.ISO_8859_1));
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.ISO_8859_1));
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.ISO_8859_1));
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.ISO_8859_1));
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.ISO_8859_1));
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.ISO_8859_1));
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.ISO_8859_1));
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.ISO_8859_1));
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.ISO_8859_1));
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.ISO_8859_1));
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.ISO_8859_1));
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.ISO_8859_1));
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.ISO_8859_1));
263 final ChunkedInputStream in = new ChunkedInputStream(inBuffer, inputStream);
264 Assertions.assertThrows(MalformedChunkCodingException.class, in::read);
265 }
266
267 @Test
268 public void testEmptyChunkedInputStream() throws IOException {
269 final String s = "0\r\n";
270 final SessionInputBuffer inBuffer = new SessionInputBufferImpl(16);
271 final ByteArrayInputStream inputStream = new ByteArrayInputStream(s.getBytes(StandardCharsets.ISO_8859_1));
272 final ChunkedInputStream in = new ChunkedInputStream(inBuffer, inputStream);
273 final byte[] buffer = new byte[300];
274 final ByteArrayOutputStream out = new ByteArrayOutputStream();
275 int len;
276 while ((len = in.read(buffer)) > 0) {
277 out.write(buffer, 0, len);
278 }
279 Assertions.assertEquals(0, out.size());
280 in.close();
281 }
282
283 @Test
284 public void testTooLongChunkHeader() throws IOException {
285 final String s = "5; and some very looooong commend\r\n12345\r\n0\r\n";
286 final SessionInputBuffer inBuffer1 = new SessionInputBufferImpl(16);
287 final ByteArrayInputStream inputStream1 = new ByteArrayInputStream(s.getBytes(StandardCharsets.ISO_8859_1));
288 final ChunkedInputStream in1 = new ChunkedInputStream(inBuffer1, inputStream1);
289 final byte[] buffer = new byte[300];
290 Assertions.assertEquals(5, in1.read(buffer));
291 in1.close();
292
293 final SessionInputBuffer inBuffer2 = new SessionInputBufferImpl(16, 10);
294 final ByteArrayInputStream inputStream2 = new ByteArrayInputStream(s.getBytes(StandardCharsets.ISO_8859_1));
295 final ChunkedInputStream in2 = new ChunkedInputStream(inBuffer2, inputStream2);
296 Assertions.assertThrows(MessageConstraintException.class, () -> in2.read(buffer));
297 }
298
299 @Test
300 public void testChunkedOutputStreamClose() throws IOException {
301 final SessionOutputBuffer outbuffer = new SessionOutputBufferImpl(16);
302 final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
303 final ChunkedOutputStream out = new ChunkedOutputStream(outbuffer, outputStream, 2048);
304 out.close();
305 out.close();
306 Assertions.assertThrows(IOException.class, () -> out.write(new byte[] {1,2,3}));
307 Assertions.assertThrows(IOException.class, () -> out.write(1));
308 }
309
310 @Test
311 public void testChunkedConsistence() throws IOException {
312 final String input = "76126;27823abcd;:q38a-\nkjc\rk%1ad\tkh/asdui\r\njkh+?\\suweb";
313 final SessionOutputBuffer outbuffer = new SessionOutputBufferImpl(16);
314 final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
315 final ChunkedOutputStream out = new ChunkedOutputStream(outbuffer, outputStream, 2048);
316 out.write(input.getBytes(StandardCharsets.ISO_8859_1));
317 out.flush();
318 out.close();
319 out.close();
320 final SessionInputBuffer inBuffer = new SessionInputBufferImpl(16);
321 final ByteArrayInputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray());
322 final ChunkedInputStream in = new ChunkedInputStream(inBuffer, inputStream);
323
324 final byte[] d = new byte[10];
325 final ByteArrayOutputStream result = new ByteArrayOutputStream();
326 int len = 0;
327 while ((len = in.read(d)) > 0) {
328 result.write(d, 0, len);
329 }
330
331 final String output = new String(result.toByteArray(), StandardCharsets.ISO_8859_1);
332 Assertions.assertEquals(input, output);
333 in.close();
334 }
335
336 @Test
337 public void testChunkedOutputStreamWithTrailers() throws IOException {
338 final SessionOutputBuffer outbuffer = new SessionOutputBufferImpl(16);
339 final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
340 final ChunkedOutputStream out = new ChunkedOutputStream(outbuffer, outputStream, 2, () -> Arrays.asList(
341 new BasicHeader("E", ""),
342 new BasicHeader("Y", "Z"))
343 );
344 out.write('x');
345 out.finish();
346 out.close();
347
348 final String content = new String(outputStream.toByteArray(), StandardCharsets.US_ASCII);
349 Assertions.assertEquals("1\r\nx\r\n0\r\nE: \r\nY: Z\r\n\r\n", content);
350 }
351
352 @Test
353 public void testChunkedOutputStream() throws IOException {
354 final SessionOutputBuffer outbuffer = new SessionOutputBufferImpl(16);
355 final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
356 final ChunkedOutputStream out = new ChunkedOutputStream(outbuffer, outputStream, 2);
357 out.write('1');
358 out.write('2');
359 out.write('3');
360 out.write('4');
361 out.finish();
362 out.close();
363
364 final String content = new String(outputStream.toByteArray(), StandardCharsets.US_ASCII);
365 Assertions.assertEquals("2\r\n12\r\n2\r\n34\r\n0\r\n\r\n", content);
366 }
367
368 @Test
369 public void testChunkedOutputStreamLargeChunk() throws IOException {
370 final SessionOutputBuffer outbuffer = new SessionOutputBufferImpl(16);
371 final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
372 final ChunkedOutputStream out = new ChunkedOutputStream(outbuffer, outputStream, 2);
373 out.write(new byte[] {'1', '2', '3', '4'});
374 out.finish();
375 out.close();
376
377 final String content = new String(outputStream.toByteArray(), StandardCharsets.US_ASCII);
378 Assertions.assertEquals("4\r\n1234\r\n0\r\n\r\n", content);
379 }
380
381 @Test
382 public void testChunkedOutputStreamSmallChunk() throws IOException {
383 final SessionOutputBuffer outbuffer = new SessionOutputBufferImpl(16);
384 final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
385 final ChunkedOutputStream out = new ChunkedOutputStream(outbuffer, outputStream, 2);
386 out.write('1');
387 out.finish();
388 out.close();
389
390 final String content = new String(outputStream.toByteArray(), StandardCharsets.US_ASCII);
391 Assertions.assertEquals("1\r\n1\r\n0\r\n\r\n", content);
392 }
393
394 @Test
395 public void testResumeOnSocketTimeoutInData() throws IOException {
396 final String s = "5\r\n01234\r\n5\r\n5\0006789\r\na\r\n0123\000456789\r\n0\r\n";
397 final SessionInputBuffer inBuffer = new SessionInputBufferImpl(16);
398 final TimeoutByteArrayInputStream inputStream = new TimeoutByteArrayInputStream(s.getBytes(StandardCharsets.ISO_8859_1));
399 final ChunkedInputStream in = new ChunkedInputStream(inBuffer, inputStream);
400
401 final byte[] tmp = new byte[3];
402
403 int bytesRead = 0;
404 int timeouts = 0;
405
406 int i = 0;
407 while (i != -1) {
408 try {
409 i = in.read(tmp);
410 if (i > 0) {
411 bytesRead += i;
412 }
413 } catch (final InterruptedIOException ex) {
414 timeouts++;
415 }
416 }
417 Assertions.assertEquals(20, bytesRead);
418 Assertions.assertEquals(2, timeouts);
419 in.close();
420 }
421
422 @Test
423 public void testResumeOnSocketTimeoutInChunk() throws IOException {
424 final String s = "5\000\r\000\n\00001234\r\n\0005\r\n56789\r\na\r\n0123456789\r\n\0000\r\n";
425 final SessionInputBuffer inBuffer = new SessionInputBufferImpl(16);
426 final TimeoutByteArrayInputStream inputStream = new TimeoutByteArrayInputStream(s.getBytes(StandardCharsets.ISO_8859_1));
427 final ChunkedInputStream in = new ChunkedInputStream(inBuffer, inputStream);
428
429 final byte[] tmp = new byte[3];
430
431 int bytesRead = 0;
432 int timeouts = 0;
433
434 int i = 0;
435 while (i != -1) {
436 try {
437 i = in.read(tmp);
438 if (i > 0) {
439 bytesRead += i;
440 }
441 } catch (final InterruptedIOException ex) {
442 timeouts++;
443 }
444 }
445 Assertions.assertEquals(20, bytesRead);
446 Assertions.assertEquals(5, timeouts);
447 in.close();
448 }
449
450
451 @Test
452 public void testHugeChunk() throws IOException {
453
454 final SessionInputBuffer inBuffer = new SessionInputBufferImpl(16);
455 final ByteArrayInputStream inputStream = new ByteArrayInputStream("1234567890abcdef\r\n01234567".getBytes(
456 StandardCharsets.ISO_8859_1));
457 final ChunkedInputStream in = new ChunkedInputStream(inBuffer, inputStream);
458
459 final ByteArrayOutputStream out = new ByteArrayOutputStream();
460 for (int i = 0; i < 8; ++i) {
461 out.write(in.read());
462 }
463
464 final String result = new String(out.toByteArray(), StandardCharsets.ISO_8859_1);
465 Assertions.assertEquals("01234567", result);
466 }
467
468 }
469