aboutsummaryrefslogtreecommitdiffstats
path: root/test/rfc6265bis_SUITE.erl
diff options
context:
space:
mode:
Diffstat (limited to 'test/rfc6265bis_SUITE.erl')
-rw-r--r--test/rfc6265bis_SUITE.erl602
1 files changed, 475 insertions, 127 deletions
diff --git a/test/rfc6265bis_SUITE.erl b/test/rfc6265bis_SUITE.erl
index 4301fc3..7adc82d 100644
--- a/test/rfc6265bis_SUITE.erl
+++ b/test/rfc6265bis_SUITE.erl
@@ -30,19 +30,13 @@ all() ->
].
groups() ->
- CommonTests = ct_helper:all(?MODULE) -- [wpt_http_state],
- NumFiles = length(get_test_files()),
- NumDisabledTlsFiles = length(get_disabled_tls_test_files()),
+ CommonTests = ct_helper:all(?MODULE),
[
- {http, [parallel], CommonTests
- ++ [{testcase, wpt_http_state, [{repeat, NumFiles}]}]},
- {https, [parallel], CommonTests
- ++ [{testcase, wpt_http_state, [{repeat, NumFiles - NumDisabledTlsFiles}]}]},
+ {http, [parallel], CommonTests},
+ {https, [parallel], CommonTests},
%% Websocket over HTTP/2 is currently not supported.
- {h2c, [parallel], (CommonTests -- [wpt_secure_ws])
- ++ [{testcase, wpt_http_state, [{repeat, NumFiles}]}]},
- {h2, [parallel], (CommonTests -- [wpt_secure_ws])
- ++ [{testcase, wpt_http_state, [{repeat, NumFiles - NumDisabledTlsFiles}]}]}
+ {h2c, [parallel], (CommonTests -- [wpt_secure_ws])},
+ {h2, [parallel], (CommonTests -- [wpt_secure_ws])}
].
init_per_group(Ref, Config0) when Ref =:= http; Ref =:= h2c ->
@@ -53,7 +47,7 @@ init_per_group(Ref, Config0) when Ref =:= http; Ref =:= h2c ->
Config = gun_test:init_cowboy_tcp(Ref, #{
env => #{dispatch => cowboy_router:compile(init_routes())}
}, Config0),
- init_per_group_common([{transport, tcp}, {protocol, Protocol}|Config]);
+ [{transport, tcp}, {protocol, Protocol}|Config];
init_per_group(Ref, Config0) when Ref =:= https; Ref =:= h2 ->
Protocol = case Ref of
https -> http;
@@ -62,11 +56,7 @@ init_per_group(Ref, Config0) when Ref =:= https; Ref =:= h2 ->
Config = gun_test:init_cowboy_tls(Ref, #{
env => #{dispatch => cowboy_router:compile(init_routes())}
}, Config0),
- init_per_group_common([{transport, tls}, {protocol, Protocol}|Config]).
-
-init_per_group_common(Config = [{transport, Transport}|_]) ->
- GiverPid = spawn(fun() -> do_test_giver_init(Transport) end),
- [{test_giver_pid, GiverPid}|Config].
+ [{transport, tls}, {protocol, Protocol}|Config].
end_per_group(Ref, _) ->
cowboy:stop_listener(Ref).
@@ -86,60 +76,6 @@ init_routes() -> [
]}
].
-%% Test files.
-
-get_test_files() ->
- %% Hardcoded path, but I doubt it's going to break anytime soon.
- gun_cookies:wpt_http_state_test_files("../../test/").
-
-get_disabled_tls_test_files() ->
- %% These tests include the Secure attribute and are written for
- %% clear text. They must therefore be disabled over TLS.
- [
- "../../test/wpt/cookies/0010-test",
- "../../test/wpt/cookies/attribute0001-test",
- "../../test/wpt/cookies/attribute0002-test",
- "../../test/wpt/cookies/attribute0004-test",
- "../../test/wpt/cookies/attribute0005-test",
- "../../test/wpt/cookies/attribute0007-test",
- "../../test/wpt/cookies/attribute0008-test",
- "../../test/wpt/cookies/attribute0009-test",
- "../../test/wpt/cookies/attribute0010-test",
- "../../test/wpt/cookies/attribute0011-test",
- "../../test/wpt/cookies/attribute0012-test",
- "../../test/wpt/cookies/attribute0013-test",
- "../../test/wpt/cookies/attribute0025-test",
- "../../test/wpt/cookies/attribute0026-test"
- ].
-
-do_test_giver_init(Transport) ->
- TestFiles0 = get_test_files(),
- TestFiles = case Transport of
- tcp -> TestFiles0;
- tls -> TestFiles0 -- get_disabled_tls_test_files()
- end,
- do_test_giver_loop(TestFiles).
-
-do_test_giver_loop([]) ->
- ok;
-do_test_giver_loop([TestFile|Tail]) ->
- receive
- {request_test_file, FromPid, FromRef} ->
- FromPid ! {FromRef, TestFile},
- do_test_giver_loop(Tail)
- end.
-
-do_request_test_file(Config) ->
- Ref = make_ref(),
- GiverPid = config(test_giver_pid, Config),
- GiverPid ! {request_test_file, self(), Ref},
- receive
- {Ref, TestFile} ->
- TestFile
- after 1000 ->
- error(timeout)
- end.
-
%% Tests.
dont_ignore_informational_set_cookie(Config) ->
@@ -239,45 +175,273 @@ set_cookie_connect_tls(Config) ->
[{<<"a">>, <<"b">>}] = cow_cookie:parse_cookie(Body3),
gun:close(ConnPid).
--define(HOST, "web-platform.test").
+%% Web Platform Tests converted to Erlang.
+%%
+%% Tests are not automatically updated, the process is manual.
+%% Some test data is exported in JSON files in the "test/wpt" directory.
+%% https://github.com/web-platform-tests/wpt/tree/master/cookies
+
+-define(WPT_HOST, "web-platform.test").
+
+%% WPT: browser-only tests
+%%
+%% cookie-enabled-noncookie-frame.html
+%% meta-blocked.html
+%% navigated-away.html
+%% prefix/document-cookie.non-secure.html
+%% prefix/__host.document-cookie.html
+%% prefix/__host.document-cookie.https.html
+%% prefix/__secure.document-cookie.html
+%% prefix/__secure.document-cookie.https.html
+%% secure/set-from-dom.https.sub.html
+%% secure/set-from-dom.sub.html
+
+%% WPT: attributes/attributes-ctl
+%%
+%% attributes/attributes-ctl.sub.html
+%%
+%% The original tests use the DOM. We can't do that so
+%% we use a simple HTTP test instead. The original test
+%% also includes a string representation of the CTL in
+%% the cookie name. We don't bother.
+%%
+%% The expected value is only used for the \t CTL.
+%% The original test retains the \t in the value because
+%% it uses the DOM. The Set-Cookie algorithm requires
+%% us to drop it.
+wpt_attributes_ctl_domain(Config) ->
+ doc("Test cookie attribute parsing with control characters: "
+ "in Domain attribute value."),
+ do_wpt_ctl_test(fun(CTL) -> {
+ <<"testdomain">>,
+ <<"testdomain=t; Domain=test", CTL, ".co; Domain=", ?WPT_HOST>>,
+ <<"testdomain=t">>
+ } end, "/cookies/attributes", Config).
+
+wpt_attributes_ctl_domain2(Config) ->
+ doc("Test cookie attribute parsing with control characters: "
+ "after Domain attribute value."),
+ do_wpt_ctl_test(fun(CTL) -> {
+ <<"testdomain2">>,
+ <<"testdomain2=t; Domain=", ?WPT_HOST, CTL>>,
+ <<"testdomain2=t">>
+ } end, "/cookies/attributes", Config).
+
+wpt_attributes_ctl_path(Config) ->
+ doc("Test cookie attribute parsing with control characters: "
+ "in Path attribute value."),
+ do_wpt_ctl_test(fun(CTL) -> {
+ <<"testpath">>,
+ <<"testpath=t; Path=/te", CTL, "st; Path=/cookies/attributes">>,
+ <<"testpath=t">>
+ } end, "/cookies/attributes", Config).
+
+wpt_attributes_ctl_path2(Config) ->
+ doc("Test cookie attribute parsing with control characters: "
+ "after Path attribute value."),
+ do_wpt_ctl_test(fun(CTL) -> {
+ <<"testpath2">>,
+ <<"testpath2=t; Path=/cookies/attributes", CTL>>,
+ <<"testpath2=t">>
+ } end, "/cookies/attributes", Config).
+
+wpt_attributes_ctl_max_age(Config) ->
+ doc("Test cookie attribute parsing with control characters: "
+ "in Max-Age attribute value."),
+ do_wpt_ctl_test(fun(CTL) -> {
+ <<"testmaxage">>,
+ <<"testmaxage=t; Max-Age=10", CTL, "00; Max-Age=1000">>,
+ <<"testmaxage=t">>
+ } end, "/cookies/attributes", Config).
+
+wpt_attributes_ctl_max_age2(Config) ->
+ doc("Test cookie attribute parsing with control characters: "
+ "after Max-Age attribute value."),
+ do_wpt_ctl_test(fun(CTL) -> {
+ <<"testmaxage2">>,
+ <<"testmaxage2=t; Max-Age=1000", CTL>>,
+ <<"testmaxage2=t">>
+ } end, "/cookies/attributes", Config).
+
+wpt_attributes_ctl_expires(Config) ->
+ doc("Test cookie attribute parsing with control characters: "
+ "in Expires attribute value."),
+ do_wpt_ctl_test(fun(CTL) -> {
+ <<"testexpires">>,
+ <<"testexpires=t"
+ "; Expires=Fri, 01 Jan 20", CTL, "38 00:00:00 GMT"
+ "; Expires=Fri, 01 Jan 2038 00:00:00 GMT">>,
+ <<"testexpires=t">>
+ } end, "/cookies/attributes", Config).
+
+wpt_attributes_ctl_expires2(Config) ->
+ doc("Test cookie attribute parsing with control characters: "
+ "after Expires attribute value."),
+ do_wpt_ctl_test(fun(CTL) -> {
+ <<"testexpires2">>,
+ <<"testexpires2=t; Expires=Fri, 01 Jan 2038 00:00:00 GMT", CTL>>,
+ <<"testexpires2=t">>
+ } end, "/cookies/attributes", Config).
+
+wpt_attributes_ctl_secure(Config) ->
+ doc("Test cookie attribute parsing with control characters: "
+ "in Secure attribute."),
+ do_wpt_ctl_test(fun(CTL) -> {
+ <<"testsecure">>,
+ <<"testsecure=t; Sec", CTL, "ure">>,
+ <<"testsecure=t">>
+ } end, "/cookies/attributes", Config).
+
+wpt_attributes_ctl_secure2(Config) ->
+ doc("Test cookie attribute parsing with control characters: "
+ "after Secure attribute."),
+ do_wpt_ctl_test(fun(CTL) -> {
+ <<"testsecure2">>,
+ <<"testsecure2=t; Secure", CTL>>,
+ case config(transport, Config) of
+ tcp -> <<>>; %% Secure causes the cookie to be rejected over TCP.
+ tls -> <<"testsecure2=t">>
+ end
+ } end, "/cookies/attributes", Config).
+
+wpt_attributes_ctl_httponly(Config) ->
+ doc("Test cookie attribute parsing with control characters: "
+ "in HttpOnly attribute."),
+ do_wpt_ctl_test(fun(CTL) -> {
+ <<"testhttponly">>,
+ <<"testhttponly=t; Http", CTL, "Only">>,
+ <<"testhttponly=t">>
+ } end, "/cookies/attributes", Config).
+
+wpt_attributes_ctl_samesite(Config) ->
+ doc("Test cookie attribute parsing with control characters: "
+ "in SameSite attribute value."),
+ do_wpt_ctl_test(fun(CTL) -> {
+ <<"testsamesite">>,
+ <<"testsamesite=t; SameSite=No", CTL, "ne; SameSite=None">>,
+ <<"testsamesite=t">>
+ } end, "/cookies/attributes", Config).
+
+wpt_attributes_ctl_samesite2(Config) ->
+ doc("Test cookie attribute parsing with control characters: "
+ "after SameSite attribute value."),
+ do_wpt_ctl_test(fun(CTL) -> {
+ <<"testsamesite2">>,
+ <<"testsamesite2=t; SameSite=None", CTL>>,
+ <<"testsamesite2=t">>
+ } end, "/cookies/attributes", Config).
+
+%% @todo Redirect cookie test.
+%% attributes/domain.sub.html
+%% attributes/resources/domain-child.sub.html
+
+%% WPT: attributes/expires
+%%
+%% attributes/expires.html
+wpt_attributes_expires(Config) ->
+ doc("Test expires attribute parsing."),
+ do_wpt_json_test("attributes_expires", "/cookies/attributes", Config).
+
+%% WPT: attributes/invalid
+%%
+%% attributes/invalid.html
+wpt_attributes_invalid(Config) ->
+ doc("Test invalid attribute parsing."),
+ do_wpt_json_test("attributes_invalid", "/cookies/attributes", Config).
+
+%% WPT: attributes/max_age
+%%
+%% attributes/max-age.html
+wpt_attributes_max_age(Config) ->
+ doc("Test max-age attribute parsing."),
+ do_wpt_json_test("attributes_max_age", "/cookies/attributes", Config).
+
+%% WPT: attributes/path
+%%
+%% attributes/path.html
+wpt_attributes_path(Config) ->
+ doc("Test cookie path attribute parsing."),
+ do_wpt_json_test("attributes_path", "/cookies/attributes", Config).
+
+%% @todo Redirect cookie test.
+%% attributes/path-redirect.html
+%% attributes/resources/pathfakeout.html
+%% attributes/resources/path-redirect-shared.js
+%% attributes/resources/path.html
+%% attributes/resources/path.html.headers
+%% attributes/resources/path/one.html
+%% attributes/resources/path/three.html
+%% attributes/resources/path/two.html
+%% attributes/resources/pathfakeout/one.html
+
+%% WPT: attributes/secure
+%%
+%% attributes/secure.https.html
+%% attributes/secure-non-secure.html
+%% attributes/resources/secure-non-secure-child.html
+wpt_attributes_secure(Config) ->
+ doc("Test cookie secure attribute parsing."),
+ TestFile = case config(transport, Config) of
+ tcp -> "attributes_secure_non_secure";
+ tls -> "attributes_secure"
+ end,
+ do_wpt_json_test(TestFile, "/cookies/attributes", Config).
%% WPT: domain/domain-attribute-host-with-and-without-leading-period
+%%
+%% domain/domain-attribute-host-with-and-without-leading-period.sub.https.html
+%% domain/domain-attribute-host-with-and-without-leading-period.sub.https.html.sub.headers
wpt_domain_with_and_without_leading_period(Config) ->
doc("Domain with and without leading period."),
#{
same_origin := [{<<"a">>, <<"c">>}],
subdomain := [{<<"a">>, <<"c">>}]
- } = do_domain_test(Config, "domain_with_and_without_leading_period"),
+ } = do_wpt_domain_test(Config, "domain_with_and_without_leading_period"),
ok.
%% WPT: domain/domain-attribute-host-with-leading-period
+%%
+%% domain/domain-attribute-host-with-leading-period.sub.https.html
+%% domain/domain-attribute-host-with-leading-period.sub.https.html.sub.headers
wpt_domain_with_leading_period(Config) ->
doc("Domain with leading period."),
#{
same_origin := [{<<"a">>, <<"b">>}],
subdomain := [{<<"a">>, <<"b">>}]
- } = do_domain_test(Config, "domain_with_leading_period"),
+ } = do_wpt_domain_test(Config, "domain_with_leading_period"),
ok.
+%% @todo WPT: domain/domain-attribute-idn-host
+%%
+%% domain/domain-attribute-idn-host.sub.https.html
+%% domain/support/idn-child.sub.https.html
+%% domain/support/idn.py
+
%% WPT: domain/domain-attribute-matches-host
+%%
+%% domain/domain-attribute-matches-host.sub.https.html
+%% domain/domain-attribute-matches-host.sub.https.html.sub.headers
wpt_domain_matches_host(Config) ->
doc("Domain matches host header."),
#{
same_origin := [{<<"a">>, <<"b">>}],
subdomain := [{<<"a">>, <<"b">>}]
- } = do_domain_test(Config, "domain_matches_host"),
+ } = do_wpt_domain_test(Config, "domain_matches_host"),
ok.
%% WPT: domain/domain-attribute-missing
+%%
+%% domain/domain-attribute-missing.sub.html
+%% domain/domain-attribute-missing.sub.html.headers
wpt_domain_missing(Config) ->
doc("Domain attribute missing."),
#{
same_origin := [{<<"a">>, <<"b">>}],
subdomain := undefined
- } = do_domain_test(Config, "domain_missing"),
+ } = do_wpt_domain_test(Config, "domain_missing"),
ok.
-do_domain_test(Config, TestCase) ->
+do_wpt_domain_test(Config, TestCase) ->
Protocol = config(protocol, Config),
{ok, ConnPid} = gun:open("localhost", config(port, Config), #{
transport => config(transport, Config),
@@ -285,14 +449,14 @@ do_domain_test(Config, TestCase) ->
cookie_store => gun_cookies_list:init()
}),
{ok, Protocol} = gun:await_up(ConnPid),
- StreamRef1 = gun:get(ConnPid, ["/cookie-set?", TestCase], #{<<"host">> => ?HOST}),
+ StreamRef1 = gun:get(ConnPid, ["/cookie-set?", TestCase], #{<<"host">> => ?WPT_HOST}),
{response, fin, 204, Headers1} = gun:await(ConnPid, StreamRef1),
ct:log("Headers1:~n~p", [Headers1]),
- StreamRef2 = gun:get(ConnPid, "/cookie-echo", #{<<"host">> => ?HOST}),
+ StreamRef2 = gun:get(ConnPid, "/cookie-echo", #{<<"host">> => ?WPT_HOST}),
{response, nofin, 200, _} = gun:await(ConnPid, StreamRef2),
{ok, Body2} = gun:await_body(ConnPid, StreamRef2),
ct:log("Body2:~n~p", [Body2]),
- StreamRef3 = gun:get(ConnPid, "/cookie-echo", #{<<"host">> => "sub." ?HOST}),
+ StreamRef3 = gun:get(ConnPid, "/cookie-echo", #{<<"host">> => "sub." ?WPT_HOST}),
{response, nofin, 200, _} = gun:await(ConnPid, StreamRef3),
{ok, Body3} = gun:await_body(ConnPid, StreamRef3),
ct:log("Body3:~n~p", [Body3]),
@@ -302,41 +466,50 @@ do_domain_test(Config, TestCase) ->
subdomain => case Body3 of <<"UNDEF">> -> undefined; _ -> cow_cookie:parse_cookie(Body3) end
}.
-%% WPT: http-state/*-tests
-wpt_http_state(Config) ->
- TestFile = do_request_test_file(Config),
- Test = string:replace(filename:basename(TestFile), "-test", ""),
- doc("http-state: " ++ Test),
- ct:log("Test file:~n~s", [element(2, file:read_file(TestFile))]),
- ct:log("Expected file:~n~s", [element(2, file:read_file(string:replace(TestFile, "-test", "-expected")))]),
- Protocol = config(protocol, Config),
- {ok, ConnPid} = gun:open("localhost", config(port, Config), #{
- transport => config(transport, Config),
- protocols => [Protocol],
- cookie_store => gun_cookies_list:init()
- }),
- {ok, Protocol} = gun:await_up(ConnPid),
- StreamRef1 = gun:get(ConnPid, "/cookie-parser?" ++ Test, #{<<"host">> => "home.example.org"}),
- {response, fin, 204, Headers1} = gun:await(ConnPid, StreamRef1),
- ct:log("Headers1:~n~p", [Headers1]),
- {Host, Path} = case lists:keyfind(<<"location">>, 1, Headers1) of
- false ->
- {"home.example.org", "/cookie-parser-result?" ++ Test};
- {_, Location} ->
- case uri_string:parse(Location) of
- #{host := Host0, path := Path0, query := Qs0} ->
- {Host0, [Path0, $?, Qs0]};
- #{path := Path0, query := Qs0} ->
- {"home.example.org", [Path0, $?, Qs0]}
- end
- end,
- StreamRef2 = gun:get(ConnPid, Path, #{<<"host">> => Host}),
- %% The validation is done in the handler. An error results in a 4xx or 5xx.
- {response, fin, 204, Headers2} = gun:await(ConnPid, StreamRef2),
- ct:log("Headers2:~n~p", [Headers2]),
- gun:close(ConnPid).
+%% WPT: encoding/charset
+%%
+%% encoding/charset.html
+wpt_encoding(Config) ->
+ doc("Test UTF-8 and ASCII cookie parsing."),
+ do_wpt_json_test("encoding_charset", "/cookies/encoding", Config).
+
+%% WPT: name/name
+%%
+%% name/name.html
+wpt_name(Config) ->
+ doc("Test cookie name parsing."),
+ do_wpt_json_test("name", "/cookies/name", Config).
+
+%% WPT: name/name-ctl
+%%
+%% name/name-ctl.html
+%%
+%% The original tests use the DOM. We can't do that so
+%% we use a simple HTTP test instead. The original test
+%% also includes a string representation of the CTL in
+%% the cookie name. We don't bother.
+%%
+%% The expected value is only used for the \t CTL.
+%% The original test retains the \t in the value because
+%% it uses the DOM. The Set-Cookie algorithm requires
+%% us to drop it.
+wpt_name_ctl(Config) ->
+ doc("Test cookie name parsing with control characters."),
+ do_wpt_ctl_test(fun(CTL) -> {
+ <<"test", CTL, "name">>,
+ <<"test", CTL, "name=", CTL>>,
+ <<"test", CTL, "name=">>
+ } end, "/cookies/name", Config).
+
+%% @todo Redirect cookie test.
+%% ordering/ordering.sub.html
+%% ordering/resources/ordering-child.sub.html
+
+%% WPT: partitioned-cookies (Not implemented; proposal.)
%% WPT: path/default
+%%
+%% path/default.html
wpt_path_default(Config) ->
doc("Cookie set on the default path can be retrieved."),
Protocol = config(protocol, Config),
@@ -367,6 +540,8 @@ wpt_path_default(Config) ->
gun:close(ConnPid).
%% WPT: path/match
+%%
+%% path/match.html
wpt_path_match(Config) ->
doc("Cookie path match."),
MatchTests = [
@@ -423,22 +598,25 @@ wpt_path_match(Config) ->
ok.
%% WPT: prefix/__host.header
+%%
+%% prefix/__host.header.html
+%% prefix/__host.header.https.html
wpt_prefix_host(Config) ->
doc("__Host- prefix."),
Tests = case config(transport, Config) of
tcp -> [
{<<"__Host-foo=bar; Path=/;">>, false},
- {<<"__Host-foo=bar; Path=/;domain=" ?HOST>>, false},
+ {<<"__Host-foo=bar; Path=/;domain=" ?WPT_HOST>>, false},
{<<"__Host-foo=bar; Path=/;Max-Age=10">>, false},
{<<"__Host-foo=bar; Path=/;HttpOnly">>, false},
{<<"__Host-foo=bar; Secure; Path=/;">>, false},
- {<<"__Host-foo=bar; Secure; Path=/;domain=" ?HOST>>, false},
+ {<<"__Host-foo=bar; Secure; Path=/;domain=" ?WPT_HOST>>, false},
{<<"__Host-foo=bar; Secure; Path=/;Max-Age=10">>, false},
{<<"__Host-foo=bar; Secure; Path=/;HttpOnly">>, false},
- {<<"__Host-foo=bar; Secure; Path=/; Domain=" ?HOST "; ">>, false},
- {<<"__Host-foo=bar; Secure; Path=/; Domain=" ?HOST "; domain=" ?HOST>>, false},
- {<<"__Host-foo=bar; Secure; Path=/; Domain=" ?HOST "; Max-Age=10">>, false},
- {<<"__Host-foo=bar; Secure; Path=/; Domain=" ?HOST "; HttpOnly">>, false},
+ {<<"__Host-foo=bar; Secure; Path=/; Domain=" ?WPT_HOST "; ">>, false},
+ {<<"__Host-foo=bar; Secure; Path=/; Domain=" ?WPT_HOST "; domain=" ?WPT_HOST>>, false},
+ {<<"__Host-foo=bar; Secure; Path=/; Domain=" ?WPT_HOST "; Max-Age=10">>, false},
+ {<<"__Host-foo=bar; Secure; Path=/; Domain=" ?WPT_HOST "; HttpOnly">>, false},
{<<"__Host-foo=bar; Secure; Path=/cookies/resources/list.py">>, false}
];
tls -> [
@@ -448,9 +626,9 @@ wpt_prefix_host(Config) ->
{<<"__Host-foo=bar; Secure; Path=/;">>, true},
{<<"__Host-foo=bar; Secure; Path=/;Max-Age=10">>, true},
{<<"__Host-foo=bar; Secure; Path=/;HttpOnly">>, true},
- {<<"__Host-foo=bar; Secure; Path=/; Domain=" ?HOST "; ">>, false},
- {<<"__Host-foo=bar; Secure; Path=/; Domain=" ?HOST "; Max-Age=10">>, false},
- {<<"__Host-foo=bar; Secure; Path=/; Domain=" ?HOST "; HttpOnly">>, false},
+ {<<"__Host-foo=bar; Secure; Path=/; Domain=" ?WPT_HOST "; ">>, false},
+ {<<"__Host-foo=bar; Secure; Path=/; Domain=" ?WPT_HOST "; Max-Age=10">>, false},
+ {<<"__Host-foo=bar; Secure; Path=/; Domain=" ?WPT_HOST "; HttpOnly">>, false},
{<<"__Host-foo=bar; Secure; Path=/cookies/resources/list.py">>, false}
]
end,
@@ -459,16 +637,19 @@ wpt_prefix_host(Config) ->
ok.
%% WPT: prefix/__secure.header
+%%
+%% prefix/__secure.header.html
+%% prefix/__secure.header.https.html
wpt_prefix_secure(Config) ->
doc("__Secure- prefix."),
Tests = case config(transport, Config) of
tcp -> [
{<<"__Secure-foo=bar; Path=/;">>, false},
- {<<"__Secure-foo=bar; Path=/;domain=" ?HOST>>, false},
+ {<<"__Secure-foo=bar; Path=/;domain=" ?WPT_HOST>>, false},
{<<"__Secure-foo=bar; Path=/;Max-Age=10">>, false},
{<<"__Secure-foo=bar; Path=/;HttpOnly">>, false},
{<<"__Secure-foo=bar; Secure; Path=/;">>, false},
- {<<"__Secure-foo=bar; Secure; Path=/;domain=" ?HOST>>, false},
+ {<<"__Secure-foo=bar; Secure; Path=/;domain=" ?WPT_HOST>>, false},
{<<"__Secure-foo=bar; Secure; Path=/;Max-Age=10">>, false},
{<<"__Secure-foo=bar; Secure; Path=/;HttpOnly">>, false}
];
@@ -497,13 +678,13 @@ do_wpt_prefix_common(Config, TestCase, Expected, Name) ->
{ok, Protocol} = gun:await_up(ConnPid),
%% Set and retrieve the cookie.
StreamRef1 = gun:get(ConnPid, "/cookies/resources/set.py?prefix", #{
- <<"host">> => ?HOST,
+ <<"host">> => ?WPT_HOST,
<<"please-set-cookie">> => TestCase
}),
{response, fin, 204, Headers1} = gun:await(ConnPid, StreamRef1),
ct:log("Headers1:~n~p", [Headers1]),
StreamRef2 = gun:get(ConnPid, "/cookies/resources/echo.py", #{
- <<"host">> => ?HOST
+ <<"host">> => ?WPT_HOST
}),
{response, nofin, 200, _} = gun:await(ConnPid, StreamRef2),
{ok, Body2} = gun:await_body(ConnPid, StreamRef2),
@@ -518,9 +699,16 @@ do_wpt_prefix_common(Config, TestCase, Expected, Name) ->
end,
gun:close(ConnPid).
-%% WPT: samesite-none-secure/ (Not implemented.)
%% WPT: samesite/ (Not implemented.)
+%% WPT: samesite-none-secure/ (Not implemented.)
+%% WPT: schemeful-same-site/ (Not implemented.)
+%% WPT: secure/set-from-http.*
+%%
+%% secure/set-from-http.sub.html
+%% secure/set-from-http.sub.html.headers
+%% secure/set-from-http.https.sub.html
+%% secure/set-from-http.https.sub.html.headers
wpt_secure(Config) ->
doc("Secure attribute."),
case config(transport, Config) of
@@ -554,6 +742,9 @@ do_wpt_secure_common(Config, TestCase) ->
end.
%% WPT: secure/set-from-ws*
+%%
+%% secure/set-from-ws.sub.html
+%% secure/set-from-wss.https.sub.html
wpt_secure_ws(Config) ->
doc("Secure attribute in Websocket upgrade response."),
case config(transport, Config) of
@@ -593,3 +784,160 @@ do_wpt_secure_ws_common(Config) ->
<<"UNDEF">> -> undefined;
_ -> cow_cookie:parse_cookie(Body2)
end.
+
+%% WPT: size/attributes
+%%
+%% size/attributes.www.sub.html
+wpt_size_attributes(Config) ->
+ doc("Test cookie attribute size restrictions."),
+ do_wpt_json_test("size_attributes", "/cookies/size", Config).
+
+%% WPT: size/name-and-value
+%%
+%% size/name-and-value.html
+wpt_size_name_and_value(Config) ->
+ doc("Test cookie name/value size restrictions."),
+ do_wpt_json_test("size_name_and_value", "/cookies/size", Config).
+
+%% WPT: value/value
+%%
+%% value/value.html
+wpt_value(Config) ->
+ doc("Test cookie value parsing."),
+ Tests = do_load_json("value"),
+ _ = [begin
+ #{
+ <<"name">> := Name,
+ <<"cookie">> := Cookie,
+ <<"expected">> := Expected
+ } = Test,
+ false = maps:is_key(<<"defaultPath">>, Test),
+ do_wpt_set_test(<<"/cookies/value">>,
+ Name, Cookie, Expected, Config)
+ end || Test <- Tests,
+ %% The original test uses the DOM, we use HTTP, and are
+ %% required to drop the cookie entirely if it contains
+ %% a \n (RFC6265bis 5.4) so we skip this test.
+ maps:get(<<"expected">>, Test) =/= <<"test=13">>],
+ ok.
+
+%% WPT: value/value-ctl
+%%
+%% value/value-ctl.html
+%%
+%% The original tests use the DOM. We can't do that so
+%% we use a simple HTTP test instead. The original test
+%% also includes a string representation of the CTL in
+%% the cookie value. We don't bother.
+%%
+%% The expected value is only used for the \t CTL.
+%% The original test retains the \t in the value because
+%% it uses the DOM. The Set-Cookie algorithm requires
+%% us to drop it.
+wpt_value_ctl(Config) ->
+ doc("Test cookie value parsing with control characters."),
+ do_wpt_ctl_test(fun(CTL) -> {
+ <<"test">>,
+ <<"test=", CTL, "value">>,
+ <<"test=value">>
+ } end, "/cookies/value", Config).
+
+%% JSON files are created by taking the Javascript Object
+%% from the HTML files in the WPT suite, using the browser
+%% Developer console to convert into JSON:
+%% Obj = <Paste>
+%% JSON.stringify(Obj)
+%% Then copying the result into the JSON file; removing
+%% the quoting (first and last character) and if needed
+%% fixing the escaping in Vim using:
+%% :%s/\\\\/\\/g
+%% The host may also need to be replaced to match WPT_HOST.
+do_load_json(File0) ->
+ File = "../../test/wpt/cookies/" ++ File0 ++ ".json",
+ {ok, Bin} = file:read_file(File),
+ jsx:decode(Bin, [{return_maps, true}]).
+
+do_wpt_json_test(TestFile, TestPath, Config) ->
+ Tests = do_load_json(TestFile),
+ _ = [begin
+ #{
+ <<"name">> := Name,
+ <<"cookie">> := Cookie,
+ <<"expected">> := Expected
+ } = Test,
+ DefaultPath = maps:get(<<"defaultPath">>, Test, true),
+ do_wpt_set_test(TestPath, Name, Cookie, Expected, DefaultPath, Config)
+ end || Test <- Tests],
+ ok.
+
+do_wpt_ctl_test(Fun, TestPath, Config) ->
+ %% Control characters are defined by RFC5234 to be %x00-1F / %x7F.
+ %% We exclude \r for HTTP/1.1 because this causes errors
+ %% at the header parsing level.
+ CTLs0 = lists:seq(0, 16#1F) ++ [16#7F],
+ CTLs = case config(protocol, Config) of
+ http -> CTLs0 -- "\r";
+ http2 -> CTLs0
+ end,
+ %% All CTLs except \t should cause the cookie to be rejected.
+ _ = [begin
+ {Name, Cookie, Expected} = Fun(CTL),
+ case CTL of
+ $\t ->
+ do_wpt_set_test(TestPath, Name, Cookie, Expected, false, Config);
+ _ ->
+ do_wpt_set_test(TestPath, Name, Cookie, <<>>, false, Config)
+ end
+ end || CTL <- CTLs],
+ ok.
+
+%% Equivalent to httpCookieTest.
+do_wpt_set_test(TestPath, Name, Cookie, Expected, Config) ->
+ do_wpt_set_test(TestPath, Name, Cookie, Expected, true, Config).
+
+do_wpt_set_test(TestPath, Name, Cookie, Expected, DefaultPath, Config) ->
+ ct:log("Name: ~s", [Name]),
+ Protocol = config(protocol, Config),
+ {ok, ConnPid} = gun:open("localhost", config(port, Config), #{
+ transport => config(transport, Config),
+ protocols => [Protocol],
+ cookie_store => gun_cookies_list:init()
+ }),
+ {ok, Protocol} = gun:await_up(ConnPid),
+ StreamRef1 = gun:get(ConnPid,
+ ["/cookie-set?ttb=", cow_qs:urlencode(term_to_binary(Cookie))],
+ #{<<"host">> => ?WPT_HOST}),
+ {response, fin, 204, Headers} = gun:await(ConnPid, StreamRef1),
+ ct:log("Headers:~n~p", [Headers]),
+ #{cookie_store := Store} = gun:info(ConnPid),
+ ct:log("Store:~n~p", [Store]),
+ Result1 = case DefaultPath of
+ true ->
+ %% We do another request to get the cookie.
+ StreamRef2 = gun:get(ConnPid, "/cookie-echo",
+ #{<<"host">> => ?WPT_HOST}),
+ {response, nofin, 200, _} = gun:await(ConnPid, StreamRef2),
+ {ok, Body2} = gun:await_body(ConnPid, StreamRef2),
+ case Body2 of
+ <<"UNDEF">> -> <<>>;
+ _ -> Body2
+ end;
+ false ->
+ %% We call this function to get a request header representation
+ %% of a cookie, similar to what document.cookie returns.
+ case gun_cookies:add_cookie_header(
+ case config(transport, Config) of
+ tcp -> <<"http">>;
+ tls -> <<"https">>
+ end,
+ <<?WPT_HOST>>, TestPath, [], Store) of
+ {[{<<"cookie">>, Result0}], _} ->
+ Result0;
+ {[], _} ->
+ <<>>
+ end
+ end,
+ Result = unicode:characters_to_binary(Result1),
+ ct:log("Expected:~n~p~nResult:~n~p", [Expected, Result]),
+ {Name, Cookie, Expected} = {Name, Cookie, Result},
+ gun:close(ConnPid).