diff options
Diffstat (limited to 'src/cowboy_protocol.erl')
-rw-r--r-- | src/cowboy_protocol.erl | 79 |
1 files changed, 46 insertions, 33 deletions
diff --git a/src/cowboy_protocol.erl b/src/cowboy_protocol.erl index b0b5aa6..bf81e52 100644 --- a/src/cowboy_protocol.erl +++ b/src/cowboy_protocol.erl @@ -20,11 +20,23 @@ %% <dt>dispatch</dt><dd>The dispatch list for this protocol.</dd> %% <dt>max_empty_lines</dt><dd>Max number of empty lines before a request. %% Defaults to 5.</dd> +%% <dt>max_header_name_length</dt><dd>Max length allowed for header names. +%% Defaults to 64.</dd> +%% <dt>max_header_value_length</dt><dd>Max length allowed for header values. +%% Defaults to 4096.</dd> +%% <dt>max_headers</dt><dd>Max number of headers allowed. +%% Defaults to 100.</dd> +%% <dt>max_keepalive</dt><dd>Max number of requests allowed in a single +%% keep-alive session. Defaults to infinity.</dd> +%% <dt>max_request_line_length</dt><dd>Max length allowed for the request +%% line. Defaults to 4096.</dd> +%% <dt>onrequest</dt><dd>Optional fun that allows Req interaction before +%% any dispatching is done. Host info, path info and bindings are thus +%% not available at this point.</dd> +%% <dt>onresponse</dt><dd>Optional fun that allows replacing a response +%% sent by the application based on its status code or headers.</dd> %% <dt>timeout</dt><dd>Time in milliseconds before an idle %% connection is closed. Defaults to 5000 milliseconds.</dd> -%% <dt>urldecode</dt><dd>Function and options argument to use when decoding -%% URL encoded strings. Defaults to `{fun cowboy_http:urldecode/2, crash}'. -%% </dd> %% </dl> %% %% Note that there is no need to monitor these processes when using Cowboy as @@ -56,13 +68,13 @@ dispatch :: cowboy_dispatcher:dispatch_rules(), onrequest :: undefined | onrequest_fun(), onresponse = undefined :: undefined | onresponse_fun(), - urldecode :: {fun((binary(), T) -> binary()), T}, - max_empty_lines :: integer(), - req_keepalive = 1 :: integer(), - max_keepalive :: integer(), - max_request_line_length :: integer(), - max_header_name_length :: integer(), - max_header_value_length :: integer(), + max_empty_lines :: non_neg_integer(), + req_keepalive = 1 :: non_neg_integer(), + max_keepalive :: non_neg_integer(), + max_request_line_length :: non_neg_integer(), + max_header_name_length :: non_neg_integer(), + max_header_value_length :: non_neg_integer(), + max_headers :: non_neg_integer(), timeout :: timeout(), hibernate = false :: boolean(), loop_timeout = infinity :: timeout(), @@ -92,24 +104,22 @@ get_value(Key, Opts, Default) -> init(ListenerPid, Socket, Transport, Opts) -> Dispatch = get_value(dispatch, Opts, []), MaxEmptyLines = get_value(max_empty_lines, Opts, 5), - MaxKeepalive = get_value(max_keepalive, Opts, infinity), - MaxRequestLineLength = get_value(max_request_line_length, Opts, 4096), MaxHeaderNameLength = get_value(max_header_name_length, Opts, 64), MaxHeaderValueLength = get_value(max_header_value_length, Opts, 4096), + MaxHeaders = get_value(max_headers, Opts, 100), + MaxKeepalive = get_value(max_keepalive, Opts, infinity), + MaxRequestLineLength = get_value(max_request_line_length, Opts, 4096), OnRequest = get_value(onrequest, Opts, undefined), OnResponse = get_value(onresponse, Opts, undefined), Timeout = get_value(timeout, Opts, 5000), - URLDecDefault = {fun cowboy_http:urldecode/2, crash}, - URLDec = get_value(urldecode, Opts, URLDecDefault), ok = ranch:accept_ack(ListenerPid), wait_request(<<>>, #state{listener=ListenerPid, socket=Socket, transport=Transport, dispatch=Dispatch, max_empty_lines=MaxEmptyLines, max_keepalive=MaxKeepalive, max_request_line_length=MaxRequestLineLength, max_header_name_length=MaxHeaderNameLength, - max_header_value_length=MaxHeaderValueLength, - timeout=Timeout, onrequest=OnRequest, onresponse=OnResponse, - urldecode=URLDec}, 0). + max_header_value_length=MaxHeaderValueLength, max_headers=MaxHeaders, + timeout=Timeout, onrequest=OnRequest, onresponse=OnResponse}, 0). %% Request parsing. %% @@ -139,7 +149,7 @@ parse_request(Buffer, State=#state{max_request_line_length=MaxLength, max_empty_lines=MaxEmpty}, ReqEmpty) -> case binary:match(Buffer, <<"\n">>) of nomatch when byte_size(Buffer) > MaxLength -> - error_terminate(413, State); + error_terminate(414, State); nomatch -> wait_request(Buffer, State, ReqEmpty); {1, _} when ReqEmpty =:= MaxEmpty -> @@ -207,6 +217,10 @@ parse_version(<< "HTTP/1.0\r\n", Rest/bits >>, S, M, P, Q, F) -> parse_version(_, State, _, _, _, _) -> error_terminate(505, State). +%% Stop receiving data if we have more than allowed number of headers. +wait_header(_, State=#state{max_headers=MaxHeaders}, _, _, _, _, _, Headers) + when length(Headers) >= MaxHeaders -> + error_terminate(400, State); wait_header(Buffer, State=#state{socket=Socket, transport=Transport, timeout=Timeout}, M, P, Q, F, V, H) -> case Transport:recv(Socket, 0, Timeout) of @@ -225,7 +239,7 @@ parse_header(Buffer, State=#state{max_header_name_length=MaxLength}, M, P, Q, F, V, H) -> case binary:match(Buffer, <<":">>) of nomatch when byte_size(Buffer) > MaxLength -> - error_terminate(413, State); + error_terminate(400, State); nomatch -> wait_header(Buffer, State, M, P, Q, F, V, H); {_, _} -> @@ -300,7 +314,7 @@ parse_hd_before_value(Buffer, State=#state{ max_header_value_length=MaxLength}, M, P, Q, F, V, H, N) -> case binary:match(Buffer, <<"\n">>) of nomatch when byte_size(Buffer) > MaxLength -> - error_terminate(413, State); + error_terminate(400, State); nomatch -> wait_hd_before_value(Buffer, State, M, P, Q, F, V, H, N); {_, _} -> @@ -353,7 +367,7 @@ parse_hd_value(<< C, Rest/bits >>, S, M, P, Q, F, V, H, N, SoFar) -> parse_hd_value(Rest, S, M, P, Q, F, V, H, N, << SoFar/binary, C >>); parse_hd_value(<<>>, State=#state{max_header_value_length=MaxLength}, _, _, _, _, _, _, _, SoFar) when byte_size(SoFar) > MaxLength -> - error_terminate(413, State); + error_terminate(400, State); parse_hd_value(<<>>, S, M, P, Q, F, V, H, N, SoFar) -> wait_hd_value(<<>>, S, M, P, Q, F, V, H, N, SoFar). @@ -421,11 +435,11 @@ parse_host(<< C, Rest/bits >>, Acc) -> request(Buffer, State=#state{socket=Socket, transport=Transport, req_keepalive=ReqKeepalive, max_keepalive=MaxKeepalive, - onresponse=OnResponse, urldecode=URLDecode}, + onresponse=OnResponse}, Method, Path, Query, Fragment, Version, Headers, Host, Port) -> Req = cowboy_req:new(Socket, Transport, Method, Path, Query, Fragment, Version, Headers, Host, Port, Buffer, ReqKeepalive < MaxKeepalive, - OnResponse, URLDecode), + OnResponse), onrequest(Req, State, Host, Path). %% Call the global onrequest callback. The callback can send a reply, @@ -437,16 +451,14 @@ onrequest(Req, State=#state{onrequest=undefined}, Host, Path) -> dispatch(Req, State, Host, Path); onrequest(Req, State=#state{onrequest=OnRequest}, Host, Path) -> Req2 = OnRequest(Req), - case cowboy_req:get_resp_state(Req2) of + case cowboy_req:get(resp_state, Req2) of waiting -> dispatch(Req2, State, Host, Path); _ -> next_request(Req2, State, ok) end. -spec dispatch(cowboy_req:req(), #state{}, binary(), binary()) -> ok. -dispatch(Req, State=#state{dispatch=Dispatch, urldecode={URLDecFun, URLDecArg}}, - Host, Path) -> - case cowboy_dispatcher:match(Dispatch, - fun(Bin) -> URLDecFun(Bin, URLDecArg) end, Host, Path) of +dispatch(Req, State=#state{dispatch=Dispatch}, Host, Path) -> + case cowboy_dispatcher:match(Dispatch, Host, Path) of {ok, Handler, Opts, Bindings, HostInfo, PathInfo} -> Req2 = cowboy_req:set_bindings(HostInfo, PathInfo, Bindings, Req), handler_init(Req2, State, Handler, Opts); @@ -540,6 +552,7 @@ handler_loop_timeout(State=#state{loop_timeout=Timeout, TRef = erlang:start_timer(Timeout, self(), ?MODULE), State#state{loop_timeout_ref=TRef}. +%% @private -spec handler_loop(cowboy_req:req(), #state{}, module(), any()) -> ok. handler_loop(Req, State=#state{loop_timeout_ref=TRef}, Handler, HandlerState) -> receive @@ -597,13 +610,13 @@ terminate_request(Req, State, Handler, HandlerState) -> -spec next_request(cowboy_req:req(), #state{}, any()) -> ok. next_request(Req, State=#state{req_keepalive=Keepalive}, HandlerRes) -> cowboy_req:ensure_response(Req, 204), - {BodyRes, Buffer} = case cowboy_req:skip_body(Req) of - {ok, Req2} -> {ok, cowboy_req:get_buffer(Req2)}; - {error, _} -> {close, <<>>} + {BodyRes, [Buffer, Connection]} = case cowboy_req:skip_body(Req) of + {ok, Req2} -> {ok, cowboy_req:get([buffer, connection], Req2)}; + {error, _} -> {close, [<<>>, close]} end, %% Flush the resp_sent message before moving on. receive {cowboy_req, resp_sent} -> ok after 0 -> ok end, - case {HandlerRes, BodyRes, cowboy_req:get_connection(Req)} of + case {HandlerRes, BodyRes, Connection} of {ok, ok, keepalive} -> ?MODULE:parse_request(Buffer, State#state{ req_keepalive=Keepalive + 1}, 0); @@ -620,7 +633,7 @@ error_terminate(Code, State=#state{socket=Socket, transport=Transport, after 0 -> _ = cowboy_req:reply(Code, cowboy_req:new(Socket, Transport, <<"GET">>, <<>>, <<>>, <<>>, {1, 1}, [], <<>>, undefined, - <<>>, false, OnResponse, undefined)), + <<>>, false, OnResponse)), ok end, terminate(State). |