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.H1Config;
43  import org.apache.hc.core5.http.impl.BasicHttpTransportMetrics;
44  import org.apache.hc.core5.http.nio.SessionInputBuffer;
45  import org.junit.Assert;
46  import org.junit.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          Assert.assertEquals(16, bytesRead);
66          Assert.assertEquals("0123456789abcdef", CodecTestUtils.convert(dst));
67          final List<? extends Header> trailers = decoder.getTrailers();
68          Assert.assertEquals(null, trailers);
69  
70          dst.clear();
71          bytesRead = decoder.read(dst);
72          Assert.assertEquals(-1, bytesRead);
73          Assert.assertTrue(decoder.isCompleted());
74          Assert.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          Assert.assertEquals(26, bytesRead);
99          Assert.assertEquals("12345678901234561234512345", CodecTestUtils.convert(dst));
100 
101         final List<? extends Header> trailers = decoder.getTrailers();
102         Assert.assertEquals(2, trailers.size());
103         Assert.assertEquals("Footer1", trailers.get(0).getName());
104         Assert.assertEquals("abcde", trailers.get(0).getValue());
105         Assert.assertEquals("Footer2", trailers.get(1).getName());
106         Assert.assertEquals("fghij", trailers.get(1).getValue());
107 
108         dst.clear();
109         bytesRead = decoder.read(dst);
110         Assert.assertEquals(-1, bytesRead);
111         Assert.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         Assert.assertEquals(16, bytesRead);
140         Assert.assertEquals("0123456789abcdef", CodecTestUtils.convert(dst));
141         Assert.assertTrue(decoder.isCompleted());
142 
143         dst.clear();
144         bytesRead = decoder.read(dst);
145         Assert.assertEquals(-1, bytesRead);
146         Assert.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         try {
164             decoder.read(dst);
165             Assert.fail("MalformedChunkCodingException should have been thrown");
166         } catch (final MalformedChunkCodingException ex) {
167             // expected
168         }
169     }
170 
171     @Test
172     public void testIncompleteChunkDecoding() throws Exception {
173         final String[] chunks = {
174                 "10;",
175                 "key=\"value\"\r",
176                 "\n123456789012345",
177                 "6\r\n5\r\n12",
178                 "345\r\n6\r",
179                 "\nabcdef\r",
180                 "\n0\r\nFoot",
181                 "er1: abcde\r\nFooter2: f",
182                 "ghij\r\n\r\n"
183         };
184         final ReadableByteChannel channel = new ReadableByteChannelMock(
185                 chunks, StandardCharsets.US_ASCII);
186 
187         final SessionInputBuffer inbuf = new SessionInputBufferImpl(1024, 256, 0, StandardCharsets.US_ASCII);
188         final BasicHttpTransportMetrics metrics = new BasicHttpTransportMetrics();
189         final ByteBuffer dst = ByteBuffer.allocate(1024);
190 
191         final ChunkDecoder decoder = new ChunkDecoder(channel, inbuf, metrics);
192 
193         int bytesRead = 0;
194         while (dst.hasRemaining() && !decoder.isCompleted()) {
195             final int i = decoder.read(dst);
196             if (i > 0) {
197                 bytesRead += i;
198             }
199         }
200 
201         Assert.assertEquals(27, bytesRead);
202         Assert.assertEquals("123456789012345612345abcdef", CodecTestUtils.convert(dst));
203         Assert.assertTrue(decoder.isCompleted());
204 
205         final List<? extends Header> trailers = decoder.getTrailers();
206         Assert.assertEquals(2, trailers.size());
207         Assert.assertEquals("Footer1", trailers.get(0).getName());
208         Assert.assertEquals("abcde", trailers.get(0).getValue());
209         Assert.assertEquals("Footer2", trailers.get(1).getName());
210         Assert.assertEquals("fghij", trailers.get(1).getValue());
211 
212         dst.clear();
213         bytesRead = decoder.read(dst);
214         Assert.assertEquals(-1, bytesRead);
215         Assert.assertTrue(decoder.isCompleted());
216     }
217 
218     @Test(expected=MalformedChunkCodingException.class)
219     public void testMalformedChunkSizeDecoding() throws Exception {
220         final String s = "5\r\n01234\r\n5zz\r\n56789\r\n6\r\nabcdef\r\n0\r\n\r\n";
221         final ReadableByteChannel channel = new ReadableByteChannelMock(
222                 new String[] {s}, StandardCharsets.US_ASCII);
223 
224         final SessionInputBuffer inbuf = new SessionInputBufferImpl(1024, 256, 0, StandardCharsets.US_ASCII);
225         final BasicHttpTransportMetrics metrics = new BasicHttpTransportMetrics();
226         final ChunkDecoder decoder = new ChunkDecoder(channel, inbuf, metrics);
227 
228         final ByteBuffer dst = ByteBuffer.allocate(1024);
229         decoder.read(dst);
230     }
231 
232     @Test(expected=MalformedChunkCodingException.class)
233     public void testMalformedChunkEndingDecoding() throws Exception {
234         final String s = "5\r\n01234\r\n5\r\n56789\r\r6\r\nabcdef\r\n0\r\n\r\n";
235         final ReadableByteChannel channel = new ReadableByteChannelMock(
236                 new String[] {s}, StandardCharsets.US_ASCII);
237 
238         final SessionInputBuffer inbuf = new SessionInputBufferImpl(1024, 256, 0, StandardCharsets.US_ASCII);
239         final BasicHttpTransportMetrics metrics = new BasicHttpTransportMetrics();
240         final ChunkDecoder decoder = new ChunkDecoder(channel, inbuf, metrics);
241 
242         final ByteBuffer dst = ByteBuffer.allocate(1024);
243         decoder.read(dst);
244     }
245 
246     @Test(expected=TruncatedChunkException.class)
247     public void testMalformedChunkTruncatedChunk() throws Exception {
248         final String s = "3\r\n12";
249         final ReadableByteChannel channel = new ReadableByteChannelMock(
250                 new String[] {s}, StandardCharsets.US_ASCII);
251 
252         final SessionInputBuffer inbuf = new SessionInputBufferImpl(1024, 256, 0, StandardCharsets.US_ASCII);
253         final BasicHttpTransportMetrics metrics = new BasicHttpTransportMetrics();
254         final ChunkDecoder decoder = new ChunkDecoder(channel, inbuf, metrics);
255 
256         final ByteBuffer dst = ByteBuffer.allocate(1024);
257         Assert.assertEquals(2, decoder.read(dst));
258         decoder.read(dst);
259     }
260 
261     @Test
262     public void testFoldedFooters() throws Exception {
263         final String s = "10;key=\"value\"\r\n1234567890123456\r\n" +
264                 "5\r\n12345\r\n5\r\n12345\r\n0\r\nFooter1: abcde\r\n   \r\n  fghij\r\n\r\n";
265         final ReadableByteChannel channel = new ReadableByteChannelMock(
266                 new String[] {s}, StandardCharsets.US_ASCII);
267 
268         final SessionInputBuffer inbuf = new SessionInputBufferImpl(1024, 256, 0, StandardCharsets.US_ASCII);
269         final BasicHttpTransportMetrics metrics = new BasicHttpTransportMetrics();
270         final ChunkDecoder decoder = new ChunkDecoder(channel, inbuf, metrics);
271 
272         final ByteBuffer dst = ByteBuffer.allocate(1024);
273 
274         final int bytesRead = decoder.read(dst);
275         Assert.assertEquals(26, bytesRead);
276         Assert.assertEquals("12345678901234561234512345", CodecTestUtils.convert(dst));
277 
278         final List<? extends Header> trailers = decoder.getTrailers();
279         Assert.assertEquals(1, trailers.size());
280         Assert.assertEquals("Footer1", trailers.get(0).getName());
281         Assert.assertEquals("abcde  fghij", trailers.get(0).getValue());
282     }
283 
284     @Test(expected=IOException.class)
285     public void testMalformedFooters() throws Exception {
286         final String s = "10;key=\"value\"\r\n1234567890123456\r\n" +
287                 "5\r\n12345\r\n5\r\n12345\r\n0\r\nFooter1 abcde\r\n\r\n";
288         final ReadableByteChannel channel = new ReadableByteChannelMock(
289                 new String[] {s}, StandardCharsets.US_ASCII);
290 
291         final SessionInputBuffer inbuf = new SessionInputBufferImpl(1024, 256, 0, StandardCharsets.US_ASCII);
292         final BasicHttpTransportMetrics metrics = new BasicHttpTransportMetrics();
293         final ChunkDecoder decoder = new ChunkDecoder(channel, inbuf, metrics);
294 
295         final ByteBuffer dst = ByteBuffer.allocate(1024);
296         decoder.read(dst);
297     }
298 
299     @Test(expected=MalformedChunkCodingException.class)
300     public void testMissingLastCRLF() throws Exception {
301         final String s = "10\r\n1234567890123456\r\n" +
302                 "5\r\n12345\r\n5\r\n12345";
303         final ReadableByteChannel channel = new ReadableByteChannelMock(
304                 new String[] {s}, StandardCharsets.US_ASCII);
305 
306         final SessionInputBuffer inbuf = new SessionInputBufferImpl(1024, 256, 0, StandardCharsets.US_ASCII);
307         final BasicHttpTransportMetrics metrics = new BasicHttpTransportMetrics();
308         final ChunkDecoder decoder = new ChunkDecoder(channel, inbuf, metrics);
309 
310         final ByteBuffer dst = ByteBuffer.allocate(1024);
311 
312         while (dst.hasRemaining() && !decoder.isCompleted()) {
313             decoder.read(dst);
314         }
315     }
316 
317     @Test(expected=ConnectionClosedException.class)
318     public void testMissingClosingChunk() throws Exception {
319         final String s = "10\r\n1234567890123456\r\n" +
320                 "5\r\n12345\r\n5\r\n12345\r\n";
321         final ReadableByteChannel channel = new ReadableByteChannelMock(
322                 new String[] {s}, StandardCharsets.US_ASCII);
323 
324         final SessionInputBuffer inbuf = new SessionInputBufferImpl(1024, 256, 0, StandardCharsets.US_ASCII);
325         final BasicHttpTransportMetrics metrics = new BasicHttpTransportMetrics();
326         final ChunkDecoder decoder = new ChunkDecoder(channel, inbuf, metrics);
327 
328         final ByteBuffer dst = ByteBuffer.allocate(1024);
329 
330         long bytesRead = 0;
331         try {
332             while (dst.hasRemaining() && !decoder.isCompleted()) {
333                 final int i = decoder.read(dst);
334                 if (i > 0) {
335                     bytesRead += i;
336                 }
337             }
338         } catch (final MalformedChunkCodingException ex) {
339             Assert.assertEquals(26L, bytesRead);
340             Assert.assertEquals("12345678901234561234512345", CodecTestUtils.convert(dst));
341             Assert.assertTrue(decoder.isCompleted());
342             throw ex;
343         }
344     }
345 
346     @Test
347     public void testReadingWitSmallBuffer() throws Exception {
348         final String s = "10\r\n1234567890123456\r\n" +
349                 "40\r\n12345678901234561234567890123456" +
350                 "12345678901234561234567890123456\r\n0\r\n";
351         final ReadableByteChannel channel = new ReadableByteChannelMock(
352                 new String[] {s}, StandardCharsets.US_ASCII);
353 
354         final SessionInputBuffer inbuf = new SessionInputBufferImpl(1024, 256, 0, StandardCharsets.US_ASCII);
355         final BasicHttpTransportMetrics metrics = new BasicHttpTransportMetrics();
356         final ChunkDecoder decoder = new ChunkDecoder(channel, inbuf, metrics);
357 
358         final ByteBuffer dst = ByteBuffer.allocate(1024);
359         final ByteBuffer tmp = ByteBuffer.allocate(10);
360 
361         int bytesRead = 0;
362         while (dst.hasRemaining() && !decoder.isCompleted()) {
363             final int i = decoder.read(tmp);
364             if (i > 0) {
365                 bytesRead += i;
366                 tmp.flip();
367                 dst.put(tmp);
368                 tmp.compact();
369             }
370         }
371 
372         Assert.assertEquals(80, bytesRead);
373         Assert.assertEquals("12345678901234561234567890123456" +
374                 "12345678901234561234567890123456" +
375                 "1234567890123456", CodecTestUtils.convert(dst));
376         Assert.assertTrue(decoder.isCompleted());
377     }
378 
379     @Test
380     public void testEndOfStreamConditionReadingFooters() throws Exception {
381         final String s = "10\r\n1234567890123456\r\n" +
382                 "5\r\n12345\r\n5\r\n12345\r\n0\r\n";
383         final ReadableByteChannel channel = new ReadableByteChannelMock(
384                 new String[] {s}, StandardCharsets.US_ASCII);
385 
386         final SessionInputBuffer inbuf = new SessionInputBufferImpl(1024, 256, 0, StandardCharsets.US_ASCII);
387         final BasicHttpTransportMetrics metrics = new BasicHttpTransportMetrics();
388         final ChunkDecoder decoder = new ChunkDecoder(channel, inbuf, metrics);
389 
390         final ByteBuffer dst = ByteBuffer.allocate(1024);
391 
392         int bytesRead = 0;
393         while (dst.hasRemaining() && !decoder.isCompleted()) {
394             final int i = decoder.read(dst);
395             if (i > 0) {
396                 bytesRead += i;
397             }
398         }
399 
400         Assert.assertEquals(26, bytesRead);
401         Assert.assertEquals("12345678901234561234512345", CodecTestUtils.convert(dst));
402         Assert.assertTrue(decoder.isCompleted());
403     }
404 
405     @Test
406     public void testTooLongChunkHeader() throws Exception {
407         final String s = "5; and some very looooong comment\r\n12345\r\n0\r\n";
408         final ReadableByteChannel channel1 = new ReadableByteChannelMock(
409                 new String[] {s}, StandardCharsets.US_ASCII);
410 
411         final BasicHttpTransportMetrics metrics1 = new BasicHttpTransportMetrics();
412         final SessionInputBuffer inbuf1 = new SessionInputBufferImpl(1024, 256);
413         final ChunkDecoder decoder1 = new ChunkDecoder(channel1, inbuf1, metrics1);
414 
415         final ByteBuffer dst = ByteBuffer.allocate(1024);
416 
417         while (dst.hasRemaining() && !decoder1.isCompleted()) {
418             decoder1.read(dst);
419         }
420         Assert.assertEquals("12345", CodecTestUtils.convert(dst));
421         Assert.assertTrue(decoder1.isCompleted());
422 
423         final ReadableByteChannel channel2 = new ReadableByteChannelMock(
424                 new String[] {s}, StandardCharsets.US_ASCII);
425 
426         final SessionInputBuffer inbuf2 = new SessionInputBufferImpl(1024, 256, 10);
427         final BasicHttpTransportMetrics metrics2 = new BasicHttpTransportMetrics();
428         final ChunkDecoder decoder2 = new ChunkDecoder(channel2, inbuf2, metrics2);
429 
430         dst.clear();
431         try {
432             decoder2.read(dst);
433             Assert.fail("MessageConstraintException expected");
434         } catch (final MessageConstraintException ex) {
435         }
436     }
437 
438     @Test
439     public void testTooLongFooter() throws Exception {
440         final String s = "10\r\n1234567890123456\r\n" +
441                 "0\r\nFooter1: looooooooooooooooooooooooooooooooooooooooooooooooooooooog\r\n\r\n";
442 //        final String s = "10\r\n1234567890123456\r\n" +
443 //                "0\r\nFooter1: looooooooooooooooooooooooooooooooooooooooog\r\n   \r\n  fghij\r\n\r\n";
444         final ReadableByteChannel channel1 = new ReadableByteChannelMock(
445                 new String[] {s}, StandardCharsets.US_ASCII);
446         final SessionInputBuffer inbuf1 = new SessionInputBufferImpl(1024, 256, 0);
447         final BasicHttpTransportMetrics metrics1 = new BasicHttpTransportMetrics();
448         final ChunkDecoder decoder1 = new ChunkDecoder(channel1, inbuf1, metrics1);
449 
450         final ByteBuffer dst = ByteBuffer.allocate(1024);
451 
452         final int bytesRead = decoder1.read(dst);
453         Assert.assertEquals(16, bytesRead);
454         Assert.assertEquals("1234567890123456", CodecTestUtils.convert(dst));
455         final List<? extends Header> trailers = decoder1.getTrailers();
456         Assert.assertNotNull(trailers);
457         Assert.assertEquals(1, trailers.size());
458 
459         final ReadableByteChannel channel2 = new ReadableByteChannelMock(
460                 new String[] {s}, StandardCharsets.US_ASCII);
461         final SessionInputBuffer inbuf2 = new SessionInputBufferImpl(1024, 256,
462                 25, StandardCharsets.US_ASCII);
463         final BasicHttpTransportMetrics metrics2 = new BasicHttpTransportMetrics();
464         final ChunkDecoder decoder2 = new ChunkDecoder(channel2, inbuf2, metrics2);
465 
466         dst.clear();
467         try {
468             decoder2.read(dst);
469             Assert.fail("MessageConstraintException expected");
470         } catch (final MessageConstraintException ex) {
471         }
472     }
473 
474     @Test
475     public void testTooLongFoldedFooter() throws Exception {
476         final String s = "10\r\n1234567890123456\r\n" +
477                 "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";
478         final ReadableByteChannel channel1 = new ReadableByteChannelMock(
479                 new String[] {s}, StandardCharsets.US_ASCII);
480         final SessionInputBuffer inbuf1 = new SessionInputBufferImpl(1024, 256,
481                 0, StandardCharsets.US_ASCII);
482         final BasicHttpTransportMetrics metrics1 = new BasicHttpTransportMetrics();
483         final ChunkDecoder decoder1 = new ChunkDecoder(channel1, inbuf1, metrics1);
484 
485         final ByteBuffer dst = ByteBuffer.allocate(1024);
486 
487         final int bytesRead = decoder1.read(dst);
488         Assert.assertEquals(16, bytesRead);
489         Assert.assertEquals("1234567890123456", CodecTestUtils.convert(dst));
490         final List<? extends Header> trailers = decoder1.getTrailers();
491         Assert.assertNotNull(trailers);
492         Assert.assertEquals(1, trailers.size());
493 
494         final H1Config h1Config = H1Config.custom()
495                 .setMaxLineLength(25)
496                 .build();
497         final ReadableByteChannel channel2 = new ReadableByteChannelMock(
498                 new String[] {s}, StandardCharsets.US_ASCII);
499         final SessionInputBuffer inbuf2 = new SessionInputBufferImpl(1024, 256,0, StandardCharsets.US_ASCII);
500         final BasicHttpTransportMetrics metrics2 = new BasicHttpTransportMetrics();
501         final ChunkDecoder decoder2 = new ChunkDecoder(channel2, inbuf2, h1Config, metrics2);
502 
503         dst.clear();
504         try {
505             decoder2.read(dst);
506             Assert.fail("MessageConstraintException expected");
507         } catch (final MessageConstraintException ex) {
508         }
509     }
510 
511     @Test
512     public void testTooManyFooters() throws Exception {
513         final String s = "10\r\n1234567890123456\r\n" +
514                 "0\r\nFooter1: blah\r\nFooter2: blah\r\nFooter3: blah\r\nFooter4: blah\r\n\r\n";
515         final ReadableByteChannel channel1 = new ReadableByteChannelMock(
516                 new String[] {s}, StandardCharsets.US_ASCII);
517         final SessionInputBuffer inbuf1 = new SessionInputBufferImpl(1024, 256,0, StandardCharsets.US_ASCII);
518         final BasicHttpTransportMetrics metrics1 = new BasicHttpTransportMetrics();
519         final ChunkDecoder decoder1 = new ChunkDecoder(channel1, inbuf1, metrics1);
520 
521         final ByteBuffer dst = ByteBuffer.allocate(1024);
522 
523         final int bytesRead = decoder1.read(dst);
524         Assert.assertEquals(16, bytesRead);
525         Assert.assertEquals("1234567890123456", CodecTestUtils.convert(dst));
526         final List<? extends Header> trailers = decoder1.getTrailers();
527         Assert.assertNotNull(trailers);
528         Assert.assertEquals(4, trailers.size());
529 
530         final H1Config h1Config = H1Config.custom()
531                 .setMaxHeaderCount(3).build();
532         final ReadableByteChannel channel2 = new ReadableByteChannelMock(
533                 new String[] {s}, StandardCharsets.US_ASCII);
534         final SessionInputBuffer inbuf2 = new SessionInputBufferImpl(1024, 256,
535                 0, StandardCharsets.US_ASCII);
536         final BasicHttpTransportMetrics metrics2 = new BasicHttpTransportMetrics();
537         final ChunkDecoder decoder2 = new ChunkDecoder(channel2, inbuf2, h1Config, metrics2);
538 
539         dst.clear();
540         try {
541             decoder2.read(dst);
542             Assert.fail("MessageConstraintException expected");
543         } catch (final MessageConstraintException ex) {
544         }
545     }
546 
547     @Test
548     public void testInvalidConstructor() {
549         final ReadableByteChannel channel = new ReadableByteChannelMock(
550                 new String[] {"stuff;", "more stuff"}, StandardCharsets.US_ASCII);
551 
552         final SessionInputBuffer inbuf = new SessionInputBufferImpl(1024, 256, 0, StandardCharsets.US_ASCII);
553         try {
554             new ChunkDecoder(null, null, null);
555             Assert.fail("IllegalArgumentException should have been thrown");
556         } catch (final IllegalArgumentException ex) {
557             // ignore
558         }
559         try {
560             new ChunkDecoder(channel, null, null);
561             Assert.fail("IllegalArgumentException should have been thrown");
562         } catch (final IllegalArgumentException ex) {
563             // ignore
564         }
565         try {
566             new ChunkDecoder(channel, inbuf, null);
567             Assert.fail("IllegalArgumentException should have been thrown");
568         } catch (final IllegalArgumentException ex) {
569             // ignore
570         }
571     }
572 
573     @Test(expected=IllegalArgumentException.class)
574     public void testInvalidInput() throws Exception {
575         final String s = "10;key=\"value\"\r\n1234567890123456\r\n" +
576                 "5\r\n12345\r\n5\r\n12345\r\n0\r\nFooter1 abcde\r\n\r\n";
577         final ReadableByteChannel channel = new ReadableByteChannelMock(
578                 new String[] {s}, StandardCharsets.US_ASCII);
579 
580         final SessionInputBuffer inbuf = new SessionInputBufferImpl(1024, 256, 0, StandardCharsets.US_ASCII);
581         final BasicHttpTransportMetrics metrics = new BasicHttpTransportMetrics();
582         final ChunkDecoder decoder = new ChunkDecoder(channel, inbuf, metrics);
583         decoder.read(null);
584     }
585 
586     @Test
587     public void testHugeChunk() throws Exception {
588         final String s = "1234567890abcdef\r\n0123456789abcdef";
589         final ReadableByteChannel channel = new ReadableByteChannelMock(new String[] {s}, StandardCharsets.US_ASCII);
590         final SessionInputBuffer inbuf = new SessionInputBufferImpl(1024, 256, 0, StandardCharsets.US_ASCII);
591         final BasicHttpTransportMetrics metrics = new BasicHttpTransportMetrics();
592         final ChunkDecoder decoder = new ChunkDecoder(channel, inbuf, metrics);
593 
594         final ByteBuffer dst = ByteBuffer.allocate(4);
595 
596         int bytesRead = decoder.read(dst);
597         Assert.assertEquals(4, bytesRead);
598         Assert.assertEquals("0123", CodecTestUtils.convert(dst));
599         dst.clear();
600         bytesRead = decoder.read(dst);
601         Assert.assertEquals(4, bytesRead);
602         Assert.assertEquals("4567", CodecTestUtils.convert(dst));
603     }
604 
605 }