@@ -147,6 +147,118 @@ public void testResponseChunked() {
147147 assertNull (ch .readInbound ());
148148 }
149149
150+ @ Test
151+ public void testResponseChunkedWithValidUncommonPatterns () {
152+ EmbeddedChannel ch = new EmbeddedChannel (new HttpResponseDecoder ());
153+ ch .writeInbound (Unpooled .copiedBuffer ("HTTP/1.1 200 OK\r \n Transfer-Encoding: chunked\r \n \r \n " ,
154+ CharsetUtil .US_ASCII ));
155+
156+ HttpResponse res = ch .readInbound ();
157+ assertThat (res .protocolVersion (), sameInstance (HttpVersion .HTTP_1_1 ));
158+ assertThat (res .status (), is (HttpResponseStatus .OK ));
159+
160+ byte [] data = new byte [1 ];
161+ for (int i = 0 ; i < data .length ; i ++) {
162+ data [i ] = (byte ) i ;
163+ }
164+
165+ // leading whitespace, trailing whitespace
166+
167+ assertFalse (ch .writeInbound (Unpooled .copiedBuffer (" " + Integer .toHexString (data .length ) + " \r \n " ,
168+ CharsetUtil .US_ASCII )));
169+ assertTrue (ch .writeInbound (Unpooled .copiedBuffer (data )));
170+ HttpContent content = ch .readInbound ();
171+ assertEquals (data .length , content .content ().readableBytes ());
172+
173+ byte [] decodedData = new byte [data .length ];
174+ content .content ().readBytes (decodedData );
175+ assertArrayEquals (data , decodedData );
176+ content .release ();
177+
178+ assertFalse (ch .writeInbound (Unpooled .copiedBuffer ("\r \n " , CharsetUtil .US_ASCII )));
179+
180+ // leading whitespace, trailing control char
181+
182+ assertFalse (ch .writeInbound (Unpooled .copiedBuffer (" " + Integer .toHexString (data .length ) + "\0 \r \n " ,
183+ CharsetUtil .US_ASCII )));
184+ assertTrue (ch .writeInbound (Unpooled .copiedBuffer (data )));
185+ content = ch .readInbound ();
186+ assertEquals (data .length , content .content ().readableBytes ());
187+
188+ decodedData = new byte [data .length ];
189+ content .content ().readBytes (decodedData );
190+ assertArrayEquals (data , decodedData );
191+ content .release ();
192+
193+ assertFalse (ch .writeInbound (Unpooled .copiedBuffer ("\r \n " , CharsetUtil .US_ASCII )));
194+
195+ // leading whitespace, trailing semicolon
196+
197+ assertFalse (ch .writeInbound (Unpooled .copiedBuffer (" " + Integer .toHexString (data .length ) + ";\r \n " ,
198+ CharsetUtil .US_ASCII )));
199+ assertTrue (ch .writeInbound (Unpooled .copiedBuffer (data )));
200+ content = ch .readInbound ();
201+ assertEquals (data .length , content .content ().readableBytes ());
202+
203+ decodedData = new byte [data .length ];
204+ content .content ().readBytes (decodedData );
205+ assertArrayEquals (data , decodedData );
206+ content .release ();
207+
208+ assertFalse (ch .writeInbound (Unpooled .copiedBuffer ("\r \n " , CharsetUtil .US_ASCII )));
209+
210+ // Write the last chunk.
211+ ch .writeInbound (Unpooled .copiedBuffer ("0\r \n \r \n " , CharsetUtil .US_ASCII ));
212+
213+ // Ensure the last chunk was decoded.
214+ LastHttpContent lastContent = ch .readInbound ();
215+ assertFalse (lastContent .content ().isReadable ());
216+ lastContent .release ();
217+
218+ ch .finish ();
219+ assertNull (ch .readInbound ());
220+ }
221+
222+ @ Test
223+ public void testResponseChunkedWithControlChars () {
224+ EmbeddedChannel ch = new EmbeddedChannel (new HttpResponseDecoder ());
225+ ch .writeInbound (Unpooled .copiedBuffer ("HTTP/1.1 200 OK\r \n Transfer-Encoding: chunked\r \n \r \n " ,
226+ CharsetUtil .US_ASCII ));
227+
228+ HttpResponse res = ch .readInbound ();
229+ assertThat (res .protocolVersion (), sameInstance (HttpVersion .HTTP_1_1 ));
230+ assertThat (res .status (), is (HttpResponseStatus .OK ));
231+
232+ byte [] data = new byte [1 ];
233+ for (int i = 0 ; i < data .length ; i ++) {
234+ data [i ] = (byte ) i ;
235+ }
236+
237+ assertFalse (ch .writeInbound (Unpooled .copiedBuffer (" " + Integer .toHexString (data .length ) + " \r \n " ,
238+ CharsetUtil .US_ASCII )));
239+ assertTrue (ch .writeInbound (Unpooled .copiedBuffer (data )));
240+ HttpContent content = ch .readInbound ();
241+ assertEquals (data .length , content .content ().readableBytes ());
242+
243+ byte [] decodedData = new byte [data .length ];
244+ content .content ().readBytes (decodedData );
245+ assertArrayEquals (data , decodedData );
246+ content .release ();
247+
248+ assertFalse (ch .writeInbound (Unpooled .copiedBuffer ("\r \n " , CharsetUtil .US_ASCII )));
249+
250+ // Write the last chunk.
251+ ch .writeInbound (Unpooled .copiedBuffer ("0\r \n \r \n " , CharsetUtil .US_ASCII ));
252+
253+ // Ensure the last chunk was decoded.
254+ LastHttpContent lastContent = ch .readInbound ();
255+ assertFalse (lastContent .content ().isReadable ());
256+ lastContent .release ();
257+
258+ assertFalse (ch .finish ());
259+ assertNull (ch .readInbound ());
260+ }
261+
150262 @ Test
151263 public void testResponseDisallowPartialChunks () {
152264 HttpResponseDecoder decoder = new HttpResponseDecoder (
@@ -741,6 +853,121 @@ public void testGarbageChunk() {
741853 assertThat (channel .finish (), is (false ));
742854 }
743855
856+ @ Test
857+ public void testWhiteSpaceGarbageChunk () {
858+ EmbeddedChannel channel = new EmbeddedChannel (new HttpResponseDecoder ());
859+ String responseWithIllegalChunk =
860+ "HTTP/1.1 200 OK\r \n " +
861+ "Transfer-Encoding: chunked\r \n \r \n " +
862+ " \r \n " ;
863+
864+ channel .writeInbound (Unpooled .copiedBuffer (responseWithIllegalChunk , CharsetUtil .US_ASCII ));
865+ assertThat (channel .readInbound (), is (instanceOf (HttpResponse .class )));
866+
867+ // Ensure that the decoder generates the last chunk with correct decoder result.
868+ LastHttpContent invalidChunk = channel .readInbound ();
869+ assertThat (invalidChunk .decoderResult ().isFailure (), is (true ));
870+ invalidChunk .release ();
871+
872+ // And no more messages should be produced by the decoder.
873+ assertThat (channel .readInbound (), is (nullValue ()));
874+
875+ // .. even after the connection is closed.
876+ assertThat (channel .finish (), is (false ));
877+ }
878+
879+ @ Test
880+ public void testLeadingWhiteSpacesSemiColonGarbageChunk () {
881+ EmbeddedChannel channel = new EmbeddedChannel (new HttpResponseDecoder ());
882+ String responseWithIllegalChunk =
883+ "HTTP/1.1 200 OK\r \n " +
884+ "Transfer-Encoding: chunked\r \n \r \n " +
885+ " ;\r \n " ;
886+
887+ channel .writeInbound (Unpooled .copiedBuffer (responseWithIllegalChunk , CharsetUtil .US_ASCII ));
888+ assertThat (channel .readInbound (), is (instanceOf (HttpResponse .class )));
889+
890+ // Ensure that the decoder generates the last chunk with correct decoder result.
891+ LastHttpContent invalidChunk = channel .readInbound ();
892+ assertThat (invalidChunk .decoderResult ().isFailure (), is (true ));
893+ invalidChunk .release ();
894+
895+ // And no more messages should be produced by the decoder.
896+ assertThat (channel .readInbound (), is (nullValue ()));
897+
898+ // .. even after the connection is closed.
899+ assertThat (channel .finish (), is (false ));
900+ }
901+
902+ @ Test
903+ public void testControlCharGarbageChunk () {
904+ EmbeddedChannel channel = new EmbeddedChannel (new HttpResponseDecoder ());
905+ String responseWithIllegalChunk =
906+ "HTTP/1.1 200 OK\r \n " +
907+ "Transfer-Encoding: chunked\r \n \r \n " +
908+ "\0 \r \n " ;
909+
910+ channel .writeInbound (Unpooled .copiedBuffer (responseWithIllegalChunk , CharsetUtil .US_ASCII ));
911+ assertThat (channel .readInbound (), is (instanceOf (HttpResponse .class )));
912+
913+ // Ensure that the decoder generates the last chunk with correct decoder result.
914+ LastHttpContent invalidChunk = channel .readInbound ();
915+ assertThat (invalidChunk .decoderResult ().isFailure (), is (true ));
916+ invalidChunk .release ();
917+
918+ // And no more messages should be produced by the decoder.
919+ assertThat (channel .readInbound (), is (nullValue ()));
920+
921+ // .. even after the connection is closed.
922+ assertThat (channel .finish (), is (false ));
923+ }
924+
925+ @ Test
926+ public void testLeadingWhiteSpacesControlCharGarbageChunk () {
927+ EmbeddedChannel channel = new EmbeddedChannel (new HttpResponseDecoder ());
928+ String responseWithIllegalChunk =
929+ "HTTP/1.1 200 OK\r \n " +
930+ "Transfer-Encoding: chunked\r \n \r \n " +
931+ " \0 \r \n " ;
932+
933+ channel .writeInbound (Unpooled .copiedBuffer (responseWithIllegalChunk , CharsetUtil .US_ASCII ));
934+ assertThat (channel .readInbound (), is (instanceOf (HttpResponse .class )));
935+
936+ // Ensure that the decoder generates the last chunk with correct decoder result.
937+ LastHttpContent invalidChunk = channel .readInbound ();
938+ assertThat (invalidChunk .decoderResult ().isFailure (), is (true ));
939+ invalidChunk .release ();
940+
941+ // And no more messages should be produced by the decoder.
942+ assertThat (channel .readInbound (), is (nullValue ()));
943+
944+ // .. even after the connection is closed.
945+ assertThat (channel .finish (), is (false ));
946+ }
947+
948+ @ Test
949+ public void testGarbageChunkAfterWhiteSpaces () {
950+ EmbeddedChannel channel = new EmbeddedChannel (new HttpResponseDecoder ());
951+ String responseWithIllegalChunk =
952+ "HTTP/1.1 200 OK\r \n " +
953+ "Transfer-Encoding: chunked\r \n \r \n " +
954+ " 12345N1 ;\r \n " ;
955+
956+ channel .writeInbound (Unpooled .copiedBuffer (responseWithIllegalChunk , CharsetUtil .US_ASCII ));
957+ assertThat (channel .readInbound (), is (instanceOf (HttpResponse .class )));
958+
959+ // Ensure that the decoder generates the last chunk with correct decoder result.
960+ LastHttpContent invalidChunk = channel .readInbound ();
961+ assertThat (invalidChunk .decoderResult ().isFailure (), is (true ));
962+ invalidChunk .release ();
963+
964+ // And no more messages should be produced by the decoder.
965+ assertThat (channel .readInbound (), is (nullValue ()));
966+
967+ // .. even after the connection is closed.
968+ assertThat (channel .finish (), is (false ));
969+ }
970+
744971 @ Test
745972 public void testConnectionClosedBeforeHeadersReceived () {
746973 EmbeddedChannel channel = new EmbeddedChannel (new HttpResponseDecoder ());
0 commit comments