diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/cowboy_http.erl | 11 | ||||
-rw-r--r-- | src/cowboy_req.erl | 57 | ||||
-rw-r--r-- | src/cowboy_stream_h.erl | 21 |
3 files changed, 68 insertions, 21 deletions
diff --git a/src/cowboy_http.erl b/src/cowboy_http.erl index 5ee5ceb..c9c6383 100644 --- a/src/cowboy_http.erl +++ b/src/cowboy_http.erl @@ -785,8 +785,15 @@ commands(State, StreamID, [{flow, _Length}|Tail]) -> commands(State, StreamID, Tail); %% Error responses are sent only if a response wasn't sent already. -commands(State=#state{out_state=wait}, StreamID, [{error_response, StatusCode, Headers, Body}|Tail]) -> - commands(State, StreamID, [{response, StatusCode, Headers, Body}|Tail]); +commands(State=#state{out_state=wait}, StreamID, [{error_response, Status, Headers0, Body}|Tail]) -> + %% We close the connection when the error response is 408, as it + %% indicates a timeout and the RFC recommends that we stop here. (RFC7231 6.5.7) + Headers = case Status of + 408 -> Headers0#{<<"connection">> => <<"close">>}; + <<"408", _/bits>> -> Headers0#{<<"connection">> => <<"close">>}; + _ -> Headers0 + end, + commands(State, StreamID, [{response, Status, Headers, Body}|Tail]); commands(State, StreamID, [{error_response, _, _, _}|Tail]) -> commands(State, StreamID, Tail); %% Send an informational response. diff --git a/src/cowboy_req.erl b/src/cowboy_req.erl index 0106771..84e1b9d 100644 --- a/src/cowboy_req.erl +++ b/src/cowboy_req.erl @@ -184,7 +184,13 @@ qs(#{qs := Qs}) -> %% @todo Might be useful to limit the number of keys. -spec parse_qs(req()) -> [{binary(), binary() | true}]. parse_qs(#{qs := Qs}) -> - cow_qs:parse_qs(Qs). + try + cow_qs:parse_qs(Qs) + catch _:_ -> + erlang:raise(exit, {request_error, qs, + 'Malformed query string; application/x-www-form-urlencoded expected.' + }, erlang:get_stacktrace()) + end. -spec match_qs(cowboy:fields(), req()) -> map(). match_qs(Fields, Req) -> @@ -353,15 +359,21 @@ headers(#{headers := Headers}) -> -spec parse_header(binary(), Req) -> any() when Req::req(). parse_header(Name = <<"content-length">>, Req) -> - parse_header(Name, Req, 0, fun cow_http_hd:parse_content_length/1); + parse_header(Name, Req, 0); parse_header(Name = <<"cookie">>, Req) -> - parse_header(Name, Req, [], fun cow_cookie:parse_cookie/1); + parse_header(Name, Req, []); parse_header(Name, Req) -> parse_header(Name, Req, undefined). -spec parse_header(binary(), Req, any()) -> any() when Req::req(). parse_header(Name, Req, Default) -> - parse_header(Name, Req, Default, parse_header_fun(Name)). + try + parse_header(Name, Req, Default, parse_header_fun(Name)) + catch _:_ -> + erlang:raise(exit, {request_error, {header, Name}, + 'Malformed header. Please consult the relevant specification.' + }, erlang:get_stacktrace()) + end. parse_header_fun(<<"accept">>) -> fun cow_http_hd:parse_accept/1; parse_header_fun(<<"accept-charset">>) -> fun cow_http_hd:parse_accept_charset/1; @@ -447,8 +459,26 @@ read_urlencoded_body(Req) -> -spec read_urlencoded_body(Req, read_body_opts()) -> {ok, [{binary(), binary() | true}], Req} when Req::req(). read_urlencoded_body(Req0, Opts) -> - {ok, Body, Req} = read_body(Req0, Opts), - {ok, cow_qs:parse_qs(Body), Req}. + case read_body(Req0, Opts) of + {ok, Body, Req} -> + try + {ok, cow_qs:parse_qs(Body), Req} + catch _:_ -> + erlang:raise(exit, {request_error, urlencoded_body, + 'Malformed body; application/x-www-form-urlencoded expected.' + }, erlang:get_stacktrace()) + end; + {more, Body, _} -> + Length = maps:get(length, Opts, 64000), + if + byte_size(Body) < Length -> + exit({request_error, timeout, + 'The request body was not received within the configured time.'}); + true -> + exit({request_error, payload_too_large, + 'The request body is larger than allowed by configuration.'}) + end + end. %% Multipart. @@ -471,7 +501,7 @@ read_part(Req, Opts) -> end. read_part(Buffer, Opts, Req=#{multipart := {Boundary, _}}) -> - case cow_multipart:parse_headers(Buffer, Boundary) of + try cow_multipart:parse_headers(Buffer, Boundary) of more -> {Data, Req2} = stream_multipart(Req, Opts), read_part(<< Buffer/binary, Data/binary >>, Opts, Req2); @@ -486,6 +516,10 @@ read_part(Buffer, Opts, Req=#{multipart := {Boundary, _}}) -> %% Ignore epilogue. {done, _} -> {done, Req#{multipart => done}} + catch _:_ -> + erlang:raise(exit, {request_error, {multipart, headers}, + 'Malformed body; multipart expected.' + }, erlang:get_stacktrace()) end. -spec read_part_body(Req) @@ -529,8 +563,13 @@ read_part_body(Buffer, Opts, Req=#{multipart := {Boundary, _}}, Acc) -> init_multipart(Req) -> {<<"multipart">>, _, Params} = parse_header(<<"content-type">>, Req), - {_, Boundary} = lists:keyfind(<<"boundary">>, 1, Params), - Req#{multipart => {Boundary, <<>>}}. + case lists:keyfind(<<"boundary">>, 1, Params) of + {_, Boundary} -> + Req#{multipart => {Boundary, <<>>}}; + false -> + exit({request_error, {multipart, boundary}, + 'Missing boundary parameter for multipart media type.'}) + end. stream_multipart(Req=#{multipart := done}, _) -> {<<>>, Req}; diff --git a/src/cowboy_stream_h.erl b/src/cowboy_stream_h.erl index 5113f2e..4459679 100644 --- a/src/cowboy_stream_h.erl +++ b/src/cowboy_stream_h.erl @@ -69,14 +69,17 @@ data(_StreamID, IsFin, Data, State=#state{pid=Pid, read_body_ref=Ref, -spec info(cowboy_stream:streamid(), any(), State) -> {cowboy_stream:commands(), State} when State::#state{}. info(_StreamID, {'EXIT', Pid, normal}, State=#state{pid=Pid}) -> - %% @todo Do we even reach this clause? {[stop], State}; -info(_StreamID, {'EXIT', Pid, {_Reason, [T1, T2|_]}}, State=#state{pid=Pid}) - when element(1, T1) =:= cow_http_hd; element(1, T2) =:= cow_http_hd -> - %% @todo Have an option to enable/disable this specific crash report? +info(_StreamID, {'EXIT', Pid, {{request_error, Reason, _HumanReadable}, _}}, State=#state{pid=Pid}) -> + %% @todo Optionally report the crash to help debugging. %%report_crash(Ref, StreamID, Pid, Reason, Stacktrace), + Status = case Reason of + timeout -> 408; + payload_too_large -> 413; + _ -> 400 + end, %% @todo Headers? Details in body? More stuff in debug only? - {[{error_response, 400, #{<<"content-length">> => <<"0">>}, <<>>}, stop], State}; + {[{error_response, Status, #{<<"content-length">> => <<"0">>}, <<>>}, stop], State}; info(StreamID, Exit = {'EXIT', Pid, {Reason, Stacktrace}}, State=#state{ref=Ref, pid=Pid}) -> report_crash(Ref, StreamID, Pid, Reason, Stacktrace), {[ @@ -162,11 +165,9 @@ report_crash(Ref, StreamID, Pid, Reason, Stacktrace) -> proc_lib_hack(Req, Env, Middlewares) -> try execute(Req, Env, Middlewares) - catch - _:Reason when element(1, Reason) =:= cowboy_handler -> - exit(Reason); - _:Reason -> - exit({Reason, erlang:get_stacktrace()}) + catch _:Reason -> + %% @todo Have a way to identify OTP 20 to not do this twice? + exit({Reason, erlang:get_stacktrace()}) end. %% @todo |