aboutsummaryrefslogtreecommitdiffstats
path: root/src/cowboy_http.erl
diff options
context:
space:
mode:
authorLoïc Hoguin <[email protected]>2017-12-07 22:12:34 +0100
committerLoïc Hoguin <[email protected]>2017-12-07 22:33:52 +0100
commitb000d53855592c5470a8c2f2dcebc4915ec4d4d1 (patch)
tree355923b7751c5fe943de5f028b1e96cda90e6604 /src/cowboy_http.erl
parentc2b813684edca59d8b77c33f2632ef243bed7449 (diff)
downloadcowboy-b000d53855592c5470a8c2f2dcebc4915ec4d4d1.tar.gz
cowboy-b000d53855592c5470a8c2f2dcebc4915ec4d4d1.tar.bz2
cowboy-b000d53855592c5470a8c2f2dcebc4915ec4d4d1.zip
Add more rfc7231 tests and a new max_skip_body_length option
The option controls how much body we accept to skip for HTTP/1.1 connections when the user code did not consume the body fully. It defaults to 1MB.
Diffstat (limited to 'src/cowboy_http.erl')
-rw-r--r--src/cowboy_http.erl58
1 files changed, 38 insertions, 20 deletions
diff --git a/src/cowboy_http.erl b/src/cowboy_http.erl
index bc8fbb9..2301abb 100644
--- a/src/cowboy_http.erl
+++ b/src/cowboy_http.erl
@@ -58,6 +58,8 @@
%% by not reading from the socket when the window is empty).
-record(ps_body, {
+ length :: non_neg_integer() | undefined,
+ received = 0 :: non_neg_integer(),
%% @todo flow
transfer_decode_fun :: fun(), %% @todo better type
transfer_decode_state :: any() %% @todo better type
@@ -305,7 +307,8 @@ after_parse({data, StreamID, IsFin, Data, State=#state{
stream_reset(State, StreamID, {internal_error, {Class, Exception},
'Unhandled exception in cowboy_stream:data/4.'})
end;
-%% No corresponding stream, skip.
+%% No corresponding stream. We must skip the body of the previous request
+%% in order to process the next one.
after_parse({data, _, _, _, State, Buffer}) ->
before_loop(State, Buffer);
after_parse({more, State, Buffer}) ->
@@ -667,6 +670,7 @@ request(Buffer, State0=#state{ref=Ref, transport=Transport, peer=Peer, sock=Sock
State = case HasBody of
true ->
State0#state{in_state=#ps_body{
+ length = BodyLength,
transfer_decode_fun = TDecodeFun,
transfer_decode_state = TDecodeState
}};
@@ -735,7 +739,8 @@ http2_upgrade(State=#state{parent=Parent, ref=Ref, socket=Socket, transport=Tran
%% Request body parsing.
parse_body(Buffer, State=#state{in_streamid=StreamID, in_state=
- PS=#ps_body{transfer_decode_fun=TDecode, transfer_decode_state=TState0}}) ->
+ PS=#ps_body{received=Received, transfer_decode_fun=TDecode,
+ transfer_decode_state=TState0}}) ->
%% @todo Proper trailers.
try TDecode(Buffer, TState0) of
more ->
@@ -744,15 +749,18 @@ parse_body(Buffer, State=#state{in_streamid=StreamID, in_state=
{more, Data, TState} ->
%% @todo Asks for 0 or more bytes.
{data, StreamID, nofin, Data, State#state{in_state=
- PS#ps_body{transfer_decode_state=TState}}, <<>>};
+ PS#ps_body{received=Received + byte_size(Data),
+ transfer_decode_state=TState}}, <<>>};
{more, Data, _Length, TState} when is_integer(_Length) ->
%% @todo Asks for Length more bytes.
{data, StreamID, nofin, Data, State#state{in_state=
- PS#ps_body{transfer_decode_state=TState}}, <<>>};
+ PS#ps_body{received=Received + byte_size(Data),
+ transfer_decode_state=TState}}, <<>>};
{more, Data, Rest, TState} ->
%% @todo Asks for 0 or more bytes.
{data, StreamID, nofin, Data, State#state{in_state=
- PS#ps_body{transfer_decode_state=TState}}, Rest};
+ PS#ps_body{received=Received + byte_size(Data),
+ transfer_decode_state=TState}}, Rest};
{done, _HasTrailers, Rest} ->
{data, StreamID, fin, <<>>, set_timeout(
State#state{in_streamid=StreamID + 1, in_state=#ps_request_line{}}), Rest};
@@ -1043,8 +1051,8 @@ stream_reset(State, StreamID, StreamError={internal_error, _, _}) ->
% stream_terminate(State#state{out_state=done}, StreamID, StreamError).
stream_terminate(State, StreamID, StreamError).
-stream_terminate(State0=#state{out_streamid=OutStreamID, out_state=OutState,
- streams=Streams0, children=Children0}, StreamID, Reason) ->
+stream_terminate(State0=#state{opts=Opts, in_state=InState, out_streamid=OutStreamID,
+ out_state=OutState, streams=Streams0, children=Children0}, StreamID, Reason) ->
#stream{version=Version} = lists:keyfind(StreamID, #stream.id, Streams0),
State1 = #state{streams=Streams1} = case OutState of
wait when element(1, Reason) =:= internal_error ->
@@ -1070,22 +1078,32 @@ stream_terminate(State0=#state{out_streamid=OutStreamID, out_state=OutState,
[] -> set_timeout(State2);
_ -> State2
end,
- %% Move on to the next stream.
- %% @todo Skip the body, if any, or drop the connection if too large.
+ %% We want to drop the connection if the body was not read fully
+ %% and we don't know its length or more remains to be read than
+ %% configuration allows.
%% @todo Only do this if Current =:= StreamID.
- NextOutStreamID = OutStreamID + 1,
- case lists:keyfind(NextOutStreamID, #stream.id, Streams) of
- false ->
- %% @todo This is clearly wrong, if the stream is gone we need to check if
- %% there used to be such a stream, and if there was to send an error.
- State#state{out_streamid=NextOutStreamID, out_state=wait, streams=Streams, children=Children};
- #stream{queue=Commands} ->
- %% @todo Remove queue from the stream.
- commands(State#state{out_streamid=NextOutStreamID, out_state=wait,
- streams=Streams, children=Children}, NextOutStreamID, Commands)
+ MaxSkipBodyLength = maps:get(max_skip_body_length, Opts, 1000000),
+ case InState of
+ #ps_body{length=undefined} ->
+ terminate(State#state{streams=Streams, children=Children}, skip_body_unknown_length);
+ #ps_body{length=Len, received=Received} when Received + MaxSkipBodyLength < Len ->
+ terminate(State#state{streams=Streams, children=Children}, skip_body_too_large);
+ _ ->
+ %% Move on to the next stream.
+ NextOutStreamID = OutStreamID + 1,
+ case lists:keyfind(NextOutStreamID, #stream.id, Streams) of
+ false ->
+ %% @todo This is clearly wrong, if the stream is gone we need to check if
+ %% there used to be such a stream, and if there was to send an error.
+ State#state{out_streamid=NextOutStreamID, out_state=wait,
+ streams=Streams, children=Children};
+ #stream{queue=Commands} ->
+ %% @todo Remove queue from the stream.
+ commands(State#state{out_streamid=NextOutStreamID, out_state=wait,
+ streams=Streams, children=Children}, NextOutStreamID, Commands)
+ end
end.
-%% @todo Taken directly from _http2
stream_call_terminate(StreamID, Reason, StreamState) ->
try
cowboy_stream:terminate(StreamID, Reason, StreamState)