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.http.impl.nio.codecs;
29  
30  import java.io.IOException;
31  import java.nio.ByteBuffer;
32  import java.nio.channels.ReadableByteChannel;
33  
34  import org.apache.http.ConnectionClosedException;
35  import org.apache.http.Consts;
36  import org.apache.http.Header;
37  import org.apache.http.MalformedChunkCodingException;
38  import org.apache.http.MessageConstraintException;
39  import org.apache.http.ReadableByteChannelMock;
40  import org.apache.http.TruncatedChunkException;
41  import org.apache.http.config.MessageConstraints;
42  import org.apache.http.impl.io.HttpTransportMetricsImpl;
43  import org.apache.http.impl.nio.reactor.SessionInputBufferImpl;
44  import org.apache.http.nio.reactor.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}, Consts.ASCII);
58          final SessionInputBuffer inbuf = new SessionInputBufferImpl(1024, 256, Consts.ASCII);
59          final HttpTransportMetricsImpl metrics = new HttpTransportMetricsImpl();
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 Header[] footers = decoder.getFooters();
68          Assert.assertEquals(0, footers.length);
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}, Consts.ASCII);
83  
84          final SessionInputBuffer inbuf = new SessionInputBufferImpl(1024, 256, Consts.ASCII);
85          final HttpTransportMetricsImpl metrics = new HttpTransportMetricsImpl();
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 Header[] footers = decoder.getFooters();
102         Assert.assertEquals(2, footers.length);
103         Assert.assertEquals("Footer1", footers[0].getName());
104         Assert.assertEquals("abcde", footers[0].getValue());
105         Assert.assertEquals("Footer2", footers[1].getName());
106         Assert.assertEquals("fghij", footers[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}, Consts.ASCII);
120 
121         final SessionInputBuffer inbuf = new SessionInputBufferImpl(1024, 256, Consts.ASCII);
122         final HttpTransportMetricsImpl metrics = new HttpTransportMetricsImpl();
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}, Consts.ASCII);
156 
157         final SessionInputBuffer inbuf = new SessionInputBufferImpl(32, 32, Consts.ASCII);
158         final HttpTransportMetricsImpl metrics = new HttpTransportMetricsImpl();
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, Consts.ASCII);
186 
187         final SessionInputBuffer inbuf = new SessionInputBufferImpl(1024, 256, Consts.ASCII);
188         final HttpTransportMetricsImpl metrics = new HttpTransportMetricsImpl();
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 Header[] footers = decoder.getFooters();
206         Assert.assertEquals(2, footers.length);
207         Assert.assertEquals("Footer1", footers[0].getName());
208         Assert.assertEquals("abcde", footers[0].getValue());
209         Assert.assertEquals("Footer2", footers[1].getName());
210         Assert.assertEquals("fghij", footers[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}, Consts.ASCII);
223 
224         final SessionInputBuffer inbuf = new SessionInputBufferImpl(1024, 256, Consts.ASCII);
225         final HttpTransportMetricsImpl metrics = new HttpTransportMetricsImpl();
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}, Consts.ASCII);
237 
238         final SessionInputBuffer inbuf = new SessionInputBufferImpl(1024, 256, Consts.ASCII);
239         final HttpTransportMetricsImpl metrics = new HttpTransportMetricsImpl();
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}, Consts.ASCII);
251 
252         final SessionInputBuffer inbuf = new SessionInputBufferImpl(1024, 256, Consts.ASCII);
253         final HttpTransportMetricsImpl metrics = new HttpTransportMetricsImpl();
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}, Consts.ASCII);
267 
268         final SessionInputBuffer inbuf = new SessionInputBufferImpl(1024, 256, Consts.ASCII);
269         final HttpTransportMetricsImpl metrics = new HttpTransportMetricsImpl();
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 Header[] footers = decoder.getFooters();
279         Assert.assertEquals(1, footers.length);
280         Assert.assertEquals("Footer1", footers[0].getName());
281         Assert.assertEquals("abcde  fghij", footers[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}, Consts.ASCII);
290 
291         final SessionInputBuffer inbuf = new SessionInputBufferImpl(1024, 256, Consts.ASCII);
292         final HttpTransportMetricsImpl metrics = new HttpTransportMetricsImpl();
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}, Consts.ASCII);
305 
306         final SessionInputBuffer inbuf = new SessionInputBufferImpl(1024, 256, Consts.ASCII);
307         final HttpTransportMetricsImpl metrics = new HttpTransportMetricsImpl();
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}, Consts.ASCII);
323 
324         final SessionInputBuffer inbuf = new SessionInputBufferImpl(1024, 256, Consts.ASCII);
325         final HttpTransportMetricsImpl metrics = new HttpTransportMetricsImpl();
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}, Consts.ASCII);
353 
354         final SessionInputBuffer inbuf = new SessionInputBufferImpl(1024, 256, Consts.ASCII);
355         final HttpTransportMetricsImpl metrics = new HttpTransportMetricsImpl();
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}, Consts.ASCII);
385 
386         final SessionInputBuffer inbuf = new SessionInputBufferImpl(1024, 256, Consts.ASCII);
387         final HttpTransportMetricsImpl metrics = new HttpTransportMetricsImpl();
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}, Consts.ASCII);
410 
411         final SessionInputBuffer inbuf1 = new SessionInputBufferImpl(1024, 256,
412                 MessageConstraints.DEFAULT, null, null);
413         final HttpTransportMetricsImpl metrics1 = new HttpTransportMetricsImpl();
414         final ChunkDecoder decoder1 = new ChunkDecoder(channel1, inbuf1, metrics1);
415 
416         final ByteBuffer dst = ByteBuffer.allocate(1024);
417 
418         while (dst.hasRemaining() && !decoder1.isCompleted()) {
419             decoder1.read(dst);
420         }
421         Assert.assertEquals("12345", CodecTestUtils.convert(dst));
422         Assert.assertTrue(decoder1.isCompleted());
423 
424         final ReadableByteChannel channel2 = new ReadableByteChannelMock(
425                 new String[] {s}, Consts.ASCII);
426 
427         final SessionInputBuffer inbuf2 = new SessionInputBufferImpl(1024, 256,
428                 MessageConstraints.lineLen(10), null, null);
429         final HttpTransportMetricsImpl metrics2 = new HttpTransportMetricsImpl();
430         final ChunkDecoder decoder2 = new ChunkDecoder(channel2, inbuf2, metrics2);
431 
432         dst.clear();
433         try {
434             decoder2.read(dst);
435             Assert.fail("MessageConstraintException expected");
436         } catch (final MessageConstraintException ex) {
437         }
438     }
439 
440     @Test
441     public void testTooLongFooter() throws Exception {
442         final String s = "10\r\n1234567890123456\r\n" +
443                 "0\r\nFooter1: looooooooooooooooooooooooooooooooooooooooooooooooooooooog\r\n\r\n";
444 //        final String s = "10\r\n1234567890123456\r\n" +
445 //                "0\r\nFooter1: looooooooooooooooooooooooooooooooooooooooog\r\n   \r\n  fghij\r\n\r\n";
446         final ReadableByteChannel channel1 = new ReadableByteChannelMock(
447                 new String[] {s}, Consts.ASCII);
448         final SessionInputBuffer inbuf1 = new SessionInputBufferImpl(1024, 256,
449                 MessageConstraints.DEFAULT, Consts.ASCII);
450         final HttpTransportMetricsImpl metrics1 = new HttpTransportMetricsImpl();
451         final ChunkDecoder decoder1 = new ChunkDecoder(channel1, inbuf1, metrics1);
452 
453         final ByteBuffer dst = ByteBuffer.allocate(1024);
454 
455         final int bytesRead = decoder1.read(dst);
456         Assert.assertEquals(16, bytesRead);
457         Assert.assertEquals("1234567890123456", CodecTestUtils.convert(dst));
458         final Header[] footers = decoder1.getFooters();
459         Assert.assertNotNull(footers);
460         Assert.assertEquals(1, footers.length);
461 
462         final ReadableByteChannel channel2 = new ReadableByteChannelMock(
463                 new String[] {s}, Consts.ASCII);
464         final SessionInputBuffer inbuf2 = new SessionInputBufferImpl(1024, 256,
465                 MessageConstraints.lineLen(25), Consts.ASCII);
466         final HttpTransportMetricsImpl metrics2 = new HttpTransportMetricsImpl();
467         final ChunkDecoder decoder2 = new ChunkDecoder(channel2, inbuf2, metrics2);
468 
469         dst.clear();
470         try {
471             decoder2.read(dst);
472             Assert.fail("MessageConstraintException expected");
473         } catch (final MessageConstraintException ex) {
474         }
475     }
476 
477     @Test
478     public void testTooLongFoldedFooter() throws Exception {
479         final String s = "10\r\n1234567890123456\r\n" +
480                 "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";
481         final ReadableByteChannel channel1 = new ReadableByteChannelMock(
482                 new String[] {s}, Consts.ASCII);
483         final SessionInputBuffer inbuf1 = new SessionInputBufferImpl(1024, 256,
484                 MessageConstraints.DEFAULT, Consts.ASCII);
485         final HttpTransportMetricsImpl metrics1 = new HttpTransportMetricsImpl();
486         final ChunkDecoder decoder1 = new ChunkDecoder(channel1, inbuf1, metrics1);
487 
488         final ByteBuffer dst = ByteBuffer.allocate(1024);
489 
490         final int bytesRead = decoder1.read(dst);
491         Assert.assertEquals(16, bytesRead);
492         Assert.assertEquals("1234567890123456", CodecTestUtils.convert(dst));
493         final Header[] footers = decoder1.getFooters();
494         Assert.assertNotNull(footers);
495         Assert.assertEquals(1, footers.length);
496 
497         final MessageConstraints constraints = MessageConstraints.lineLen(25);
498         final ReadableByteChannel channel2 = new ReadableByteChannelMock(
499                 new String[] {s}, Consts.ASCII);
500         final SessionInputBuffer inbuf2 = new SessionInputBufferImpl(1024, 256,
501                 constraints, Consts.ASCII);
502         final HttpTransportMetricsImpl metrics2 = new HttpTransportMetricsImpl();
503         final ChunkDecoder decoder2 = new ChunkDecoder(channel2, inbuf2, constraints, metrics2);
504 
505         dst.clear();
506         try {
507             decoder2.read(dst);
508             Assert.fail("MessageConstraintException expected");
509         } catch (final MessageConstraintException ex) {
510         }
511     }
512 
513     @Test
514     public void testTooManyFooters() throws Exception {
515         final String s = "10\r\n1234567890123456\r\n" +
516                 "0\r\nFooter1: blah\r\nFooter2: blah\r\nFooter3: blah\r\nFooter4: blah\r\n\r\n";
517         final ReadableByteChannel channel1 = new ReadableByteChannelMock(
518                 new String[] {s}, Consts.ASCII);
519         final SessionInputBuffer inbuf1 = new SessionInputBufferImpl(1024, 256,
520                 MessageConstraints.DEFAULT, Consts.ASCII);
521         final HttpTransportMetricsImpl metrics1 = new HttpTransportMetricsImpl();
522         final ChunkDecoder decoder1 = new ChunkDecoder(channel1, inbuf1, metrics1);
523 
524         final ByteBuffer dst = ByteBuffer.allocate(1024);
525 
526         final int bytesRead = decoder1.read(dst);
527         Assert.assertEquals(16, bytesRead);
528         Assert.assertEquals("1234567890123456", CodecTestUtils.convert(dst));
529         final Header[] footers = decoder1.getFooters();
530         Assert.assertNotNull(footers);
531         Assert.assertEquals(4, footers.length);
532 
533         final MessageConstraints constraints = MessageConstraints.custom()
534                 .setMaxHeaderCount(3).build();
535         final ReadableByteChannel channel2 = new ReadableByteChannelMock(
536                 new String[] {s}, Consts.ASCII);
537         final SessionInputBuffer inbuf2 = new SessionInputBufferImpl(1024, 256,
538                 constraints, Consts.ASCII);
539         final HttpTransportMetricsImpl metrics2 = new HttpTransportMetricsImpl();
540         final ChunkDecoder decoder2 = new ChunkDecoder(channel2, inbuf2, constraints, metrics2);
541 
542         dst.clear();
543         try {
544             decoder2.read(dst);
545             Assert.fail("MessageConstraintException expected");
546         } catch (final MessageConstraintException ex) {
547         }
548     }
549 
550     @Test
551     public void testInvalidConstructor() {
552         final ReadableByteChannel channel = new ReadableByteChannelMock(
553                 new String[] {"stuff;", "more stuff"}, Consts.ASCII);
554 
555         final SessionInputBuffer inbuf = new SessionInputBufferImpl(1024, 256, Consts.ASCII);
556         try {
557             new ChunkDecoder(null, null, null);
558             Assert.fail("IllegalArgumentException should have been thrown");
559         } catch (final IllegalArgumentException ex) {
560             // ignore
561         }
562         try {
563             new ChunkDecoder(channel, null, null);
564             Assert.fail("IllegalArgumentException should have been thrown");
565         } catch (final IllegalArgumentException ex) {
566             // ignore
567         }
568         try {
569             new ChunkDecoder(channel, inbuf, null);
570             Assert.fail("IllegalArgumentException should have been thrown");
571         } catch (final IllegalArgumentException ex) {
572             // ignore
573         }
574     }
575 
576     @Test(expected=IllegalArgumentException.class)
577     public void testInvalidInput() throws Exception {
578         final String s = "10;key=\"value\"\r\n1234567890123456\r\n" +
579                 "5\r\n12345\r\n5\r\n12345\r\n0\r\nFooter1 abcde\r\n\r\n";
580         final ReadableByteChannel channel = new ReadableByteChannelMock(
581                 new String[] {s}, Consts.ASCII);
582 
583         final SessionInputBuffer inbuf = new SessionInputBufferImpl(1024, 256, Consts.ASCII);
584         final HttpTransportMetricsImpl metrics = new HttpTransportMetricsImpl();
585         final ChunkDecoder decoder = new ChunkDecoder(channel, inbuf, metrics);
586         decoder.read(null);
587     }
588 
589     @Test
590     public void testHugeChunk() throws Exception {
591         final String s = "1234567890abcdef\r\n0123456789abcdef";
592         final ReadableByteChannel channel = new ReadableByteChannelMock(
593                 new String[] {s}, Consts.ASCII);
594         final SessionInputBuffer inbuf = new SessionInputBufferImpl(1024, 256, Consts.ASCII);
595         final HttpTransportMetricsImpl metrics = new HttpTransportMetricsImpl();
596         final ChunkDecoder decoder = new ChunkDecoder(channel, inbuf, metrics);
597 
598         final ByteBuffer dst = ByteBuffer.allocate(4);
599 
600         int bytesRead = decoder.read(dst);
601         Assert.assertEquals(4, bytesRead);
602         Assert.assertEquals("0123", CodecTestUtils.convert(dst));
603         dst.clear();
604         bytesRead = decoder.read(dst);
605         Assert.assertEquals(4, bytesRead);
606         Assert.assertEquals("4567", CodecTestUtils.convert(dst));
607     }
608 
609 }