diff options
Diffstat (limited to 'src/cowboy_req.erl')
-rw-r--r-- | src/cowboy_req.erl | 84 |
1 files changed, 63 insertions, 21 deletions
diff --git a/src/cowboy_req.erl b/src/cowboy_req.erl index 33aaa33..7f3b566 100644 --- a/src/cowboy_req.erl +++ b/src/cowboy_req.erl @@ -118,6 +118,12 @@ -include_lib("eunit/include/eunit.hrl"). -endif. +-type cookie_option() :: {max_age, non_neg_integer()} + | {domain, binary()} | {path, binary()} + | {secure, boolean()} | {http_only, boolean()}. +-type cookie_opts() :: [cookie_option()]. +-export_type([cookie_opts/0]). + -type resp_body_fun() :: fun(() -> {sent, non_neg_integer()}). -record(http_req, { @@ -183,15 +189,13 @@ new(Socket, Transport, Method, Path, Query, Fragment, method=Method, path=Path, qs=Query, fragment=Fragment, version=Version, headers=Headers, host=Host, port=Port, buffer=Buffer, onresponse=OnResponse}, - case CanKeepalive of + case CanKeepalive and (Version =:= {1, 1}) of false -> Req#http_req{connection=close}; true -> case lists:keyfind(<<"connection">>, 1, Headers) of - false when Version =:= {1, 1} -> - Req; %% keepalive false -> - Req#http_req{connection=close}; + Req; %% keepalive {_, ConnectionHeader} -> Tokens = parse_connection_before(ConnectionHeader, []), Connection = connection_to_atom(Tokens), @@ -335,11 +339,11 @@ host_url(Req=#http_req{transport=Transport, host=Host, port=Port}) -> -spec url(Req) -> {undefined | binary(), Req} when Req::req(). url(Req=#http_req{}) -> {HostURL, Req2} = host_url(Req), - url2(HostURL, Req2). + url(HostURL, Req2). -url2(undefined, Req=#http_req{}) -> +url(undefined, Req=#http_req{}) -> {undefined, Req}; -url2(HostURL, Req=#http_req{path=Path, qs=QS, fragment=Fragment}) -> +url(HostURL, Req=#http_req{path=Path, qs=QS, fragment=Fragment}) -> QS2 = case QS of <<>> -> <<>>; _ -> << "?", QS/binary >> @@ -439,6 +443,8 @@ parse_header(Name, Req, Default) when Name =:= <<"content-length">> -> parse_header(Name, Req, Default, fun cowboy_http:digits/1); parse_header(Name, Req, Default) when Name =:= <<"content-type">> -> parse_header(Name, Req, Default, fun cowboy_http:content_type/1); +parse_header(Name = <<"cookie">>, Req, Default) -> + parse_header(Name, Req, Default, fun cowboy_http:cookie_list/1); parse_header(Name, Req, Default) when Name =:= <<"expect">> -> parse_header(Name, Req, Default, fun (Value) -> @@ -490,11 +496,10 @@ cookie(Name, Req) when is_binary(Name) -> -spec cookie(binary(), Req, Default) -> {binary() | true | Default, Req} when Req::req(), Default::any(). cookie(Name, Req=#http_req{cookies=undefined}, Default) when is_binary(Name) -> - case header(<<"cookie">>, Req) of - {undefined, Req2} -> + case parse_header(<<"cookie">>, Req) of + {ok, undefined, Req2} -> {Default, Req2#http_req{cookies=[]}}; - {RawCookie, Req2} -> - Cookies = cowboy_cookies:parse_cookie(RawCookie), + {ok, Cookies, Req2} -> cookie(Name, Req2#http_req{cookies=Cookies}, Default) end; cookie(Name, Req, Default) -> @@ -506,11 +511,10 @@ cookie(Name, Req, Default) -> %% @doc Return the full list of cookie values. -spec cookies(Req) -> {list({binary(), binary() | true}), Req} when Req::req(). cookies(Req=#http_req{cookies=undefined}) -> - case header(<<"cookie">>, Req) of - {undefined, Req2} -> + case parse_header(<<"cookie">>, Req) of + {ok, undefined, Req2} -> {[], Req2#http_req{cookies=[]}}; - {RawCookie, Req2} -> - Cookies = cowboy_cookies:parse_cookie(RawCookie), + {ok, Cookies, Req2} -> cookies(Req2#http_req{cookies=Cookies}) end; cookies(Req=#http_req{cookies=Cookies}) -> @@ -540,7 +544,7 @@ meta(Name, Req, Default) -> %% If the value already exists it will be overwritten. -spec set_meta(atom(), any(), Req) -> Req when Req::req(). set_meta(Name, Value, Req=#http_req{meta=Meta}) -> - Req#http_req{meta=[{Name, Value}|lists:keydelete(Name, 1, Meta)]}. + Req#http_req{meta=lists:keyreplace(Name, 1, Meta, {Name, Value})}. %% Request Body API. @@ -803,11 +807,17 @@ multipart_skip(Req) -> %% Response API. %% @doc Add a cookie header to the response. --spec set_resp_cookie(binary(), binary(), - [cowboy_cookies:cookie_option()], Req) -> Req when Req::req(). -set_resp_cookie(Name, Value, Options, Req) -> - {HeaderName, HeaderValue} = cowboy_cookies:cookie(Name, Value, Options), - set_resp_header(HeaderName, HeaderValue, Req). +%% +%% The cookie name cannot contain any of the following characters: +%% =,;\s\t\r\n\013\014 +%% +%% The cookie value cannot contain any of the following characters: +%% ,; \t\r\n\013\014 +-spec set_resp_cookie(iodata(), iodata(), cookie_opts(), Req) + -> Req when Req::req(). +set_resp_cookie(Name, Value, Opts, Req) -> + Cookie = cowboy_http:cookie_to_iodata(Name, Value, Opts), + set_resp_header(<<"set-cookie">>, Cookie, Req). %% @doc Add a header to the response. -spec set_resp_header(binary(), iodata(), Req) @@ -1145,8 +1155,18 @@ response_merge_headers(Headers, RespHeaders, DefaultHeaders) -> -spec merge_headers(cowboy_http:headers(), cowboy_http:headers()) -> cowboy_http:headers(). + +%% Merge headers by prepending the tuples in the second list to the +%% first list. It also handles Set-Cookie properly, which supports +%% duplicated entries. Notice that, while the RFC2109 does allow more +%% than one cookie to be set per Set-Cookie header, we are following +%% the implementation of common web servers and applications which +%% return many distinct headers per each Set-Cookie entry to avoid +%% issues with clients/browser which may not support it. merge_headers(Headers, []) -> Headers; +merge_headers(Headers, [{<<"set-cookie">>, Value}|Tail]) -> + merge_headers([{<<"set-cookie">>, Value}|Headers], Tail); merge_headers(Headers, [{Name, Value}|Tail]) -> Headers2 = case lists:keymember(Name, 1, Headers) of true -> Headers; @@ -1343,4 +1363,26 @@ connection_to_atom_test_() -> [{lists:flatten(io_lib:format("~p", [T])), fun() -> R = connection_to_atom(T) end} || {T, R} <- Tests]. +merge_headers_test() -> + Left0 = [{<<"content-length">>,<<"13">>},{<<"server">>,<<"Cowboy">>}], + Right0 = [{<<"set-cookie">>,<<"foo=bar">>},{<<"content-length">>,<<"11">>}], + + ?assertMatch( + [{<<"set-cookie">>,<<"foo=bar">>}, + {<<"content-length">>,<<"13">>}, + {<<"server">>,<<"Cowboy">>}], + merge_headers(Left0, Right0)), + + Left1 = [{<<"content-length">>,<<"13">>},{<<"server">>,<<"Cowboy">>}], + Right1 = [{<<"set-cookie">>,<<"foo=bar">>},{<<"set-cookie">>,<<"bar=baz">>}], + + ?assertMatch( + [{<<"set-cookie">>,<<"bar=baz">>}, + {<<"set-cookie">>,<<"foo=bar">>}, + {<<"content-length">>,<<"13">>}, + {<<"server">>,<<"Cowboy">>}], + merge_headers(Left1, Right1)), + + ok. + -endif. |