mirror of
https://github.com/ninenines/cowboy.git
synced 2025-07-14 20:30:23 +00:00
Check the length before reading the body in body/1 and body_qs/1
This commit is contained in:
parent
233cf43ab9
commit
84d7671e91
5 changed files with 168 additions and 17 deletions
21
guide/req.md
21
guide/req.md
|
@ -91,11 +91,22 @@ was passed in the request, then Cowboy will return a size
|
|||
of `undefined`, as it has no way of knowing it.
|
||||
|
||||
If you know the request contains a body, and that it is
|
||||
of appropriate size, then you can read it directly with
|
||||
either `body/1` or `body_qs/1`. Otherwise, you will want
|
||||
to stream it with `stream_body/1` and `skip_body/1`, with
|
||||
the streaming process optionally initialized using `init_stream/4`
|
||||
or `init_stream/5`.
|
||||
within 8MB (for `body/1`) or 16KB (for `body_qs/1`) bytes,
|
||||
then you can read it directly with either `body/1` or `body_qs/1`.
|
||||
If you want to override the default size limits of `body/1`
|
||||
or `body_qs/1`, you can pass the maximum body length byte
|
||||
size as first parameter to `body/2` and `body_qs/2` or pass
|
||||
atom `infinity` to ignore size limits.
|
||||
|
||||
If the request contains bigger body than allowed default sizes
|
||||
or supplied maximum body length, `body/1`, `body/2`, `body_qs/1`
|
||||
and `body_qs/2` will return `{error, badlength}`. If the request
|
||||
contains chunked body, `body/1`, `body/2`, `body_qs/1`
|
||||
and `body_qs/2` will return `{error, chunked}`.
|
||||
If you get either of the above two errors, you will want to
|
||||
handle the body of the request using `stream_body/1` and
|
||||
`skip_body/1`, with the streaming process optionally
|
||||
initialized using `init_stream/4` or `init_stream/5`.
|
||||
|
||||
Multipart request body
|
||||
----------------------
|
||||
|
|
|
@ -82,7 +82,9 @@
|
|||
-export([stream_body/1]).
|
||||
-export([skip_body/1]).
|
||||
-export([body/1]).
|
||||
-export([body/2]).
|
||||
-export([body_qs/1]).
|
||||
-export([body_qs/2]).
|
||||
-export([multipart_data/1]).
|
||||
-export([multipart_skip/1]).
|
||||
|
||||
|
@ -729,17 +731,40 @@ content_decode(ContentDecode, Data, Req) ->
|
|||
{error, Reason} -> {error, Reason}
|
||||
end.
|
||||
|
||||
%% @doc Return the full body sent with the request.
|
||||
%% @equiv body(8000000, Req)
|
||||
-spec body(Req) -> {ok, binary(), Req} | {error, atom()} when Req::req().
|
||||
body(Req) ->
|
||||
body(Req, <<>>).
|
||||
body(8000000, Req).
|
||||
|
||||
-spec body(Req, binary())
|
||||
%% @doc Return the body sent with the request.
|
||||
-spec body(non_neg_integer() | infinity, Req)
|
||||
-> {ok, binary(), Req} | {error, atom()} when Req::req().
|
||||
body(Req, Acc) ->
|
||||
body(infinity, Req) ->
|
||||
case parse_header(<<"transfer-encoding">>, Req) of
|
||||
{ok, [<<"identity">>], Req2} ->
|
||||
read_body(Req2, <<>>);
|
||||
{ok, _, _} ->
|
||||
{error, chunked}
|
||||
end;
|
||||
body(MaxBodyLength, Req) ->
|
||||
case parse_header(<<"transfer-encoding">>, Req) of
|
||||
{ok, [<<"identity">>], Req2} ->
|
||||
{ok, Length, Req3} = parse_header(<<"content-length">>, Req2, 0),
|
||||
if Length > MaxBodyLength ->
|
||||
{error, badlength};
|
||||
true ->
|
||||
read_body(Req3, <<>>)
|
||||
end;
|
||||
{ok, _, _} ->
|
||||
{error, chunked}
|
||||
end.
|
||||
|
||||
-spec read_body(Req, binary())
|
||||
-> {ok, binary(), Req} | {error, atom()} when Req::req().
|
||||
read_body(Req, Acc) ->
|
||||
case stream_body(Req) of
|
||||
{ok, Data, Req2} ->
|
||||
body(Req2, << Acc/binary, Data/binary >>);
|
||||
read_body(Req2, << Acc/binary, Data/binary >>);
|
||||
{done, Req2} ->
|
||||
{ok, Acc, Req2};
|
||||
{error, Reason} ->
|
||||
|
@ -754,13 +779,21 @@ skip_body(Req) ->
|
|||
{error, Reason} -> {error, Reason}
|
||||
end.
|
||||
|
||||
%% @doc Return the full body sent with the request, parsed as an
|
||||
%% application/x-www-form-urlencoded string. Essentially a POST query string.
|
||||
%% @equiv body_qs(16000, Req)
|
||||
-spec body_qs(Req)
|
||||
-> {ok, [{binary(), binary() | true}], Req} | {error, atom()}
|
||||
when Req::req().
|
||||
body_qs(Req) ->
|
||||
case body(Req) of
|
||||
body_qs(16000, Req).
|
||||
|
||||
%% @doc Return the body sent with the request, parsed as an
|
||||
%% application/x-www-form-urlencoded string.
|
||||
%% Essentially a POST query string.
|
||||
-spec body_qs(non_neg_integer() | infinity, Req)
|
||||
-> {ok, [{binary(), binary() | true}], Req} | {error, atom()}
|
||||
when Req::req().
|
||||
body_qs(MaxBodyLength, Req) ->
|
||||
case body(MaxBodyLength, Req) of
|
||||
{ok, Body, Req2} ->
|
||||
{ok, cowboy_http:x_www_form_urlencoded(Body), Req2};
|
||||
{error, Reason} ->
|
||||
|
|
|
@ -30,6 +30,9 @@
|
|||
-export([check_status/1]).
|
||||
-export([chunked_response/1]).
|
||||
-export([echo_body/1]).
|
||||
-export([echo_body_max_length/1]).
|
||||
-export([echo_body_qs/1]).
|
||||
-export([echo_body_qs_max_length/1]).
|
||||
-export([error_chain_handle_after_reply/1]).
|
||||
-export([error_chain_handle_before_reply/1]).
|
||||
-export([error_handle_after_reply/1]).
|
||||
|
@ -102,6 +105,9 @@ groups() ->
|
|||
check_status,
|
||||
chunked_response,
|
||||
echo_body,
|
||||
echo_body_max_length,
|
||||
echo_body_qs,
|
||||
echo_body_qs_max_length,
|
||||
error_chain_handle_after_reply,
|
||||
error_chain_handle_before_reply,
|
||||
error_handle_after_reply,
|
||||
|
@ -348,6 +354,7 @@ init_dispatch(Config) ->
|
|||
{file, <<"test_file.css">>}]},
|
||||
{"/multipart", http_handler_multipart, []},
|
||||
{"/echo/body", http_handler_echo_body, []},
|
||||
{"/echo/body_qs", http_handler_body_qs, []},
|
||||
{"/param_all", rest_param_all, []},
|
||||
{"/bad_accept", rest_simple_resource, []},
|
||||
{"/simple", rest_simple_resource, []},
|
||||
|
@ -533,6 +540,41 @@ echo_body(Config) ->
|
|||
{ok, Body, _} = cowboy_client:response_body(Client3)
|
||||
end || Size <- lists:seq(MTU - 500, MTU)].
|
||||
|
||||
%% Check if sending request whose size is bigger than 1000000 bytes causes 413
|
||||
echo_body_max_length(Config) ->
|
||||
Client = ?config(client, Config),
|
||||
Body = <<$a:8000008>>,
|
||||
{ok, Client2} = cowboy_client:request(<<"POST">>,
|
||||
build_url("/echo/body", Config),
|
||||
[{<<"connection">>, <<"close">>}],
|
||||
Body, Client),
|
||||
{ok, 413, _, _} = cowboy_client:response(Client2).
|
||||
|
||||
% check if body_qs echo's back results
|
||||
echo_body_qs(Config) ->
|
||||
Client = ?config(client, Config),
|
||||
Body = <<"echo=67890">>,
|
||||
{ok, Client2} = cowboy_client:request(<<"POST">>,
|
||||
build_url("/echo/body_qs", Config),
|
||||
[{<<"connection">>, <<"close">>}],
|
||||
Body, Client),
|
||||
{ok, 200, _, Client3} = cowboy_client:response(Client2),
|
||||
{ok, <<"67890">>, _} = cowboy_client:response_body(Client3).
|
||||
|
||||
%% Check if sending request whose size is bigger 16000 bytes causes 413
|
||||
echo_body_qs_max_length(Config) ->
|
||||
Client = ?config(client, Config),
|
||||
DefaultMaxBodyQsLength = 16000,
|
||||
% subtract "echo=" minus 1 byte from max to hit the limit
|
||||
Bits = (DefaultMaxBodyQsLength - 4) * 8,
|
||||
AppendedBody = <<$a:Bits>>,
|
||||
Body = <<"echo=", AppendedBody/binary>>,
|
||||
{ok, Client2} = cowboy_client:request(<<"POST">>,
|
||||
build_url("/echo/body_qs", Config),
|
||||
[{<<"connection">>, <<"close">>}],
|
||||
Body, Client),
|
||||
{ok, 413, _, _} = cowboy_client:response(Client2).
|
||||
|
||||
error_chain_handle_after_reply(Config) ->
|
||||
Client = ?config(client, Config),
|
||||
{ok, Client2} = cowboy_client:request(<<"GET">>,
|
||||
|
|
39
test/http_handler_body_qs.erl
Normal file
39
test/http_handler_body_qs.erl
Normal file
|
@ -0,0 +1,39 @@
|
|||
%% Feel free to use, reuse and abuse the code in this file.
|
||||
|
||||
-module(http_handler_body_qs).
|
||||
-behaviour(cowboy_http_handler).
|
||||
-export([init/3, handle/2, terminate/3]).
|
||||
|
||||
init({_, http}, Req, _) ->
|
||||
{ok, Req, undefined}.
|
||||
|
||||
handle(Req, State) ->
|
||||
{Method, Req2} = cowboy_req:method(Req),
|
||||
HasBody = cowboy_req:has_body(Req2),
|
||||
{ok, Req3} = maybe_echo(Method, HasBody, Req2),
|
||||
{ok, Req3, State}.
|
||||
|
||||
maybe_echo(<<"POST">>, true, Req) ->
|
||||
case cowboy_req:body_qs(Req) of
|
||||
{error,badlength} ->
|
||||
echo(badlength, Req);
|
||||
{ok, PostVals, Req2} ->
|
||||
echo(proplists:get_value(<<"echo">>, PostVals), Req2)
|
||||
end;
|
||||
|
||||
maybe_echo(<<"POST">>, false, Req) ->
|
||||
cowboy_req:reply(400, [], <<"Missing body.">>, Req);
|
||||
maybe_echo(_, _, Req) ->
|
||||
%% Method not allowed.
|
||||
cowboy_req:reply(405, Req).
|
||||
|
||||
echo(badlength, Req) ->
|
||||
cowboy_req:reply(413, [], <<"POST body bigger than 16000 bytes">>, Req);
|
||||
echo(undefined, Req) ->
|
||||
cowboy_req:reply(400, [], <<"Missing echo parameter.">>, Req);
|
||||
echo(Echo, Req) ->
|
||||
cowboy_req:reply(200,
|
||||
[{<<"content-encoding">>, <<"utf-8">>}], Echo, Req).
|
||||
|
||||
terminate(_, _, _) ->
|
||||
ok.
|
|
@ -9,11 +9,37 @@ init({_, http}, Req, _) ->
|
|||
|
||||
handle(Req, State) ->
|
||||
true = cowboy_req:has_body(Req),
|
||||
{ok, Body, Req2} = cowboy_req:body(Req),
|
||||
{Size, Req3} = cowboy_req:body_length(Req2),
|
||||
{ok, Req3} = case cowboy_req:body(1000000, Req) of
|
||||
{error, chunked} -> handle_chunked(Req);
|
||||
{error, badlength} -> handle_badlength(Req);
|
||||
{ok, Body, Req2} -> handle_body(Req2, Body)
|
||||
end,
|
||||
{ok, Req3, State}.
|
||||
|
||||
handle_chunked(Req) ->
|
||||
{ok, Data, Req2} = read_body(Req, <<>>, 1000000),
|
||||
{ok, Req3} = cowboy_req:reply(200, [], Data, Req2),
|
||||
{ok, Req3}.
|
||||
|
||||
handle_badlength(Req) ->
|
||||
{ok, Req2} = cowboy_req:reply(413, [], <<"Request entity too large">>, Req),
|
||||
{ok, Req2}.
|
||||
|
||||
handle_body(Req, Body) ->
|
||||
{Size, Req2} = cowboy_req:body_length(Req),
|
||||
Size = byte_size(Body),
|
||||
{ok, Req4} = cowboy_req:reply(200, [], Body, Req3),
|
||||
{ok, Req4, State}.
|
||||
{ok, Req3} = cowboy_req:reply(200, [], Body, Req2),
|
||||
{ok, Req3}.
|
||||
|
||||
terminate(_, _, _) ->
|
||||
ok.
|
||||
|
||||
% Read chunked request content
|
||||
read_body(Req, Acc, BodyLengthRemaining) ->
|
||||
case cowboy_req:stream_body(Req) of
|
||||
{ok, Data, Req2} ->
|
||||
BodyLengthRem = BodyLengthRemaining - byte_size(Data),
|
||||
read_body(Req2, << Acc/binary, Data/binary >>, BodyLengthRem);
|
||||
{done, Req2} ->
|
||||
{ok, Acc, Req2}
|
||||
end.
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue