View Javadoc
1   /*
2    * ====================================================================
3    * Licensed to the Apache Software Foundation (ASF) under one
4    * or more contributor license agreements.  See the NOTICE file
5    * distributed with this work for additional information
6    * regarding copyright ownership.  The ASF licenses this file
7    * to you under the Apache License, Version 2.0 (the
8    * "License"); you may not use this file except in compliance
9    * with the License.  You may obtain a copy of the License at
10   *
11   *   http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing,
14   * software distributed under the License is distributed on an
15   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16   * KIND, either express or implied.  See the License for the
17   * specific language governing permissions and limitations
18   * under the License.
19   * ====================================================================
20   *
21   * This software consists of voluntary contributions made by many
22   * individuals on behalf of the Apache Software Foundation.  For more
23   * information on the Apache Software Foundation, please see
24   * <http://www.apache.org/>.
25   *
26   */
27  
28  package org.apache.hc.core5.http.impl.nio;
29  
30  import java.io.IOException;
31  import java.nio.ByteBuffer;
32  import java.nio.channels.ReadableByteChannel;
33  import java.nio.charset.StandardCharsets;
34  import java.util.List;
35  
36  import org.apache.hc.core5.http.ConnectionClosedException;
37  import org.apache.hc.core5.http.Header;
38  import org.apache.hc.core5.http.MalformedChunkCodingException;
39  import org.apache.hc.core5.http.MessageConstraintException;
40  import org.apache.hc.core5.http.ReadableByteChannelMock;
41  import org.apache.hc.core5.http.TruncatedChunkException;
42  import org.apache.hc.core5.http.config.Http1Config;
43  import org.apache.hc.core5.http.impl.BasicHttpTransportMetrics;
44  import org.apache.hc.core5.http.nio.SessionInputBuffer;
45  import org.junit.jupiter.api.Assertions;
46  import org.junit.jupiter.api.Test;
47  
48  /**
49   * Simple tests for {@link ChunkDecoder}.
50   */
51  public class TestChunkDecoder {
52  
53      @Test
54      public void testBasicDecoding() throws Exception {
55          final String s = "5\r\n01234\r\n5\r\n56789\r\n6\r\nabcdef\r\n0\r\n\r\n";
56          final ReadableByteChannel channel = new ReadableByteChannelMock(
57                  new String[] {s}, StandardCharsets.US_ASCII);
58          final SessionInputBuffer inbuf = new SessionInputBufferImpl(1024, 256, 0, StandardCharsets.US_ASCII);
59          final BasicHttpTransportMetrics metrics = new BasicHttpTransportMetrics();
60          final ChunkDecoder decoder = new ChunkDecoder(channel, inbuf, metrics);
61  
62          final ByteBuffer dst = ByteBuffer.allocate(1024);
63  
64          int bytesRead = decoder.read(dst);
65          Assertions.assertEquals(16, bytesRead);
66          Assertions.assertEquals("0123456789abcdef", CodecTestUtils.convert(dst));
67          final List<? extends Header> trailers = decoder.getTrailers();
68          Assertions.assertNull(trailers);
69  
70          dst.clear();
71          bytesRead = decoder.read(dst);
72          Assertions.assertEquals(-1, bytesRead);
73          Assertions.assertTrue(decoder.isCompleted());
74          Assertions.assertEquals("[chunk-coded; completed: true]", decoder.toString());
75      }
76  
77      @Test
78      public void testComplexDecoding() throws Exception {
79          final String s = "10;key=\"value\"\r\n1234567890123456\r\n" +
80                  "5\r\n12345\r\n5\r\n12345\r\n0\r\nFooter1: abcde\r\nFooter2: fghij\r\n\r\n";
81          final ReadableByteChannel channel = new ReadableByteChannelMock(
82                  new String[] {s}, StandardCharsets.US_ASCII);
83  
84          final SessionInputBuffer inbuf = new SessionInputBufferImpl(1024, 256, 0, StandardCharsets.US_ASCII);
85          final BasicHttpTransportMetrics metrics = new BasicHttpTransportMetrics();
86          final ChunkDecoder decoder = new ChunkDecoder(channel, inbuf, metrics);
87  
88          final ByteBuffer dst = ByteBuffer.allocate(1024);
89  
90          int bytesRead = 0;
91          while (dst.hasRemaining() && !decoder.isCompleted()) {
92              final int i = decoder.read(dst);
93              if (i > 0) {
94                  bytesRead += i;
95              }
96          }
97  
98          Assertions.assertEquals(26, bytesRead);
99          Assertions.assertEquals("12345678901234561234512345", CodecTestUtils.convert(dst));
100 
101         final List<? extends Header> trailers = decoder.getTrailers();
102         Assertions.assertEquals(2, trailers.size());
103         Assertions.assertEquals("Footer1", trailers.get(0).getName());
104         Assertions.assertEquals("abcde", trailers.get(0).getValue());
105         Assertions.assertEquals("Footer2", trailers.get(1).getName());
106         Assertions.assertEquals("fghij", trailers.get(1).getValue());
107 
108         dst.clear();
109         bytesRead = decoder.read(dst);
110         Assertions.assertEquals(-1, bytesRead);
111         Assertions.assertTrue(decoder.isCompleted());
112     }
113 
114     @Test
115     public void testDecodingWithSmallBuffer() throws Exception {
116         final String s1 = "5\r\n01234\r\n5\r\n5678";
117         final String s2 = "9\r\n6\r\nabcdef\r\n0\r\n\r\n";
118         final ReadableByteChannel channel = new ReadableByteChannelMock(
119                 new String[] {s1, s2}, StandardCharsets.US_ASCII);
120 
121         final SessionInputBuffer inbuf = new SessionInputBufferImpl(1024, 256, 0, StandardCharsets.US_ASCII);
122         final BasicHttpTransportMetrics metrics = new BasicHttpTransportMetrics();
123         final ChunkDecoder decoder = new ChunkDecoder(channel, inbuf, metrics);
124 
125         final ByteBuffer dst = ByteBuffer.allocate(1024);
126         final ByteBuffer tmp = ByteBuffer.allocate(4);
127 
128         int bytesRead = 0;
129         while (dst.hasRemaining() && !decoder.isCompleted()) {
130             final int i = decoder.read(tmp);
131             if (i > 0) {
132                 bytesRead += i;
133             }
134             tmp.flip();
135             dst.put(tmp);
136             tmp.compact();
137         }
138 
139         Assertions.assertEquals(16, bytesRead);
140         Assertions.assertEquals("0123456789abcdef", CodecTestUtils.convert(dst));
141         Assertions.assertTrue(decoder.isCompleted());
142 
143         dst.clear();
144         bytesRead = decoder.read(dst);
145         Assertions.assertEquals(-1, bytesRead);
146         Assertions.assertTrue(decoder.isCompleted());
147     }
148 
149     @Test
150     public void testMalformedChunk() throws Exception {
151         final String s = "5\r\n01234----------------------------------------------------------" +
152                 "-----------------------------------------------------------------------------" +
153                 "-----------------------------------------------------------------------------";
154         final ReadableByteChannel channel = new ReadableByteChannelMock(
155                 new String[] {s}, StandardCharsets.US_ASCII);
156 
157         final SessionInputBuffer inbuf = new SessionInputBufferImpl(32, 32, 0, StandardCharsets.US_ASCII);
158         final BasicHttpTransportMetrics metrics = new BasicHttpTransportMetrics();
159         final ChunkDecoder decoder = new ChunkDecoder(channel, inbuf, metrics);
160 
161         final ByteBuffer dst = ByteBuffer.allocate(1024);
162 
163         Assertions.assertThrows(MalformedChunkCodingException.class, () -> decoder.read(dst));
164     }
165 
166     @Test
167     public void testIncompleteChunkDecoding() throws Exception {
168         final String[] chunks = {
169                 "10;",
170                 "key=\"value\"\r",
171                 "\n123456789012345",
172                 "6\r\n5\r\n12",
173                 "345\r\n6\r",
174                 "\nabcdef\r",
175                 "\n0\r\nFoot",
176                 "er1: abcde\r\nFooter2: f",
177                 "ghij\r\n\r\n"
178         };
179         final ReadableByteChannel channel = new ReadableByteChannelMock(
180                 chunks, StandardCharsets.US_ASCII);
181 
182         final SessionInputBuffer inbuf = new SessionInputBufferImpl(1024, 256, 0, StandardCharsets.US_ASCII);
183         final BasicHttpTransportMetrics metrics = new BasicHttpTransportMetrics();
184         final ByteBuffer dst = ByteBuffer.allocate(1024);
185 
186         final ChunkDecoder decoder = new ChunkDecoder(channel, inbuf, metrics);
187 
188         int bytesRead = 0;
189         while (dst.hasRemaining() && !decoder.isCompleted()) {
190             final int i = decoder.read(dst);
191             if (i > 0) {
192                 bytesRead += i;
193             }
194         }
195 
196         Assertions.assertEquals(27, bytesRead);
197         Assertions.assertEquals("123456789012345612345abcdef", CodecTestUtils.convert(dst));
198         Assertions.assertTrue(decoder.isCompleted());
199 
200         final List<? extends Header> trailers = decoder.getTrailers();
201         Assertions.assertEquals(2, trailers.size());
202         Assertions.assertEquals("Footer1", trailers.get(0).getName());
203         Assertions.assertEquals("abcde", trailers.get(0).getValue());
204         Assertions.assertEquals("Footer2", trailers.get(1).getName());
205         Assertions.assertEquals("fghij", trailers.get(1).getValue());
206 
207         dst.clear();
208         bytesRead = decoder.read(dst);
209         Assertions.assertEquals(-1, bytesRead);
210         Assertions.assertTrue(decoder.isCompleted());
211     }
212 
213     @Test
214     public void testMalformedChunkSizeDecoding() throws Exception {
215         final String s = "5\r\n01234\r\n5zz\r\n56789\r\n6\r\nabcdef\r\n0\r\n\r\n";
216         final ReadableByteChannel channel = new ReadableByteChannelMock(
217                 new String[] {s}, StandardCharsets.US_ASCII);
218 
219         final SessionInputBuffer inbuf = new SessionInputBufferImpl(1024, 256, 0, StandardCharsets.US_ASCII);
220         final BasicHttpTransportMetrics metrics = new BasicHttpTransportMetrics();
221         final ChunkDecoder decoder = new ChunkDecoder(channel, inbuf, metrics);
222 
223         final ByteBuffer dst = ByteBuffer.allocate(1024);
224         Assertions.assertThrows(MalformedChunkCodingException.class, () ->
225                 decoder.read(dst));
226     }
227 
228     @Test
229     public void testMalformedChunkEndingDecoding() throws Exception {
230         final String s = "5\r\n01234\r\n5\r\n56789\r\r6\r\nabcdef\r\n0\r\n\r\n";
231         final ReadableByteChannel channel = new ReadableByteChannelMock(
232                 new String[] {s}, StandardCharsets.US_ASCII);
233 
234         final SessionInputBuffer inbuf = new SessionInputBufferImpl(1024, 256, 0, StandardCharsets.US_ASCII);
235         final BasicHttpTransportMetrics metrics = new BasicHttpTransportMetrics();
236         final ChunkDecoder decoder = new ChunkDecoder(channel, inbuf, metrics);
237 
238         final ByteBuffer dst = ByteBuffer.allocate(1024);
239         Assertions.assertThrows(MalformedChunkCodingException.class, () ->
240                 decoder.read(dst));
241     }
242 
243     @Test
244     public void testMalformedChunkTruncatedChunk() throws Exception {
245         final String s = "3\r\n12";
246         final ReadableByteChannel channel = new ReadableByteChannelMock(
247                 new String[] {s}, StandardCharsets.US_ASCII);
248 
249         final SessionInputBuffer inbuf = new SessionInputBufferImpl(1024, 256, 0, StandardCharsets.US_ASCII);
250         final BasicHttpTransportMetrics metrics = new BasicHttpTransportMetrics();
251         final ChunkDecoder decoder = new ChunkDecoder(channel, inbuf, metrics);
252 
253         final ByteBuffer dst = ByteBuffer.allocate(1024);
254         Assertions.assertEquals(2, decoder.read(dst));
255         Assertions.assertThrows(TruncatedChunkException.class, () ->
256                 decoder.read(dst));
257     }
258 
259     @Test
260     public void testFoldedFooters() throws Exception {
261         final String s = "10;key=\"value\"\r\n1234567890123456\r\n" +
262                 "5\r\n12345\r\n5\r\n12345\r\n0\r\nFooter1: abcde\r\n   \r\n  fghij\r\n\r\n";
263         final ReadableByteChannel channel = new ReadableByteChannelMock(
264                 new String[] {s}, StandardCharsets.US_ASCII);
265 
266         final SessionInputBuffer inbuf = new SessionInputBufferImpl(1024, 256, 0, StandardCharsets.US_ASCII);
267         final BasicHttpTransportMetrics metrics = new BasicHttpTransportMetrics();
268         final ChunkDecoder decoder = new ChunkDecoder(channel, inbuf, metrics);
269 
270         final ByteBuffer dst = ByteBuffer.allocate(1024);
271 
272         final int bytesRead = decoder.read(dst);
273         Assertions.assertEquals(26, bytesRead);
274         Assertions.assertEquals("12345678901234561234512345", CodecTestUtils.convert(dst));
275 
276         final List<? extends Header> trailers = decoder.getTrailers();
277         Assertions.assertEquals(1, trailers.size());
278         Assertions.assertEquals("Footer1", trailers.get(0).getName());
279         Assertions.assertEquals("abcde  fghij", trailers.get(0).getValue());
280     }
281 
282     @Test
283     public void testMalformedFooters() throws Exception {
284         final String s = "10;key=\"value\"\r\n1234567890123456\r\n" +
285                 "5\r\n12345\r\n5\r\n12345\r\n0\r\nFooter1 abcde\r\n\r\n";
286         final ReadableByteChannel channel = new ReadableByteChannelMock(
287                 new String[] {s}, StandardCharsets.US_ASCII);
288 
289         final SessionInputBuffer inbuf = new SessionInputBufferImpl(1024, 256, 0, StandardCharsets.US_ASCII);
290         final BasicHttpTransportMetrics metrics = new BasicHttpTransportMetrics();
291         final ChunkDecoder decoder = new ChunkDecoder(channel, inbuf, metrics);
292 
293         final ByteBuffer dst = ByteBuffer.allocate(1024);
294         Assertions.assertThrows(IOException.class, () ->
295                 decoder.read(dst));
296     }
297 
298     @Test
299     public void testMissingLastCRLF() throws Exception {
300         final String s = "10\r\n1234567890123456\r\n" +
301                 "5\r\n12345\r\n5\r\n12345";
302         final ReadableByteChannel channel = new ReadableByteChannelMock(
303                 new String[] {s}, StandardCharsets.US_ASCII);
304 
305         final SessionInputBuffer inbuf = new SessionInputBufferImpl(1024, 256, 0, StandardCharsets.US_ASCII);
306         final BasicHttpTransportMetrics metrics = new BasicHttpTransportMetrics();
307         final ChunkDecoder decoder = new ChunkDecoder(channel, inbuf, metrics);
308 
309         final ByteBuffer dst = ByteBuffer.allocate(1024);
310 
311         Assertions.assertThrows(MalformedChunkCodingException.class, () -> {
312             while (dst.hasRemaining() && !decoder.isCompleted()) {
313                 decoder.read(dst);
314             }
315         });
316     }
317 
318     @Test
319     public void testMissingClosingChunk() throws Exception {
320         final String s = "10\r\n1234567890123456\r\n" +
321                 "5\r\n12345\r\n5\r\n12345\r\n";
322         final ReadableByteChannel channel = new ReadableByteChannelMock(
323                 new String[] {s}, StandardCharsets.US_ASCII);
324 
325         final SessionInputBuffer inbuf = new SessionInputBufferImpl(1024, 256, 0, StandardCharsets.US_ASCII);
326         final BasicHttpTransportMetrics metrics = new BasicHttpTransportMetrics();
327         final ChunkDecoder decoder = new ChunkDecoder(channel, inbuf, metrics);
328 
329         final ByteBuffer dst = ByteBuffer.allocate(1024);
330 
331         Assertions.assertThrows(ConnectionClosedException.class, () -> {
332             long bytesRead = 0;
333             try {
334                 while (dst.hasRemaining() && !decoder.isCompleted()) {
335                     final int i = decoder.read(dst);
336                     if (i > 0) {
337                         bytesRead += i;
338                     }
339                 }
340             } catch (final MalformedChunkCodingException ex) {
341                 Assertions.assertEquals(26L, bytesRead);
342                 Assertions.assertEquals("12345678901234561234512345", CodecTestUtils.convert(dst));
343                 Assertions.assertTrue(decoder.isCompleted());
344                 throw ex;
345             }
346         });
347     }
348 
349     @Test
350     public void testReadingWitSmallBuffer() throws Exception {
351         final String s = "10\r\n1234567890123456\r\n" +
352                 "40\r\n12345678901234561234567890123456" +
353                 "12345678901234561234567890123456\r\n0\r\n";
354         final ReadableByteChannel channel = new ReadableByteChannelMock(
355                 new String[] {s}, StandardCharsets.US_ASCII);
356 
357         final SessionInputBuffer inbuf = new SessionInputBufferImpl(1024, 256, 0, StandardCharsets.US_ASCII);
358         final BasicHttpTransportMetrics metrics = new BasicHttpTransportMetrics();
359         final ChunkDecoder decoder = new ChunkDecoder(channel, inbuf, metrics);
360 
361         final ByteBuffer dst = ByteBuffer.allocate(1024);
362         final ByteBuffer tmp = ByteBuffer.allocate(10);
363 
364         int bytesRead = 0;
365         while (dst.hasRemaining() && !decoder.isCompleted()) {
366             final int i = decoder.read(tmp);
367             if (i > 0) {
368                 bytesRead += i;
369                 tmp.flip();
370                 dst.put(tmp);
371                 tmp.compact();
372             }
373         }
374 
375         Assertions.assertEquals(80, bytesRead);
376         Assertions.assertEquals("12345678901234561234567890123456" +
377                 "12345678901234561234567890123456" +
378                 "1234567890123456", CodecTestUtils.convert(dst));
379         Assertions.assertTrue(decoder.isCompleted());
380     }
381 
382     @Test
383     public void testEndOfStreamConditionReadingFooters() throws Exception {
384         final String s = "10\r\n1234567890123456\r\n" +
385                 "5\r\n12345\r\n5\r\n12345\r\n0\r\n";
386         final ReadableByteChannel channel = new ReadableByteChannelMock(
387                 new String[] {s}, StandardCharsets.US_ASCII);
388 
389         final SessionInputBuffer inbuf = new SessionInputBufferImpl(1024, 256, 0, StandardCharsets.US_ASCII);
390         final BasicHttpTransportMetrics metrics = new BasicHttpTransportMetrics();
391         final ChunkDecoder decoder = new ChunkDecoder(channel, inbuf, metrics);
392 
393         final ByteBuffer dst = ByteBuffer.allocate(1024);
394 
395         int bytesRead = 0;
396         while (dst.hasRemaining() && !decoder.isCompleted()) {
397             final int i = decoder.read(dst);
398             if (i > 0) {
399                 bytesRead += i;
400             }
401         }
402 
403         Assertions.assertEquals(26, bytesRead);
404         Assertions.assertEquals("12345678901234561234512345", CodecTestUtils.convert(dst));
405         Assertions.assertTrue(decoder.isCompleted());
406     }
407 
408     @Test
409     public void testTooLongChunkHeader() throws Exception {
410         final String s = "5; and some very looooong comment\r\n12345\r\n0\r\n";
411         final ReadableByteChannel channel1 = new ReadableByteChannelMock(
412                 new String[] {s}, StandardCharsets.US_ASCII);
413 
414         final BasicHttpTransportMetrics metrics1 = new BasicHttpTransportMetrics();
415         final SessionInputBuffer inbuf1 = new SessionInputBufferImpl(1024, 256);
416         final ChunkDecoder decoder1 = new ChunkDecoder(channel1, inbuf1, metrics1);
417 
418         final ByteBuffer dst = ByteBuffer.allocate(1024);
419 
420         while (dst.hasRemaining() && !decoder1.isCompleted()) {
421             decoder1.read(dst);
422         }
423         Assertions.assertEquals("12345", CodecTestUtils.convert(dst));
424         Assertions.assertTrue(decoder1.isCompleted());
425 
426         final ReadableByteChannel channel2 = new ReadableByteChannelMock(
427                 new String[] {s}, StandardCharsets.US_ASCII);
428 
429         final SessionInputBuffer inbuf2 = new SessionInputBufferImpl(1024, 256, 10);
430         final BasicHttpTransportMetrics metrics2 = new BasicHttpTransportMetrics();
431         final ChunkDecoder decoder2 = new ChunkDecoder(channel2, inbuf2, metrics2);
432 
433         dst.clear();
434         Assertions.assertThrows(MessageConstraintException.class, () -> decoder2.read(dst));
435     }
436 
437     @Test
438     public void testTooLongFooter() throws Exception {
439         final String s = "10\r\n1234567890123456\r\n" +
440                 "0\r\nFooter1: looooooooooooooooooooooooooooooooooooooooooooooooooooooog\r\n\r\n";
441         final ReadableByteChannel channel1 = new ReadableByteChannelMock(
442                 new String[] {s}, StandardCharsets.US_ASCII);
443         final SessionInputBuffer inbuf1 = new SessionInputBufferImpl(1024, 256, 0);
444         final BasicHttpTransportMetrics metrics1 = new BasicHttpTransportMetrics();
445         final ChunkDecoder decoder1 = new ChunkDecoder(channel1, inbuf1, metrics1);
446 
447         final ByteBuffer dst = ByteBuffer.allocate(1024);
448 
449         final int bytesRead = decoder1.read(dst);
450         Assertions.assertEquals(16, bytesRead);
451         Assertions.assertEquals("1234567890123456", CodecTestUtils.convert(dst));
452         final List<? extends Header> trailers = decoder1.getTrailers();
453         Assertions.assertNotNull(trailers);
454         Assertions.assertEquals(1, trailers.size());
455 
456         final ReadableByteChannel channel2 = new ReadableByteChannelMock(
457                 new String[] {s}, StandardCharsets.US_ASCII);
458         final SessionInputBuffer inbuf2 = new SessionInputBufferImpl(1024, 256,
459                 25, StandardCharsets.US_ASCII);
460         final BasicHttpTransportMetrics metrics2 = new BasicHttpTransportMetrics();
461         final ChunkDecoder decoder2 = new ChunkDecoder(channel2, inbuf2, metrics2);
462 
463         dst.clear();
464         Assertions.assertThrows(MessageConstraintException.class, () -> decoder2.read(dst));
465     }
466 
467     @Test
468     public void testTooLongFoldedFooter() throws Exception {
469         final String s = "10\r\n1234567890123456\r\n" +
470                 "0\r\nFooter1: blah\r\n  blah\r\n  blah\r\n  blah\r\n  blah\r\n  blah\r\n  blah\r\n  blah\r\n\r\n";
471         final ReadableByteChannel channel1 = new ReadableByteChannelMock(
472                 new String[] {s}, StandardCharsets.US_ASCII);
473         final SessionInputBuffer inbuf1 = new SessionInputBufferImpl(1024, 256,
474                 0, StandardCharsets.US_ASCII);
475         final BasicHttpTransportMetrics metrics1 = new BasicHttpTransportMetrics();
476         final ChunkDecoder decoder1 = new ChunkDecoder(channel1, inbuf1, metrics1);
477 
478         final ByteBuffer dst = ByteBuffer.allocate(1024);
479 
480         final int bytesRead = decoder1.read(dst);
481         Assertions.assertEquals(16, bytesRead);
482         Assertions.assertEquals("1234567890123456", CodecTestUtils.convert(dst));
483         final List<? extends Header> trailers = decoder1.getTrailers();
484         Assertions.assertNotNull(trailers);
485         Assertions.assertEquals(1, trailers.size());
486 
487         final Http1Config http1Config = Http1Config.custom()
488                 .setMaxLineLength(25)
489                 .build();
490         final ReadableByteChannel channel2 = new ReadableByteChannelMock(
491                 new String[] {s}, StandardCharsets.US_ASCII);
492         final SessionInputBuffer inbuf2 = new SessionInputBufferImpl(1024, 256,0, StandardCharsets.US_ASCII);
493         final BasicHttpTransportMetrics metrics2 = new BasicHttpTransportMetrics();
494         final ChunkDecoder decoder2 = new ChunkDecoder(channel2, inbuf2, http1Config, metrics2);
495 
496         dst.clear();
497         Assertions.assertThrows(MessageConstraintException.class, () -> decoder2.read(dst));
498     }
499 
500     @Test
501     public void testTooManyFooters() throws Exception {
502         final String s = "10\r\n1234567890123456\r\n" +
503                 "0\r\nFooter1: blah\r\nFooter2: blah\r\nFooter3: blah\r\nFooter4: blah\r\n\r\n";
504         final ReadableByteChannel channel1 = new ReadableByteChannelMock(
505                 new String[] {s}, StandardCharsets.US_ASCII);
506         final SessionInputBuffer inbuf1 = new SessionInputBufferImpl(1024, 256,0, StandardCharsets.US_ASCII);
507         final BasicHttpTransportMetrics metrics1 = new BasicHttpTransportMetrics();
508         final ChunkDecoder decoder1 = new ChunkDecoder(channel1, inbuf1, metrics1);
509 
510         final ByteBuffer dst = ByteBuffer.allocate(1024);
511 
512         final int bytesRead = decoder1.read(dst);
513         Assertions.assertEquals(16, bytesRead);
514         Assertions.assertEquals("1234567890123456", CodecTestUtils.convert(dst));
515         final List<? extends Header> trailers = decoder1.getTrailers();
516         Assertions.assertNotNull(trailers);
517         Assertions.assertEquals(4, trailers.size());
518 
519         final Http1Config http1Config = Http1Config.custom()
520                 .setMaxHeaderCount(3).build();
521         final ReadableByteChannel channel2 = new ReadableByteChannelMock(
522                 new String[] {s}, StandardCharsets.US_ASCII);
523         final SessionInputBuffer inbuf2 = new SessionInputBufferImpl(1024, 256,
524                 0, StandardCharsets.US_ASCII);
525         final BasicHttpTransportMetrics metrics2 = new BasicHttpTransportMetrics();
526         final ChunkDecoder decoder2 = new ChunkDecoder(channel2, inbuf2, http1Config, metrics2);
527 
528         dst.clear();
529         Assertions.assertThrows(MessageConstraintException.class, () -> decoder2.read(dst));
530     }
531 
532     @Test
533     public void testInvalidConstructor() {
534         final ReadableByteChannel channel = new ReadableByteChannelMock(
535                 new String[] {"stuff;", "more stuff"}, StandardCharsets.US_ASCII);
536 
537         final SessionInputBuffer inbuf = new SessionInputBufferImpl(1024, 256, 0, StandardCharsets.US_ASCII);
538         Assertions.assertThrows(NullPointerException.class, () -> new ChunkDecoder(null, null, null));
539         Assertions.assertThrows(NullPointerException.class, () -> new ChunkDecoder(channel, inbuf, null));
540     }
541 
542     @Test
543     public void testInvalidInput() throws Exception {
544         final String s = "10;key=\"value\"\r\n1234567890123456\r\n" +
545                 "5\r\n12345\r\n5\r\n12345\r\n0\r\nFooter1 abcde\r\n\r\n";
546         final ReadableByteChannel channel = new ReadableByteChannelMock(
547                 new String[] {s}, StandardCharsets.US_ASCII);
548 
549         final SessionInputBuffer inbuf = new SessionInputBufferImpl(1024, 256, 0, StandardCharsets.US_ASCII);
550         final BasicHttpTransportMetrics metrics = new BasicHttpTransportMetrics();
551         final ChunkDecoder decoder = new ChunkDecoder(channel, inbuf, metrics);
552         Assertions.assertThrows(NullPointerException.class, () ->
553                 decoder.read(null));
554     }
555 
556     @Test
557     public void testHugeChunk() throws Exception {
558         final String s = "1234567890abcdef\r\n0123456789abcdef";
559         final ReadableByteChannel channel = new ReadableByteChannelMock(new String[] {s}, StandardCharsets.US_ASCII);
560         final SessionInputBuffer inbuf = new SessionInputBufferImpl(1024, 256, 0, StandardCharsets.US_ASCII);
561         final BasicHttpTransportMetrics metrics = new BasicHttpTransportMetrics();
562         final ChunkDecoder decoder = new ChunkDecoder(channel, inbuf, metrics);
563 
564         final ByteBuffer dst = ByteBuffer.allocate(4);
565 
566         int bytesRead = decoder.read(dst);
567         Assertions.assertEquals(4, bytesRead);
568         Assertions.assertEquals("0123", CodecTestUtils.convert(dst));
569         dst.clear();
570         bytesRead = decoder.read(dst);
571         Assertions.assertEquals(4, bytesRead);
572         Assertions.assertEquals("4567", CodecTestUtils.convert(dst));
573     }
574 
575 }