aboutsummaryrefslogtreecommitdiffstats
path: root/src/cow_cookie.erl
diff options
context:
space:
mode:
Diffstat (limited to 'src/cow_cookie.erl')
-rw-r--r--src/cow_cookie.erl98
1 files changed, 63 insertions, 35 deletions
diff --git a/src/cow_cookie.erl b/src/cow_cookie.erl
index 93a8e61..11cf339 100644
--- a/src/cow_cookie.erl
+++ b/src/cow_cookie.erl
@@ -1,4 +1,4 @@
-%% Copyright (c) 2013-2020, Loïc Hoguin <[email protected]>
+%% Copyright (c) 2013-2023, Loïc Hoguin <[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
@@ -26,7 +26,7 @@
path => binary(),
secure => true,
http_only => true,
- same_site => strict | lax | none
+ same_site => default | none | strict | lax
}.
-export_type([cookie_attrs/0]).
@@ -35,7 +35,7 @@
http_only => boolean(),
max_age => non_neg_integer(),
path => binary(),
- same_site => strict | lax | none,
+ same_site => default | none | strict | lax,
secure => boolean()
}.
-export_type([cookie_opts/0]).
@@ -173,17 +173,32 @@ parse_cookie_error_test_() ->
-> {ok, binary(), binary(), cookie_attrs()}
| ignore.
parse_set_cookie(SetCookie) ->
- {NameValuePair, UnparsedAttrs} = take_until_semicolon(SetCookie, <<>>),
- {Name, Value} = case binary:split(NameValuePair, <<$=>>) of
- [Value0] -> {<<>>, trim(Value0)};
- [Name0, Value0] -> {trim(Name0), trim(Value0)}
- end,
- case {Name, Value} of
- {<<>>, <<>>} ->
+ case has_non_ws_ctl(SetCookie) of
+ true ->
ignore;
- _ ->
- Attrs = parse_set_cookie_attrs(UnparsedAttrs, #{}),
- {ok, Name, Value, Attrs}
+ false ->
+ {NameValuePair, UnparsedAttrs} = take_until_semicolon(SetCookie, <<>>),
+ {Name, Value} = case binary:split(NameValuePair, <<$=>>) of
+ [Value0] -> {<<>>, trim(Value0)};
+ [Name0, Value0] -> {trim(Name0), trim(Value0)}
+ end,
+ case {Name, Value} of
+ {<<>>, <<>>} ->
+ ignore;
+ _ ->
+ Attrs = parse_set_cookie_attrs(UnparsedAttrs, #{}),
+ {ok, Name, Value, Attrs}
+ end
+ end.
+
+has_non_ws_ctl(<<>>) ->
+ false;
+has_non_ws_ctl(<<C,R/bits>>) ->
+ if
+ C =< 16#08 -> true;
+ C >= 16#0A, C =< 16#1F -> true;
+ C =:= 16#7F -> true;
+ true -> has_non_ws_ctl(R)
end.
parse_set_cookie_attrs(<<>>, Attrs) ->
@@ -194,13 +209,18 @@ parse_set_cookie_attrs(<<$;,Rest0/bits>>, Attrs) ->
[Name0] -> {trim(Name0), <<>>};
[Name0, Value0] -> {trim(Name0), trim(Value0)}
end,
- case parse_set_cookie_attr(?LOWER(Name), Value) of
- {ok, AttrName, AttrValue} ->
- parse_set_cookie_attrs(Rest, Attrs#{AttrName => AttrValue});
- {ignore, AttrName} ->
- parse_set_cookie_attrs(Rest, maps:remove(AttrName, Attrs));
- ignore ->
- parse_set_cookie_attrs(Rest, Attrs)
+ if
+ byte_size(Value) > 1024 ->
+ parse_set_cookie_attrs(Rest, Attrs);
+ true ->
+ case parse_set_cookie_attr(?LOWER(Name), Value) of
+ {ok, AttrName, AttrValue} ->
+ parse_set_cookie_attrs(Rest, Attrs#{AttrName => AttrValue});
+ {ignore, AttrName} ->
+ parse_set_cookie_attrs(Rest, maps:remove(AttrName, Attrs));
+ ignore ->
+ parse_set_cookie_attrs(Rest, Attrs)
+ end
end.
take_until_semicolon(Rest = <<$;,_/bits>>, Acc) -> {Acc, Rest};
@@ -254,16 +274,15 @@ parse_set_cookie_attr(<<"httponly">>, _) ->
{ok, http_only, true};
parse_set_cookie_attr(<<"samesite">>, Value) ->
case ?LOWER(Value) of
+ <<"none">> ->
+ {ok, same_site, none};
<<"strict">> ->
{ok, same_site, strict};
<<"lax">> ->
{ok, same_site, lax};
- %% Clients may have different defaults than "None".
- <<"none">> ->
- {ok, same_site, none};
%% Unknown values and lack of value are equivalent.
_ ->
- ignore
+ {ok, same_site, default}
end;
parse_set_cookie_attr(_, _) ->
ignore.
@@ -282,6 +301,10 @@ parse_set_cookie_test_() ->
{ok, <<"a">>, <<"b">>, #{domain => <<"foo.example.org">>}}},
{<<"a=b; Path=/path/to/resource; Path=/">>,
{ok, <<"a">>, <<"b">>, #{path => <<"/">>}}},
+ {<<"a=b; SameSite=UnknownValue">>, {ok, <<"a">>, <<"b">>, #{same_site => default}}},
+ {<<"a=b; SameSite=None">>, {ok, <<"a">>, <<"b">>, #{same_site => none}}},
+ {<<"a=b; SameSite=Lax">>, {ok, <<"a">>, <<"b">>, #{same_site => lax}}},
+ {<<"a=b; SameSite=Strict">>, {ok, <<"a">>, <<"b">>, #{same_site => strict}}},
{<<"a=b; SameSite=Lax; SameSite=Strict">>,
{ok, <<"a">>, <<"b">>, #{same_site => strict}}}
],
@@ -331,7 +354,7 @@ setcookie(Name, Value, Opts) ->
<<$\s>>, <<$\t>>, <<$\r>>, <<$\n>>, <<$\013>>, <<$\014>>]),
nomatch = binary:match(iolist_to_binary(Value), [<<$,>>, <<$;>>,
<<$\s>>, <<$\t>>, <<$\r>>, <<$\n>>, <<$\013>>, <<$\014>>]),
- [Name, <<"=">>, Value, <<"; Version=1">>, attributes(maps:to_list(Opts))].
+ [Name, <<"=">>, Value, attributes(maps:to_list(Opts))].
attributes([]) -> [];
attributes([{domain, Domain}|Tail]) -> [<<"; Domain=">>, Domain|attributes(Tail)];
@@ -349,9 +372,10 @@ attributes([Opt={max_age, _}|_]) ->
attributes([{path, Path}|Tail]) -> [<<"; Path=">>, Path|attributes(Tail)];
attributes([{secure, false}|Tail]) -> attributes(Tail);
attributes([{secure, true}|Tail]) -> [<<"; Secure">>|attributes(Tail)];
+attributes([{same_site, default}|Tail]) -> attributes(Tail);
+attributes([{same_site, none}|Tail]) -> [<<"; SameSite=None">>|attributes(Tail)];
attributes([{same_site, lax}|Tail]) -> [<<"; SameSite=Lax">>|attributes(Tail)];
attributes([{same_site, strict}|Tail]) -> [<<"; SameSite=Strict">>|attributes(Tail)];
-attributes([{same_site, none}|Tail]) -> [<<"; SameSite=None">>|attributes(Tail)];
%% Skip unknown options.
attributes([_|Tail]) -> attributes(Tail).
@@ -361,26 +385,32 @@ setcookie_test_() ->
Tests = [
{<<"Customer">>, <<"WILE_E_COYOTE">>,
#{http_only => true, domain => <<"acme.com">>},
- <<"Customer=WILE_E_COYOTE; Version=1; "
+ <<"Customer=WILE_E_COYOTE; "
"Domain=acme.com; HttpOnly">>},
{<<"Customer">>, <<"WILE_E_COYOTE">>,
#{path => <<"/acme">>},
- <<"Customer=WILE_E_COYOTE; Version=1; Path=/acme">>},
+ <<"Customer=WILE_E_COYOTE; Path=/acme">>},
{<<"Customer">>, <<"WILE_E_COYOTE">>,
#{secure => true},
- <<"Customer=WILE_E_COYOTE; Version=1; Secure">>},
+ <<"Customer=WILE_E_COYOTE; Secure">>},
{<<"Customer">>, <<"WILE_E_COYOTE">>,
#{secure => false, http_only => false},
- <<"Customer=WILE_E_COYOTE; Version=1">>},
+ <<"Customer=WILE_E_COYOTE">>},
+ {<<"Customer">>, <<"WILE_E_COYOTE">>,
+ #{same_site => default},
+ <<"Customer=WILE_E_COYOTE">>},
+ {<<"Customer">>, <<"WILE_E_COYOTE">>,
+ #{same_site => none},
+ <<"Customer=WILE_E_COYOTE; SameSite=None">>},
{<<"Customer">>, <<"WILE_E_COYOTE">>,
#{same_site => lax},
- <<"Customer=WILE_E_COYOTE; Version=1; SameSite=Lax">>},
+ <<"Customer=WILE_E_COYOTE; SameSite=Lax">>},
{<<"Customer">>, <<"WILE_E_COYOTE">>,
#{same_site => strict},
- <<"Customer=WILE_E_COYOTE; Version=1; SameSite=Strict">>},
+ <<"Customer=WILE_E_COYOTE; SameSite=Strict">>},
{<<"Customer">>, <<"WILE_E_COYOTE">>,
#{path => <<"/acme">>, badoption => <<"negatory">>},
- <<"Customer=WILE_E_COYOTE; Version=1; Path=/acme">>}
+ <<"Customer=WILE_E_COYOTE; Path=/acme">>}
],
[{R, fun() -> R = iolist_to_binary(setcookie(N, V, O)) end}
|| {N, V, O, R} <- Tests].
@@ -391,7 +421,6 @@ setcookie_max_age_test() ->
setcookie(N, V, O)), <<";">>, [global])
end,
[<<"Customer=WILE_E_COYOTE">>,
- <<" Version=1">>,
<<" Expires=", _/binary>>,
<<" Max-Age=111">>,
<<" Secure">>] = F(<<"Customer">>, <<"WILE_E_COYOTE">>,
@@ -400,7 +429,6 @@ setcookie_max_age_test() ->
{'EXIT', {{badarg, {max_age, -111}}, _}} -> ok
end,
[<<"Customer=WILE_E_COYOTE">>,
- <<" Version=1">>,
<<" Expires=", _/binary>>,
<<" Max-Age=86417">>] = F(<<"Customer">>, <<"WILE_E_COYOTE">>,
#{max_age => 86417}),