aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLoïc Hoguin <[email protected]>2011-10-19 20:35:55 +0200
committerLoïc Hoguin <[email protected]>2011-10-19 20:35:55 +0200
commitfe5f0ca539fe883f397c97a5793f86e2de4a34f3 (patch)
treee5d1fd2627572d11b16b221a53d9c58eb620b20f
parent97df2a2d797818771aedff281c153d082470f7d2 (diff)
downloadcowboy-fe5f0ca539fe883f397c97a5793f86e2de4a34f3.tar.gz
cowboy-fe5f0ca539fe883f397c97a5793f86e2de4a34f3.tar.bz2
cowboy-fe5f0ca539fe883f397c97a5793f86e2de4a34f3.zip
Add a max_line_length to the HTTP protocol
Allows to limit the size of request and header lines, thus preventing Cowboy from infinitely reading from the socket and never finding an end of line. Defaults to 4096 bytes.
-rw-r--r--src/cowboy_http_protocol.erl16
-rw-r--r--test/http_SUITE.erl23
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].