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