diff --git a/src/elli_http.erl b/src/elli_http.erl index 3c95a3b..0a0f58b 100644 --- a/src/elli_http.erl +++ b/src/elli_http.erl @@ -427,7 +427,8 @@ get_request_(Socket, Buffer, Options, {Mod, Args} = Callback) -> exit(normal) end. -recv_request(Socket, Buffer, Options, {Mod, Args} = _Callback) -> +recv_request(Socket, Buffer, Options, {Mod, Args} = Callback) -> + ok = check_max_buffer_size(Socket, Buffer, Options, Callback, 414), case elli_tcp:recv(Socket, 0, request_timeout(Options)) of {ok, Data} -> <>; @@ -470,6 +471,7 @@ get_headers(Socket, Buffer, Headers, Count, Opts, {Mod, Args} = Callback) -> {ok, {http_error, _}, Rest} -> get_headers(Socket, Rest, Headers, Count, Opts, Callback); {more, _} -> + ok = check_max_buffer_size(Socket, Buffer, Opts, Callback, 413), case elli_tcp:recv(Socket, 0, header_timeout(Opts)) of {ok, Data} -> get_headers(Socket, <>, @@ -583,6 +585,33 @@ do_check_max_size_x2(Socket, ContentLength, Buffer, MaxSize) elli_tcp:send(Socket, Response); do_check_max_size_x2(_, _, _, _) -> ok. +%% @doc Same as check_max_size/5 but for arbitrary buffers (request line, headers) +check_max_buffer_size(Socket, Buffer, Opts, {Mod, Args}, ErrorCode) -> + MaxSize = max_body_size(Opts), + BufferSize = size(Buffer), + case MaxSize > BufferSize of + true -> + ok; + false -> + case ErrorCode of + 413 -> + handle_event(Mod, bad_request, [{headers_size, BufferSize}], Args); + 414 -> + handle_event(Mod, bad_request, [{request_line_size, BufferSize}], Args) + end, + + case MaxSize * 2 > BufferSize of + true -> + Response = http_response(ErrorCode), + elli_tcp:send(Socket, Response); + false -> + ok + end, + + elli_tcp:close(Socket), + exit(normal) + end. + -spec mk_req(Method, PathTuple, Headers, Body, V, Socket, Callback) -> Req when Method :: elli:http_method(), PathTuple :: {PathType :: atom(), RawPath :: binary()}, diff --git a/test/elli_tests.erl b/test/elli_tests.erl index a7b6f08..b6e4ad7 100644 --- a/test/elli_tests.erl +++ b/test/elli_tests.erl @@ -48,8 +48,12 @@ elli_test_() -> ?_test(ip()), ?_test(found()), ?_test(too_many_headers()), + ?_test(too_big_headers()), + ?_test(way_too_big_headers()), ?_test(too_big_body()), ?_test(way_too_big_body()), + ?_test(too_big_request_line()), + ?_test(way_too_big_request_line()), ?_test(bad_request_line()), ?_test(content_length()), ?_test(user_content_length()), @@ -351,6 +355,18 @@ too_many_headers() -> Response = hackney:get("http://localhost:3001/foo", Headers), ?assertMatch(400, status(Response)). +too_big_headers() -> + CookieHeader = {"Cookie", binary:copy(<<"a">>, 1024 * 1001)}, + Headers = [CookieHeader], + Response = hackney:get("http://localhost:3001/foo", Headers), + ?assertMatch(413, status(Response)). + +way_too_big_headers() -> + CookieHeader = {"Cookie", binary:copy(<<"a">>, 1024 * 2200)}, + Headers = [CookieHeader], + ?assertMatch({error, closed}, + hackney:get("http://localhost:3001/foo", Headers)). + too_big_body() -> Body = binary:copy(<<"x">>, (1024 * 1000) + 1), Response = hackney:post("http://localhost:3001/foo", [], Body), @@ -361,6 +377,23 @@ way_too_big_body() -> ?assertMatch({error, closed}, hackney:post("http://localhost:3001/foo", [], Body)). +too_big_request_line() -> + Path = binary:copy(<<"a">>, 1024 * 1001), + {ok, Socket} = gen_tcp:connect("127.0.0.1", 3001, + [{active, false}, binary]), + Req = <<"GET /", Path/binary, " HTTP/1.1\r\n">>, + gen_tcp:send(Socket, Req), + ?assertMatch({ok, <<"HTTP/1.1 414 Request-URI Too Long\r\n" + "Content-Length: 0\r\n\r\n">>}, + gen_tcp:recv(Socket, 0)). + +way_too_big_request_line() -> + Path = binary:copy(<<"a">>, 1024 * 2200), + {ok, Socket} = gen_tcp:connect("127.0.0.1", 3001, + [{active, false}, binary]), + Req = <<"GET /", Path/binary, " HTTP/1.1\r\n">>, + gen_tcp:send(Socket, Req), + ?assertMatch({error, closed}, gen_tcp:recv(Socket, 0)). bad_request_line() -> {ok, Socket} = gen_tcp:connect("127.0.0.1", 3001,