mirror of
https://github.com/ninenines/cowboy.git
synced 2025-07-14 20:30:23 +00:00
Fix decoding of chunked body.
Previously cowboy_http:te_chunked/2 would enter an incorrect state if it tried to parse an incomplete chunk when the length was known from the partial chunk. Previosuly cowboy_http:te_chunked/2 expected the trailing "\r\n" to always be present if chunk body was present in the buffer. This is not guaranteed and so this commit accommodates that situation.
This commit is contained in:
parent
116acaead7
commit
f0cc2d01e6
2 changed files with 82 additions and 6 deletions
|
@ -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)
|
||||
|
|
|
@ -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)])),
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue