Browse Source

Merge branch 'fix-chunked-req' of git://github.com/fishcakez/cowboy

Loïc Hoguin 12 years ago
parent
commit
f66a6fc57a
2 changed files with 82 additions and 6 deletions
  1. 32 6
      src/cowboy_http.erl
  2. 50 0
      test/http_SUITE.erl

+ 32 - 6
src/cowboy_http.erl

@@ -963,8 +963,17 @@ te_chunked(Data, {0, Streamed}) ->
 	%% @todo We are expecting an hex size, not a general token.
 	token(Data,
 		fun (<< "\r\n", Rest/binary >>, BinLen) ->
-				Len = list_to_integer(binary_to_list(BinLen), 16),
-				te_chunked(Rest, {Len, Streamed});
+				case list_to_integer(binary_to_list(BinLen), 16) of
+					%% Final chunk is parsed in one go above. Rest would be
+					%% <<\r\n">> if complete.
+					0 when byte_size(Rest) < 2 ->
+						more;
+					%% Normal chunk. Add 2 to Len for trailing <<"\r\n">>. Note
+					%% that repeated <<"-2\r\n">> would be streamed, and
+					%% accumulated, until out of memory if Len could be -2.
+					Len when Len > 0 ->
+						te_chunked(Rest, {Len + 2, Streamed})
+				end;
 			%% Chunk size shouldn't take too many bytes,
 			%% don't try to stream forever.
 			(Rest, _) when byte_size(Rest) < 16 ->
@@ -972,11 +981,28 @@ te_chunked(Data, {0, Streamed}) ->
 			(_, _) ->
 				{error, badarg}
 		end);
-te_chunked(Data, {ChunkRem, Streamed}) when byte_size(Data) >= ChunkRem + 2 ->
-	<< Chunk:ChunkRem/binary, "\r\n", Rest/binary >> = Data,
-	{ok, Chunk, Rest, {0, Streamed + byte_size(Chunk)}};
+%% <<"\n">> from trailing <<"\r\n">>.
+te_chunked(<< "\n", Rest/binary>>, {1, Streamed}) ->
+	{ok, <<>>, Rest, {0, Streamed}};
+te_chunked(<<>>, State={1, _Streamed}) ->
+	{more, 1, <<>>, State};
+%% Remainder of chunk (if any) and as much of trailing <<"\r\n">> as possible.
+te_chunked(Data, {ChunkRem, Streamed}) when byte_size(Data) >= ChunkRem - 2 ->
+	ChunkSize = ChunkRem - 2,
+	Streamed2 = Streamed + ChunkSize,
+	case Data of
+		<< Chunk:ChunkSize/binary, "\r\n", Rest/binary >> ->
+			{ok, Chunk, Rest, {0, Streamed2}};
+		<< Chunk:ChunkSize/binary, "\r" >> ->
+			{more, 1, Chunk, {1, Streamed2}};
+		<< Chunk:ChunkSize/binary >> ->
+			{more, 2, Chunk, {2, Streamed2}}
+	end;
+%% Incomplete chunk.
 te_chunked(Data, {ChunkRem, Streamed}) ->
-	{more, ChunkRem + 2, Data, {ChunkRem, Streamed}}.
+	ChunkRem2 = ChunkRem - byte_size(Data),
+	Streamed2 = Streamed + byte_size(Data),
+	{more, ChunkRem2, Data, {ChunkRem2, Streamed2}}.
 
 %% @doc Decode an identity stream.
 -spec te_identity(Bin, TransferState)

+ 50 - 0
test/http_SUITE.erl

@@ -88,6 +88,8 @@
 -export([te_chunked/1]).
 -export([te_chunked_chopped/1]).
 -export([te_chunked_delayed/1]).
+-export([te_chunked_split_body/1]).
+-export([te_chunked_split_crlf/1]).
 -export([te_identity/1]).
 
 %% ct.
@@ -162,6 +164,8 @@ groups() ->
 		te_chunked,
 		te_chunked_chopped,
 		te_chunked_delayed,
+		te_chunked_split_body,
+		te_chunked_split_crlf,
 		te_identity
 	],
 	[
@@ -1281,6 +1285,52 @@ te_chunked_delayed(Config) ->
 	{ok, 200, _, Client3} = cowboy_client:response(Client2),
 	{ok, Body, _} = cowboy_client:response_body(Client3).
 
+te_chunked_split_body(Config) ->
+	Client = ?config(client, Config),
+	Body = list_to_binary(io_lib:format("~p", [lists:seq(1, 100)])),
+	Chunks = body_to_chunks(50, Body, []),
+	{ok, Client2} = cowboy_client:request(<<"GET">>,
+		build_url("/echo/body", Config),
+		[{<<"transfer-encoding">>, <<"chunked">>}], Client),
+	{ok, Transport, Socket} = cowboy_client:transport(Client2),
+	_ = [begin
+		case Chunk of
+			%% Final chunk.
+			<<"0\r\n\r\n">> ->
+				ok = Transport:send(Socket, Chunk);
+			_ ->
+				%% Chunk of form <<"9\r\nChunkBody\r\n">>.
+				[Size, ChunkBody, <<>>] =
+					binary:split(Chunk, [<<"\r\n">>], [global]),
+				PartASize = random:uniform(byte_size(ChunkBody)),
+				<<PartA:PartASize/binary, PartB/binary>> = ChunkBody,
+				ok = Transport:send(Socket, [Size, <<"\r\n">>, PartA]),
+				ok = timer:sleep(10),
+				ok = Transport:send(Socket, [PartB, <<"\r\n">>])
+		end
+	end || Chunk <- Chunks],
+	{ok, 200, _, Client3} = cowboy_client:response(Client2),
+	{ok, Body, _} = cowboy_client:response_body(Client3).
+
+te_chunked_split_crlf(Config) ->
+	Client = ?config(client, Config),
+	Body = list_to_binary(io_lib:format("~p", [lists:seq(1, 100)])),
+	Chunks = body_to_chunks(50, Body, []),
+	{ok, Client2} = cowboy_client:request(<<"GET">>,
+		build_url("/echo/body", Config),
+		[{<<"transfer-encoding">>, <<"chunked">>}], Client),
+	{ok, Transport, Socket} = cowboy_client:transport(Client2),
+	_ = [begin
+		%% <<"\r\n">> is last 2 bytes of Chunk split before or after <<"\r">>.
+		Len = byte_size(Chunk) - (random:uniform(2) - 1),
+		<<Chunk2:Len/binary, End/binary>> = Chunk,
+		ok = Transport:send(Socket, Chunk2),
+		ok = timer:sleep(10),
+		ok = Transport:send(Socket, End)
+	end || Chunk <- Chunks],
+	{ok, 200, _, Client3} = cowboy_client:response(Client2),
+	{ok, Body, _} = cowboy_client:response_body(Client3).
+
 te_identity(Config) ->
 	Client = ?config(client, Config),
 	Body = list_to_binary(io_lib:format("~p", [lists:seq(1, 100)])),