diff options
-rw-r--r-- | src/cowboy_http_protocol.erl | 16 | ||||
-rw-r--r-- | test/http_SUITE.erl | 23 |
2 files changed, 28 insertions, 11 deletions
diff --git a/src/cowboy_http_protocol.erl b/src/cowboy_http_protocol.erl index 33c6333..b91101a 100644 --- a/src/cowboy_http_protocol.erl +++ b/src/cowboy_http_protocol.erl @@ -46,6 +46,7 @@ handler :: {module(), any()}, req_empty_lines = 0 :: integer(), max_empty_lines :: integer(), + max_line_length :: integer(), timeout :: timeout(), buffer = <<>> :: binary(), hibernate = false :: boolean(), @@ -68,17 +69,22 @@ start_link(ListenerPid, Socket, Transport, Opts) -> init(ListenerPid, Socket, Transport, Opts) -> Dispatch = proplists:get_value(dispatch, Opts, []), MaxEmptyLines = proplists:get_value(max_empty_lines, Opts, 5), + MaxLineLength = proplists:get_value(max_line_length, Opts, 4096), Timeout = proplists:get_value(timeout, Opts, 5000), receive shoot -> ok end, wait_request(#state{listener=ListenerPid, socket=Socket, transport=Transport, - dispatch=Dispatch, max_empty_lines=MaxEmptyLines, timeout=Timeout}). + dispatch=Dispatch, max_empty_lines=MaxEmptyLines, + max_line_length=MaxLineLength, timeout=Timeout}). %% @private -spec parse_request(#state{}) -> ok | none(). -%% @todo Use decode_packet options to limit length? -parse_request(State=#state{buffer=Buffer}) -> +%% We limit the length of the Request-line to MaxLength to avoid endlessly +%% reading from the socket and eventually crashing. +parse_request(State=#state{buffer=Buffer, max_line_length=MaxLength}) -> case erlang:decode_packet(http_bin, Buffer, []) of {ok, Request, Rest} -> request(Request, State#state{buffer=Rest}); + {more, _Length} when byte_size(Buffer) > MaxLength -> + error_terminate(413, State); {more, _Length} -> wait_request(State); {error, _Reason} -> error_terminate(400, State) end. @@ -123,9 +129,11 @@ request({http_error, _Any}, State) -> error_terminate(400, State). -spec parse_header(#http_req{}, #state{}) -> ok | none(). -parse_header(Req, State=#state{buffer=Buffer}) -> +parse_header(Req, State=#state{buffer=Buffer, max_line_length=MaxLength}) -> case erlang:decode_packet(httph_bin, Buffer, []) of {ok, Header, Rest} -> header(Header, Req, State#state{buffer=Rest}); + {more, _Length} when byte_size(Buffer) > MaxLength -> + error_terminate(413, State); {more, _Length} -> wait_header(Req, State); {error, _Reason} -> error_terminate(400, State) end. diff --git a/test/http_SUITE.erl b/test/http_SUITE.erl index d32804e..b5ee98b 100644 --- a/test/http_SUITE.erl +++ b/test/http_SUITE.erl @@ -19,7 +19,7 @@ -export([all/0, groups/0, init_per_suite/1, end_per_suite/1, init_per_group/2, end_per_group/2]). %% ct. -export([chunked_response/1, headers_dupe/1, headers_huge/1, - keepalive_nl/1, nc_rand/1, pipeline/1, raw/1, + keepalive_nl/1, nc_rand/1, nc_zero/1, pipeline/1, raw/1, ws0/1, ws8/1, ws8_single_bytes/1, ws8_init_shutdown/1, ws_timeout_hibernate/1]). %% http. -export([http_200/1, http_404/1]). %% http and https. @@ -33,7 +33,7 @@ all() -> groups() -> BaseTests = [http_200, http_404], [{http, [], [chunked_response, headers_dupe, headers_huge, - keepalive_nl, nc_rand, pipeline, raw, + keepalive_nl, nc_rand, nc_zero, pipeline, raw, ws0, ws8, ws8_single_bytes, ws8_init_shutdown, ws_timeout_hibernate] ++ BaseTests}, {https, [], BaseTests}, {misc, [], [http_10_hostless]}]. @@ -126,7 +126,7 @@ headers_dupe(Config) -> headers_huge(Config) -> Cookie = lists:flatten(["whatever_man_biiiiiiiiiiiig_cookie_me_want_77=" - "Wed Apr 06 2011 10:38:52 GMT-0500 (CDT)" || _N <- lists:seq(1, 1000)]), + "Wed Apr 06 2011 10:38:52 GMT-0500 (CDT)" || _N <- lists:seq(1, 40)]), {_Packet, 200} = raw_req(["GET / HTTP/1.0\r\nHost: localhost\r\n" "Set-Cookie: ", Cookie, "\r\n\r\n"], Config). @@ -149,6 +149,12 @@ keepalive_nl_loop(Socket, N) -> keepalive_nl_loop(Socket, N - 1). nc_rand(Config) -> + nc_reqs(Config, "/dev/urandom"). + +nc_zero(Config) -> + nc_reqs(Config, "/dev/zero"). + +nc_reqs(Config, Input) -> Cat = os:find_executable("cat"), Nc = os:find_executable("nc"), case {Cat, Nc} of @@ -159,13 +165,13 @@ nc_rand(Config) -> _Good -> %% Throw garbage at the server then check if it's still up. {port, Port} = lists:keyfind(port, 1, Config), - [nc_rand_run(Port) || _N <- lists:seq(1, 100)], + [nc_run_req(Port, Input) || _N <- lists:seq(1, 100)], Packet = "GET / HTTP/1.0\r\nHost: localhost\r\n\r\n", {Packet, 200} = raw_req(Packet, Config) end. -nc_rand_run(Port) -> - os:cmd("cat /dev/urandom | nc localhost " ++ integer_to_list(Port)). +nc_run_req(Port, Input) -> + os:cmd("cat " ++ Input ++ " | nc localhost " ++ integer_to_list(Port)). pipeline(Config) -> {port, Port} = lists:keyfind(port, 1, Config), @@ -212,6 +218,7 @@ raw_req(Packet, Config) -> {Packet, Res}. raw(Config) -> + Huge = [$0 || _N <- lists:seq(1, 5000)], Tests = [ {"\r\n\r\n\r\n\r\n\r\nGET / HTTP/1.1\r\nHost: localhost\r\n\r\n", 200}, {"\n", 400}, @@ -229,7 +236,9 @@ raw(Config) -> {"GET http://localhost/ HTTP/1.1\r\n\r\n", 501}, {"GET / HTTP/1.2\r\nHost: localhost\r\n\r\n", 505}, {"GET /init_shutdown HTTP/1.1\r\nHost: localhost\r\n\r\n", 666}, - {"GET /long_polling HTTP/1.1\r\nHost: localhost\r\n\r\n", 102} + {"GET /long_polling HTTP/1.1\r\nHost: localhost\r\n\r\n", 102}, + {Huge, 413}, + {"GET / HTTP/1.1\r\n" ++ Huge, 413} ], [{Packet, StatusCode} = raw_req(Packet, Config) || {Packet, StatusCode} <- Tests]. |