aboutsummaryrefslogtreecommitdiffstats
path: root/src/cowboy_req.erl
diff options
context:
space:
mode:
Diffstat (limited to 'src/cowboy_req.erl')
-rw-r--r--src/cowboy_req.erl84
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.