diff options
Diffstat (limited to 'src/cowboy_req.erl')
-rw-r--r-- | src/cowboy_req.erl | 92 |
1 files changed, 73 insertions, 19 deletions
diff --git a/src/cowboy_req.erl b/src/cowboy_req.erl index 90c5a3a..550054e 100644 --- a/src/cowboy_req.erl +++ b/src/cowboy_req.erl @@ -1,5 +1,5 @@ -%% Copyright (c) 2011-2017, Loïc Hoguin <[email protected]> -%% Copyright (c) 2011, Anthony Ramine <[email protected]> +%% Copyright (c) Loïc Hoguin <[email protected]> +%% Copyright (c) Anthony Ramine <[email protected]> %% %% Permission to use, copy, modify, and/or distribute this software for any %% purpose with or without fee is hereby granted, provided that the above @@ -445,6 +445,7 @@ parse_header_fun(<<"sec-websocket-protocol">>) -> fun cow_http_hd:parse_sec_webs parse_header_fun(<<"sec-websocket-version">>) -> fun cow_http_hd:parse_sec_websocket_version_req/1; parse_header_fun(<<"trailer">>) -> fun cow_http_hd:parse_trailer/1; parse_header_fun(<<"upgrade">>) -> fun cow_http_hd:parse_upgrade/1; +parse_header_fun(<<"wt-available-protocols">>) -> fun cow_http_hd:parse_wt_available_protocols/1; parse_header_fun(<<"x-forwarded-for">>) -> fun cow_http_hd:parse_x_forwarded_for/1. parse_header(Name, Req, Default, ParseFun) -> @@ -462,7 +463,7 @@ filter_cookies(Names0, Req=#{headers := Headers}) -> case header(<<"cookie">>, Req) of undefined -> Req; Value0 -> - Cookies0 = binary:split(Value0, <<$;>>), + Cookies0 = binary:split(Value0, <<$;>>, [global]), Cookies = lists:filter(fun(Cookie) -> lists:member(cookie_name(Cookie), Names) end, Cookies0), @@ -521,7 +522,11 @@ read_body(Req=#{has_read_body := true}, _) -> read_body(Req, Opts) -> Length = maps:get(length, Opts, 8000000), Period = maps:get(period, Opts, 15000), - Timeout = maps:get(timeout, Opts, Period + 1000), + DefaultTimeout = case Period of + infinity -> infinity; %% infinity + 1000 = infinity. + _ -> Period + 1000 + end, + Timeout = maps:get(timeout, Opts, DefaultTimeout), Ref = make_ref(), cast({read_body, self(), Ref, Length, Period}, Req), receive @@ -710,22 +715,43 @@ set_resp_cookie(Name, Value, Req, Opts) -> RespCookies = maps:get(resp_cookies, Req, #{}), Req#{resp_cookies => RespCookies#{Name => Cookie}}. -%% @todo We could add has_resp_cookie and delete_resp_cookie now. +%% @todo We could add has_resp_cookie and unset_resp_cookie now. -spec set_resp_header(binary(), iodata(), Req) -> Req when Req::req(). +set_resp_header(<<"set-cookie">>, _, _) -> + exit({response_error, invalid_header, + 'Response cookies must be set using cowboy_req:set_resp_cookie/3,4.'}); set_resp_header(Name, Value, Req=#{resp_headers := RespHeaders}) -> Req#{resp_headers => RespHeaders#{Name => Value}}; set_resp_header(Name,Value, Req) -> Req#{resp_headers => #{Name => Value}}. --spec set_resp_headers(cowboy:http_headers(), Req) +-spec set_resp_headers(cowboy:http_headers() | [{binary(), iodata()}], Req) -> Req when Req::req(). +set_resp_headers(Headers, Req) when is_list(Headers) -> + set_resp_headers_list(Headers, Req, #{}); +set_resp_headers(#{<<"set-cookie">> := _}, _) -> + exit({response_error, invalid_header, + 'Response cookies must be set using cowboy_req:set_resp_cookie/3,4.'}); set_resp_headers(Headers, Req=#{resp_headers := RespHeaders}) -> Req#{resp_headers => maps:merge(RespHeaders, Headers)}; set_resp_headers(Headers, Req) -> Req#{resp_headers => Headers}. +set_resp_headers_list([], Req, Acc) -> + set_resp_headers(Acc, Req); +set_resp_headers_list([{<<"set-cookie">>, _}|_], _, _) -> + exit({response_error, invalid_header, + 'Response cookies must be set using cowboy_req:set_resp_cookie/3,4.'}); +set_resp_headers_list([{Name, Value}|Tail], Req, Acc) -> + case Acc of + #{Name := ValueAcc} -> + set_resp_headers_list(Tail, Req, Acc#{Name => [ValueAcc, <<", ">>, Value]}); + _ -> + set_resp_headers_list(Tail, Req, Acc#{Name => Value}) + end. + -spec resp_header(binary(), req()) -> binary() | undefined. resp_header(Name, Req) -> resp_header(Name, Req, undefined). @@ -775,7 +801,11 @@ inform(Status, Req) -> -spec inform(cowboy:http_status(), cowboy:http_headers(), req()) -> ok. inform(_, _, #{has_sent_resp := _}) -> - error(function_clause); %% @todo Better error message. + exit({response_error, response_already_sent, + 'The final response has already been sent.'}); +inform(_, #{<<"set-cookie">> := _}, _) -> + exit({response_error, invalid_header, + 'Response cookies must be set using cowboy_req:set_resp_cookie/3,4.'}); inform(Status, Headers, Req) when is_integer(Status); is_binary(Status) -> cast({inform, Status, Headers}, Req). @@ -793,7 +823,11 @@ reply(Status, Headers, Req) -> -spec reply(cowboy:http_status(), cowboy:http_headers(), resp_body(), Req) -> Req when Req::req(). reply(_, _, _, #{has_sent_resp := _}) -> - error(function_clause); %% @todo Better error message. + exit({response_error, response_already_sent, + 'The final response has already been sent.'}); +reply(_, #{<<"set-cookie">> := _}, _, _) -> + exit({response_error, invalid_header, + 'Response cookies must be set using cowboy_req:set_resp_cookie/3,4.'}); reply(Status, Headers, {sendfile, _, 0, _}, Req) when is_integer(Status); is_binary(Status) -> do_reply(Status, Headers#{ @@ -809,20 +843,26 @@ reply(Status, Headers, SendFile = {sendfile, _, Len, _}, Req) %% Neither status code must include a response body. (RFC7230 3.3) reply(Status, Headers, Body, Req) when Status =:= 204; Status =:= 304 -> - 0 = iolist_size(Body), - do_reply(Status, Headers, Body, Req); + do_reply_ensure_no_body(Status, Headers, Body, Req); reply(Status = <<"204",_/bits>>, Headers, Body, Req) -> - 0 = iolist_size(Body), - do_reply(Status, Headers, Body, Req); + do_reply_ensure_no_body(Status, Headers, Body, Req); reply(Status = <<"304",_/bits>>, Headers, Body, Req) -> - 0 = iolist_size(Body), - do_reply(Status, Headers, Body, Req); + do_reply_ensure_no_body(Status, Headers, Body, Req); reply(Status, Headers, Body, Req) when is_integer(Status); is_binary(Status) -> do_reply(Status, Headers#{ <<"content-length">> => integer_to_binary(iolist_size(Body)) }, Body, Req). +do_reply_ensure_no_body(Status, Headers, Body, Req) -> + case iolist_size(Body) of + 0 -> + do_reply(Status, Headers, Body, Req); + _ -> + exit({response_error, payload_too_large, + '204 and 304 responses must not include a body. (RFC7230 3.3)'}) + end. + %% Don't send any body for HEAD responses. While the protocol code is %% supposed to enforce this rule, we prefer to avoid copying too much %% data around if we can avoid it. @@ -843,16 +883,19 @@ stream_reply(Status, Req) -> -spec stream_reply(cowboy:http_status(), cowboy:http_headers(), Req) -> Req when Req::req(). stream_reply(_, _, #{has_sent_resp := _}) -> - error(function_clause); + exit({response_error, response_already_sent, + 'The final response has already been sent.'}); +stream_reply(_, #{<<"set-cookie">> := _}, _) -> + exit({response_error, invalid_header, + 'Response cookies must be set using cowboy_req:set_resp_cookie/3,4.'}); %% 204 and 304 responses must NOT send a body. We therefore %% transform the call to a full response and expect the user %% to NOT call stream_body/3 afterwards. (RFC7230 3.3) -stream_reply(Status = 204, Headers=#{}, Req) -> +stream_reply(Status, Headers=#{}, Req) + when Status =:= 204; Status =:= 304 -> reply(Status, Headers, <<>>, Req); stream_reply(Status = <<"204",_/bits>>, Headers=#{}, Req) -> reply(Status, Headers, <<>>, Req); -stream_reply(Status = 304, Headers=#{}, Req) -> - reply(Status, Headers, <<>>, Req); stream_reply(Status = <<"304",_/bits>>, Headers=#{}, Req) -> reply(Status, Headers, <<>>, Req); stream_reply(Status, Headers=#{}, Req) when is_integer(Status); is_binary(Status) -> @@ -896,6 +939,9 @@ stream_events(Events, IsFin, Req=#{has_sent_resp := headers}) -> stream_body({data, self(), IsFin, cow_sse:events(Events)}, Req). -spec stream_trailers(cowboy:http_headers(), req()) -> ok. +stream_trailers(#{<<"set-cookie">> := _}, _) -> + exit({response_error, invalid_header, + 'Response cookies must be set using cowboy_req:set_resp_cookie/3,4.'}); stream_trailers(Trailers, Req=#{has_sent_resp := headers}) -> cast({trailers, Trailers}, Req). @@ -907,6 +953,9 @@ push(Path, Headers, Req) -> %% @todo Path, Headers, Opts, everything should be in proper binary, %% or normalized when creating the Req object. -spec push(iodata(), cowboy:http_headers(), req(), push_opts()) -> ok. +push(_, _, #{has_sent_resp := _}, _) -> + exit({response_error, response_already_sent, + 'The final response has already been sent.'}); push(Path, Headers, Req=#{scheme := Scheme0, host := Host0, port := Port0}, Opts) -> Method = maps:get(method, Opts, <<"GET">>), Scheme = maps:get(scheme, Opts, Scheme0), @@ -991,7 +1040,12 @@ filter([], Map, Errors) -> _ -> {error, Errors} end; filter([{Key, Constraints}|Tail], Map, Errors) -> - filter_constraints(Tail, Map, Errors, Key, maps:get(Key, Map), Constraints); + case maps:find(Key, Map) of + {ok, Value} -> + filter_constraints(Tail, Map, Errors, Key, Value, Constraints); + error -> + filter(Tail, Map, Errors#{Key => required}) + end; filter([{Key, Constraints, Default}|Tail], Map, Errors) -> case maps:find(Key, Map) of {ok, Value} -> |