diff options
Diffstat (limited to 'testing/web-platform/tests/cookies')
128 files changed, 6193 insertions, 0 deletions
diff --git a/testing/web-platform/tests/cookies/META.yml b/testing/web-platform/tests/cookies/META.yml new file mode 100644 index 0000000000..d0743949b6 --- /dev/null +++ b/testing/web-platform/tests/cookies/META.yml @@ -0,0 +1,2 @@ +suggested_reviewers: + - mikewest diff --git a/testing/web-platform/tests/cookies/README.md b/testing/web-platform/tests/cookies/README.md new file mode 100644 index 0000000000..ed86aebf18 --- /dev/null +++ b/testing/web-platform/tests/cookies/README.md @@ -0,0 +1,2 @@ +This directory contains tests for +[Leave Secure Cookies Alone](https://tools.ietf.org/html/draft-ietf-httpbis-cookie-alone-01). diff --git a/testing/web-platform/tests/cookies/__init__.py b/testing/web-platform/tests/cookies/__init__.py new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/testing/web-platform/tests/cookies/__init__.py diff --git a/testing/web-platform/tests/cookies/attributes/attributes-ctl.sub.html b/testing/web-platform/tests/cookies/attributes/attributes-ctl.sub.html new file mode 100644 index 0000000000..e741dfd9c2 --- /dev/null +++ b/testing/web-platform/tests/cookies/attributes/attributes-ctl.sub.html @@ -0,0 +1,99 @@ +<!doctype html> +<html> + <head> + <meta charset=utf-8> + <title>Test cookie attribute parsing with control characters</title> + <meta name=help href="https://tools.ietf.org/html/rfc6265#section-5.2"> + <meta name="timeout" content="long"> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <script src="/resources/testdriver.js"></script> + <script src="/resources/testdriver-vendor.js"></script> + <script src="/cookies/resources/cookie-test.js"></script> + </head> + <body> + <div id=log></div> + <script> + const host = "{{host}}"; + const path = "/cookies/attributes"; + + // Tests for control characters (CTLs) in a cookie's attribute values. + // CTLs are defined by RFC 5234 to be %x00-1F / %x7F. + const CTLS = getCtlCharacters(); + + // All CTLs, with the exception of %x09 (the tab character), should + // cause the cookie to be rejected. + // In these tests we rely on subsequent attributes with the same name + // overriding the earlier one. In the cases where the control character + // should cause the entire cookie line to be rejected, if the control + // character were not present the cookie line should be one that + // would not be rejected. That way, if the attribute value is ignored + // instead of the cookie line being rejected, the test will catch it. + for (const ctl of CTLS) { + const controlCharacterAttributeTests = [ + { + cookie: `test${ctl.code}domain=t; Domain=test${ctl.chr}.co; Domain=${host};`, + name: `Cookie with %x${ctl.code.toString(16)} in Domain attribute value is handled correctly.`, + }, + { + cookie: `test${ctl.code}domain2=t; Domain=${host}${ctl.chr};`, + name: `Cookie with %x${ctl.code.toString(16)} after Domain attribute value is handled correctly.`, + }, + { + cookie: `test${ctl.code}path=t; Path=/te${ctl.chr}st; Path=${path}`, + name: `Cookie with %x${ctl.code.toString(16)} in Path attribute value is handled correctly.`, + }, + { + cookie: `test${ctl.code}path2=t; Path=${path}${ctl.chr};`, + name: `Cookie with %x${ctl.code.toString(16)} after Path attribute value is handled correctly.`, + }, + { + cookie: `test${ctl.code}maxage=t; Max-Age=10${ctl.chr}00; Max-Age=1000;`, + name: `Cookie with %x${ctl.code.toString(16)} in Max-Age attribute value is handled correctly.`, + }, + { + cookie: `test${ctl.code}maxage2=t; Max-Age=1000${ctl.chr};`, + name: `Cookie with %x${ctl.code.toString(16)} after Max-Age attribute value is handled correctly.`, + }, + { + cookie: `test${ctl.code}expires=t; Expires=Fri, 01 Jan 20${ctl.chr}38 00:00:00 GMT; ` + + 'Expires=Fri, 01 Jan 2038 00:00:00 GMT;', + name: `Cookie with %x${ctl.code.toString(16)} in Expires attribute value is handled correctly.`, + }, + { + cookie: `test${ctl.code}expires2=t; Expires=Fri, 01 Jan 2038 00:00:00 GMT${ctl.chr};`, + name: `Cookie with %x${ctl.code.toString(16)} after Expires attribute value is handled correctly.`, + }, + { + cookie: `test${ctl.code}secure=t; Sec${ctl.chr}ure;`, + name: `Cookie with %x${ctl.code.toString(16)} in Secure attribute is handled correctly.`, + }, + { + cookie: `test${ctl.code}secure2=t; Secure${ctl.chr};`, + name: `Cookie with %x${ctl.code.toString(16)} after Secure attribute is handled correctly.`, + }, + { + cookie: `test${ctl.code}httponly=t; Http${ctl.chr}Only;`, + name: `Cookie with %x${ctl.code.toString(16)} in HttpOnly attribute is handled correctly.`, + }, + { + cookie: `test${ctl.code}samesite=t; SameSite=La${ctl.chr}x; SameSite=Lax;`, + name: `Cookie with %x${ctl.code.toString(16)} in SameSite attribute value is handled correctly.`, + }, + { + cookie: `test${ctl.code}samesite2=t; SameSite=Lax${ctl.chr};`, + name: `Cookie with %x${ctl.code.toString(16)} after SameSite attribute value is handled correctly.`, + }, + ]; + + for (const test of controlCharacterAttributeTests) { + if (ctl.code === 0x09) { + domCookieTest(test.cookie, test.cookie.split(";")[0], test.name); + } else { + domCookieTest(test.cookie, "", test.name); + } + } + } + </script> + </body> +</html> diff --git a/testing/web-platform/tests/cookies/attributes/domain.sub.html b/testing/web-platform/tests/cookies/attributes/domain.sub.html new file mode 100644 index 0000000000..17bc3267c2 --- /dev/null +++ b/testing/web-platform/tests/cookies/attributes/domain.sub.html @@ -0,0 +1,24 @@ +<!doctype html> +<html> + <head> + <meta charset=utf-8> + <title>Test cookie domain attribute parsing</title> + <meta name=help href="https://tools.ietf.org/html/rfc6265#section-5.2.3"> + <meta name="timeout" content="long"> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <script src="/resources/testdriver.js"></script> + <script src="/resources/testdriver-vendor.js"></script> + </head> + <body> + <script> + const port = "{{ports[http][0]}}"; + const wwwHost = "{{domains[www]}}"; + + test(t => { + const win = window.open(`http://${wwwHost}:${port}/cookies/attributes/resources/domain-child.sub.html`); + fetch_tests_from_window(win); + }); + </script> + </body> +</html> diff --git a/testing/web-platform/tests/cookies/attributes/expires.html b/testing/web-platform/tests/cookies/attributes/expires.html new file mode 100644 index 0000000000..a6bacfd74e --- /dev/null +++ b/testing/web-platform/tests/cookies/attributes/expires.html @@ -0,0 +1,56 @@ +<!doctype html> +<html> + <head> + <meta charset=utf-8> + <title>Test expires attribute parsing</title> + <meta name=help href="https://tools.ietf.org/html/rfc6265#section-5.1.1"> + <meta name="timeout" content="long"> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <script src="/resources/testdriver.js"></script> + <script src="/resources/testdriver-vendor.js"></script> + <script src="/cookies/resources/cookie-test.js"></script> + </head> + <body> + <div id=log></div> + <script> + const expiresTests = [ + { + cookie: "test=1; Expires=Fri, 01 Jan 2038 00:00:00 GMT", + expected: "test=1", + name: "Set cookie with expires value containing a comma", + }, + { + cookie: "test=2; Expires=Fri 01 Jan 2038 00:00:00 GMT, baz=qux", + expected: "test=2", + name: "Set cookie with expires value followed by comma", + }, + { + cookie: "test=3; Expires=Fri, 01 Jan 2038 00:00:00 GMT", + expected: "test=3", + name: "Set cookie with future expiration", + }, + { + cookie: ["test=expired; Expires=Fri, 07 Aug 2007 08:04:19 GMT", "test=4; Expires=Fri, 07 Aug 2027 08:04:19 GMT"], + expected: "test=4", + name: "Set expired cookie along with valid cookie", + }, + { + cookie: "test=5; expires=Thu, 10 Apr 1980 16:33:12 GMT", + expected: "", + name: "Don't set cookie with expires set to the past", + }, + ]; + + // These tests evaluate setting cookies with expiration via HTTP headers. + for (const test of expiresTests) { + httpCookieTest(test.cookie, test.expected, test.name + " via HTTP headers"); + } + + // These tests evaluate setting cookies with expiration via document.cookie. + for (const test of expiresTests) { + domCookieTest(test.cookie, test.expected, test.name + " via document.cookie"); + } + </script> + </body> +</html>
\ No newline at end of file diff --git a/testing/web-platform/tests/cookies/attributes/invalid.html b/testing/web-platform/tests/cookies/attributes/invalid.html new file mode 100644 index 0000000000..6d4a53916d --- /dev/null +++ b/testing/web-platform/tests/cookies/attributes/invalid.html @@ -0,0 +1,171 @@ +<!doctype html> +<html> + <head> + <meta charset=utf-8> + <title>Test invalid attribute parsing</title> + <meta name=help href="https://tools.ietf.org/html/rfc6265#section-5.2"> + <meta name="timeout" content="long"> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <script src="/resources/testdriver.js"></script> + <script src="/resources/testdriver-vendor.js"></script> + <script src="/cookies/resources/cookie-test.js"></script> + </head> + <body> + <div id=log></div> + <script> + // These tests ensure that invalid attributes don't affect + // cookie parsing. `Path` isn't important to the tests where it appears, + // but it's used to be able to place the invalid attribute in different + // locations. + const invalidAttributeTests = [ + { + cookie: "test=1; lol; Path=/", + expected: "test=1", + name: "Set cookie with invalid attribute", + defaultPath: false + }, + { + cookie: "test=2; Path=/; lol", + expected: "test=2", + name: "Set cookie ending with invalid attribute.", + defaultPath: false + }, + { + cookie: "test=3; Path=/; 'lol'", + expected: "test=3", + name: "Set cookie ending with quoted invalid attribute.", + defaultPath: false + }, + { + cookie: 'test=4; Path=/; "lol"', + expected: "test=4", + name: "Set cookie ending with double-quoted invalid attribute.", + defaultPath: false + }, + { + cookie: "test=5; Path=/; lol=", + expected: "test=5", + name: "Set cookie ending with invalid attribute equals.", + defaultPath: false + }, + { + cookie: 'test=6; lol="aaa;bbb"; Path=/', + expected: "test=6", + name: "Set cookie with two invalid attributes (lol=\"aaa and bbb).", + defaultPath: false + }, + { + cookie: 'test=7; Path=/; lol="aaa;bbb"', + expected: "test=7", + name: "Set cookie ending with two invalid attributes (lol=\"aaa and bbb).", + defaultPath: false + }, + { + cookie: 'test=8; "Secure"', + expected: "test=8", + // This gets parsed as an unrecognized \"Secure\" attribute, not a valid + // Secure attribute. That's why it gets set on an non-secure origin. + name: "Set cookie for quoted Secure attribute", + }, + { + cookie: "test=9; Secure qux", + expected: "test=9", + // This should be parsed as an unrecognized "Secure qux" attribute + // and ignored. That is, the cookie will not be Secure. + name: "Set cookie for Secure qux", + }, + { + cookie: "test=10; b,az=qux", + expected: "test=10", + name: "Ignore invalid attribute name with comma", + }, + { + cookie: "test=11; baz=q,ux", + expected: "test=11", + name: "Ignore invalid attribute value with comma", + }, + { + cookie: " test = 12 ;foo;;; bar", + expected: "test=12", + name: "Set cookie ignoring multiple invalid attributes, whitespace, and semicolons", + }, + { + cookie: " test=== 13 ;foo;;; bar", + expected: "test=== 13", + name: "Set cookie with multiple '='s in its value, ignoring multiple invalid attributes, whitespace, and semicolons", + }, + { + cookie: "test=14; version=1;", + expected: "test=14", + name: "Set cookie with (invalid) version=1 attribute", + }, + { + cookie: "test=15; version=1000;", + expected: "test=15", + name: "Set cookie with (invalid) version=1000 attribute", + }, + { + cookie: "test=16; customvalue='1000 or more';", + expected: "test=16", + name: "Set cookie ignoring anything after ; (which looks like an invalid attribute)", + }, + { + cookie: "test=17; customvalue='1000 or more'", + expected: "test=17", + name: "Set cookie ignoring anything after ; (which looks like an invalid attribute, with no trailing semicolon)", + }, + { + cookie: "test=18; foo=bar, a=b", + expected: "test=18", + name: "Ignore keys after semicolon", + }, + { + cookie: "test=19;max-age=3600, c=d;path=/", + expected: "test=19", + name: "Ignore attributes after semicolon", + defaultPath: false, + }, + { + cookie: ["testA=20", "=", "testb=20"], + expected: "testA=20; testb=20", + name: "Ignore `Set-Cookie: =`", + }, + { + cookie: ["test=21", ""], + expected: "test=21", + name: "Ignore empty cookie string", + }, + { + cookie: ["test22", "="], + expected: "test22", + name: "Ignore `Set-Cookie: =` with other `Set-Cookie` headers", + }, + { + cookie: ["testA23", "; testB23"], + expected: "testA23", + name: "Ignore name- and value-less `Set-Cookie: ; bar`", + }, + { + cookie: ["test24", " "], + expected: "test24", + name: "Ignore name- and value-less `Set-Cookie: `", + }, + { + cookie: ["test25", "\t"], + expected: "test25", + name: "Ignore name- and value-less `Set-Cookie: \\t`", + }, + { + cookie: "test=26; domain=.parser.test; ;; ;=; ,,, ===,abc,=; abracadabra! max-age=20;=;;", + expected: "", + name: "Ignore cookie with domain that won't domain match (along with other invalid noise)", + }, + ]; + + for (const test of invalidAttributeTests) { + httpCookieTest(test.cookie, test.expected, test.name, test.defaultPath); + } + </script> + </body> +</html>
\ No newline at end of file diff --git a/testing/web-platform/tests/cookies/attributes/max-age.html b/testing/web-platform/tests/cookies/attributes/max-age.html new file mode 100644 index 0000000000..7b7ff6ed4c --- /dev/null +++ b/testing/web-platform/tests/cookies/attributes/max-age.html @@ -0,0 +1,78 @@ +<!doctype html> +<html> + <head> + <meta charset=utf-8> + <title>Test max-age attribute parsing</title> + <meta name=help href="https://tools.ietf.org/html/rfc6265#section-5.3.2"> + <meta name="timeout" content="long"> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <script src="/resources/testdriver.js"></script> + <script src="/resources/testdriver-vendor.js"></script> + <script src="/cookies/resources/cookie-test.js"></script> + </head> + <body> + <div id=log></div> + <script> + // TODO: there is more to test here, these tests capture the old + // ported http-state tests. Feel free to delete this comment when more + // are added. + const maxAgeTests = [ + { + cookie: "test=1; Max-Age=50,399", + expected: "test=1", + name: "Ignore max-age attribute with invalid non-zero-digit (containing a comma)", + }, + { + cookie: "test=2; max-age=10000", + expected: "test=2", + name: "Set cookie with age", + }, + { + cookie: "test=3; max-age=0", + expected: "", + name: "Set no cookie with max-age=0", + }, + { + cookie: "test=4; max-age=-1", + expected: "", + name: "Set no cookie with max-age=-1", + }, + { + cookie: "test=5; max-age=-20", + expected: "", + name: "Set no cookie with max-age=-20", + }, + { + cookie: ["testA=6; max-age=60", "testB=6; max-age=60"], + expected: "testA=6; testB=6", + name: "Set multiple cookies with max-age attribute", + }, + { + cookie: ["testA=7; max-age=60", "testB=7; max-age=60", "testA=differentvalue; max-age=0"], + expected: "testB=7", + name: "Expire later cookie with same name and max-age=0", + }, + { + cookie: ["testA=8; max-age=60", "testB=8; max-age=60", "testA=differentvalue; max-age=0", "testC=8; max-age=0"], + expected: "testB=8", + name: "Expire later cookie with same name and max-age=0, and don't set cookie with max-age=0", + }, + { + cookie: ['test="9! = foo;bar\";" parser; max-age=6', "test9; max-age=2.63,"], + expected: 'test="9! = foo; test9', + name: "Set mulitiple cookies with valid max-age values", + }, + { + cookie: ["test=10; max-age=0", "test10; max-age=0"], + expected: "", + name: "Don't set multiple cookies with max-age=0", + }, + ]; + + for (const test of maxAgeTests) { + httpCookieTest(test.cookie, test.expected, test.name); + } + </script> + </body> +</html>
\ No newline at end of file diff --git a/testing/web-platform/tests/cookies/attributes/path-redirect.html b/testing/web-platform/tests/cookies/attributes/path-redirect.html new file mode 100644 index 0000000000..574879971f --- /dev/null +++ b/testing/web-platform/tests/cookies/attributes/path-redirect.html @@ -0,0 +1,128 @@ +<!doctype html> +<html> + <head> + <meta charset=utf-8> + <title>Test cookie path attribute parsing</title> + <meta name=help href="https://tools.ietf.org/html/rfc6265#section-5.2.4"> + <meta name="timeout" content="long"> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <script src="/resources/testdriver.js"></script> + <script src="/resources/testdriver-vendor.js"></script> + <script src="/cookies/resources/cookie-test.js"></script> + </head> + <body> + <script> + const pathRedirectTests = [ + { + cookie: "test=1; path=/cookies/attributes/resources/path.html", + expected: "test=1", + name: "Cookie sent for exact redirected path match", + location: "/cookies/attributes/resources/path.html", + }, + { + cookie: "test=2; path=/cookies/attributes/resources/path/one.html", + expected: "test=2", + name: "Cookie sent for exact redirected path match, one level deeper", + location: "/cookies/attributes/resources/path/one.html", + }, + { + cookie: "test=3; path=/cookies/attributes/resources/path/", + expected: "test=3", + name: "Cookie sent for redirected path with trailing '/' and the redirected URL is one level deeper", + location: "/cookies/attributes/resources/path/one.html", + }, + { + cookie: "test=4; path=/cookies/attributes/resources/path/", + expected: "test=4", + name: "Cookie sent for redirected path with trailing '/' and a double '/' in the redirected URL", + location: "/cookies/attributes/resources/path//one.html", + }, + { + cookie: "test=5; path=/cookies/attributes/resources/path/one.html;", + expected: "test=5", + name: "Cookie sent for redirected path match with a trailing ';' after an unquoted Path", + location: "/cookies/attributes/resources/path/one.html", + }, + { + cookie: 'test=6; path="/cookies/attributes/resources/path/one.html;"', + expected: "", + name: "No cookie sent for redirected path match with a trailing ';' inside a quoted Path", + location: "/cookies/attributes/resources/path/one.html", + }, + { + cookie: "test=7a; path=/cookies/attributes/resources/p%61th/three.html", + expected: "", + name: "No cookie sent for redirected path match with partially URL encoded path", + location: "/cookies/attributes/resources/path/three.html", + }, + { + cookie: ["test=8a; path=/cookies/attributes/resources", + "test=8b; path=/cookies/attributes/resources/"], + expected: "test=8b; test=8a", + name: "Multiple cookies sent for multiple redirected path matches, sorted by length", + location: "/cookies/attributes/resources/path.html", + }, + { + cookie: "test=9; path=/cookies/attributes/resources/path.html", + expected: "", + name: "No cookie sent for redirected path mismatch where path and redirected URL begin with same string", + location: "/cookies/attributes/resources/pathfakeout.html", + }, + { + cookie: "test=10; path=/cookies/attributes/resources/path/one.html", + expected: "", + name: "No cookie sent for redirected path mismatch where final path directory component and redirected URL resource begin with same string", + location: "/cookies/attributes/resources/path.html", + }, + { + cookie: "test=11; path=/cookies/attributes/resources/path/one.html", + expected: "", + name: "No cookie sent for redirected path mismatch where final path directory component begins with same string as redirected URL final directory component", + location: "/cookies/attributes/resources/pathfakeout/one.html", + }, + { + cookie: "test=12; path=/cookies/attributes/resources/path/one.html", + expected: "", + name: "No cookie sent for redirected path mismatch for different resources inside the same final directory component", + location: "/cookies/attributes/resources/path/two.html", + }, + { + cookie: "test=13; path=/cookies/attributes/resources/path/one.html/", + expected: "", + name: "No cookie sent for redirected path mismatch where final path directory component ends in '/' and does not match redirected URL", + location: "/cookies/attributes/resources/path/two.html", + }, + { + cookie: "test=14; path=/cookies/attributes/resources/path/", + expected: "", + name: "No cookie sent for redirected path mismatch with a similar start to the redirected URL", + location: "/cookies/attributes/resources/pathfakeout.html", + }, + { + cookie: "test=15; path=/cookies/attributes/resources/path/one.html?", + expected: "", + name: "No cookie sent for redirected path mismatch with trailing '?' after unquoted Path", + location: "/cookies/attributes/resources/path/one.html", + }, + { + cookie: "test=16; path=/cookies/attributes/resources/path/one.html#", + expected: "", + name: "No cookie sent for redirected path mismatch with trailing '#' after unquoted Path", + location: "/cookies/attributes/resources/path/one.html", + }, + { + cookie: "test=17; path=/cookies/attributes/resources/path/one.html/", + expected: "", + name: "No cookie sent for redirected path mismatch with trailing '/' after unquoted Path", + location: "/cookies/attributes/resources/path/one.html", + }, + ]; + + for (const test of pathRedirectTests) { + httpRedirectCookieTest(test.cookie, test.expected, test.name, + test.location); + } + </script> + </body> +</html> diff --git a/testing/web-platform/tests/cookies/attributes/path.html b/testing/web-platform/tests/cookies/attributes/path.html new file mode 100644 index 0000000000..81adc08a19 --- /dev/null +++ b/testing/web-platform/tests/cookies/attributes/path.html @@ -0,0 +1,144 @@ +<!doctype html> +<html> + <head> + <meta charset=utf-8> + <title>Test cookie path attribute parsing</title> + <meta name=help href="https://tools.ietf.org/html/rfc6265#section-5.2.4"> + <meta name="timeout" content="long"> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <script src="/resources/testdriver.js"></script> + <script src="/resources/testdriver-vendor.js"></script> + <script src="/cookies/resources/cookie-test.js"></script> + </head> + <body> + <script> + const pathTests = [ + { + cookie: "test=1; Path", + expected: "test=1", + name: "Set cookie for bare Path", + }, + { + cookie: "test=2; Path=", + expected: "test=2", + name: "Set cookie for Path=", + }, + { + cookie: "test=3; Path=/", + expected: "test=3", + name: "Set cookie for Path=/", + defaultPath: false, + }, + { + cookie: "test=4; Path=/qux", + expected: "", + name: "No cookie returned for mismatched path", + defaultPath: false, + }, + { + cookie: "test=5; Path =/qux", + expected: "", + name: "No cookie returned for path space equals mismatched path", + defaultPath: false, + }, + { + cookie: "test=6; Path= /qux", + expected: "", + name: "No cookie returned for path equals space mismatched path", + defaultPath: false, + }, + { + cookie: "test=7; Path=/qux ; taz", + expected: "", + name: "No cookie returned for mismatched path and attribute", + defaultPath: false, + }, + { + cookie: "test=8; Path=/qux; Path=/", + expected: "test=8", + name: "Set cookie for mismatched and root path", + }, + { + cookie: "test=9; Path=/; Path=/qux", + expected: "", + name: "No cookie returned for root and mismatched path", + defaultPath: false, + }, + { + cookie: "test=10; Path=/lol; Path=/qux", + expected: "", + name: "No cookie returned for multiple mismatched paths", + defaultPath: false, + }, + { + cookie: ["testA=11; path=/", "testB=11; path=/cookies/attributes"], + expected: "testB=11; testA=11", + name: "Return 2 cookies sorted by matching path length (earlier name with shorter path set first)", + defaultPath: false, + }, + { + cookie: ["testB=12; path=/", "testA=12; path=/cookies/attributes"], + expected: "testA=12; testB=12", + name: "Return 2 cookies sorted by matching path length (later name with shorter path set first)", + defaultPath: false, + }, + { + cookie: ["testA=13; path=/cookies/attributes", "testB=13; path=/"], + expected: "testA=13; testB=13", + name: "Return 2 cookies sorted by matching path length (earlier name with longer path set first)", + defaultPath: false, + }, + { + cookie: ["testB=14; path=/cookies/attributes", "testA=14; path=/"], + expected: "testB=14; testA=14", + name: "Return 2 cookies sorted by matching path length (later name with longer path set first)", + defaultPath: false, + }, + { + cookie: ["test=15; path=/cookies/attributes/foo"], + expected: "", + name: "No cookie returned for partial path match", + defaultPath: false, + }, + { + cookie: ["test=16", "test=0; path=/cookies/attributes/foo"], + expected: "test=16", + name: "No cookie returned for partial path match, return cookie for default path", + }, + { + cookie: ["test=17; path= /"], + expected: "test=17", + name: "Return cookie for path= / (whitespace after equals)", + }, + { + cookie: ["test=18; path=/cookies/ATTRIBUTES"], + expected: "", + name: "No cookie returned for case mismatched path", + defaultPath: false, + }, + { + cookie: ["testA=19; path = /cookies/attributes", "testB=19; path = /book"], + expected: "testA=19", + name: "Return cookie A on path match, no cookie returned for path mismatch (plus whitespace)", + defaultPath: false, + }, + { + cookie: ["test=20; path=; path=/dog"], + expected: "", + name: "No cookie returned for mismatched path (after bare path=)", + defaultPath: false, + }, + { + cookie: ["test=21; path=/dog; path="], + expected: "test=21", + name: "Return cookie for bare path= (after mismatched path)", + }, + ]; + + for (const test of pathTests) { + httpCookieTest(test.cookie, test.expected, test.name, test.defaultPath); + } + </script> + </body> +</html> diff --git a/testing/web-platform/tests/cookies/attributes/resources/domain-child.sub.html b/testing/web-platform/tests/cookies/attributes/resources/domain-child.sub.html new file mode 100644 index 0000000000..515079b783 --- /dev/null +++ b/testing/web-platform/tests/cookies/attributes/resources/domain-child.sub.html @@ -0,0 +1,401 @@ +<!doctype html> +<html> + <head> + <meta charset=utf-8> + <title>Test cookie domain attribute parsing</title> + <meta name=help href="https://tools.ietf.org/html/rfc6265#section-5.2.3"> + <meta name="timeout" content="long"> + <script src="/resources/testharness.js"></script> + <script src="/resources/testdriver.js"></script> + <script src="/resources/testdriver-vendor.js"></script> + <script src="/cookies/resources/cookie-test.js"></script> + </head> + <body> + <script> + const path = "path=/cookies/attributes" + const port = "{{ports[http][0]}}"; + const host = "{{host}}"; // example.org + const wwwHost = "{{domains[www]}}"; // home.example.org + const www1Host = "{{domains[www1]}}"; // sibling.example.org + const www2wwwHost = "{{domains[www2.www]}}"; // subdomain.home.example.org + + // naive helper method to return the TLD for a given domain + const getTLD = domain => { + let match = /\.[a-z]+$/.exec(domain); + if (match) { + return match[0]; + } else { + throw 'Domain is malformed!'; + } + } + + // helper to take a domain like "www.example.org" + // and return a string like "www.eXaMpLe.org" + const makeBizarre = domain => { + let bizarre = ""; + let domainArray = domain.split("."); + let secondLevel = domainArray[domainArray.length - 2]; + for (let i in secondLevel) { + if (i % 2 == 1) { + bizarre += secondLevel[i].toUpperCase(); + } else { + bizarre += secondLevel[i]; + } + } + domainArray[domainArray.length - 2] = bizarre; + return domainArray.join("."); + } + + // helper to change the current TLD to a TLD that doesn't exist, and is + // unlikely to exist in the future. (the main point is that the TLD + // *changes*, so there is no domain match, but we cant' predict how WPT + // servers may be set up in the wild so picking any valid TLD has the risk + // of future (unintentional) domain matching. + const changeTLD = domain => { + let domainArray = domain.split("."); + domainArray[domainArray.length - 1] += "zzz"; + return domainArray.join("."); + } + + const domainTests = [ + { + cookie: `test=1; domain=${wwwHost}`, + expected: "test=1", + name: "Return cookie for a domain match", + location: `http://${wwwHost}:${port}/cookies/attributes/resources/path.html`, + }, + { + cookie: `test=2; domain=${wwwHost}`, + expected: "", + name: "No cookie returned for domain mismatch (subdomains differ post-redirect)", + location: `http://${www1Host}:${port}/cookies/attributes/resources/path.html`, + }, + { + cookie: `test=3; domain=.${wwwHost}`, + expected: "test=3", + name: "Return cookie for a domain match with leading '.'", + location: `http://${wwwHost}:${port}/cookies/attributes/resources/path.html`, + }, + { + cookie: `test=4; domain=${wwwHost}`, + expected: "test=4", + name: "Return cookie for domain match (domain attribute is suffix of the host name and first level subdomain)", + location: `http://${www2wwwHost}:${port}/cookies/attributes/resources/path.html`, + }, + { + cookie: `test=5; domain=.${wwwHost}`, + expected: "test=5", + name: "Return cookie for domain match (domain attribute is suffix of the host name and first level subdomain, with leading '.')", + location: `http://${www2wwwHost}:${port}/cookies/attributes/resources/path.html`, + }, + { + cookie: `test=6; domain=.${wwwHost}`, + expected: "", + name: "No cookie returned for domain mismatch (subdomains differ, with leading '.')", + location: `http://${www1Host}:${port}/cookies/attributes/resources/path.html`, + }, + { + cookie: `test=7; domain=${www1Host}`, + expected: "", + name: "No cookie returned for domain mismatch when cookie was created (which would match after the redirect, with one subdomain level)", + location: `http://${www1Host}:${port}/cookies/attributes/resources/path.html`, + }, + { + cookie: `test=8; domain=.${host}`, + expected: "test=8", + name: "Return cookie for domain match (domain attribute is suffix of the host name, with leading '.')", + location: `http://${wwwHost}:${port}/cookies/attributes/resources/path.html`, + }, + { + cookie: `test=9; domain=${host}`, + expected: "test=9", + name: "Return cookie for domain match (domain attribute is suffix of the host name)", + location: `http://${wwwHost}:${port}/cookies/attributes/resources/path.html`, + }, + { + cookie: `test=10; domain=..${wwwHost}`, + expected: "", + name: "No cookie returned for domain attribute with double leading '.'", + location: `http://${wwwHost}:${port}/cookies/attributes/resources/path.html`, + }, + { + cookie: `test=11; domain=www..${host}`, + expected: "", + name: "No cookie returned for domain attribute with subdomain followed by ..", + location: `http://${wwwHost}:${port}/cookies/attributes/resources/path.html`, + }, + { + cookie: `test=12; domain= .${wwwHost}`, + expected: "test=12", + name: "Return cookie for a domain match with leading whitespace and '.'", + location: `http://${wwwHost}:${port}/cookies/attributes/resources/path.html`, + }, + { + cookie: `test=13; domain= . ${wwwHost}`, + expected: "", + name: "No cookie returned for domain attribute with whitespace that surrounds a leading '.'", + location: `http://${wwwHost}:${port}/cookies/attributes/resources/path.html`, + }, + { + cookie: `test=14; domain=${wwwHost}.`, + expected: "", + name: "No cookie returned for domain attribute with trailing '.'", + location: `http://${wwwHost}:${port}/cookies/attributes/resources/path.html`, + }, + { + cookie: `test=15; domain=${wwwHost}..`, + expected: "", + name: "No cookie returned for domain attribute with trailing '..'", + location: `http://${wwwHost}:${port}/cookies/attributes/resources/path.html`, + }, + { + cookie: `test=16; domain=${wwwHost} .`, + expected: "", + name: "No cookie returned for domain attribute with trailing whitespace and '.'", + location: `http://${wwwHost}:${port}/cookies/attributes/resources/path.html`, + }, + { + cookie: `test=17; domain=${getTLD(host)}`, + expected: "", + name: "No cookie returned for domain attribute with TLD as value", + location: `http://${wwwHost}:${port}/cookies/attributes/resources/path.html`, + }, + { + cookie: `test=18; domain=.${getTLD(host)}`, + expected: "", + name: "No cookie returned for domain attribute with TLD as value, with leading '.'", + location: `http://${wwwHost}:${port}/cookies/attributes/resources/path.html`, + }, + { + cookie: `test=18b; domain=.${getTLD(host)}.`, + expected: "", + name: "No cookie returned for domain attribute with TLD as value, with leading and trailing '.'", + location: `http://${wwwHost}:${port}/cookies/attributes/resources/path.html`, + }, + { + cookie: [`testA=19; domain=${wwwHost}`, `testB=19; domain=.${wwwHost}`], + expected: "testA=19; testB=19", + name: "Return multiple cookies that match on domain (without and with leading '.')", + location: `http://${wwwHost}:${port}/cookies/attributes/resources/path.html`, + }, + { + cookie: [`testB=20; domain=.${wwwHost}`, `testA=20; domain=${wwwHost}`], + expected: "testB=20; testA=20", + name: "Return multiple cookies that match on domain (with and without leading '.')", + location: `http://${wwwHost}:${port}/cookies/attributes/resources/path.html`, + }, + { + cookie: `test=21; domain="${wwwHost}"`, + expected: "", + name: "No cookie returned for domain attribute value between quotes", + location: `http://${wwwHost}:${port}/cookies/attributes/resources/path.html`, + }, + { + cookie: [`testA=22; domain=${wwwHost}`, `testB=22; domain=.${host}`], + expected: "testA=22; testB=22", + name: "Return multiple cookies that match on subdomain and domain (without and with leading '.')", + location: `http://${wwwHost}:${port}/cookies/attributes/resources/path.html`, + }, + { + cookie: [`testB=23; domain=.${host}`, `testA=23; domain=${wwwHost}`], + expected: "testB=23; testA=23", + name: "Return multiple cookies that match on domain and subdomain (with and without leading '.')", + location: `http://${wwwHost}:${port}/cookies/attributes/resources/path.html`, + }, + { + cookie: `test=24; domain=.${host}; domain=${wwwHost}`, + expected: "", + name: "No cookie returned when domain attribute does not domain-match (and first does)", + location: `http://${www1Host}:${port}/cookies/attributes/resources/path.html`, + }, + { + cookie: `test=25; domain=${wwwHost}; domain=.${host}`, + expected: "test=25", + name: "Return cookie for domain attribute match (first does not, but second does)", + location: `http://${www1Host}:${port}/cookies/attributes/resources/path.html`, + }, + { + cookie: `test=26; domain=${makeBizarre(wwwHost)}`, + expected: "test=26", + name: "Return cookie for domain match (with bizarre capitalization for domain attribute value)", + location: `http://${wwwHost}:${port}/cookies/attributes/resources/path.html`, + }, + { + cookie: `test=27; domain="${wwwHost}:${port}"`, + expected: "", + name: "No cookie returned for domain attribute value with port", + location: `http://${wwwHost}:${port}/cookies/attributes/resources/path.html`, + }, + { + cookie: `test=28; domain=${www2wwwHost}`, + expected: "", + name: "No cookie returned for domain mismatch when cookie was created (which would match after the redirect, with two subdomain levels)", + location: `http://${www2wwwHost}:${port}/cookies/attributes/resources/path.html`, + }, + { + cookie: `test=29`, + expected: "", + name: "No cookie returned for cookie set on different domain (with no domain attribute)", + location: `http://${www2wwwHost}:${port}/cookies/attributes/resources/path.html`, + }, + { + cookie: "test=30; domain=", + expected: "test=30", + name: "Return cookie set with bare domain= attribute", + location: `http://${wwwHost}:${port}/cookies/attributes/resources/path.html`, + }, + { + cookie: `test=31; domain=${wwwHost}`, + expected: "test=31", + name: "Return cookie that domain-matches with bizarre-cased URL", + location: `http://${makeBizarre(wwwHost)}:${port}/cookies/attributes/resources/path.html`, + }, + { + cookie: `test=32; domain=${wwwHost}; domain=${changeTLD(wwwHost)}`, + expected: "", + name: "No cookie returned for domain attribute mismatch (first attribute matches, but second does not)", + location: `http://${wwwHost}:${port}/cookies/attributes/resources/path.html`, + }, + { + cookie: `test=33; domain=${changeTLD(wwwHost)}; domain=${wwwHost}`, + expected: "test=33", + name: "Return cookie for domain match (first attribute doesn't, but second does)", + location: `http://${wwwHost}:${port}/cookies/attributes/resources/path.html`, + }, + { + cookie: `test=34; domain=${wwwHost}; domain=${changeTLD(wwwHost)}; domain=${wwwHost}`, + expected: "test=34", + name: "Return cookie for domain match (first attribute matches, second doesn't, third does)", + location: `http://${wwwHost}:${port}/cookies/attributes/resources/path.html`, + }, + { + cookie: `test=35; domain=${changeTLD(wwwHost)}; domain=${wwwHost}; domain=${changeTLD(wwwHost)}`, + expected: "", + name: "No cookie returned for domain attribute mismatch (first attribute doesn't, second does, third doesn't)", + location: `http://${wwwHost}:${port}/cookies/attributes/resources/path.html`, + }, + { + cookie: `test=36; domain=${wwwHost}; domain=${wwwHost}`, + expected: "test=36", + name: "Return cookie for domain match (with two identical domain attributes)", + location: `http://${wwwHost}:${port}/cookies/attributes/resources/path.html`, + }, + { + cookie: `test=37; domain=${wwwHost}; domain=${host}`, + expected: "test=37", + name: "Return cookie for domain match (with first domain attribute a match for host name and second as suffix of host name)", + location: `http://${wwwHost}:${port}/cookies/attributes/resources/path.html`, + }, + { + cookie: `test=38; domain=${host}; domain=${wwwHost}`, + expected: "test=38", + name: "Return cookie for domain match (with first domain attribute as suffix of host name and second a match for host name)", + location: `http://${wwwHost}:${port}/cookies/attributes/resources/path.html`, + }, + { + cookie: `test=39; domain=.${www1Host}`, + expected: "", + name: "No cookie set on domain mismatch before a (domain matching) redirect", + location: `http://${www1Host}:${port}/cookies/attributes/resources/path.html`, + }, + { + cookie: `test=40; domain=.${www2wwwHost}`, + expected: "", + name: "No cookie set on domain mismatch before a (domain matching) redirect (for second level subdomain)", + location: `http://${www2wwwHost}:${port}/cookies/attributes/resources/path.html`, + }, + { + cookie: `test=41; domain=${host}; domain=`, + expected: "test=41", + name: "Return cookie for domain match (with first domain attribute as suffix of host name and second a bare attribute)", + location: `http://${wwwHost}:${port}/cookies/attributes/resources/path.html`, + }, + { + cookie: `test=42; domain=${www1Host}; domain=`, + expected: "test=42", + name: "Cookie returned for bare domain attribute following mismatched domain attribute (after redirect to same-origin page).", + location: `http://${wwwHost}:${port}/cookies/attributes/resources/path.html`, + }, + { + cookie: `test=43; domain=${www1Host}; domain=`, + expected: "", + name: "No cookie returned for domain mismatch (first attribute is a different subdomain and second is bare)", + location: `http://${www2wwwHost}:${port}/cookies/attributes/resources/path.html`, + }, + { + cookie: [`test=not44; domain=${wwwHost}`, `test=44; domain=.${wwwHost}`], + expected: "test=44", + name: "Cookies with same name, path, and domain (differing only in leading '.') overwrite each other ('.' second)", + location: `http://${wwwHost}:${port}/cookies/attributes/resources/path.html`, + }, + { + cookie: [`test=not45; domain=.${wwwHost}`, `test=45; domain=${wwwHost}`], + expected: "test=45", + name: "Cookies with same name, path, and domain (differing only in leading '.') overwrite each other ('.' first)", + location: `http://${wwwHost}:${port}/cookies/attributes/resources/path.html`, + }, + { + cookie: `test=46; domain=.`, + expected: "", + name: "No cookie returned for domain with single dot ('.') value.", + location: `http://${wwwHost}:${port}/cookies/attributes/resources/path.html`, + }, + { + cookie: `test=46b; domain=.; domain=${host}`, + expected: "test=46b", + name: "Return cookie with valid domain after domain with single dot ('.') value.", + location: `http://${wwwHost}:${port}/cookies/attributes/resources/path.html`, + }, + { + cookie: ["test=47", `test=47b; domain=${host}`,`test=47b; domain=${www1Host}; domain=`], + expected: "test=47b; test=47b", + name: "Empty domain treated as host cookie 1", + location: `http://${wwwHost}:${port}/cookies/attributes/resources/path.html`, + }, + { + cookie: ["test=48", `test=48b; domain=${host}`,`test=48b; domain=${host}; domain=`], + expected: "test=48b; test=48b", + name: "Empty domain treated as host cookie 2", + location: `http://${wwwHost}:${port}/cookies/attributes/resources/path.html`, + }, + { + cookie: ["test=49", `test=49b; domain=${host}`,`test=49b; domain=`], + expected: "test=49b; test=49b", + name: "Empty domain treated as host cookie 3", + location: `http://${wwwHost}:${port}/cookies/attributes/resources/path.html`, + }, + { + cookie: ["test=50", `test=50b; domain=${host}`,`test=50b; domain=${www1Host}; domain=`], + expected: "test=50b", + name: "No host cookies returned for host cookies after non-host redirect 1", + location: `http://${www2wwwHost}:${port}/cookies/attributes/resources/path.html`, + }, + { + cookie: ["test=51", `test=51b; domain=${host}`,`test=51b; domain=${host}; domain=`], + expected: "test=51b", + name: "No host cookies returned for host cookies after non-host redirect 2", + location: `http://${www2wwwHost}:${port}/cookies/attributes/resources/path.html`, + }, + { + cookie: ["test=52", `test=52b; domain=${host}`,`test=52b; domain=`], + expected: "test=52b", + name: "No host cookies returned for host cookies after non-host redirect 3", + location: `http://${www2wwwHost}:${port}/cookies/attributes/resources/path.html`, + }, + ]; + + for (const test of domainTests) { + if (Array.isArray(test.cookie)) { + for (let i in test.cookie) { + test.cookie[i] += `; ${path}`; + } + } else { + test.cookie += `; ${path}`; + } + + httpRedirectCookieTest(test.cookie, test.expected, test.name, + test.location); + } + </script> + </body> +</html> diff --git a/testing/web-platform/tests/cookies/attributes/resources/path-redirect-shared.js b/testing/web-platform/tests/cookies/attributes/resources/path-redirect-shared.js new file mode 100644 index 0000000000..777d0abd33 --- /dev/null +++ b/testing/web-platform/tests/cookies/attributes/resources/path-redirect-shared.js @@ -0,0 +1,11 @@ +// Note: this function has a dependency on testdriver.js. Any test files calling +// it should include testdriver.js and testdriver-vendor.js +window.addEventListener("message", (e) => { + setTestContextUsingRootWindow(); + if (e.data == "getAndExpireCookiesForRedirectTest") { + const cookies = document.cookie; + test_driver.delete_all_cookies().then(() => { + e.source.postMessage({"cookies": cookies}, '*'); + }); + } +});
\ No newline at end of file diff --git a/testing/web-platform/tests/cookies/attributes/resources/path.html b/testing/web-platform/tests/cookies/attributes/resources/path.html new file mode 100644 index 0000000000..5ff90b9f15 --- /dev/null +++ b/testing/web-platform/tests/cookies/attributes/resources/path.html @@ -0,0 +1,14 @@ +<!doctype html> +<html> +<head> + <meta charset=utf-8> + <title>helper iframe for matching cookie path redirect tests</title> + <meta name=help href="http://tools.ietf.org/html/rfc6265#section-5.1.4"> +</head> +<body> + <script src="/resources/testdriver.js"></script> + <script src="/resources/testdriver-vendor.js"></script> + <script src="/cookies/resources/cookie-test.js"></script> + <script src="/cookies/attributes/resources/path-redirect-shared.js"></script> +</body> +</html>
\ No newline at end of file diff --git a/testing/web-platform/tests/cookies/attributes/resources/path.html.headers b/testing/web-platform/tests/cookies/attributes/resources/path.html.headers new file mode 100644 index 0000000000..23de552c1a --- /dev/null +++ b/testing/web-platform/tests/cookies/attributes/resources/path.html.headers @@ -0,0 +1 @@ +Access-Control-Allow-Origin: *
\ No newline at end of file diff --git a/testing/web-platform/tests/cookies/attributes/resources/path/one.html b/testing/web-platform/tests/cookies/attributes/resources/path/one.html new file mode 100644 index 0000000000..5ff90b9f15 --- /dev/null +++ b/testing/web-platform/tests/cookies/attributes/resources/path/one.html @@ -0,0 +1,14 @@ +<!doctype html> +<html> +<head> + <meta charset=utf-8> + <title>helper iframe for matching cookie path redirect tests</title> + <meta name=help href="http://tools.ietf.org/html/rfc6265#section-5.1.4"> +</head> +<body> + <script src="/resources/testdriver.js"></script> + <script src="/resources/testdriver-vendor.js"></script> + <script src="/cookies/resources/cookie-test.js"></script> + <script src="/cookies/attributes/resources/path-redirect-shared.js"></script> +</body> +</html>
\ No newline at end of file diff --git a/testing/web-platform/tests/cookies/attributes/resources/path/three.html b/testing/web-platform/tests/cookies/attributes/resources/path/three.html new file mode 100644 index 0000000000..5ff90b9f15 --- /dev/null +++ b/testing/web-platform/tests/cookies/attributes/resources/path/three.html @@ -0,0 +1,14 @@ +<!doctype html> +<html> +<head> + <meta charset=utf-8> + <title>helper iframe for matching cookie path redirect tests</title> + <meta name=help href="http://tools.ietf.org/html/rfc6265#section-5.1.4"> +</head> +<body> + <script src="/resources/testdriver.js"></script> + <script src="/resources/testdriver-vendor.js"></script> + <script src="/cookies/resources/cookie-test.js"></script> + <script src="/cookies/attributes/resources/path-redirect-shared.js"></script> +</body> +</html>
\ No newline at end of file diff --git a/testing/web-platform/tests/cookies/attributes/resources/path/two.html b/testing/web-platform/tests/cookies/attributes/resources/path/two.html new file mode 100644 index 0000000000..5ff90b9f15 --- /dev/null +++ b/testing/web-platform/tests/cookies/attributes/resources/path/two.html @@ -0,0 +1,14 @@ +<!doctype html> +<html> +<head> + <meta charset=utf-8> + <title>helper iframe for matching cookie path redirect tests</title> + <meta name=help href="http://tools.ietf.org/html/rfc6265#section-5.1.4"> +</head> +<body> + <script src="/resources/testdriver.js"></script> + <script src="/resources/testdriver-vendor.js"></script> + <script src="/cookies/resources/cookie-test.js"></script> + <script src="/cookies/attributes/resources/path-redirect-shared.js"></script> +</body> +</html>
\ No newline at end of file diff --git a/testing/web-platform/tests/cookies/attributes/resources/pathfakeout.html b/testing/web-platform/tests/cookies/attributes/resources/pathfakeout.html new file mode 100644 index 0000000000..5ff90b9f15 --- /dev/null +++ b/testing/web-platform/tests/cookies/attributes/resources/pathfakeout.html @@ -0,0 +1,14 @@ +<!doctype html> +<html> +<head> + <meta charset=utf-8> + <title>helper iframe for matching cookie path redirect tests</title> + <meta name=help href="http://tools.ietf.org/html/rfc6265#section-5.1.4"> +</head> +<body> + <script src="/resources/testdriver.js"></script> + <script src="/resources/testdriver-vendor.js"></script> + <script src="/cookies/resources/cookie-test.js"></script> + <script src="/cookies/attributes/resources/path-redirect-shared.js"></script> +</body> +</html>
\ No newline at end of file diff --git a/testing/web-platform/tests/cookies/attributes/resources/pathfakeout/one.html b/testing/web-platform/tests/cookies/attributes/resources/pathfakeout/one.html new file mode 100644 index 0000000000..5ff90b9f15 --- /dev/null +++ b/testing/web-platform/tests/cookies/attributes/resources/pathfakeout/one.html @@ -0,0 +1,14 @@ +<!doctype html> +<html> +<head> + <meta charset=utf-8> + <title>helper iframe for matching cookie path redirect tests</title> + <meta name=help href="http://tools.ietf.org/html/rfc6265#section-5.1.4"> +</head> +<body> + <script src="/resources/testdriver.js"></script> + <script src="/resources/testdriver-vendor.js"></script> + <script src="/cookies/resources/cookie-test.js"></script> + <script src="/cookies/attributes/resources/path-redirect-shared.js"></script> +</body> +</html>
\ No newline at end of file diff --git a/testing/web-platform/tests/cookies/attributes/resources/secure-non-secure-child.html b/testing/web-platform/tests/cookies/attributes/resources/secure-non-secure-child.html new file mode 100644 index 0000000000..e5d68b8d07 --- /dev/null +++ b/testing/web-platform/tests/cookies/attributes/resources/secure-non-secure-child.html @@ -0,0 +1,85 @@ +<!doctype html> +<html> + <head> + <meta charset=utf-8> + <title>Test cookie secure attribute parsing (on non-secure page)</title> + <meta name=help href="https://tools.ietf.org/html/rfc6265#section-5.2.5"> + <meta name="timeout" content="long"> + <script src="/resources/testharness.js"></script> + <script src="/resources/testdriver.js"></script> + <script src="/resources/testdriver-vendor.js"></script> + <script src="/cookies/resources/cookie-test.js"></script> + </head> + <body> + <script> + setTestContextUsingRootWindow(); + // These tests are the non-secure analog to secure.https.html. + // They're not in the /cookies/attributes folder because they shouldn't + // be run by themselves. Instead, /cookies/attributes/secure.https.html + // opens this in a non-secure window. + const secureNonSecureTests = [ + { + cookie: "test=1; Secure", + expected: "", + name: "(non-secure) Ignore cookie for Secure attribute", + }, + { + cookie: "test=2; seCURe", + expected: "", + name: "(non-secure) Ignore cookie for seCURe attribute", + }, + { + cookie: "test=3; Secure=", + expected: "", + name: "(non-secure) Ignore cookie for for Secure= attribute", + }, + { + cookie: "test=4; Secure=aaaa", + expected: "", + name: "(non-secure) Ignore cookie for Secure=aaaa", + }, + { + cookie: "test=5; Secure =aaaaa", + expected: "", + name: "(non-secure) Ignore cookie for Secure space equals", + }, + { + cookie: "test=6; Secure= aaaaa", + expected: "", + name: "(non-secure) Ignore cookie for Secure equals space", + }, + { + cookie: "test=7; Secure", + expected: "", + name: "(non-secure) Ignore cookie for spaced Secure", + }, + { + cookie: "test=8; Secure ;", + expected: "", + name: "(non-secure) Ignore cookie for space Secure with ;", + }, + { + cookie: "__Secure-test=9; Secure", + expected: "", + name: "(non-secure) Ignore cookie with __Secure- prefix and Secure", + }, + { + cookie: "__Secure-test=10", + expected: "", + name: "(non-secure) Ignore cookie with __Secure- prefix and without Secure", + }, + // This is really a test that the cookie name isn't URL-decoded, but this + // is here to be next to the other __Secure- prefix tests. + { + cookie: "__%53ecure-test=11", + expected: "__%53ecure-test=11", + name: "(non-secure) Cookie returned with __%53ecure- prefix and without Secure", + }, + ]; + + for (const test of secureNonSecureTests) { + httpCookieTest(test.cookie, test.expected, test.name, test.defaultPath); + } + </script> + </body> +</html> diff --git a/testing/web-platform/tests/cookies/attributes/secure-non-secure.html b/testing/web-platform/tests/cookies/attributes/secure-non-secure.html new file mode 100644 index 0000000000..578cdbc1f7 --- /dev/null +++ b/testing/web-platform/tests/cookies/attributes/secure-non-secure.html @@ -0,0 +1,23 @@ +<!doctype html> +<html> + <head> + <meta charset=utf-8> + <title>Test cookie secure attribute parsing (non-secure origin)</title> + <meta name=help href="https://tools.ietf.org/html/rfc6265#section-5.2.5"> + <meta name="timeout" content="long"> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <script src="/resources/testdriver.js"></script> + <script src="/resources/testdriver-vendor.js"></script> + <script src="/cookies/resources/cookie-helper.sub.js"></script> + </head> + <body> + <div id=log></div> + <script> + test(t => { + const win = window.open(`${INSECURE_ORIGIN}/cookies/attributes/resources/secure-non-secure-child.html`); + fetch_tests_from_window(win); + }); + </script> + </body> +</html> diff --git a/testing/web-platform/tests/cookies/attributes/secure.https.html b/testing/web-platform/tests/cookies/attributes/secure.https.html new file mode 100644 index 0000000000..9308899694 --- /dev/null +++ b/testing/web-platform/tests/cookies/attributes/secure.https.html @@ -0,0 +1,65 @@ +<!doctype html> +<html> + <head> + <meta charset=utf-8> + <title>Test cookie secure attribute parsing</title> + <meta name=help href="https://tools.ietf.org/html/rfc6265#section-5.2.5"> + <meta name="timeout" content="long"> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <script src="/resources/testdriver.js"></script> + <script src="/resources/testdriver-vendor.js"></script> + <script src="/cookies/resources/cookie-test.js"></script> + </head> + <body> + <div id=log></div> + <script> + const secureTests = [ + { + cookie: "test=1; Secure", + expected: "test=1", + name: "Set cookie for Secure attribute", + }, + { + cookie: "test=2; seCURe", + expected: "test=2", + name: "Set cookie for seCURe attribute", + }, + { + cookie: "test=3; Secure=", + expected: "test=3", + name: "Set cookie for for Secure= attribute", + }, + { + cookie: "test=4; Secure=aaaa", + expected: "test=4", + name: "Set cookie for Secure=aaaa", + }, + { + cookie: "test=5; Secure =aaaaa", + expected: "test=5", + name: "Set cookie for Secure space equals", + }, + { + cookie: "test=6; Secure= aaaaa", + expected: "test=6", + name: "Set cookie for Secure equals space", + }, + { + cookie: "test=7; Secure", + expected: "test=7", + name: "Set cookie for spaced Secure", + }, + { + cookie: "test=8; Secure ;", + expected: "test=8", + name: "Set cookie for space Secure with ;", + } + ]; + + for (const test of secureTests) { + httpCookieTest(test.cookie, test.expected, test.name, test.defaultPath); + } + </script> + </body> +</html> diff --git a/testing/web-platform/tests/cookies/cookie-enabled-noncookie-frame.html b/testing/web-platform/tests/cookies/cookie-enabled-noncookie-frame.html new file mode 100644 index 0000000000..2dbbc0bbdc --- /dev/null +++ b/testing/web-platform/tests/cookies/cookie-enabled-noncookie-frame.html @@ -0,0 +1,30 @@ +<!DOCTYPE html> +<head> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> +</head> +<body> + <script> + var t = async_test("navigator.cookieEnabled behavior on frames without cookie access"); + window.onmessage = t.step_func_done(ev => { + // Surprisingly, the legacy behavior here is to return true; this actually + // does match the spec definition since false is supposed to be returned + // when a document.cookie write is ignored --- and here it would throw + // a security exception, not be ignored. + assert_true(ev.data); + }); + + t.step(() => { + var iframe = document.createElement("iframe"); + iframe.sandbox = "allow-scripts"; + iframe.srcdoc = "<scr" + "ipt>" + + "window.onmessage = function() {" + + " parent.postMessage(navigator.cookieEnabled, '*'); " + + "}</scr" + "ipt>"; + iframe.onload = function() { + iframe.contentWindow.postMessage({}, "*"); + } + document.body.appendChild(iframe); + }); + </script> +</body> diff --git a/testing/web-platform/tests/cookies/domain/domain-attribute-host-with-and-without-leading-period.sub.https.html b/testing/web-platform/tests/cookies/domain/domain-attribute-host-with-and-without-leading-period.sub.https.html new file mode 100644 index 0000000000..b5f770b848 --- /dev/null +++ b/testing/web-platform/tests/cookies/domain/domain-attribute-host-with-and-without-leading-period.sub.https.html @@ -0,0 +1,40 @@ +<!DOCTYPE html> +<head> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <script src="/cookies/resources/cookie-helper.sub.js"></script> +</head> +<body> + <script> + // + // Set-Cookie: domain-attribute-host-with-and-without-leading-period=b; Path=/; Domain=.{{host}} + // Set-Cookie: domain-attribute-host-with-and-without-leading-period=c; Path=/; Domain={{host}} + // + const cookieName = "domain-attribute-host-with-and-without-leading-period"; + // Clean up cookie at the end to avoid interfering with subsequent tests. + add_completion_callback(tests => document.cookie = + `${cookieName}=0; Path=/; Domain={{host}}; expires=01-jan-1970 00:00:00 GMT`); + + test(t => { + assert_dom_cookie(cookieName, "c", true); + }, "Domain=.{{host}} => Second value available via `document.cookie`"); + + async_test(t => { + fetch("/cookies/resources/list.py", { credentials: "include" }) + .then(t.step_func(r => r.json())) + .then(t.step_func_done(r => { + assert_equals(r[cookieName], "c"); + })) + .catch(_ => assert_unreached); + }, "Domain=.{{host}} => Second value sent with same-origin requests."); + + async_test(t => { + fetch(`${SECURE_SUBDOMAIN_ORIGIN}/cookies/resources/list.py`, { credentials: "include" }) + .then(t.step_func(r => r.json())) + .then(t.step_func_done(r => { + assert_equals(r[cookieName], "c"); + })) + .catch(_ => assert_unreached); + }, "Domain=.{{host}} => Second value sent with subdomain requests."); + </script> +</body> diff --git a/testing/web-platform/tests/cookies/domain/domain-attribute-host-with-and-without-leading-period.sub.https.html.sub.headers b/testing/web-platform/tests/cookies/domain/domain-attribute-host-with-and-without-leading-period.sub.https.html.sub.headers new file mode 100644 index 0000000000..77d3d8c0c4 --- /dev/null +++ b/testing/web-platform/tests/cookies/domain/domain-attribute-host-with-and-without-leading-period.sub.https.html.sub.headers @@ -0,0 +1,2 @@ +Set-Cookie: domain-attribute-host-with-and-without-leading-period=b; Path=/; Domain=.{{host}} +Set-Cookie: domain-attribute-host-with-and-without-leading-period=c; Path=/; Domain={{host}} diff --git a/testing/web-platform/tests/cookies/domain/domain-attribute-host-with-leading-period.sub.https.html b/testing/web-platform/tests/cookies/domain/domain-attribute-host-with-leading-period.sub.https.html new file mode 100644 index 0000000000..3ec52fd40b --- /dev/null +++ b/testing/web-platform/tests/cookies/domain/domain-attribute-host-with-leading-period.sub.https.html @@ -0,0 +1,39 @@ +<!DOCTYPE html> +<head> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <script src="/cookies/resources/cookie-helper.sub.js"></script> +</head> +<body> + <script> + // + // Set-Cookie: domain-attribute-host-with-leading-period=b; Path=/; Domain=.{{host}} + // + const cookieName = "domain-attribute-host-with-leading-period"; + // Clean up cookie at the end to avoid interfering with subsequent tests. + add_completion_callback(tests => document.cookie = + `${cookieName}=0; Path=/; Domain=.{{host}}; expires=01-jan-1970 00:00:00 GMT`); + + test(t => { + assert_dom_cookie(cookieName, "b", true); + }, "Domain=.{{host}} => available via `document.cookie`"); + + async_test(t => { + fetch("/cookies/resources/list.py", { credentials: "include" }) + .then(t.step_func(r => r.json())) + .then(t.step_func_done(r => { + assert_equals(r[cookieName], "b"); + })) + .catch(_ => assert_unreached); + }, "Domain=.{{host}} => sent with same-origin requests."); + + async_test(t => { + fetch(`${SECURE_SUBDOMAIN_ORIGIN}/cookies/resources/list.py`, { credentials: "include" }) + .then(t.step_func(r => r.json())) + .then(t.step_func_done(r => { + assert_equals(r[cookieName], "b"); + })) + .catch(_ => assert_unreached); + }, "Domain=.{{host}} => sent with subdomain requests."); + </script> +</body> diff --git a/testing/web-platform/tests/cookies/domain/domain-attribute-host-with-leading-period.sub.https.html.sub.headers b/testing/web-platform/tests/cookies/domain/domain-attribute-host-with-leading-period.sub.https.html.sub.headers new file mode 100644 index 0000000000..7de4ae2e6a --- /dev/null +++ b/testing/web-platform/tests/cookies/domain/domain-attribute-host-with-leading-period.sub.https.html.sub.headers @@ -0,0 +1 @@ +Set-Cookie: domain-attribute-host-with-leading-period=b; Path=/; Domain=.{{host}} diff --git a/testing/web-platform/tests/cookies/domain/domain-attribute-idn-host.sub.https.html b/testing/web-platform/tests/cookies/domain/domain-attribute-idn-host.sub.https.html new file mode 100644 index 0000000000..ae4bf3cbb3 --- /dev/null +++ b/testing/web-platform/tests/cookies/domain/domain-attribute-idn-host.sub.https.html @@ -0,0 +1,18 @@ +<!DOCTYPE html> +<html> + <head> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <script src="/resources/testdriver.js"></script> + <script src="/resources/testdriver-vendor.js"></script> + </head> + <body> + <script> + let url = new URL(document.location); + url.host = "{{hosts[][élève]}}"; + let url2 = new URL("support/idn-child.sub.https.html", url); + let child_window = window.open(url2.href); + fetch_tests_from_window(child_window); + </script> + </body> +</html> diff --git a/testing/web-platform/tests/cookies/domain/domain-attribute-matches-host.sub.https.html b/testing/web-platform/tests/cookies/domain/domain-attribute-matches-host.sub.https.html new file mode 100644 index 0000000000..ac786dd882 --- /dev/null +++ b/testing/web-platform/tests/cookies/domain/domain-attribute-matches-host.sub.https.html @@ -0,0 +1,39 @@ +<!DOCTYPE html> +<head> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <script src="/cookies/resources/cookie-helper.sub.js"></script> +</head> +<body> + <script> + // + // Set-Cookie: domain-attribute-matches-host=b; Path=/; Domain={{host}} + // + const cookieName = "domain-attribute-matches-host"; + // Clean up cookie at the end to avoid interfering with subsequent tests. + add_completion_callback(tests => document.cookie = + `${cookieName}=0; Path=/; Domain={{host}}; expires=01-jan-1970 00:00:00 GMT`); + + test(t => { + assert_dom_cookie(cookieName, "b", true); + }, "Domain={{host}} => available via `document.cookie`"); + + async_test(t => { + fetch("/cookies/resources/list.py", { credentials: "include" }) + .then(t.step_func(r => r.json())) + .then(t.step_func_done(r => { + assert_equals(r[cookieName], "b"); + })) + .catch(_ => assert_unreached); + }, "Domain={{host}} => sent with same-origin requests."); + + async_test(t => { + fetch(`${SECURE_SUBDOMAIN_ORIGIN}/cookies/resources/list.py`, { credentials: "include" }) + .then(t.step_func(r => r.json())) + .then(t.step_func_done(r => { + assert_equals(r[cookieName], "b"); + })) + .catch(_ => assert_unreached); + }, "Domain={{host}} => sent with subdomain requests."); + </script> +</body> diff --git a/testing/web-platform/tests/cookies/domain/domain-attribute-matches-host.sub.https.html.sub.headers b/testing/web-platform/tests/cookies/domain/domain-attribute-matches-host.sub.https.html.sub.headers new file mode 100644 index 0000000000..8a2329e8c2 --- /dev/null +++ b/testing/web-platform/tests/cookies/domain/domain-attribute-matches-host.sub.https.html.sub.headers @@ -0,0 +1 @@ +Set-Cookie: domain-attribute-matches-host=b; Path=/; Domain={{host}} diff --git a/testing/web-platform/tests/cookies/domain/domain-attribute-missing.sub.html b/testing/web-platform/tests/cookies/domain/domain-attribute-missing.sub.html new file mode 100644 index 0000000000..44776ca629 --- /dev/null +++ b/testing/web-platform/tests/cookies/domain/domain-attribute-missing.sub.html @@ -0,0 +1,39 @@ +<!DOCTYPE html> +<head> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <script src="/cookies/resources/cookie-helper.sub.js"></script> +</head> +<body> + <script> + // + // Set-Cookie: domain-attribute-missing=b; Path=/ + // + const cookieName = "domain-attribute-missing"; + // Clean up cookie at the end to avoid interfering with subsequent tests. + add_completion_callback(tests => document.cookie = + `${cookieName}=0; Path=/; expires=01-jan-1970 00:00:00 GMT`); + + test(t => { + assert_dom_cookie(cookieName, "b", true); + }, "No domain attribute => available via `document.cookie`"); + + async_test(t => { + fetch("/cookies/resources/list.py", { credentials: "include" }) + .then(t.step_func(r => r.json())) + .then(t.step_func_done(r => { + assert_equals(r[cookieName], "b"); + })) + .catch(_ => assert_unreached); + }, "No domain attribute => sent with same-origin requests."); + + async_test(t => { + fetch(`${SECURE_SUBDOMAIN_ORIGIN}/cookies/resources/list.py`, { credentials: "include" }) + .then(t.step_func(r => r.json())) + .then(t.step_func_done(r => { + assert_equals(r[cookieName], undefined); + })) + .catch(_ => assert_unreached); + }, "No domain attribute => not sent with subdomain requests."); + </script> +</body> diff --git a/testing/web-platform/tests/cookies/domain/domain-attribute-missing.sub.html.headers b/testing/web-platform/tests/cookies/domain/domain-attribute-missing.sub.html.headers new file mode 100644 index 0000000000..3ee2833a45 --- /dev/null +++ b/testing/web-platform/tests/cookies/domain/domain-attribute-missing.sub.html.headers @@ -0,0 +1 @@ +Set-Cookie: domain-attribute-missing=b; Path=/ diff --git a/testing/web-platform/tests/cookies/domain/support/idn-child.sub.https.html b/testing/web-platform/tests/cookies/domain/support/idn-child.sub.https.html new file mode 100644 index 0000000000..d3510959fb --- /dev/null +++ b/testing/web-platform/tests/cookies/domain/support/idn-child.sub.https.html @@ -0,0 +1,72 @@ +<!DOCTYPE html> +<html> +<head> + <script src="/resources/testharness.js"></script> + <script src="/resources/testdriver.js"></script> + <script src="/resources/testdriver-vendor.js"></script> + <script src="/cookies/resources/cookie-test.js"></script> +</head> +<body> + <script> + async function assert_cookie(response, cookie) { + const get = await response.text(); + assert_equals(get, cookie); + assert_equals(document.cookie, cookie); + } + + async function assert_no_cookie(response) { + const get = await response.text(); + assert_equals(get, "no cookies"); + assert_equals(document.cookie, ""); + } + + promise_test(async t => { + t.add_cleanup(async () => { await fetch("idn.py?delete-utf8&host={{host}}") }); + let response = await fetch("idn.py?set-utf8&host={{host}}"); + assert_equals(await response.text(), "set"); + response = await fetch("idn.py?get&host={{host}}"); + await assert_no_cookie(response); + }, "UTF8-encoded IDN in domain attribute"); + + promise_test(async t => { + t.add_cleanup(async () => { await fetch("idn.py?delete-utf8-dot&host={{host}}") }); + let response = await fetch("idn.py?set-utf8-dot&host={{host}}"); + assert_equals(await response.text(), "set"); + response = await fetch("idn.py?get&host={{host}}"); + await assert_no_cookie(response); + }, "UTF8-encoded IDN with non-ASCII dot in domain attribute"); + + promise_test(async t => { + t.add_cleanup(async () => { await fetch("idn.py?delete-wrong-utf8&host={{host}}") }); + let response = await fetch("idn.py?set-wrong-utf8&host={{host}}"); + assert_equals(await response.text(), "set"); + response = await fetch("idn.py?get&host={{host}}"); + await assert_no_cookie(response); + }, "wrong UTF8-encoded IDN in domain attribute"); + + promise_test(async t => { + t.add_cleanup(async () => { await fetch("idn.py?delete-punycode&host={{host}}") }); + let response = await fetch("idn.py?set-punycode&host={{host}}"); + assert_equals(await response.text(), "set"); + response = await fetch("idn.py?get&host={{host}}"); + await assert_cookie(response, "punycode=set"); + }, "punycode IDN in domain attribute"); + + promise_test(async t => { + t.add_cleanup(async () => { await fetch("idn.py?delete-wrong-punycode&host={{host}}") }); + let response = await fetch("idn.py?set-wrong-punycode&host={{host}}"); + assert_equals(await response.text(), "set"); + response = await fetch("idn.py?get&host={{host}}"); + await assert_no_cookie(response); + }, "wrong punycode IDN in domain attribute"); + + promise_test(async t => { + t.add_cleanup(async () => { await fetch("idn.py?delete-invalid-byte&host={{host}}") }); + let response = await fetch("idn.py?set-invalid-byte&host={{host}}"); + assert_equals(await response.text(), "set"); + response = await fetch("idn.py?get&host={{host}}"); + await assert_no_cookie(response); + }, "IDN with invalid UTF-8 bytes in domain attribute"); + </script> +</body> +</html> diff --git a/testing/web-platform/tests/cookies/domain/support/idn.py b/testing/web-platform/tests/cookies/domain/support/idn.py new file mode 100644 index 0000000000..d75ed056f5 --- /dev/null +++ b/testing/web-platform/tests/cookies/domain/support/idn.py @@ -0,0 +1,61 @@ +# élève. +utf8_subdomain = b"Domain=\xC3\xA9\x6C\xC3\xA8\x76\x65." +# élève。 +utf8_dot_subdomain = b"Domain=\xC3\xA9\x6C\xC3\xA8\x76\x65\xE3\x80\x82" +# élève. +punycode_subdomain = b"Domain=xn--lve-6lad." +# ÿlève. +wrong_utf8_subdomain = b"Domain=\xC3\xBF\x6C\xC3\xA8\x76\x65." +# ÿlève. +wrong_punycode_subdomain = b"Domain=xn--lve-6la7i." +# élève with invalid FF byte at the end +invalid_byte_subdomain = b"Domain=\xC3\xA9\x6C\xC3\xA8\x76\x65\xFF." + +def main(request, response): + host = request.GET.get(b"host") + + if b"set-utf8" in request.GET: + response.headers.append(b"Set-Cookie", b"utf8=set;" + utf8_subdomain + host) + response.content = "set" + if b"set-utf8-dot" in request.GET: + response.headers.append(b"Set-Cookie", b"utf8-dot=set;" + utf8_dot_subdomain + host) + response.content = "set" + elif b"set-wrong-utf8" in request.GET: + response.headers.append(b"Set-Cookie", b"wrong-utf8=set;" + wrong_utf8_subdomain + host) + response.content = "set" + elif b"set-punycode" in request.GET: + response.headers.append(b"Set-Cookie", b"punycode=set;" + punycode_subdomain + host) + response.content = "set" + elif b"set-wrong-punycode" in request.GET: + response.headers.append(b"Set-Cookie", b"wrong-punycode=set;" + wrong_punycode_subdomain + host) + response.content = "set" + elif b"set-invalid-byte" in request.GET: + response.headers.append(b"Set-Cookie", b"invalid-byte=set;" + invalid_byte_subdomain + host) + response.content = "set" + + elif b"get" in request.GET: + if b"Cookie" in request.headers: + response.content = request.headers[b"Cookie"] + else: + response.content = "no cookies" + + elif b"delete-utf8" in request.GET: + response.headers.append(b"Set-Cookie", b"utf8=unset;Max-Age=0;" + utf8_subdomain + host) + response.content = "delete" + elif b"delete-utf8-dot" in request.GET: + response.headers.append(b"Set-Cookie", b"utf8-dot=unset;Max-Age=0;" + utf8_dot_subdomain + host) + response.content = "delete" + elif b"delete-wrong-utf8" in request.GET: + response.headers.append(b"Set-Cookie", b"wrong-utf8=unset;Max-Age=0;" + wrong_utf8_subdomain + host) + response.content = "delete" + elif b"delete-punycode" in request.GET: + response.headers.append(b"Set-Cookie", b"punycode=unset;Max-Age=0;" + punycode_subdomain + host) + response.content = "delete" + elif b"delete-wrong-punycode" in request.GET: + response.headers.append(b"Set-Cookie", b"wrong-punycode=unset;Max-Age=0;" + wrong_punycode_subdomain + host) + response.content = "delete" + elif b"delete-invalid-byte" in request.GET: + response.headers.append(b"Set-Cookie", b"invalid-byte=unset;Max-Age=0;" + invalid_byte_subdomain + host) + response.content = "delete" + + response.headers.append(b"Content-Type", b"text/plain") diff --git a/testing/web-platform/tests/cookies/encoding/charset.html b/testing/web-platform/tests/cookies/encoding/charset.html new file mode 100644 index 0000000000..55fcc58aea --- /dev/null +++ b/testing/web-platform/tests/cookies/encoding/charset.html @@ -0,0 +1,55 @@ +<!doctype html> +<html> + <head> + <meta charset=utf-8> + <title>Test utf-8 and ASCII cookie parsing</title> + <meta name=help href="https://tools.ietf.org/html/rfc6265#section-4.1.1"> + <meta name="timeout" content="long"> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <script src="/resources/testdriver.js"></script> + <script src="/resources/testdriver-vendor.js"></script> + <script src="/cookies/resources/cookie-test.js"></script> + </head> + <body> + <div id=log></div> + <script> + const charsetTests = [ + { + cookie: "test=1春节回家路·春运完全手册", + expected: "test=1春节回家路·春运完全手册", + name: "ASCII name and utf-8 value", + }, + { + cookie: "тест=2", + expected: "тест=2", + name: "utf-8 name and ASCII value", + }, + { + cookie: 'test="3春节回家路·春运完全手册"', + expected: 'test="3春节回家路·春运完全手册"', + name: "ASCII name and quoted utf-8 value", + }, + { + cookie: "春节回=4家路·春运完全手册", + expected: "春节回=4家路·春运完全手册", + name: "utf-8 name and value", + }, + { + cookie: '"春节回=5家路·春运完全手册"', + expected: '"春节回=5家路·春运完全手册"', + name: "quoted utf-8 name and value", + }, + { + cookie: "春节回=6家路·春运; 完全手册", + expected: "春节回=6家路·春运", + name: "utf-8 name and value, with (invalid) utf-8 attribute", + }, + ]; + + for (const test of charsetTests) { + httpCookieTest(test.cookie, test.expected, test.name); + } + </script> + </body> +</html>
\ No newline at end of file diff --git a/testing/web-platform/tests/cookies/meta-blocked.html b/testing/web-platform/tests/cookies/meta-blocked.html new file mode 100644 index 0000000000..1b86e65c87 --- /dev/null +++ b/testing/web-platform/tests/cookies/meta-blocked.html @@ -0,0 +1,13 @@ +<!DOCTYPE html> +<head> + <meta http-equiv="set-cookie" content="meta-set-cookie=1"> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> +</head> +<body> + <script> + test(t => { + assert_equals(document.cookie.indexOf('meta-set-cookie'), -1); + }, "Cookie is not set from `<meta>`."); + </script> +</body> diff --git a/testing/web-platform/tests/cookies/name/name-ctl.html b/testing/web-platform/tests/cookies/name/name-ctl.html new file mode 100644 index 0000000000..6ff2305b3a --- /dev/null +++ b/testing/web-platform/tests/cookies/name/name-ctl.html @@ -0,0 +1,63 @@ +<!doctype html> +<html> + <head> + <meta charset=utf-8> + <title>Test cookie name parsing with control characters</title> + <meta name=help href="https://tools.ietf.org/html/rfc6265#section-5.2"> + <meta name="timeout" content="long"> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <script src="/resources/testdriver.js"></script> + <script src="/resources/testdriver-vendor.js"></script> + <script src="/cookies/resources/cookie-test.js"></script> + </head> + <body> + <div id=log></div> + <script> + // Tests for control characters (CTLs) in a cookie's name. + // CTLs are defined by RFC 5234 to be %x00-1F / %x7F. + const CTLS = getCtlCharacters(); + + // All CTLs, with the exception of %x09 (the tab character), should + // cause the cookie to be rejected. + for (const ctl of CTLS) { + if (ctl.code === 0x09) { + domCookieTest( + `test${ctl.code}${ctl.chr}name=${ctl.code}`, + `test${ctl.code}${ctl.chr}name=${ctl.code}`, + `Cookie with %x${ctl.code.toString(16)} in name is accepted (DOM).`); + } else { + domCookieTest( + `test${ctl.code}${ctl.chr}name=${ctl.code}`, + '', + `Cookie with %x${ctl.code.toString(16)} in name is rejected (DOM).`); + } + } + + // Note that per RFC 9110, %x00, %x0A, and %x0D characters in the HTTP + // header MUST either cause the HTTP message to be rejected or be + // replaced with %x20 (space) characters. Both cases will result in a + // passing test here. For more info, see: + // https://www.rfc-editor.org/rfc/rfc9110.html#section-5.5 + for (const ctl of CTLS) { + if (ctl.code === 0x09) { + httpCookieTest( + `test${ctl.code}${ctl.chr}name=${ctl.code}`, + `test${ctl.code}${ctl.chr}name=${ctl.code}`, + `Cookie with %x${ctl.code.toString(16)} in name is accepted (HTTP).`); + } else if (ctl.code === 0x00 || ctl.code === 0x0A || ctl.code === 0x0D) { + httpCookieTest( + `test${ctl.code}${ctl.chr}name=${ctl.code}`, + `test${ctl.code} name=${ctl.code}`, + `Cookie with %x${ctl.code.toString(16)} in name is rejected or modified (HTTP).`, + /* defaultPath */ true, /* allowFetchFailure */ true); + } else { + httpCookieTest( + `test${ctl.code}${ctl.chr}name=${ctl.code}`, + '', + `Cookie with %x${ctl.code.toString(16)} in name is rejected (HTTP).`); + } + } + </script> + </body> +</html> diff --git a/testing/web-platform/tests/cookies/name/name.html b/testing/web-platform/tests/cookies/name/name.html new file mode 100644 index 0000000000..d7fe05560e --- /dev/null +++ b/testing/web-platform/tests/cookies/name/name.html @@ -0,0 +1,169 @@ +<!doctype html> +<html> + <head> + <meta charset=utf-8> + <title>Test cookie name parsing</title> + <meta name=help href="https://tools.ietf.org/html/rfc6265#section-5.2"> + <meta name="timeout" content="long"> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <script src="/resources/testdriver.js"></script> + <script src="/resources/testdriver-vendor.js"></script> + <script src="/cookies/resources/cookie-test.js"></script> + </head> + <body> + <div id=log></div> + <script> + const nameTests = [ + { + cookie: "test1=; path = /", + expected: "test1=", + name: "Set valueless cookie to its name with empty value", + defaultPath: false, + }, + { + cookie: "=test=2", + expected: "test=2", + name: "Set a nameless cookie (that has an = in its value)", + }, + { + cookie: "===test=2b", + expected: "==test=2b", + name: "Set a nameless cookie (that has multiple ='s in its value)", + }, + { + cookie: "=test2c", + expected: "test2c", + name: "Set a nameless cookie", + }, + { + cookie: "test =3", + expected: "test=3", + name: "Remove trailing WSP characters from the name string", + }, + { + cookie: " test=4", + expected: "test=4", + name: "Remove leading WSP characters from the name string", + }, + { + cookie: ['"test=5"=test', '"test=5'], + expected: '"test=5', + name: "Only return the new cookie (with the same name)", + }, + { + cookie: "test6;cool=dude", + expected: "test6", + name: "Ignore invalid attributes after nameless cookie", + }, + { + cookie: "$Version=1; test=7", + expected: "$Version=1", + name: "Ignore invalid attributes after valid name (that looks like Cookie2 Version attribute)", + }, + { + cookie: "test test=8", + expected: "test test=8", + name: "Set a cookie that has whitespace in its name", + }, + { + cookie: '"test9;test"=9', + expected: '"test9', + name: "Set a nameless cookie ignoring characters after first ;", + }, + { + cookie: '"test\"10;baz"=qux', + expected: '"test\"10', + name: "Set a nameless cookie ignoring characters after first ; (2)", + }, + { + cookie: ["=test=11", "test11"], + expected: "test11", + name: "Return the most recent nameless cookie", + }, + { + cookie: ["test11", "test11a"], + expected: "test11a", + name: "Return the most recent nameless cookie, without leading =", + }, + { + cookie: ["test11", "test11a", "=test11b"], + expected: "test11b", + name: "Return the most recent nameless cookie, even if preceded by =", + }, + { + cookie: ["test11", "test11a", "=test11b", "test=11c"], + expected: "test11b; test=11c", + name: "Return the most recent nameless cookie, even if preceded by =, in addition to other valid cookie", + }, + { + cookie: ["test12=11", "test12=12"], + expected: "test12=12", + name: "Use last value for cookies with identical names", + }, + { + cookie: ["testA=13", "testB=13"], + expected: "testA=13; testB=13", + name: "Keep first-in, first-out name order", + }, + { + cookie: ["a=test14", "z=test14"], + expected: "a=test14; z=test14", + name: "Keep first-in, first-out single-char name order", + }, + { + cookie: ["z=test15", "a=test15"], + expected: "z=test15; a=test15", + name: "Keep non-alphabetic first-in, first-out name order", + }, + { + cookie: "z=test16, a=test16", + expected: "z=test16, a=test16", + name: "Keep first-in, first-out order if comma-separated", + }, + { + cookie: ["testA=16", "=test16", "testB=16"], + expected: "testA=16; test16; testB=16", + name: "Set nameless cookie, given `Set-Cookie: =test16`", + }, + { + cookie: ["test17a", "test17b"], + expected: "test17b", + name: "Overwrite nameless cookie", + }, + { + cookie: ["=__Secure-abc=123", "=__Host-abc=123", "=__SeCuRe-abc=123", "=__HoSt-abc=123", "__Secure-abc", "__Host-abc", "__SeCuRe-abc", "__HoSt-abc"], + expected: "", + name: "Ignore nameless cookies that impersonate cookie prefixes", + }, + { + cookie: "=", + expected: "", + name: "Ignore cookie with empty name and empty value", + }, + { + cookie: "", + expected: "", + name: "Ignore cookie with no name or value", + }, + { + cookie: "%74%65%73%74=20", + expected: "%74%65%73%74=20", + name: "URL-encoded cookie name is not decoded", + }, + ]; + + for (const test of nameTests) { + httpCookieTest(test.cookie, test.expected, test.name); + } + + for (const name of ["a", "1", "$", "!a", "@a", "#a", "$a", "%a", + "^a", "&a", "*a", "(a", ")a", "-a", "_a", "+", + '"a', '"a=b"' + ]) { + const cookie = `${name}=test`; + httpCookieTest(cookie, cookie, `Name is set as expected for ${name}=test`); + } + </script> + </body> +</html> diff --git a/testing/web-platform/tests/cookies/navigated-away.html b/testing/web-platform/tests/cookies/navigated-away.html new file mode 100644 index 0000000000..bd89142d32 --- /dev/null +++ b/testing/web-platform/tests/cookies/navigated-away.html @@ -0,0 +1,39 @@ +<!DOCTYPE html> +<head> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> +</head> +<body> + <iframe id="if" src="about:blank"></iframe> + <script> + var t = async_test("document.cookie behavior on documents without browser context"); + t.add_cleanup(function() { + document.cookie = "nav_away_test=yes;max-age=0"; + }); + + function step2() { + t.step(function() { + // Get from saved doc should fail. + assert_equals(window.iframeDoc.cookie, ""); + + // Try set from saved doc, should do nothing. + window.iframeDoc.cookie = "nav_away_test=second"; + assert_equals(window.iframeDoc.cookie, ""); + assert_not_equals(document.cookie.indexOf("nav_away_test=yes"), -1); + }); + t.done(); + } + + t.step(function() { + document.cookie = "nav_away_test=yes"; + var iframe = document.getElementById("if"); + // Save original document. + window.iframeDoc = iframe.contentDocument; + assert_not_equals(window.iframeDoc.cookie.indexOf("nav_away_test=yes"), -1); + + // Navigate away. + iframe.onload = step2; + iframe.contentWindow.location = "/common/blank.html"; + }) + </script> +</body> diff --git a/testing/web-platform/tests/cookies/ordering/ordering.sub.html b/testing/web-platform/tests/cookies/ordering/ordering.sub.html new file mode 100644 index 0000000000..2c4db4e20f --- /dev/null +++ b/testing/web-platform/tests/cookies/ordering/ordering.sub.html @@ -0,0 +1,25 @@ +<!doctype html> +<html> + <head> + <meta charset=utf-8> + <title>Test cookie ordering</title> + <meta name=help href="https://tools.ietf.org/html/draft-ietf-httpbis-rfc6265bis-07#section-5.5"> + <meta name="timeout" content="long"> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <script src="/resources/testdriver.js"></script> + <script src="/resources/testdriver-vendor.js"></script> + </head> + </head> + <body> + <script> + const port = "{{ports[http][0]}}"; + const wwwHost = "{{domains[www]}}"; + + test(t => { + const win = window.open(`http://${wwwHost}:${port}/cookies/ordering/resources/ordering-child.sub.html`); + fetch_tests_from_window(win); + }); + </script> + </body> +</html> diff --git a/testing/web-platform/tests/cookies/ordering/resources/ordering-child.sub.html b/testing/web-platform/tests/cookies/ordering/resources/ordering-child.sub.html new file mode 100644 index 0000000000..b40ec64814 --- /dev/null +++ b/testing/web-platform/tests/cookies/ordering/resources/ordering-child.sub.html @@ -0,0 +1,76 @@ +<!doctype html> +<html> + <head> + <meta charset=utf-8> + <title>Test cookie ordering</title> + <meta name=help href="https://tools.ietf.org/html/draft-ietf-httpbis-rfc6265bis-07#section-5.5"> + <meta name="timeout" content="long"> + <script src="/resources/testharness.js"></script> + <script src="/resources/testdriver.js"></script> + <script src="/resources/testdriver-vendor.js"></script> + <script src="/cookies/resources/cookie-test.js"></script> + </head> + <body> + <div id=log></div> + <script> + const host = "{{host}}"; + const wwwHost = "{{domains[www]}}"; + + const orderingTests = [ + { + cookie: [ + "testA=1", + "testB=1; path=/cookies", + "testC=1; path=/", + "testD=1; path=/cooking", + `testE=1; domain=.${host}; path=/`, + `testF=1; domain=.${host}; path=/cookies/attributes`, + ], + expected: "testF=1; testB=1; testC=1; testE=1", + name: "Cookies with longer path attribute values are ordered before shorter ones", + location: "/cookies/attributes/resources/path/one.html", + }, + { + cookie: [ + "testA=2", + "testB=2; path=/cookies/attributes/resources", + "testC=2; path=/", + "testD=2; path=/cooking", + `testE=2; domain=.${host}`, + `testF=2; domain=.${host}; path=/cookies/attributes`, + `testG=2; domain=.${host}; path=/cookies/attributes/resources/path`, + "testH=2; path=/cookies", + ], + expected: "testG=2; testB=2; testF=2; testH=2; testC=2", + name: "Cookies with longer path attribute values are ordered before shorter ones (2)", + location: "/cookies/attributes/resources/path/one.html", + }, + { + cookie: [ + "testA=3; path=/cookies/attributes/resources/path", + "testB=3; path=/cookies/attributes/resources/path/one.html", + "testC=3; path=/cookies/attributes", + ], + expected: "testB=3; testA=3; testC=3", + name: "Cookies with longer paths are listed before cookies with shorter paths", + location: "/cookies/attributes/resources/path/one.html", + }, + { + cookie: [ + "testZ=4; path=/cookies", + "testB=4; path=/cookies/attributes/resources/path", + "testA=4; path=/cookies", + ], + expected: "testB=4; testZ=4; testA=4", + name: "For equal length paths, list the cookie with an earlier creation time first", + location: "/cookies/attributes/resources/path/one.html", + }, + ]; + + for (const test of orderingTests) { + httpRedirectCookieTest(test.cookie, test.expected, test.name, + test.location); + } + </script> + </body> +</html>
\ No newline at end of file diff --git a/testing/web-platform/tests/cookies/partitioned-cookies/partitioned-cookies.tentative.https.html b/testing/web-platform/tests/cookies/partitioned-cookies/partitioned-cookies.tentative.https.html new file mode 100644 index 0000000000..deab669101 --- /dev/null +++ b/testing/web-platform/tests/cookies/partitioned-cookies/partitioned-cookies.tentative.https.html @@ -0,0 +1,72 @@ +<!DOCTYPE html> +<meta charset="utf-8"/> +<meta name="timeout" content="long"> +<meta name="help" href="https://github.com/WICG/CHIPS#chips-cookies-having-independent-partitioned-state"> +<title>Test partitioned cookies</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/get-host-info.sub.js"></script> +<script src="/cookies/resources/cookie-helper.sub.js"></script> +<script src="/cookies/partitioned-cookies/resources/test-helpers.js"></script> + +<body> +<script> + +document.body.onload = async () => { + // First, the test sets a SameSite=None;Partitioned; cookie. + const attributes = "Secure;Path=/;SameSite=None;Partitioned"; + const httpCookieName = "__Host-pchttp"; + await credFetch( + `${self.origin}/cookies/resources/set.py?${httpCookieName}=foobar;${ + attributes}`); + + // Set another partitioned cookie using document.cookie. + const domCookieName = "__Host-pcdom"; + document.cookie = `${domCookieName}=foobar;${attributes}`; + + // Set another partitioned cookie using the CookieStore API, if supported. + if (window.cookieStore) { + const cookieStoreCookieName = "__Host-pccookiestore"; + await cookieStore.set({ + name: cookieStoreCookieName, + value: "foobar", + path: "/", + sameSite: "none", + partitioned: true, + }); + } + + const cookieNames = getCookieNames(); + + // Verify that the cookies are sent in requests from this top-level site. + testHttpPartitionedCookies({ + origin: self.origin, + cookieNames, + expectsCookie: true, + }); + + // Verify that the cookies are exposed to the DOM on this top-level site. + testDomPartitionedCookies({ + cookieNames, + expectsCookie: true, + }); + testCookieStorePartitionedCookies({ + cookieNames, + expectsCookie: true, + }); + + // Open a cross-site window which will make a request to this window's origin. + // If partitioned cookies are disabled, then the cookies set above will still + // be accessible. + // If partitioned cookies are enabled, then the cookies should not be + // accessible to their origin in a window with a different top-level site. + const crossSiteUrl = new URL( + `./resources/partitioned-cookies-cross-site-window.html?origin=${ + encodeURIComponent(self.origin)}`, + get_host_info().HTTPS_NOTSAMESITE_ORIGIN + self.location.pathname); + const popup = window.open(crossSiteUrl); + fetch_tests_from_window(popup); +}; + +</script> +</body> diff --git a/testing/web-platform/tests/cookies/partitioned-cookies/resources/partitioned-cookies-cross-site-embed.html b/testing/web-platform/tests/cookies/partitioned-cookies/resources/partitioned-cookies-cross-site-embed.html new file mode 100644 index 0000000000..05a99626dc --- /dev/null +++ b/testing/web-platform/tests/cookies/partitioned-cookies/resources/partitioned-cookies-cross-site-embed.html @@ -0,0 +1,26 @@ +<!DOCTYPE html> +<meta charset="utf-8"/> +<meta name="timeout" content="long"> +<title>Test site embedded in a cross-site context</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/get-host-info.sub.js"></script> +<script src="/cookies/resources/cookie-helper.sub.js"></script> +<script src="/cookies/partitioned-cookies/resources/test-helpers.js"></script> +<body> +<script> + +const cookieNames = getCookieNames(); + +testDomPartitionedCookies({ + cookieNames, + expectsCookie: false, +}); + +testCookieStorePartitionedCookies({ + cookieNames, + expectsCookie: false, +}); + +</script> +</body> diff --git a/testing/web-platform/tests/cookies/partitioned-cookies/resources/partitioned-cookies-cross-site-window.html b/testing/web-platform/tests/cookies/partitioned-cookies/resources/partitioned-cookies-cross-site-window.html new file mode 100644 index 0000000000..ca1a27c8a0 --- /dev/null +++ b/testing/web-platform/tests/cookies/partitioned-cookies/resources/partitioned-cookies-cross-site-window.html @@ -0,0 +1,43 @@ +<!DOCTYPE html> +<meta charset="utf-8"/> +<meta name="timeout" content="long"> +<title>Cross-site window</title> +<script src="/resources/testharness.js"></script> +<script src="/common/get-host-info.sub.js"></script> +<script src="/cookies/resources/cookie-helper.sub.js"></script> +<script src="/cookies/partitioned-cookies/resources/test-helpers.js"></script> + +<body> +<script> + +let origin; + +// Test that parent window passed its origin in the URL parameters correctly. +test(() => { + assert_true(window.location.search.startsWith("?origin=")); + origin = decodeURIComponent(window.location.search.slice( + window.location.search.indexOf("?origin=") + 8)); +}, "Cross-site window opened correctly"); + +// Test that the request to the parent window's origin does not contain the +// partitioned cookie. +testHttpPartitionedCookies({ + origin, + cookieNames: getCookieNames(), + expectsCookie: false, +}); + +// Create a cross-site <iframe> which embeds the cookies' origin into this +// page. +const iframe = document.createElement("iframe"); +const url = new URL( + "/cookies/partitioned-cookies/resources/" + + "partitioned-cookies-cross-site-embed.html", + origin); +iframe.src = String(url); +document.body.appendChild(iframe); + +fetch_tests_from_window(iframe.contentWindow); + +</script> +</body> diff --git a/testing/web-platform/tests/cookies/partitioned-cookies/resources/test-helpers.js b/testing/web-platform/tests/cookies/partitioned-cookies/resources/test-helpers.js new file mode 100644 index 0000000000..0ecaa63c39 --- /dev/null +++ b/testing/web-platform/tests/cookies/partitioned-cookies/resources/test-helpers.js @@ -0,0 +1,64 @@ +// Test that a partitioned cookie set by |origin| with name |cookieName| is +// or is not sent in a request to |origin|. +// +// If |expectsCookie| is true, then the test cookie should be present in the +// request. +function testHttpPartitionedCookies({origin, cookieNames, expectsCookie}) { + promise_test(async () => { + const resp = await credFetch(`${origin}/cookies/resources/list.py`); + const cookies = await resp.json(); + for (const cookieName of cookieNames) { + assert_equals( + cookies.hasOwnProperty(cookieName), expectsCookie, + getPartitionedCookieAssertDesc(expectsCookie, cookieName)); + } + }, getPartitionedCookieTestName(expectsCookie, "HTTP")); +} + +function getPartitionedCookieTestName(expectsCookie, cookieType) { + if (expectsCookie) { + return "Partitioned cookies accessible on the top-level site they are " + + `created in via ${cookieType}`; + } + return "Partitioned cookies are not accessible on a different top-level " + + `site via ${cookieType}`; +} + +function getPartitionedCookieAssertDesc(expectsCookie, cookieName) { + if (expectsCookie) { + return `Expected ${cookieName} to be available on the top-level site it ` + + "was created in"; + } + return `Expected ${cookieName} to not be available on a different ` + + "top-level site"; +} + +function testDomPartitionedCookies({cookieNames, expectsCookie}) { + test(() => { + for (const cookieName of cookieNames) { + assert_equals( + document.cookie.includes(cookieName), expectsCookie, + getPartitionedCookieAssertDesc(expectsCookie, cookieName)); + } + }, getPartitionedCookieTestName(expectsCookie, "DOM")); +} + +function testCookieStorePartitionedCookies({cookieNames, expectsCookie}) { + if (!window.cookieStore) return; + promise_test(async () => { + const cookies = await cookieStore.getAll({partitioned: true}); + for (const cookieName of cookieNames) { + assert_equals( + !!cookies.find(c => c.name === cookieName), expectsCookie, + getPartitionedCookieAssertDesc(expectsCookie, cookieName)); + } + }, getPartitionedCookieTestName(expectsCookie, "CookieStore")); +} + +function getCookieNames() { + const cookieNames = ["__Host-pchttp", "__Host-pcdom"]; + if (window.cookieStore) { + cookieNames.push("__Host-pccookiestore"); + } + return cookieNames; +} diff --git a/testing/web-platform/tests/cookies/path/default.html b/testing/web-platform/tests/cookies/path/default.html new file mode 100644 index 0000000000..dbe99a7ee5 --- /dev/null +++ b/testing/web-platform/tests/cookies/path/default.html @@ -0,0 +1,51 @@ +<!doctype html> +<html> +<head> + <meta charset=utf-8> + <title>Test for default cookie path</title> + <meta name=help href="http://tools.ietf.org/html/rfc6265#section-5.1.4"> + + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <script src="/resources/testdriver.js"></script> + <script src="/resources/testdriver-vendor.js"></script> +</head> +<body> +<div id=log></div> + +<script> +var body = document.getElementsByTagName('body')[0]; +var createIframe = function (src, done) { + var iframe = document.createElement('iframe'); + iframe.src = src; + body.appendChild(iframe); + iframe.onload = function () { + done(iframe); + }; +}; + +async_test(function (t) { + var iframe; + var verify = t.step_func(function () { + assert_true( + !!iframe.contentWindow.isCookieSet('cookies-path-default'), + 'cookie can be retrieved from expected path' + ); + iframe.contentWindow.expireCookies().then(t.step_func(function () { + assert_false( + !!iframe.contentWindow.isCookieSet('cookies-path-default'), + 'cookie can be referenced using the expected path' + ); + t.done(); + })); + }); + + createIframe('/cookies/resources/echo-cookie.html', t.step_func(function (_iframe) { + iframe = _iframe; + + createIframe('/cookies/resources/set.py?cookies-path-default=1', verify); + })); +}); +</script> +</body> +</html> diff --git a/testing/web-platform/tests/cookies/path/match.html b/testing/web-platform/tests/cookies/path/match.html new file mode 100644 index 0000000000..d517836e4d --- /dev/null +++ b/testing/web-platform/tests/cookies/path/match.html @@ -0,0 +1,113 @@ +<!doctype html> +<html> +<head> + <meta charset=utf-8> + <title>tests for matching cookie paths</title> + <meta name="timeout" content="long"> + <meta name=help href="http://tools.ietf.org/html/rfc6265#section-5.1.4"> + + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <script src="/resources/testdriver.js"></script> + <script src="/resources/testdriver-vendor.js"></script> + <script src="/cookies/resources/testharness-helpers.js"></script> +</head> +<body> +<div id=log></div> + +<script> +var body = document.getElementsByTagName('body')[0]; +var createIframeThen = function (callback) { + var iframe = document.createElement('iframe'); + iframe.src = "/cookies/resources/echo-cookie.html"; + body.appendChild(iframe); + iframe.onload = callback; + return iframe; +}; +var testCookiePathFromDOM = function (testCase, test) { + var iframe = createIframeThen(test.step_func(function () { + iframe.contentWindow.setCookie('dom-' + testCase.name, testCase.path); + var cookieSet = iframe.contentWindow.isCookieSet('dom-' + testCase.name, testCase.path); + if (testCase.match === false) { + assert_equals(cookieSet, null); + } else { + assert_not_equals(cookieSet, null, "Cookie path from DOM should not be `null`"); + } + iframe.contentWindow.expireCookies().then(() => test.done()); + })); +}; +var testCookiePathFromHeader = function (testCase, test) { + var iframe = createIframeThen(test.step_func(function () { + iframe.contentWindow.fetchCookieThen('header-' + testCase.name, testCase.path).then(test.step_func(function (response) { + assert_true(response.ok); + var cookieSet = iframe.contentWindow.isCookieSet('header-' + testCase.name, testCase.path); + iframe.contentWindow.expireCookies().then(test.step_func(function () { + if (testCase.match === false) { + assert_equals(cookieSet, null); + } else { + assert_not_equals(cookieSet, null, "Cookie path from header should not be `null`"); + } + test.done(); + })); + })).catch(test.unreached_func()); + })); +}; + +var tests = [{ + "name": "match-slash", + "path": "/", +}, { + "name": "match-page", + "path": "match.html", +}, { + "name": "match-prefix", + "path": "cookies", +}, { + "name": "match-slash-prefix", + "path": "/cookies", +}, { + "name": "match-slash-prefix-slash", + "path": "/cookies/", +}, { + "name": "match-exact-page", + "path": "/cookies/resources/echo-cookie.html", +}, { + "name": "no-match", + "path": "/cook", + "match": false +}, { + "name": "no-match-subpath", + "path": "/w/", + "match": false +}]; + +var domTests = tests.map(function (testCase) { + var testName = "`document.cookie` on /cookies/resources/echo-cookie.html sets cookie with path: " + testCase.path; + if (testCase.match === false) { + testName = "`document.cookie` on /cookies/resources/echo-cookie.html DOES NOT set cookie for path: " + testCase.path; + } + return [ + testName, + function () { + testCookiePathFromDOM(testCase, this); + } + ]; +}); + +var headerTests = tests.map(function (testCase) { + var testName = "`Set-Cookie` on /cookies/resources/echo-cookie.html sets cookie with path: " + testCase.path; + if (testCase.match === false) { + testName = "`Set-Cookie` on /cookies/resources/echo-cookie.html DOES NOT set cookie for path: " + testCase.path; + } + return [ + testName, + function () { + testCookiePathFromHeader(testCase, this); + } + ]; +}); + +executeTestsSerially(domTests.concat(headerTests)); +</script> +</body> +</html> diff --git a/testing/web-platform/tests/cookies/prefix/__host.document-cookie.html b/testing/web-platform/tests/cookies/prefix/__host.document-cookie.html new file mode 100644 index 0000000000..2aeb0901f6 --- /dev/null +++ b/testing/web-platform/tests/cookies/prefix/__host.document-cookie.html @@ -0,0 +1,57 @@ +<!DOCTYPE html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/cookies/resources/cookie-helper.sub.js"></script> +<script> + ["", "domain="+document.location.hostname, "MaxAge=10", "HttpOnly"].forEach(extraParams => { + // Without 'secure' + set_prefixed_cookie_via_dom_test({ + prefix: "__Host-", + params: "Path=/;" + extraParams, + shouldExistInDOM: false, + shouldExistViaHTTP: false, + title: "__Host: Non-secure origin: 'Path=/;" + extraParams + "'" + }); + + set_prefixed_cookie_via_dom_test({ + prefix: "__HoSt-", + params: "Path=/;" + extraParams, + shouldExistInDOM: false, + shouldExistViaHTTP: false, + title: "__HoSt: Non-secure origin: 'Path=/;" + extraParams + "'" + }); + + // With 'secure' + set_prefixed_cookie_via_dom_test({ + prefix: "__Host-", + params: "Secure; Path=/;" + extraParams, + shouldExistInDOM: false, + shouldExistViaHTTP: false, + title: "__Host: Non-secure origin: 'Secure; Path=/;" + extraParams + "'" + }); + + set_prefixed_cookie_via_dom_test({ + prefix: "__HoSt-", + params: "Secure; Path=/;" + extraParams, + shouldExistInDOM: false, + shouldExistViaHTTP: false, + title: "__HoSt: Non-secure origin: 'Secure; Path=/;" + extraParams + "'" + }); + }); + + set_prefixed_cookie_via_dom_test({ + prefix: "__Host-", + params: "Secure; Path=/cookies/resources/list.py", + shouldExistInDOM: false, + shouldExistViaHTTP: false, + title: "__Host: Non-secure origin: 'Secure; Path=/cookies/resources/list.py'" + }); + + set_prefixed_cookie_via_dom_test({ + prefix: "__HoSt-", + params: "Secure; Path=/cookies/resources/list.py", + shouldExistInDOM: false, + shouldExistViaHTTP: false, + title: "__HoSt: Non-secure origin: 'Secure; Path=/cookies/resources/list.py'" + }); +</script> diff --git a/testing/web-platform/tests/cookies/prefix/__host.document-cookie.https.html b/testing/web-platform/tests/cookies/prefix/__host.document-cookie.https.html new file mode 100644 index 0000000000..cc0cb34ecb --- /dev/null +++ b/testing/web-platform/tests/cookies/prefix/__host.document-cookie.https.html @@ -0,0 +1,74 @@ +<!DOCTYPE html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/cookies/resources/cookie-helper.sub.js"></script> +<script> + ["", "MaxAge=10"].forEach(extraParams => { + // Without 'secure' + set_prefixed_cookie_via_dom_test({ + prefix: "__Host-", + params: "Path=/;" + extraParams, + shouldExistInDOM: false, + shouldExistViaHTTP: false, + title: "__Host: Secure origin: Does not set 'Path=/;" + extraParams + "'" + }); + + set_prefixed_cookie_via_dom_test({ + prefix: "__HoSt-", + params: "Path=/;" + extraParams, + shouldExistInDOM: false, + shouldExistViaHTTP: false, + title: "__HoSt: Secure origin: Does not set 'Path=/;" + extraParams + "'" + }); + + // With 'secure' + set_prefixed_cookie_via_dom_test({ + prefix: "__Host-", + params: "Secure; Path=/;" + extraParams, + shouldExistInDOM: true, + shouldExistViaHTTP: true, + title: "__Host: Secure origin: Does set 'Secure; Path=/;" + extraParams + "'" + }); + + set_prefixed_cookie_via_dom_test({ + prefix: "__HoSt-", + params: "Secure; Path=/;" + extraParams, + shouldExistInDOM: true, + shouldExistViaHTTP: true, + title: "__HoSt: Secure origin: Does set 'Secure; Path=/;" + extraParams + "'" + }); + + // With 'domain' + set_prefixed_cookie_via_dom_test({ + prefix: "__Host-", + params: "Secure; Path=/; Domain=" + document.location.hostname + "; " + extraParams, + shouldExistInDOM: false, + shouldExistViaHTTP: false, + title: "__Host: Secure origin: Does not set 'Secure; Path=/; Domain=" + document.location.hostname + "; " + extraParams + "'" + }); + + set_prefixed_cookie_via_dom_test({ + prefix: "__HoSt-", + params: "Secure; Path=/; Domain=" + document.location.hostname + "; " + extraParams, + shouldExistInDOM: false, + shouldExistViaHTTP: false, + title: "__HoSt: Secure origin: Does not set 'Secure; Path=/; Domain=" + document.location.hostname + "; " + extraParams + "'" + }); + }); + + set_prefixed_cookie_via_dom_test({ + prefix: "__Host-", + params: "Secure; Path=/cookies/resources/list.py", + shouldExistInDOM: false, + shouldExistViaHTTP: false, + title: "__Host: Secure origin: Does not set 'Secure; Path=/cookies/resources/list.py'" + }); + + set_prefixed_cookie_via_dom_test({ + prefix: "__HoSt-", + params: "Secure; Path=/cookies/resources/list.py", + shouldExistInDOM: false, + shouldExistViaHTTP: false, + title: "__HoSt: Secure origin: Does not set 'Secure; Path=/cookies/resources/list.py'" + }); +</script> diff --git a/testing/web-platform/tests/cookies/prefix/__host.header.html b/testing/web-platform/tests/cookies/prefix/__host.header.html new file mode 100644 index 0000000000..7ad6782584 --- /dev/null +++ b/testing/web-platform/tests/cookies/prefix/__host.header.html @@ -0,0 +1,83 @@ +<!DOCTYPE html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/cookies/resources/cookie-helper.sub.js"></script> +<script> + ["", "domain="+document.location.hostname, "MaxAge=10", "HttpOnly"].forEach(extraParams => { + // Without 'secure' + set_prefixed_cookie_via_http_test({ + prefix: "__Host-", + params: "Path=/;" + extraParams, + origin: self.origin, + shouldExistInDOM: false, + shouldExistViaHTTP: false, + title: "__Host: Non-secure origin: Does not set 'Path=/;" + extraParams + "'" + }); + + set_prefixed_cookie_via_http_test({ + prefix: "__HoSt-", + params: "Path=/;" + extraParams, + origin: self.origin, + shouldExistInDOM: false, + shouldExistViaHTTP: false, + title: "__HoSt: Non-secure origin: Does not set 'Path=/;" + extraParams + "'" + }); + + // With 'secure' + set_prefixed_cookie_via_http_test({ + prefix: "__Host-", + params: "Secure; Path=/;" + extraParams, + origin: self.origin, + shouldExistInDOM: false, + shouldExistViaHTTP: false, + title: "__Host: Non-secure origin: Does not set 'Secure; Path=/;" + extraParams + "'" + }); + + set_prefixed_cookie_via_http_test({ + prefix: "__HoSt-", + params: "Secure; Path=/;" + extraParams, + origin: self.origin, + shouldExistInDOM: false, + shouldExistViaHTTP: false, + title: "__HoSt: Non-secure origin: Does not set 'Secure; Path=/;" + extraParams + "'" + }); + + // With 'domain' + set_prefixed_cookie_via_http_test({ + prefix: "__Host-", + params: "Secure; Path=/; Domain=" + document.location.hostname + "; " + extraParams, + origin: self.origin, + shouldExistInDOM: false, + shouldExistViaHTTP: false, + title: "__Host: Secure origin: Does not set 'Secure; Path=/; Domain=" + document.location.hostname + "; " + extraParams + "'" + }); + + set_prefixed_cookie_via_http_test({ + prefix: "__HoSt-", + params: "Secure; Path=/; Domain=" + document.location.hostname + "; " + extraParams, + origin: self.origin, + shouldExistInDOM: false, + shouldExistViaHTTP: false, + title: "__HoSt: Secure origin: Does not set 'Secure; Path=/; Domain=" + document.location.hostname + "; " + extraParams + "'" + }); + }); + + set_prefixed_cookie_via_http_test({ + prefix: "__Host-", + params: "Secure; Path=/cookies/resources/list.py", + origin: self.origin, + shouldExistInDOM: false, + shouldExistViaHTTP: false, + title: "__Host: Non-secure origin: Does not set 'Secure; Path=/cookies/resources/list.py'" + }); + + set_prefixed_cookie_via_http_test({ + prefix: "__HoSt-", + params: "Secure; Path=/cookies/resources/list.py", + origin: self.origin, + shouldExistInDOM: false, + shouldExistViaHTTP: false, + title: "__HoSt: Non-secure origin: Does not set 'Secure; Path=/cookies/resources/list.py'" + }); +</script> + diff --git a/testing/web-platform/tests/cookies/prefix/__host.header.https.html b/testing/web-platform/tests/cookies/prefix/__host.header.https.html new file mode 100644 index 0000000000..b7f1365276 --- /dev/null +++ b/testing/web-platform/tests/cookies/prefix/__host.header.https.html @@ -0,0 +1,83 @@ +<!DOCTYPE html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/cookies/resources/cookie-helper.sub.js"></script> +<script> + ["", "MaxAge=10", "HttpOnly"].forEach(extraParams => { + // Without 'secure' + set_prefixed_cookie_via_http_test({ + prefix: "__Host-", + params: "Path=/;" + extraParams, + origin: self.origin, + shouldExistInDOM: false, + shouldExistViaHTTP: false, + title: "__Host: Secure origin: Does not set 'Path=/;" + extraParams + "'" + }); + + set_prefixed_cookie_via_http_test({ + prefix: "__HoSt-", + params: "Path=/;" + extraParams, + origin: self.origin, + shouldExistInDOM: false, + shouldExistViaHTTP: false, + title: "__HoSt: Secure origin: Does not set 'Path=/;" + extraParams + "'" + }); + + // With 'secure' + set_prefixed_cookie_via_http_test({ + prefix: "__Host-", + params: "Secure; Path=/;" + extraParams, + origin: self.origin, + shouldExistInDOM: true, + shouldExistViaHTTP: true, + title: "__Host: Secure origin: Does set 'Secure; Path=/;" + extraParams + "'" + }); + + set_prefixed_cookie_via_http_test({ + prefix: "__HoSt-", + params: "Secure; Path=/;" + extraParams, + origin: self.origin, + shouldExistInDOM: true, + shouldExistViaHTTP: true, + title: "__HoSt: Secure origin: Does set 'Secure; Path=/;" + extraParams + "'" + }); + + // With 'domain' + set_prefixed_cookie_via_http_test({ + prefix: "__Host-", + params: "Secure; Path=/; Domain=" + document.location.hostname + "; " + extraParams, + origin: self.origin, + shouldExistInDOM: false, + shouldExistViaHTTP: false, + title: "__Host: Secure origin: Does not set 'Secure; Path=/; Domain=" + document.location.hostname + "; " + extraParams + "'" + }); + + set_prefixed_cookie_via_http_test({ + prefix: "__HoSt-", + params: "Secure; Path=/; Domain=" + document.location.hostname + "; " + extraParams, + origin: self.origin, + shouldExistInDOM: false, + shouldExistViaHTTP: false, + title: "__HoSt: Secure origin: Does not set 'Secure; Path=/; Domain=" + document.location.hostname + "; " + extraParams + "'" + }); + }); + + set_prefixed_cookie_via_http_test({ + prefix: "__Host-", + params: "Secure; Path=/cookies/resources/list.py", + origin: self.origin, + shouldExistInDOM: false, + shouldExistViaHTTP: false, + title: "__Host: Secure origin: Does not set 'Secure; Path=/cookies/resources/list.py'" + }); + + set_prefixed_cookie_via_http_test({ + prefix: "__HoSt-", + params: "Secure; Path=/cookies/resources/list.py", + origin: self.origin, + shouldExistInDOM: false, + shouldExistViaHTTP: false, + title: "__HoSt: Secure origin: Does not set 'Secure; Path=/cookies/resources/list.py'" + }); +</script> + diff --git a/testing/web-platform/tests/cookies/prefix/__secure.document-cookie.html b/testing/web-platform/tests/cookies/prefix/__secure.document-cookie.html new file mode 100644 index 0000000000..2246ffff4c --- /dev/null +++ b/testing/web-platform/tests/cookies/prefix/__secure.document-cookie.html @@ -0,0 +1,41 @@ +<!DOCTYPE html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/cookies/resources/cookie-helper.sub.js"></script> +<script> + ["", "MaxAge=10", "domain="+document.location.hostname].forEach(extraParams => { + // Without 'secure' + set_prefixed_cookie_via_dom_test({ + prefix: "__Secure-", + params: "Path=/;" + extraParams, + shouldExistInDOM: false, + shouldExistViaHTTP: false, + title: "__Secure: Non-secure origin: Should not set 'Path=/;" + extraParams + "'" + }); + + set_prefixed_cookie_via_dom_test({ + prefix: "__SeCuRe-", + params: "Path=/;" + extraParams, + shouldExistInDOM: false, + shouldExistViaHTTP: false, + title: "__SeCuRe: Non-secure origin: Should not set 'Path=/;" + extraParams + "'" + }); + + // With 'secure' + set_prefixed_cookie_via_dom_test({ + prefix: "__Secure-", + params: "Secure; Path=/;" + extraParams, + shouldExistInDOM: false, + shouldExistViaHTTP: false, + title: "__Secure: Non-secure origin: Should not set 'Secure; Path=/;" + extraParams + "'" + }); + + set_prefixed_cookie_via_dom_test({ + prefix: "__SeCuRe-", + params: "Secure; Path=/;" + extraParams, + shouldExistInDOM: false, + shouldExistViaHTTP: false, + title: "__SeCuRe: Non-secure origin: Should not set 'Secure; Path=/;" + extraParams + "'" + }); + }); +</script> diff --git a/testing/web-platform/tests/cookies/prefix/__secure.document-cookie.https.html b/testing/web-platform/tests/cookies/prefix/__secure.document-cookie.https.html new file mode 100644 index 0000000000..4b600032a8 --- /dev/null +++ b/testing/web-platform/tests/cookies/prefix/__secure.document-cookie.https.html @@ -0,0 +1,41 @@ +<!DOCTYPE html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/cookies/resources/cookie-helper.sub.js"></script> +<script> + ["", "MaxAge=10", "domain="+document.location.hostname].forEach(extraParams => { + // Without 'secure' + set_prefixed_cookie_via_dom_test({ + prefix: "__Secure-", + params: "Path=/;" + extraParams, + shouldExistInDOM: false, + shouldExistViaHTTP: false, + title: "__Secure: Secure origin: Should not set 'Path=/;" + extraParams + "'" + }); + + set_prefixed_cookie_via_dom_test({ + prefix: "__SeCuRe-", + params: "Path=/;" + extraParams, + shouldExistInDOM: false, + shouldExistViaHTTP: false, + title: "__SeCuRe: Secure origin: Should not set 'Path=/;" + extraParams + "'" + }); + + // With 'secure' + set_prefixed_cookie_via_dom_test({ + prefix: "__Secure-", + params: "Secure; Path=/;" + extraParams, + shouldExistInDOM: true, + shouldExistViaHTTP: true, + title: "__Secure: Secure origin: Should set 'Secure; Path=/;" + extraParams + "'" + }); + + set_prefixed_cookie_via_dom_test({ + prefix: "__SeCuRe-", + params: "Secure; Path=/;" + extraParams, + shouldExistInDOM: true, + shouldExistViaHTTP: true, + title: "__SeCuRe: Secure origin: Should set 'Secure; Path=/;" + extraParams + "'" + }); + }); +</script> diff --git a/testing/web-platform/tests/cookies/prefix/__secure.header.html b/testing/web-platform/tests/cookies/prefix/__secure.header.html new file mode 100644 index 0000000000..85665afefd --- /dev/null +++ b/testing/web-platform/tests/cookies/prefix/__secure.header.html @@ -0,0 +1,41 @@ +<!DOCTYPE html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/cookies/resources/cookie-helper.sub.js"></script> +<script> + ["", "domain="+document.location.hostname, "MaxAge=10", "HttpOnly"].forEach(extraParams => { + // Without 'secure' + set_prefixed_cookie_via_http_test({ + prefix: "__Secure-", + params: "Path=/;" + extraParams, + origin: self.origin, + shouldExistViaHTTP: false, + title: "__Secure: Non-secure origin: Should not set 'Path=/;" + extraParams + "'" + }); + + set_prefixed_cookie_via_http_test({ + prefix: "__SeCuRe-", + params: "Path=/;" + extraParams, + origin: self.origin, + shouldExistViaHTTP: false, + title: "__SeCuRe: Non-secure origin: Should not set 'Path=/;" + extraParams + "'" + }); + + // With 'secure' + set_prefixed_cookie_via_http_test({ + prefix: "__Secure-", + params: "Secure; Path=/;" + extraParams, + origin: self.origin, + shouldExistViaHTTP: false, + title: "__Secure: Non-secure origin: Should not set 'Secure; Path=/;" + extraParams + "'" + }); + + set_prefixed_cookie_via_http_test({ + prefix: "__SeCuRe-", + params: "Secure; Path=/;" + extraParams, + origin: self.origin, + shouldExistViaHTTP: false, + title: "__SeCuRe: Non-secure origin: Should not set 'Secure; Path=/;" + extraParams + "'" + }); + }); +</script> diff --git a/testing/web-platform/tests/cookies/prefix/__secure.header.https.html b/testing/web-platform/tests/cookies/prefix/__secure.header.https.html new file mode 100644 index 0000000000..a6fee03b0c --- /dev/null +++ b/testing/web-platform/tests/cookies/prefix/__secure.header.https.html @@ -0,0 +1,79 @@ +<!DOCTYPE html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/cookies/resources/cookie-helper.sub.js"></script> +<script> + ["", "MaxAge=10", "HttpOnly"].forEach(extraParams => { + // Without 'secure' + set_prefixed_cookie_via_http_test({ + prefix: "__Secure-", + params: "Path=/;" + extraParams, + origin: self.origin, + shouldExistViaHTTP: false, + title: "__Secure: secure origin: Should not set 'Path=/;" + extraParams + "'" + }); + + set_prefixed_cookie_via_http_test({ + prefix: "__SeCuRe-", + params: "Path=/;" + extraParams, + origin: self.origin, + shouldExistViaHTTP: false, + title: "__SeCuRe: secure origin: Should not set 'Path=/;" + extraParams + "'" + }); + + // With 'secure' + set_prefixed_cookie_via_http_test({ + prefix: "__Secure-", + params: "Secure;Path=/;" + extraParams, + origin: self.origin, + shouldExistViaHTTP: true, + title: "__Secure: secure origin: Should set 'Secure;Path=/;" + extraParams + "'" + }); + + set_prefixed_cookie_via_http_test({ + prefix: "__SeCuRe-", + params: "Secure;Path=/;" + extraParams, + origin: self.origin, + shouldExistViaHTTP: true, + title: "__SeCuRe: secure origin: Should set 'Secure;Path=/;" + extraParams + "'" + }); + }); + + // Without 'secure' + set_prefixed_cookie_via_http_test({ + prefix: "__Secure-", + // SameSite=None is necessary because cross-site origins cannot set SameSite cookies via fetch. + params: "Path=/;SameSite=None;domain=" + CROSS_SITE_HOST, + origin: SECURE_CROSS_SITE_ORIGIN, + shouldExistViaHTTP: false, + title: "__Secure: secure origin: Should not set 'Path=/;domain=" + CROSS_SITE_HOST + "'" + }); + + set_prefixed_cookie_via_http_test({ + prefix: "__SeCuRe-", + // SameSite=None is necessary because cross-site origins cannot set SameSite cookies via fetch. + params: "Path=/;SameSite=None;domain=" + CROSS_SITE_HOST, + origin: SECURE_CROSS_SITE_ORIGIN, + shouldExistViaHTTP: false, + title: "__SeCuRe: secure origin: Should not set 'Path=/;domain=" + CROSS_SITE_HOST + "'" + }); + + // With 'secure' + set_prefixed_cookie_via_http_test({ + prefix: "__Secure-", + // SameSite=None is necessary because cross-site origins cannot set SameSite cookies via fetch. + params: "Secure;SameSite=None;Path=/;domain=" + CROSS_SITE_HOST, + origin: SECURE_CROSS_SITE_ORIGIN, + shouldExistViaHTTP: true, + title: "__Secure: secure origin: Should set 'Secure;Path=/;domain=" + CROSS_SITE_HOST + "'" + }); + + set_prefixed_cookie_via_http_test({ + prefix: "__SeCuRe-", + // SameSite=None is necessary because cross-site origins cannot set SameSite cookies via fetch. + params: "Secure;SameSite=None;Path=/;domain=" + CROSS_SITE_HOST, + origin: SECURE_CROSS_SITE_ORIGIN, + shouldExistViaHTTP: true, + title: "__SeCuRe: secure origin: Should set 'Secure;Path=/;domain=" + CROSS_SITE_HOST + "'" + }); +</script> diff --git a/testing/web-platform/tests/cookies/prefix/document-cookie.non-secure.html b/testing/web-platform/tests/cookies/prefix/document-cookie.non-secure.html new file mode 100644 index 0000000000..efa16a8c5e --- /dev/null +++ b/testing/web-platform/tests/cookies/prefix/document-cookie.non-secure.html @@ -0,0 +1,42 @@ +<!DOCTYPE html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/cookies/resources/cookie-helper.sub.js"></script> +<script> + function create_test(prefix, params, shouldExistInDOM, shouldExistViaHTTP, title) { + promise_test(t => { + var name = prefix + "prefixtestcookie"; + erase_cookie_from_js(name, params); + t.add_cleanup(() => erase_cookie_from_js(name, params)); + var value = "" + Math.random(); + document.cookie = name + "=" + value + ";" + params; + + assert_dom_cookie(name, value, shouldExistInDOM); + + return credFetch("/cookies/resources/list.py") + .then(r => r.json()) + .then(cookies => assert_equals(cookies[name], shouldExistViaHTTP ? value : undefined)); + }, title); + } + + // No prefix + create_test("", "path=/", true, true, "No prefix, root path, no special behavior"); + create_test("", "path=/;domain=" + document.location.hostname, true, true, "No prefix, domain, no special behavior"); + + // `__Secure-` Prefix + ["", "domain="+document.location.hostname, "MaxAge=10", "HttpOnly"].forEach(params => { + create_test("__Secure-", "Path=/;" + params, false, false, "__Secure: Non-secure origin: 'Path=/;" + params + "'"); + create_test("__SeCuRe-", "Path=/;" + params, false, false, "__SeCuRe: Non-secure origin: 'Path=/;" + params + "'"); + create_test("__Secure-", "Secure; Path=/;" + params, false, false, "__Secure: Non-secure origin: 'Secure; Path=/;" + params + "'"); + create_test("__SeCuRe-", "Secure; Path=/;" + params, false, false, "__SeCuRe: Non-secure origin: 'Secure; Path=/;" + params + "'"); + }); + + // `__Host-` Prefix + ["", "domain="+document.location.hostname, "MaxAge=10", "HttpOnly"].forEach(params => { + create_test("__Host-", "Path=/;" + params, false, false, "__Host: Non-secure origin: 'Path=/; " + params + "'"); + create_test("__HoSt-", "Path=/;" + params, false, false, "__HoSt: Non-secure origin: 'Path=/; " + params + "'"); + create_test("__Host-", "Secure; Path=/;" + params, false, false, "__Host: Non-secure origin: 'Secure; Path=/; " + params + "'"); + create_test("__HoSt-", "Secure; Path=/;" + params, false, false, "__HoSt: Non-secure origin: 'Secure; Path=/; " + params + "'"); + }); + create_test("__Secure-", "Path=/cookies/resources/list.py;Secure", false, false, "__Host: Non-secure origin: 'Path=/cookies/resources/list.py;Secure'"); +</script> diff --git a/testing/web-platform/tests/cookies/resources/__init__.py b/testing/web-platform/tests/cookies/resources/__init__.py new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/testing/web-platform/tests/cookies/resources/__init__.py diff --git a/testing/web-platform/tests/cookies/resources/cookie-helper.sub.js b/testing/web-platform/tests/cookies/resources/cookie-helper.sub.js new file mode 100644 index 0000000000..3338cf0e80 --- /dev/null +++ b/testing/web-platform/tests/cookies/resources/cookie-helper.sub.js @@ -0,0 +1,284 @@ +// Set up exciting global variables for cookie tests. +(_ => { + var HOST = "{{host}}"; + var INSECURE_PORT = ":{{ports[http][0]}}"; + var SECURE_PORT = ":{{ports[https][0]}}"; + var CROSS_ORIGIN_HOST = "{{hosts[alt][]}}"; + + window.INSECURE_ORIGIN = "http://" + HOST + INSECURE_PORT; + + //For secure cookie verification + window.SECURE_ORIGIN = "https://" + HOST + SECURE_PORT; + + //standard references + window.SECURE_SUBDOMAIN_ORIGIN = "https://{{domains[www1]}}" + SECURE_PORT; + window.SECURE_CROSS_SITE_ORIGIN = "https://" + CROSS_ORIGIN_HOST + SECURE_PORT; + window.CROSS_SITE_HOST = CROSS_ORIGIN_HOST; + + // Set the global cookie name. + window.HTTP_COOKIE = "cookie_via_http"; +})(); + +// A tiny helper which returns the result of fetching |url| with credentials. +function credFetch(url) { + return fetch(url, {"credentials": "include"}) + .then(response => { + if (response.status !== 200) { + throw new Error(response.statusText); + } + return response; + }); +} + +// Returns a URL on |origin| which redirects to a given absolute URL. +function redirectTo(origin, url) { + return origin + "/cookies/resources/redirectWithCORSHeaders.py?status=307&location=" + encodeURIComponent(url); +} + +// Returns a URL on |origin| which navigates the window to the given URL (by +// setting window.location). +function navigateTo(origin, url) { + return origin + "/cookies/resources/navigate.html?location=" + encodeURIComponent(url); +} + +// Returns whether a cookie with name `name` with value `value` is in the cookie +// string (presumably obtained via document.cookie). +function cookieStringHasCookie(name, value, cookieString) { + return new RegExp(`(?:^|; )${name}=${value}(?:$|;)`).test(cookieString); +} + +// Asserts that `document.cookie` contains or does not contain (according to +// the value of |present|) a cookie named |name| with a value of |value|. +function assert_dom_cookie(name, value, present) { + assert_equals(cookieStringHasCookie(name, value, document.cookie), present, "`" + name + "=" + value + "` in `document.cookie`"); +} + +function assert_cookie(origin, obj, name, value, present) { + assert_equals(obj[name], present ? value : undefined, "`" + name + "=" + value + "` in request to `" + origin + "`."); +} + +// Remove the cookie named |name| from |origin|, then set it on |origin| anew. +// If |origin| matches `self.origin`, also assert (via `document.cookie`) that +// the cookie was correctly removed and reset. +async function create_cookie(origin, name, value, extras) { + alert("Create_cookie: " + origin + "/cookies/resources/drop.py?name=" + name); + await credFetch(origin + "/cookies/resources/drop.py?name=" + name); + if (origin == self.origin) + assert_dom_cookie(name, value, false); + await credFetch(origin + "/cookies/resources/set.py?" + name + "=" + value + ";path=/;" + extras); + if (origin == self.origin) + assert_dom_cookie(name, value, true); +} + +// +// Prefix-specific test helpers +// +function set_prefixed_cookie_via_dom_test(options) { + promise_test(t => { + var name = options.prefix + "prefixtestcookie"; + erase_cookie_from_js(name, options.params); + t.add_cleanup(() => erase_cookie_from_js(name, options.params)); + var value = "" + Math.random(); + document.cookie = name + "=" + value + ";" + options.params; + + assert_dom_cookie(name, value, options.shouldExistInDOM); + + return credFetch("/cookies/resources/list.py") + .then(r => r.json()) + .then(cookies => assert_equals(cookies[name], options.shouldExistViaHTTP ? value : undefined)); + }, options.title); +} + +function set_prefixed_cookie_via_http_test(options) { + promise_test(t => { + var name = options.prefix + "prefixtestcookie"; + var value = "" + Math.random(); + + t.add_cleanup(() => { + var cookie = name + "=0;expires=" + new Date(0).toUTCString() + ";" + + options.params; + + return credFetch(options.origin + "/cookies/resources/set.py?" + cookie); + }); + + return credFetch(options.origin + "/cookies/resources/set.py?" + name + "=" + value + ";" + options.params) + .then(_ => credFetch(options.origin + "/cookies/resources/list.py")) + .then(r => r.json()) + .then(cookies => assert_equals(cookies[name], options.shouldExistViaHTTP ? value : undefined)); + }, options.title); +} + +// +// SameSite-specific test helpers: +// + +// status for "network" cookies. +window.SameSiteStatus = { + CROSS_SITE: "cross-site", + LAX: "lax", + STRICT: "strict" +}; +// status for "document.cookie". +window.DomSameSiteStatus = { + CROSS_SITE: "cross-site", + SAME_SITE: "same-site", +}; + +const wait_for_message = (type, origin) => { + return new Promise((resolve, reject) => { + window.addEventListener('message', e => { + if (origin && e.origin != origin) { + reject("Message from unexpected origin in wait_for_message:" + e.origin); + return; + } + + if (e.data.type && e.data.type === type) + resolve(e); + }, { once: true }); + }); +}; + +// Reset SameSite test cookies on |origin|. If |origin| matches `self.origin`, assert +// (via `document.cookie`) that they were properly removed and reset. +async function resetSameSiteCookies(origin, value) { + let w = window.open(origin + "/cookies/samesite/resources/puppet.html"); + try { + await wait_for_message("READY", origin); + w.postMessage({type: "drop", useOwnOrigin: true}, "*"); + await wait_for_message("drop-complete", origin); + if (origin == self.origin) { + assert_dom_cookie("samesite_strict", value, false); + assert_dom_cookie("samesite_lax", value, false); + assert_dom_cookie("samesite_none", value, false); + assert_dom_cookie("samesite_unspecified", value, false); + } + + w.postMessage({type: "set", value: value, useOwnOrigin: true}, "*"); + await wait_for_message("set-complete", origin); + if (origin == self.origin) { + assert_dom_cookie("samesite_strict", value, true); + assert_dom_cookie("samesite_lax", value, true); + assert_dom_cookie("samesite_none", value, true); + assert_dom_cookie("samesite_unspecified", value, true); + } + } finally { + w.close(); + } +} + +// Given an |expectedStatus| and |expectedValue|, assert the |cookies| contains +// the proper set of cookie names and values. Expects SameSite-Lax-by-default. +function verifySameSiteCookieState(expectedStatus, expectedValue, cookies, domCookieStatus) { + assert_equals(cookies["samesite_none"], expectedValue, "SameSite=None cookies are always sent."); + if (expectedStatus == SameSiteStatus.CROSS_SITE) { + assert_not_equals(cookies["samesite_strict"], expectedValue, "SameSite=Strict cookies are not sent with cross-site requests."); + assert_not_equals(cookies["samesite_lax"], expectedValue, "SameSite=Lax cookies are not sent with cross-site requests."); + assert_not_equals(cookies["samesite_unspecified"], expectedValue, "Unspecified-SameSite cookies are not sent with cross-site requests."); + } else if (expectedStatus == SameSiteStatus.LAX) { + assert_not_equals(cookies["samesite_strict"], expectedValue, "SameSite=Strict cookies are not sent with lax requests."); + assert_equals(cookies["samesite_lax"], expectedValue, "SameSite=Lax cookies are sent with lax requests."); + assert_equals(cookies["samesite_unspecified"], expectedValue, "Unspecified-SameSite cookies are are sent with lax requests.") + } else if (expectedStatus == SameSiteStatus.STRICT) { + assert_equals(cookies["samesite_strict"], expectedValue, "SameSite=Strict cookies are sent with strict requests."); + assert_equals(cookies["samesite_lax"], expectedValue, "SameSite=Lax cookies are sent with strict requests."); + assert_equals(cookies["samesite_unspecified"], expectedValue, "Unspecified-SameSite cookies are are sent with strict requests.") + } + + if (cookies["domcookies"]) { + verifyDocumentCookieSameSite(domCookieStatus, expectedValue, cookies['domcookies']); + } +} + +function verifyDocumentCookieSameSite(expectedStatus, expectedValue, domcookies) { + const cookies = domcookies.split(";") + .map(cookie => cookie.trim().split("=")) + .reduce((obj, cookie) => { + obj[cookie[0]] = cookie[1]; + return obj; + }, {}); + + if (expectedStatus == DomSameSiteStatus.SAME_SITE) { + assert_equals(cookies["samesite_none"], expectedValue, "SameSite=None cookies are always included in document.cookie."); + assert_equals(cookies["samesite_unspecified"], expectedValue, "Unspecified-SameSite cookies are always included in document.cookie."); + assert_equals(cookies["samesite_strict"], expectedValue, "SameSite=Strict cookies are always included in document.cookie."); + assert_equals(cookies["samesite_lax"], expectedValue, "SameSite=Lax cookies are always included in document.cookie."); + } else if (expectedStatus == DomSameSiteStatus.CROSS_SITE) { + assert_equals(cookies["samesite_none"], expectedValue, "SameSite=None cookies are always included in document.cookie."); + assert_not_equals(cookies["samesite_unspecified"], expectedValue, "Unspecified-SameSite cookies are not included in document.cookie when cross-site."); + assert_not_equals(cookies["samesite_strict"], expectedValue, "SameSite=Strict cookies are not included in document.cookie when cross-site."); + assert_not_equals(cookies["samesite_lax"], expectedValue, "SameSite=Lax cookies are not included in document.cookie when cross-site."); + } +} + +// +// LeaveSecureCookiesAlone-specific test helpers: +// + +window.SecureStatus = { + INSECURE_COOKIE_ONLY: "1", + BOTH_COOKIES: "2", +}; + +//Reset SameSite test cookies on |origin|. If |origin| matches `self.origin`, assert +//(via `document.cookie`) that they were properly removed and reset. +function resetSecureCookies(origin, value) { +return credFetch(origin + "/cookies/resources/dropSecure.py") + .then(_ => { + if (origin == self.origin) { + assert_dom_cookie("alone_secure", value, false); + assert_dom_cookie("alone_insecure", value, false); + } + }) + .then(_ => { + return credFetch(origin + "/cookie/resources/setSecure.py?" + value) + }) +} + +// Reset SameSite=None test cookies on |origin|. If |origin| matches +// `self.origin`, assert (via `document.cookie`) that they were properly +// removed. +function resetSameSiteNoneCookies(origin, value) { + return credFetch(origin + "/cookies/resources/dropSameSiteNone.py") + .then(_ => { + if (origin == self.origin) { + assert_dom_cookie("samesite_none_insecure", value, false); + assert_dom_cookie("samesite_none_secure", value, false); + } + }) + .then(_ => { + return credFetch(origin + "/cookies/resources/setSameSiteNone.py?" + value); + }) +} + +// Reset test cookies with multiple SameSite attributes on |origin|. +// If |origin| matches `self.origin`, assert (via `document.cookie`) +// that they were properly removed. +function resetSameSiteMultiAttributeCookies(origin, value) { + return credFetch(origin + "/cookies/resources/dropSameSiteMultiAttribute.py") + .then(_ => { + if (origin == self.origin) { + assert_dom_cookie("samesite_unsupported", value, false); + assert_dom_cookie("samesite_unsupported_none", value, false); + assert_dom_cookie("samesite_unsupported_lax", value, false); + assert_dom_cookie("samesite_unsupported_strict", value, false); + assert_dom_cookie("samesite_none_unsupported", value, false); + assert_dom_cookie("samesite_lax_unsupported", value, false); + assert_dom_cookie("samesite_strict_unsupported", value, false); + assert_dom_cookie("samesite_lax_none", value, false); + } + }) + .then(_ => { + return credFetch(origin + "/cookies/resources/setSameSiteMultiAttribute.py?" + value); + }) +} + +// +// DOM based cookie manipulation APIs +// + +// erase cookie value and set for expiration +function erase_cookie_from_js(name, params) { + document.cookie = `${name}=0; expires=${new Date(0).toUTCString()}; ${params};`; + var re = new RegExp("(?:^|; )" + name); + assert_equals(re.test(document.cookie), false, "Sanity check: " + name + " has been deleted."); +} diff --git a/testing/web-platform/tests/cookies/resources/cookie-test.js b/testing/web-platform/tests/cookies/resources/cookie-test.js new file mode 100644 index 0000000000..a909e4d72f --- /dev/null +++ b/testing/web-platform/tests/cookies/resources/cookie-test.js @@ -0,0 +1,186 @@ +// getAndExpireCookiesForDefaultPathTest is a helper method to get and delete +// cookies using echo-cookie.html. +async function getAndExpireCookiesForDefaultPathTest() { + return new Promise((resolve, reject) => { + try { + const iframe = document.createElement('iframe'); + iframe.style = 'display: none'; + iframe.src = '/cookies/resources/echo-cookie.html'; + iframe.addEventListener('load', (e) => { + const win = e.target.contentWindow; + const iframeCookies = win.getCookies(); + win.expireCookies().then(() => { + document.documentElement.removeChild(iframe); + resolve(iframeCookies); + }); + }, {once: true}); + document.documentElement.appendChild(iframe); + } catch (e) { + reject(e); + } + }); +} + +// getAndExpireCookiesForRedirectTest is a helper method to get and delete +// cookies that were set from a Location header redirect. +async function getAndExpireCookiesForRedirectTest(location) { + return new Promise((resolve, reject) => { + try { + const iframe = document.createElement('iframe'); + iframe.style = 'display: none'; + iframe.src = location; + const listener = (e) => { + if (typeof e.data == 'object' && 'cookies' in e.data) { + window.removeEventListener('message', listener); + document.documentElement.removeChild(iframe); + resolve(e.data.cookies); + } + }; + window.addEventListener('message', listener); + iframe.addEventListener('load', (e) => { + e.target.contentWindow.postMessage('getAndExpireCookiesForRedirectTest', '*'); + }, {once: true}); + document.documentElement.appendChild(iframe); + } catch (e) { + reject(e); + } + }); +} + +// httpCookieTest sets a `cookie` (via HTTP), then asserts it was or was not set +// via `expectedValue` (via the DOM). Then cleans it up (via test driver). Most +// tests do not set a Path attribute, so `defaultPath` defaults to true. If the +// cookie values are expected to cause the HTTP request or response to fail, the +// test can be made to pass when this happens via `allowFetchFailure`, which +// defaults to false. +// +// `cookie` may be a single cookie string, or an array of cookie strings, where +// the order of the array items represents the order of the Set-Cookie headers +// sent by the server. +// +// Note: this function has a dependency on testdriver.js. Any test files calling +// it should include testdriver.js and testdriver-vendor.js +function httpCookieTest(cookie, expectedValue, name, defaultPath = true, + allowFetchFailure = false) { + return promise_test((t) => { + var skipAssertions = false; + return new Promise(async (resolve, reject) => { + // The result is ignored as we're expiring cookies for cleaning here. + await getAndExpireCookiesForDefaultPathTest(); + await test_driver.delete_all_cookies(); + t.add_cleanup(test_driver.delete_all_cookies); + + let encodedCookie = encodeURIComponent(JSON.stringify(cookie)); + try { + await fetch(`/cookies/resources/cookie.py?set=${encodedCookie}`); + } catch { + if (allowFetchFailure) { + skipAssertions = true; + resolve(); + } else { + reject('Failed to fetch /cookies/resources/cookie.py'); + } + } + let cookies = document.cookie; + if (defaultPath) { + // for the tests where a Path is set from the request-uri + // path, we need to go look for cookies in an iframe at that + // default path. + cookies = await getAndExpireCookiesForDefaultPathTest(); + } + resolve(cookies); + }).then((cookies) => { + if (skipAssertions) { + return; + } + if (Boolean(expectedValue)) { + assert_equals(cookies, expectedValue, 'The cookie was set as expected.'); + } else { + assert_equals(cookies, expectedValue, 'The cookie was rejected.'); + } + }); + }, name); +} + +// This is a variation on httpCookieTest, where a redirect happens via +// the Location header and we check to see if cookies are sent via +// getRedirectedCookies +// +// Note: the locations targeted by this function have a dependency on +// path-redirect-shared.js and should be sure to include it. +function httpRedirectCookieTest(cookie, expectedValue, name, location) { + return promise_test(async (t) => { + // The result is ignored as we're expiring cookies for cleaning here. + await getAndExpireCookiesForRedirectTest(location); + + const encodedCookie = encodeURIComponent(JSON.stringify(cookie)); + const encodedLocation = encodeURIComponent(location); + const setParams = `?set=${encodedCookie}&location=${encodedLocation}`; + await fetch(`/cookies/resources/cookie.py${setParams}`); + // for the tests where a redirect happens, we need to head + // to that URI to get the cookies (and then delete them there) + const cookies = await getAndExpireCookiesForRedirectTest(location); + if (Boolean(expectedValue)) { + assert_equals(cookies, expectedValue, 'The cookie was set as expected.'); + } else { + assert_equals(cookies, expectedValue, 'The cookie was rejected.'); + } + }, name); +} + +// Sets a `cookie` via the DOM, checks it against `expectedValue` via the DOM, +// then cleans it up via the DOM. This is needed in cases where going through +// HTTP headers may modify the cookie line (e.g. by stripping control +// characters). +// +// Note: this function has a dependency on testdriver.js. Any test files calling +// it should include testdriver.js and testdriver-vendor.js +function domCookieTest(cookie, expectedValue, name) { + return promise_test(async (t) => { + await test_driver.delete_all_cookies(); + t.add_cleanup(test_driver.delete_all_cookies); + + if (typeof cookie === "string") { + document.cookie = cookie; + } else if (Array.isArray(cookie)) { + for (const singlecookie of cookie) { + document.cookie = singlecookie; + } + } else { + throw new Error('Unexpected type passed into domCookieTest as cookie: ' + typeof cookie); + } + let cookies = document.cookie; + assert_equals(cookies, expectedValue, Boolean(expectedValue) ? + 'The cookie was set as expected.' : + 'The cookie was rejected.'); + }, name); +} + +// Returns an array of control characters along with their ASCII codes. Control +// characters are defined by RFC 5234 to be %x00-1F / %x7F. +function getCtlCharacters() { + const ctlCodes = [...Array(0x20).keys()] + .concat([0x7F]); + return ctlCodes.map(i => ({ code: i, chr: String.fromCharCode(i) })) +} + +// Returns a cookie string with name set to "t" * nameLength and value +// set to "1" * valueLength. Passing in 0 for either allows for creating +// a name- or value-less cookie. +// +// Note: Cookie length checking should ignore the "=". +function cookieStringWithNameAndValueLengths(nameLength, valueLength) { + return `${"t".repeat(nameLength)}=${"1".repeat(valueLength)}`; +} + +// Finds the root window.top.opener and directs test_driver commands to it. +// +// If you see a message like: "Error: Tried to run in a non-testharness window +// without a call to set_test_context." then you probably need to call this. +function setTestContextUsingRootWindow() { + let test_window = window.top; + while (test_window.opener && !test_window.opener.closed) { + test_window = test_window.opener.top; + } + test_driver.set_test_context(test_window); +} diff --git a/testing/web-platform/tests/cookies/resources/cookie.py b/testing/web-platform/tests/cookies/resources/cookie.py new file mode 100644 index 0000000000..936679bc9f --- /dev/null +++ b/testing/web-platform/tests/cookies/resources/cookie.py @@ -0,0 +1,42 @@ +import json + +from cookies.resources.helpers import setNoCacheAndCORSHeaders +from wptserve.utils import isomorphic_decode +from wptserve.utils import isomorphic_encode + +def set_cookie(headers, cookie_string): + """Helper method to add a Set-Cookie header""" + headers.append((b'Set-Cookie', isomorphic_encode(cookie_string))) + +def main(request, response): + """Set a cookie via GET params. + + Usage: `/cookie.py?set={cookie}` + + The passed-in cookie string should be stringified via JSON.stringify() (in + the case of multiple cookie headers sent in an array) and encoded via + encodeURIComponent, otherwise `parse_qsl` will split on any semicolons + (used by the Request.GET property getter). Note that values returned by + Request.GET will decode any percent-encoded sequences sent in a GET param + (which may or may not be surprising depending on what you're doing). + + Note: here we don't use Response.delete_cookie() or similar other methods + in this resources directory because there are edge cases that are impossible + to express via those APIs, namely a bare (`Path`) or empty Path (`Path=`) + attribute. Instead, we pipe through the entire cookie and append `max-age=0` + to it. + """ + headers = setNoCacheAndCORSHeaders(request, response) + + if b'set' in request.GET: + cookie = isomorphic_decode(request.GET[b'set']) + cookie = json.loads(cookie) + cookies = cookie if isinstance(cookie, list) else [cookie] + for c in cookies: + set_cookie(headers, c) + + if b'location' in request.GET: + headers.append((b'Location', request.GET[b'location'])) + return 302, headers, b'{"redirect": true}' + + return headers, b'{"success": true}' diff --git a/testing/web-platform/tests/cookies/resources/drop.py b/testing/web-platform/tests/cookies/resources/drop.py new file mode 100644 index 0000000000..612add2169 --- /dev/null +++ b/testing/web-platform/tests/cookies/resources/drop.py @@ -0,0 +1,14 @@ +from cookies.resources.helpers import makeDropCookie, readParameter, setNoCacheAndCORSHeaders + +def main(request, response): + """Respond to `/cookie/drop?name={name}` by expiring the cookie named `{name}`.""" + headers = setNoCacheAndCORSHeaders(request, response) + try: + # Expire the named cookie, and return a JSON-encoded success code. + name = readParameter(request, paramName=u"name", requireValue=True) + scheme = request.url_parts.scheme + headers.append(makeDropCookie(name, u"https" == scheme)) + return headers, b'{"success": true}' + except: + return 500, headers, b'{"error" : "Empty or missing name parameter."}' + diff --git a/testing/web-platform/tests/cookies/resources/dropSameSite.py b/testing/web-platform/tests/cookies/resources/dropSameSite.py new file mode 100644 index 0000000000..a0aa83558d --- /dev/null +++ b/testing/web-platform/tests/cookies/resources/dropSameSite.py @@ -0,0 +1,13 @@ +from cookies.resources.helpers import makeDropCookie, setNoCacheAndCORSHeaders + +def main(request, response): + """Respond to `/cookie/same-site/resources/dropSameSite.py by dropping the + four cookies set by setSameSiteCookies.py""" + headers = setNoCacheAndCORSHeaders(request, response) + + # Expire the cookies, and return a JSON-encoded success code. + headers.append(makeDropCookie(b"samesite_strict", False)) + headers.append(makeDropCookie(b"samesite_lax", False)) + headers.append(makeDropCookie(b"samesite_none", False)) + headers.append(makeDropCookie(b"samesite_unspecified", False)) + return headers, b'{"success": true}' diff --git a/testing/web-platform/tests/cookies/resources/dropSameSiteMultiAttribute.py b/testing/web-platform/tests/cookies/resources/dropSameSiteMultiAttribute.py new file mode 100644 index 0000000000..af4fbeeca4 --- /dev/null +++ b/testing/web-platform/tests/cookies/resources/dropSameSiteMultiAttribute.py @@ -0,0 +1,17 @@ +from cookies.resources.helpers import makeDropCookie, setNoCacheAndCORSHeaders + +def main(request, response): + """Respond to `/cookies/resources/dropSameSiteMultiAttribute.py by dropping + the cookies set by setSameSiteMultiAttribute.py""" + headers = setNoCacheAndCORSHeaders(request, response) + + # Expire the cookies, and return a JSON-encoded success code. + headers.append(makeDropCookie(b"samesite_unsupported", True)) + headers.append(makeDropCookie(b"samesite_unsupported_none", True)) + headers.append(makeDropCookie(b"samesite_unsupported_lax", False)) + headers.append(makeDropCookie(b"samesite_unsupported_strict", False)) + headers.append(makeDropCookie(b"samesite_none_unsupported", True)) + headers.append(makeDropCookie(b"samesite_lax_unsupported", True)) + headers.append(makeDropCookie(b"samesite_strict_unsupported", True)) + headers.append(makeDropCookie(b"samesite_lax_none", True)) + return headers, b'{"success": true}' diff --git a/testing/web-platform/tests/cookies/resources/dropSameSiteNone.py b/testing/web-platform/tests/cookies/resources/dropSameSiteNone.py new file mode 100644 index 0000000000..2d0a837b5d --- /dev/null +++ b/testing/web-platform/tests/cookies/resources/dropSameSiteNone.py @@ -0,0 +1,11 @@ +from cookies.resources.helpers import makeDropCookie, setNoCacheAndCORSHeaders + +def main(request, response): + """Respond to `/cookies/resources/dropSameSiteNone.py by dropping the + two cookies set by setSameSiteNone.py""" + headers = setNoCacheAndCORSHeaders(request, response) + + # Expire the cookies, and return a JSON-encoded success code. + headers.append(makeDropCookie(b"samesite_none_insecure", False)) + headers.append(makeDropCookie(b"samesite_none_secure", True)) + return headers, b'{"success": true}' diff --git a/testing/web-platform/tests/cookies/resources/dropSecure.py b/testing/web-platform/tests/cookies/resources/dropSecure.py new file mode 100644 index 0000000000..af71148cd6 --- /dev/null +++ b/testing/web-platform/tests/cookies/resources/dropSecure.py @@ -0,0 +1,11 @@ +from cookies.resources.helpers import makeDropCookie, setNoCacheAndCORSHeaders + +def main(request, response): + """Respond to `/cookie/drop/secure` by dropping the two cookie set by + `setSecureTestCookies()`""" + headers = setNoCacheAndCORSHeaders(request, response) + + # Expire the cookies, and return a JSON-encoded success code. + headers.append(makeDropCookie(b"alone_secure", False)) + headers.append(makeDropCookie(b"alone_insecure", False)) + return headers, b'{"success": true}' diff --git a/testing/web-platform/tests/cookies/resources/echo-cookie.html b/testing/web-platform/tests/cookies/resources/echo-cookie.html new file mode 100644 index 0000000000..ab78af8325 --- /dev/null +++ b/testing/web-platform/tests/cookies/resources/echo-cookie.html @@ -0,0 +1,31 @@ +<!doctype html> +<html> +<head> + <meta charset=utf-8> + <title>helper iframe for matching cookie path tests</title> + <meta name=help href="http://tools.ietf.org/html/rfc6265#section-5.1.4"> + <script src="/resources/testdriver.js"></script> + <script src="/resources/testdriver-vendor.js"></script> + <script src="/cookies/resources/cookie-test.js"></script> +</head> +<body> +<script> +window.setCookie = function (name, path) { + document.cookie = name + '=1; Path=' + path + ';'; +}; +window.fetchCookieThen = function (name, path) { + return fetch("/cookies/resources/set-cookie.py?name=" + encodeURIComponent(name) + "&path=" + encodeURIComponent(path), {'credentials': 'include'}); +}; +window.isCookieSet = function (name, path) { + return document.cookie.match(name + '=1'); +}; +// Note: this function has a dependency on testdriver.js. Any test files calling +// it should include testdriver.js and testdriver-vendor.js +window.expireCookies = async () => { + setTestContextUsingRootWindow(); + await test_driver.delete_all_cookies(); +}; +window.getCookies = () => document.cookie; +</script> +</body> +</html> diff --git a/testing/web-platform/tests/cookies/resources/echo-json.py b/testing/web-platform/tests/cookies/resources/echo-json.py new file mode 100644 index 0000000000..9f1568e816 --- /dev/null +++ b/testing/web-platform/tests/cookies/resources/echo-json.py @@ -0,0 +1,15 @@ +from wptserve.utils import isomorphic_decode + +def main(request, response): + headers = [(b"Content-Type", b"application/json"), + (b"Access-Control-Allow-Credentials", b"true")] + + if b"origin" in request.headers: + headers.append((b"Access-Control-Allow-Origin", request.headers[b"origin"])) + + values = [] + for key in request.cookies: + for value in request.cookies.get_list(key): + values.append(u"\"%s\": \"%s\"" % (isomorphic_decode(key), value)) + body = u"{ %s }" % u",".join(values) + return headers, body diff --git a/testing/web-platform/tests/cookies/resources/helpers.py b/testing/web-platform/tests/cookies/resources/helpers.py new file mode 100644 index 0000000000..5fee5a9a91 --- /dev/null +++ b/testing/web-platform/tests/cookies/resources/helpers.py @@ -0,0 +1,59 @@ +from urllib.parse import parse_qs + +from wptserve.utils import isomorphic_encode + +def setNoCacheAndCORSHeaders(request, response): + """Set Cache-Control, CORS and Content-Type headers appropriate for the cookie tests.""" + headers = [(b"Content-Type", b"application/json"), + (b"Access-Control-Allow-Credentials", b"true")] + + origin = b"*" + if b"origin" in request.headers: + origin = request.headers[b"origin"] + + headers.append((b"Access-Control-Allow-Origin", origin)) + #headers.append(("Access-Control-Allow-Credentials", "true")) + headers.append((b"Cache-Control", b"no-cache")) + headers.append((b"Expires", b"Fri, 01 Jan 1990 00:00:00 GMT")) + + return headers + +def makeCookieHeader(name, value, otherAttrs): + """Make a Set-Cookie header for a cookie with the name, value and attributes provided.""" + def makeAV(a, v): + if None == v or b"" == v: + return a + if isinstance(v, int): + return b"%s=%i" % (a, v) + else: + return b"%s=%s" % (a, v) + + # ensure cookie name is always first + attrs = [b"%s=%s" % (name, value)] + attrs.extend(makeAV(a, v) for (a, v) in otherAttrs.items()) + return (b"Set-Cookie", b"; ".join((attrs))) + +def makeDropCookie(name, secure): + attrs = {b"max-age": 0, b"path": b"/"} + if secure: + attrs[b"secure"] = b"" + return makeCookieHeader(name, b"", attrs) + +def readParameter(request, paramName, requireValue): + """Read a parameter from the request. Raise if requireValue is set and the + parameter has an empty value or is not present.""" + params = parse_qs(request.url_parts.query) + param = params[paramName][0].strip() + if len(param) == 0: + raise Exception(u"Empty or missing name parameter.") + return isomorphic_encode(param) + +def readCookies(request): + """Read the cookies from the client present in the request.""" + cookies = {} + for key in request.cookies: + for cookie in request.cookies.get_list(key): + # do we care we'll clobber cookies here? If so, do we + # need to modify the test to take cookie names and value lists? + cookies[key] = cookie.value + return cookies diff --git a/testing/web-platform/tests/cookies/resources/imgIfMatch.py b/testing/web-platform/tests/cookies/resources/imgIfMatch.py new file mode 100644 index 0000000000..72fa50e66e --- /dev/null +++ b/testing/web-platform/tests/cookies/resources/imgIfMatch.py @@ -0,0 +1,16 @@ +from cookies.resources import helpers + +def main(request, response): + """Respond to `/cookie/imgIfMatch?name={name}&value={value}` with a 404 if + the cookie isn't present, and a transparent GIF otherwise.""" + headers = helpers.setNoCacheAndCORSHeaders(request, response) + name = helpers.readParameter(request, paramName=u"name", requireValue=True) + value = helpers.readParameter(request, paramName=u"value", requireValue=True) + cookiesWithMatchingNames = request.cookies.get_list(name) + for cookie in cookiesWithMatchingNames: + if cookie.value == value: + # From https://github.com/mathiasbynens/small/blob/master/gif-transparent.gif + headers.append((b"Content-Type", b"image/gif")) + gif = b"\x47\x49\x46\x38\x39\x61\x01\x00\x01\x00\x80\x00\x00\xFF\xFF\xFF\x00\x00\x00\x21\xF9\x04\x01\x00\x00\x00\x00\x2C\x00\x00\x00\x00\x01\x00\x01\x00\x00\x02\x02\x44\x01\x00\x3B" + return headers, gif + return 500, headers, b'{"error": {"message": "The cookie\'s value did not match the given value."}}' diff --git a/testing/web-platform/tests/cookies/resources/list-cookies-for-script.py b/testing/web-platform/tests/cookies/resources/list-cookies-for-script.py new file mode 100644 index 0000000000..b325d1f745 --- /dev/null +++ b/testing/web-platform/tests/cookies/resources/list-cookies-for-script.py @@ -0,0 +1,12 @@ +import json +from cookies.resources import helpers + +from wptserve.utils import isomorphic_decode + +def main(request, response): + headers = helpers.setNoCacheAndCORSHeaders(request, response) + headers[0] = (b"Content-Type", b"text/javascript") + cookies = helpers.readCookies(request) + decoded_cookies = {isomorphic_decode(key): isomorphic_decode(val) for key, val in cookies.items()} + return headers, 'self._cookies = [{}];\n'.format( + ', '.join(['"{}"'.format(name) for name in decoded_cookies.keys()])) diff --git a/testing/web-platform/tests/cookies/resources/list.py b/testing/web-platform/tests/cookies/resources/list.py new file mode 100644 index 0000000000..4cb6639659 --- /dev/null +++ b/testing/web-platform/tests/cookies/resources/list.py @@ -0,0 +1,10 @@ +import json +from cookies.resources import helpers + +from wptserve.utils import isomorphic_decode + +def main(request, response): + headers = helpers.setNoCacheAndCORSHeaders(request, response) + cookies = helpers.readCookies(request) + decoded_cookies = {isomorphic_decode(key): isomorphic_decode(val) for key, val in cookies.items()} + return headers, json.dumps(decoded_cookies) diff --git a/testing/web-platform/tests/cookies/resources/navigate.html b/testing/web-platform/tests/cookies/resources/navigate.html new file mode 100644 index 0000000000..077efba569 --- /dev/null +++ b/testing/web-platform/tests/cookies/resources/navigate.html @@ -0,0 +1,8 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<script> + // Navigates the window to a location specified via URL query param. + const params = new URLSearchParams(window.location.search); + const loc = params.get('location'); + window.location = loc; +</script> diff --git a/testing/web-platform/tests/cookies/resources/postToParent.py b/testing/web-platform/tests/cookies/resources/postToParent.py new file mode 100644 index 0000000000..43f7d679fb --- /dev/null +++ b/testing/web-platform/tests/cookies/resources/postToParent.py @@ -0,0 +1,39 @@ +import json +from cookies.resources import helpers + +from wptserve.utils import isomorphic_decode + +def main(request, response): + headers = helpers.setNoCacheAndCORSHeaders(request, response) + cookies = helpers.readCookies(request) + headers.append((b"Content-Type", b"text/html; charset=utf-8")) + + tmpl = u""" +<!DOCTYPE html> +<script> + var data = %s; + data.type = "COOKIES"; + + try { + data.domcookies = document.cookie; + } catch (e) {} + + if (window.parent != window) { + window.parent.postMessage(data, "*"); + if (window.top != window.parent) + window.top.postMessage(data, "*"); + } + + + if (window.opener) + window.opener.postMessage(data, "*"); + + window.addEventListener("message", e => { + console.log(e); + if (e.data == "reload") + window.location.reload(); + }); +</script> +""" + decoded_cookies = {isomorphic_decode(key): isomorphic_decode(val) for key, val in cookies.items()} + return headers, tmpl % json.dumps(decoded_cookies) diff --git a/testing/web-platform/tests/cookies/resources/redirectWithCORSHeaders.py b/testing/web-platform/tests/cookies/resources/redirectWithCORSHeaders.py new file mode 100644 index 0000000000..0af14da3e9 --- /dev/null +++ b/testing/web-platform/tests/cookies/resources/redirectWithCORSHeaders.py @@ -0,0 +1,22 @@ +from cookies.resources.helpers import setNoCacheAndCORSHeaders + +def main(request, response): + """Simple handler that causes redirection. + + The request should typically have two query parameters: + status - The status to use for the redirection. Defaults to 302. + location - The resource to redirect to. + """ + status = 302 + if b"status" in request.GET: + try: + status = int(request.GET.first(b"status")) + except ValueError: + pass + headers = setNoCacheAndCORSHeaders(request, response) + + location = request.GET.first(b"location") + + headers.append((b"Location", location)) + + return status, headers, b"" diff --git a/testing/web-platform/tests/cookies/resources/set-cookie.py b/testing/web-platform/tests/cookies/resources/set-cookie.py new file mode 100644 index 0000000000..59b5b8006a --- /dev/null +++ b/testing/web-platform/tests/cookies/resources/set-cookie.py @@ -0,0 +1,45 @@ +from datetime import date + +def main(request, response): + """ + Returns cookie name and path from query params in a Set-Cookie header. + + e.g. + + > GET /cookies/resources/set-cookie.py?name=match-slash&path=%2F HTTP/1.1 + > Host: localhost:8000 + > User-Agent: curl/7.43.0 + > Accept: */* + > + < HTTP/1.1 200 OK + < Content-Type: application/json + < Set-Cookie: match-slash=1; Path=/; Expires=09 Jun 2021 10:18:14 GMT + < Server: BaseHTTP/0.3 Python/2.7.12 + < Date: Tue, 04 Oct 2016 18:16:06 GMT + < Content-Length: 80 + """ + + name = request.GET[b'name'] + path = request.GET[b'path'] + samesite = request.GET.get(b'samesite') + secure = b'secure' in request.GET + expiry_year = date.today().year + 1 + cookie = b"%s=1; Path=%s; Expires=09 Jun %d 10:18:14 GMT" % (name, path, expiry_year) + if samesite: + cookie += b";SameSite=%s" % samesite + if secure: + cookie += b";Secure" + + headers = [ + (b"Content-Type", b"application/json"), + (b"Set-Cookie", cookie) + ] + + # Set the cors enabled headers. + origin = request.headers.get(b"Origin") + if origin is not None and origin != b"null": + headers.append((b"Access-Control-Allow-Origin", origin)) + headers.append((b"Access-Control-Allow-Credentials", 'true')) + + body = b"var dummy='value';" + return headers, body diff --git a/testing/web-platform/tests/cookies/resources/set.py b/testing/web-platform/tests/cookies/resources/set.py new file mode 100644 index 0000000000..eda9338c92 --- /dev/null +++ b/testing/web-platform/tests/cookies/resources/set.py @@ -0,0 +1,15 @@ +from cookies.resources import helpers +from urllib.parse import unquote + +from wptserve.utils import isomorphic_encode + +def main(request, response): + """Respond to `/cookie/set?{cookie}` by echoing `{cookie}` as a `Set-Cookie` header.""" + headers = helpers.setNoCacheAndCORSHeaders(request, response) + + # Cookies may require whitespace (e.g. in the `Expires` attribute), so the + # query string should be decoded. + cookie = unquote(request.url_parts.query) + headers.append((b"Set-Cookie", isomorphic_encode(cookie))) + + return headers, b'{"success": true}' diff --git a/testing/web-platform/tests/cookies/resources/setSameSite.py b/testing/web-platform/tests/cookies/resources/setSameSite.py new file mode 100644 index 0000000000..05f0967088 --- /dev/null +++ b/testing/web-platform/tests/cookies/resources/setSameSite.py @@ -0,0 +1,32 @@ +from cookies.resources.helpers import makeCookieHeader, setNoCacheAndCORSHeaders + +from wptserve.utils import isomorphic_encode + +def main(request, response): + """Respond to `/cookie/set/samesite?{value}` by setting four cookies: + 1. `samesite_strict={value};SameSite=Strict;path=/` + 2. `samesite_lax={value};SameSite=Lax;path=/` + 3. `samesite_none={value};SameSite=None;path=/` + 4. `samesite_unspecified={value};path=/` + Then navigate to a page that will post a message back to the opener with the set cookies""" + headers = setNoCacheAndCORSHeaders(request, response) + value = isomorphic_encode(request.url_parts.query) + + headers.append((b"Content-Type", b"text/html; charset=utf-8")) + headers.append(makeCookieHeader(b"samesite_strict", value, {b"SameSite":b"Strict", b"path":b"/"})) + headers.append(makeCookieHeader(b"samesite_lax", value, {b"SameSite":b"Lax", b"path":b"/"})) + # SameSite=None cookies must be Secure. + headers.append(makeCookieHeader(b"samesite_none", value, {b"SameSite":b"None", b"path":b"/", b"Secure": b""})) + headers.append(makeCookieHeader(b"samesite_unspecified", value, {b"path":b"/"})) + + document = b""" +<!DOCTYPE html> +<script> + // A same-site navigation, which should attach all cookies including SameSite ones. + // This is necessary because this page may have been reached via a cross-site navigation, so + // we might not have access to some SameSite cookies from here. + window.location = "../samesite/resources/echo-cookies.html"; +</script> +""" + + return headers, document diff --git a/testing/web-platform/tests/cookies/resources/setSameSiteDomain.py b/testing/web-platform/tests/cookies/resources/setSameSiteDomain.py new file mode 100644 index 0000000000..c8b7a71981 --- /dev/null +++ b/testing/web-platform/tests/cookies/resources/setSameSiteDomain.py @@ -0,0 +1,36 @@ +from cookies.resources.helpers import makeCookieHeader, setNoCacheAndCORSHeaders + +from wptserve.utils import isomorphic_encode + +def main(request, response): + """Respond to `/cookie/set/samesite?{value}` by setting four cookies: + 1. `samesite_strict={value};SameSite=Strict;path=/;domain={host}` + 2. `samesite_lax={value};SameSite=Lax;path=/;domain={host}` + 3. `samesite_none={value};SameSite=None;path=/;Secure;domain={host}` + 4. `samesite_unspecified={value};path=/;domain={host}` + Where {host} is the hostname from which this page is served. (Requesting this resource + without a Host header will result in a 500 server error.) + Then navigate to a page that will post a message back to the opener with the set cookies""" + headers = setNoCacheAndCORSHeaders(request, response) + value = isomorphic_encode(request.url_parts.query) + host_header = request.headers['host'] + hostname = host_header.split(b":")[0] + host = isomorphic_encode(hostname) + headers.append((b"Content-Type", b"text/html; charset=utf-8")) + headers.append(makeCookieHeader(b"samesite_strict", value, {b"SameSite":b"Strict", b"path":b"/", b"domain":host})) + headers.append(makeCookieHeader(b"samesite_lax", value, {b"SameSite":b"Lax", b"path":b"/", b"domain":host})) + # SameSite=None cookies must be Secure. + headers.append(makeCookieHeader(b"samesite_none", value, {b"SameSite":b"None", b"path":b"/", b"Secure": b"", b"domain":host})) + headers.append(makeCookieHeader(b"samesite_unspecified", value, {b"path":b"/", b"domain":host})) + + document = b""" +<!DOCTYPE html> +<script> + // A same-site navigation, which should attach all cookies including SameSite ones. + // This is necessary because this page may have been reached via a cross-site navigation, so + // we might not have access to some SameSite cookies from here. + window.location = "../samesite/resources/echo-cookies.html"; +</script> +""" + + return headers, document diff --git a/testing/web-platform/tests/cookies/resources/setSameSiteMultiAttribute.py b/testing/web-platform/tests/cookies/resources/setSameSiteMultiAttribute.py new file mode 100644 index 0000000000..988f67f0b0 --- /dev/null +++ b/testing/web-platform/tests/cookies/resources/setSameSiteMultiAttribute.py @@ -0,0 +1,60 @@ +from cookies.resources.helpers import makeCookieHeader, setNoCacheAndCORSHeaders + +from wptserve.utils import isomorphic_encode + +def main(request, response): + """Respond to `/cookie/set/samesite?{value}` by setting the following combination of cookies: + 1. `samesite_unsupported={value};SameSite=Unsupported;path=/;Secure` + 2. `samesite_unsupported_none={value};SameSite=Unsupported;SameSite=None;path=/;Secure` + 3. `samesite_unsupported_lax={value};SameSite=Unsupported;SameSite=Lax;path=/` + 4. `samesite_unsupported_strict={value};SameSite=Unsupported;SameSite=Strict;path=/` + 5. `samesite_none_unsupported={value};SameSite=None;SameSite=Unsupported;path=/;Secure` + 6. `samesite_lax_unsupported={value};SameSite=Lax;SameSite=Unsupported;path=/;Secure` + 7. `samesite_strict_unsupported={value};SameSite=Strict;SameSite=Unsupported;path=/;Secure` + 8. `samesite_lax_none={value};SameSite=Lax;SameSite=None;path=/;Secure` + 9. `samesite_lax_strict={value};SameSite=Lax;SameSite=Strict;path=/` + 10. `samesite_strict_lax={value};SameSite=Strict;SameSite=Lax;path=/` + Then navigate to a page that will post a message back to the opener with the set cookies""" + headers = setNoCacheAndCORSHeaders(request, response) + value = isomorphic_encode(request.url_parts.query) + + headers.append((b"Content-Type", b"text/html; charset=utf-8")) + # Unknown value; single attribute + headers.append(makeCookieHeader( + b"samesite_unsupported", value, {b"SameSite":b"Unsupported", b"path":b"/", b"Secure":b""})) + + # Multiple attributes; first attribute unknown + headers.append(makeCookieHeader( + b"samesite_unsupported_none", value, {b"SameSite":b"Unsupported", b"SameSite":b"None", b"path":b"/", b"Secure":b""})) + headers.append(makeCookieHeader( + b"samesite_unsupported_lax", value, {b"SameSite":b"Unsupported", b"SameSite":b"Lax", b"path":b"/"})) + headers.append(makeCookieHeader( + b"samesite_unsupported_strict", value, {b"SameSite":b"Unsupported", b"SameSite":b"Strict", b"path":b"/"})) + + # Multiple attributes; second attribute unknown + headers.append(makeCookieHeader( + b"samesite_none_unsupported", value, {b"SameSite":b"None", b"SameSite":b"Unsupported", b"path":b"/", b"Secure":b""})) + headers.append(makeCookieHeader( + b"samesite_lax_unsupported", value, {b"SameSite":b"Lax", b"SameSite":b"Unsupported", b"path":b"/", b"Secure":b""})) + headers.append(makeCookieHeader( + b"samesite_strict_unsupported", value, {b"SameSite":b"Strict", b"SameSite":b"Unsupported", b"path":b"/", b"Secure":b""})) + + # Multiple attributes; both known + headers.append(makeCookieHeader( + b"samesite_lax_none", value, {b"SameSite":b"Lax", b"SameSite":b"None", b"path":b"/", b"Secure":b""})) + headers.append(makeCookieHeader( + b"samesite_lax_strict", value, {b"SameSite":b"Lax", b"SameSite":b"Strict", b"path":b"/"})) + headers.append(makeCookieHeader( + b"samesite_strict_lax", value, {b"SameSite":b"Strict", b"SameSite":b"Lax", b"path":b"/"})) + + document = b""" +<!DOCTYPE html> +<script> + // A same-site navigation, which should attach all cookies including SameSite ones. + // This is necessary because this page may have been reached via a cross-site navigation, so + // we might not have access to some SameSite cookies from here. + window.location = "../samesite/resources/echo-cookies.html"; +</script> +""" + + return headers, document diff --git a/testing/web-platform/tests/cookies/resources/setSameSiteNone.py b/testing/web-platform/tests/cookies/resources/setSameSiteNone.py new file mode 100644 index 0000000000..446c75eb44 --- /dev/null +++ b/testing/web-platform/tests/cookies/resources/setSameSiteNone.py @@ -0,0 +1,16 @@ +from cookies.resources.helpers import makeCookieHeader, setNoCacheAndCORSHeaders + +from wptserve.utils import isomorphic_encode + +def main(request, response): + """Respond to `/cookies/resources/setSameSiteNone.py?{value}` by setting two cookies: + 1. `samesite_none_insecure={value};SameSite=None;path=/` + 2. `samesite_none_secure={value};SameSite=None;Secure;path=/` + """ + headers = setNoCacheAndCORSHeaders(request, response) + value = isomorphic_encode(request.url_parts.query) + + headers.append(makeCookieHeader(b"samesite_none_insecure", value, {b"SameSite":b"None", b"path":b"/"})) + headers.append(makeCookieHeader(b"samesite_none_secure", value, {b"SameSite":b"None", b"Secure":b"", b"path":b"/"})) + + return headers, b'{"success": true}' diff --git a/testing/web-platform/tests/cookies/resources/setSecure.py b/testing/web-platform/tests/cookies/resources/setSecure.py new file mode 100644 index 0000000000..dd0dd1622b --- /dev/null +++ b/testing/web-platform/tests/cookies/resources/setSecure.py @@ -0,0 +1,14 @@ +from cookies.resources.helpers import makeCookieHeader, readParameter, setNoCacheAndCORSHeaders + +from wptserve.utils import isomorphic_encode + +def main(request, response): + """Respond to `/cookie/set/secure?{value}` by setting two cookies: + alone_secure={value};secure;path=/` + alone_insecure={value};path=/""" + headers = setNoCacheAndCORSHeaders(request, response) + value = isomorphic_encode(request.url_parts.query) + + headers.append(makeCookieHeader(b"alone_secure", value, {b"secure": b"", b"path": b"/"})) + headers.append(makeCookieHeader(b"alone_insecure", value, {b"path": b"/"})) + return headers, b'{"success": true}' diff --git a/testing/web-platform/tests/cookies/resources/testharness-helpers.js b/testing/web-platform/tests/cookies/resources/testharness-helpers.js new file mode 100644 index 0000000000..84368d6d99 --- /dev/null +++ b/testing/web-platform/tests/cookies/resources/testharness-helpers.js @@ -0,0 +1,49 @@ +// Given an array of potentially asynchronous tests, this function will execute +// each in serial, ensuring that one and only one test is executing at a time. +// +// The test array should look like this: +// +// +// var tests = [ +// [ +// "Test description goes here.", +// function () { +// // Test code goes here. `this` is bound to the test object. +// } +// ], +// ... +// ]; +// +// The |setup| and |teardown| arguments are functions which are executed before +// and after each test, respectively. +function executeTestsSerially(testList, setup, teardown) { + var tests = testList.map(function (t) { + return { + test: async_test(t[0]), + code: t[1] + }; + }); + + var executeNextTest = function () { + var current = tests.shift(); + if (current === undefined) { + return; + } + + // Setup the test fixtures. + if (setup) { + setup(); + } + + // Bind a callback to tear down the test fixtures. + if (teardown) { + current.test.add_cleanup(teardown); + } + + // Execute the test. + current.test.step(current.code); + }; + + add_result_callback(function () { setTimeout(executeNextTest, 0) }); + executeNextTest(); +} diff --git a/testing/web-platform/tests/cookies/samesite-none-secure/cookies-without-samesite-must-be-secure.https.html b/testing/web-platform/tests/cookies/samesite-none-secure/cookies-without-samesite-must-be-secure.https.html new file mode 100644 index 0000000000..18cf0516e6 --- /dev/null +++ b/testing/web-platform/tests/cookies/samesite-none-secure/cookies-without-samesite-must-be-secure.https.html @@ -0,0 +1,20 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<meta name="timeout" content="long"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/cookies/resources/cookie-helper.sub.js"></script> +<script> +promise_test(t => { + var value = "" + Math.random(); + return resetSameSiteNoneCookies(SECURE_ORIGIN, value) + .then(_ => { + return credFetch(SECURE_ORIGIN + "/cookies/resources/list.py") + .then(r => r.json()) + .then(cookies => { + assert_not_equals(cookies["samesite_none_insecure"], value, "Non-Secure SameSite=None cookie is rejected."); + assert_equals(cookies["samesite_none_secure"], value, "Secure SameSite=None cookie is set."); + }) + }); +}, "SameSite=None cookies are rejected unless the Secure attribute is set."); +</script> diff --git a/testing/web-platform/tests/cookies/samesite/about-blank-nested.https.html b/testing/web-platform/tests/cookies/samesite/about-blank-nested.https.html new file mode 100644 index 0000000000..d5c9b8ada3 --- /dev/null +++ b/testing/web-platform/tests/cookies/samesite/about-blank-nested.https.html @@ -0,0 +1,29 @@ +<!DOCTYPE html> +<head> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <script src="/cookies/resources/cookie-helper.sub.js"></script> +</head> +<body onload="doTests()"> + <iframe src="about:blank" id="if"> + </iframe> + <script> + function doTests() { + promise_test(async function(t) { + var child = document.getElementById("if"); + var grandKid = child.contentDocument.createElement("iframe"); + child.contentDocument.body.appendChild(grandKid); + var value = "" + Math.random(); + await resetSameSiteCookies(SECURE_ORIGIN, value); + + // Using postToParent.py here to see cookies used when navigating the page. + grandKid.src = SECURE_ORIGIN + "/cookies/resources/postToParent.py" + var e = await wait_for_message("COOKIES", SECURE_ORIGIN); + assert_cookie(SECURE_ORIGIN, e.data, "samesite_unspecified", value, true); + assert_cookie(SECURE_ORIGIN, e.data, "samesite_lax", value, true); + assert_cookie(SECURE_ORIGIN, e.data, "samesite_strict", value, true); + assert_cookie(SECURE_ORIGIN, e.data, "samesite_none", value, true); + }, "SameSite cookies with intervening about:blank iframes and navigation"); + } + </script> +</body> diff --git a/testing/web-platform/tests/cookies/samesite/about-blank-subresource.https.html b/testing/web-platform/tests/cookies/samesite/about-blank-subresource.https.html new file mode 100644 index 0000000000..0a19f30d34 --- /dev/null +++ b/testing/web-platform/tests/cookies/samesite/about-blank-subresource.https.html @@ -0,0 +1,31 @@ +<!DOCTYPE html> +<head> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <script src="/cookies/resources/cookie-helper.sub.js"></script> +</head> +<body onload="doTests()"> + <script> + function doTests() { + promise_test(async function(t) { + var child = window.open(""); + child.onmessage = (ev) => { + child.opener.postMessage(ev.data, '*'); + }; + var grandKid = child.document.createElement("iframe"); + child.document.body.appendChild(grandKid); + var value = "" + Math.random(); + await resetSameSiteCookies(SECURE_ORIGIN, value); + + // Load at what cookies a subresource below an about:blank iframe + // inheritting this origin gets. + grandKid.src = SECURE_ORIGIN + "/cookies/samesite/resources/iframe-subresource-report.html" + var e = await wait_for_message("COOKIES", SECURE_ORIGIN); + assert_cookie(SECURE_ORIGIN, e.data, "samesite_unspecified", value, true); + assert_cookie(SECURE_ORIGIN, e.data, "samesite_lax", value, true); + assert_cookie(SECURE_ORIGIN, e.data, "samesite_strict", value, true); + assert_cookie(SECURE_ORIGIN, e.data, "samesite_none", value, true); + }, "SameSite cookies on subresource of top-level about:blank window"); + } + </script> +</body> diff --git a/testing/web-platform/tests/cookies/samesite/about-blank-toplevel.https.html b/testing/web-platform/tests/cookies/samesite/about-blank-toplevel.https.html new file mode 100644 index 0000000000..3a4dde7673 --- /dev/null +++ b/testing/web-platform/tests/cookies/samesite/about-blank-toplevel.https.html @@ -0,0 +1,30 @@ +<!DOCTYPE html> +<head> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <script src="/cookies/resources/cookie-helper.sub.js"></script> +</head> +<body onload="doTests()"> + <script> + function doTests() { + promise_test(async function(t) { + var child = window.open(""); + child.onmessage = (ev) => { + child.opener.postMessage(ev.data, '*'); + }; + var grandKid = child.document.createElement("iframe"); + child.document.body.appendChild(grandKid); + var value = "" + Math.random(); + await resetSameSiteCookies(SECURE_ORIGIN, value); + + // Using postToParent.py here to see cookies used when navigating the page. + grandKid.src = SECURE_ORIGIN + "/cookies/resources/postToParent.py" + var e = await wait_for_message("COOKIES", SECURE_ORIGIN); + assert_cookie(SECURE_ORIGIN, e.data, "samesite_unspecified", value, true); + assert_cookie(SECURE_ORIGIN, e.data, "samesite_lax", value, true); + assert_cookie(SECURE_ORIGIN, e.data, "samesite_strict", value, true); + assert_cookie(SECURE_ORIGIN, e.data, "samesite_none", value, true); + }, "SameSite cookies with top-level about:blank window"); + } + </script> +</body> diff --git a/testing/web-platform/tests/cookies/samesite/fetch.https.html b/testing/web-platform/tests/cookies/samesite/fetch.https.html new file mode 100644 index 0000000000..79e49009fb --- /dev/null +++ b/testing/web-platform/tests/cookies/samesite/fetch.https.html @@ -0,0 +1,40 @@ +<!DOCTYPE html> +<meta charset="utf-8"/> +<meta name="timeout" content="long"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/cookies/resources/cookie-helper.sub.js"></script> +<script> + function create_test(origin, target, expectedStatus, title) { + promise_test(t => { + var value = "" + Math.random(); + return resetSameSiteCookies(origin, value) + .then(_ => { + return credFetch(target + "/cookies/resources/list.py") + + .then(r => r.json()) + .then(cookies => verifySameSiteCookieState(expectedStatus, value, cookies, DomSameSiteStatus.SAME_SITE)); + }); + }, title); + } + + // No redirect: + create_test(SECURE_ORIGIN, SECURE_ORIGIN, SameSiteStatus.STRICT, "Same-host fetches are strictly same-site"); + create_test(SECURE_SUBDOMAIN_ORIGIN, SECURE_SUBDOMAIN_ORIGIN, SameSiteStatus.STRICT, "Subdomain fetches are strictly same-site"); + create_test(SECURE_CROSS_SITE_ORIGIN, SECURE_CROSS_SITE_ORIGIN, SameSiteStatus.CROSS_SITE, "Cross-site fetches are cross-site"); + + // Redirect from {same-host,subdomain,cross-site} to same-host: + create_test(SECURE_ORIGIN, redirectTo(SECURE_ORIGIN, SECURE_ORIGIN), SameSiteStatus.STRICT, "Same-host redirecting to same-host fetches are strictly same-site"); + create_test(SECURE_ORIGIN, redirectTo(SECURE_SUBDOMAIN_ORIGIN, SECURE_ORIGIN), SameSiteStatus.STRICT, "Subdomain redirecting to same-host fetches are strictly same-site"); + create_test(SECURE_ORIGIN, redirectTo(SECURE_CROSS_SITE_ORIGIN, SECURE_ORIGIN), SameSiteStatus.CROSS_SITE, "Cross-site redirecting to same-host fetches are cross-site"); + + // Redirect from {same-host,subdomain,cross-site} to same-host: + create_test(SECURE_SUBDOMAIN_ORIGIN, redirectTo(SECURE_ORIGIN, SECURE_SUBDOMAIN_ORIGIN), SameSiteStatus.STRICT, "Same-host redirecting to subdomain fetches are strictly same-site"); + create_test(SECURE_SUBDOMAIN_ORIGIN, redirectTo(SECURE_SUBDOMAIN_ORIGIN, SECURE_SUBDOMAIN_ORIGIN), SameSiteStatus.STRICT, "Subdomain redirecting to subdomain fetches are strictly same-site"); + create_test(SECURE_SUBDOMAIN_ORIGIN, redirectTo(SECURE_CROSS_SITE_ORIGIN, SECURE_SUBDOMAIN_ORIGIN), SameSiteStatus.CROSS_SITE, "Cross-site redirecting to subdomain fetches are cross-site"); + + // Redirect from {same-host,subdomain,cross-site} to cross-site: + create_test(SECURE_CROSS_SITE_ORIGIN, redirectTo(SECURE_ORIGIN, SECURE_CROSS_SITE_ORIGIN), SameSiteStatus.CROSS_SITE, "Same-host redirecting to cross-site fetches are cross-site"); + create_test(SECURE_CROSS_SITE_ORIGIN, redirectTo(SECURE_SUBDOMAIN_ORIGIN, SECURE_CROSS_SITE_ORIGIN), SameSiteStatus.CROSS_SITE, "Subdomain redirecting to cross-site fetches are cross-site"); + create_test(SECURE_CROSS_SITE_ORIGIN, redirectTo(SECURE_CROSS_SITE_ORIGIN, SECURE_CROSS_SITE_ORIGIN), SameSiteStatus.CROSS_SITE, "Cross-site redirecting to cross-site fetches are cross-site"); +</script> diff --git a/testing/web-platform/tests/cookies/samesite/form-get-blank-reload.https.html b/testing/web-platform/tests/cookies/samesite/form-get-blank-reload.https.html new file mode 100644 index 0000000000..b5ab8ade91 --- /dev/null +++ b/testing/web-platform/tests/cookies/samesite/form-get-blank-reload.https.html @@ -0,0 +1,66 @@ +<!DOCTYPE html> +<meta charset="utf-8"/> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/cookies/resources/cookie-helper.sub.js"></script> +<script> + // This test creates a form whose submission GETs the page postToParent.py + // (on the specified origin) in a popup window. The popup sends a postMessage + // event back to its opener (i.e., here) with the cookies it received, which + // we verify against expectedStatus. Then, the test sends a message to the + // popup, telling it to reload itself via window.location.reload(). Again, + // the popup posts a message back here with the cookies it received. These + // cookies are verified against expectedStatusReload. + function create_test(origin, target, expectedStatus, expectedStatusReload, title) { + promise_test(t => { + var value = "" + Math.random(); + return resetSameSiteCookies(origin, value) + .then(_ => { + return new Promise((resolve, reject) => { + var f = document.createElement('form'); + f.action = target + "/cookies/resources/postToParent.py"; + f.target = "_blank"; + f.method = "GET"; + f.rel = "opener"; + + // If |target| contains a `redir` parameter, extract it, and add it + // to the form so it doesn't get dropped in the submission. + var url = new URL(f.action); + if (url.pathname = "/cookies/rfc6265/resources/redirectWithCORSHeaders.py") { + var i = document.createElement("input"); + i.name = "location"; + i.value = url.searchParams.get("location"); + i.type = "hidden"; + f.appendChild(i); + } + var reloaded = false; + var msgHandler = e => { + try { + verifySameSiteCookieState(reloaded ? expectedStatusReload : expectedStatus, value, e.data, DomSameSiteStatus.SAME_SITE); + } catch (e) { + reject(e); + } + + if (reloaded) { + window.removeEventListener("message", msgHandler); + e.source.close(); + resolve("Popup received the cookie."); + } else { + reloaded = true; + e.source.postMessage("reload", "*"); + } + }; + window.addEventListener("message", msgHandler); + document.body.appendChild(f); + + f.submit(); + }); + }); + }, title); + } + + // The reload status is always strictly same-site because this is a site-initiated reload, as opposed to a reload triggered by a user interface element. + create_test(SECURE_ORIGIN, SECURE_ORIGIN, SameSiteStatus.STRICT, SameSiteStatus.STRICT, "Reloaded same-host top-level form GETs are strictly same-site"); + create_test(SECURE_SUBDOMAIN_ORIGIN, SECURE_SUBDOMAIN_ORIGIN, SameSiteStatus.STRICT, SameSiteStatus.STRICT, "Reloaded subdomain top-level form GETs are strictly same-site"); + create_test(SECURE_CROSS_SITE_ORIGIN, SECURE_CROSS_SITE_ORIGIN, SameSiteStatus.LAX, SameSiteStatus.STRICT, "Reloaded cross-site top-level form GETs are strictly same-site"); +</script> diff --git a/testing/web-platform/tests/cookies/samesite/form-get-blank.https.html b/testing/web-platform/tests/cookies/samesite/form-get-blank.https.html new file mode 100644 index 0000000000..2fe81b2b56 --- /dev/null +++ b/testing/web-platform/tests/cookies/samesite/form-get-blank.https.html @@ -0,0 +1,68 @@ +<!DOCTYPE html> +<meta charset="utf-8"/> +<meta name="timeout" content="long"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/cookies/resources/cookie-helper.sub.js"></script> +<script> + function create_test(origin, target, expectedStatus, title) { + promise_test(t => { + var value = "" + Math.random(); + return resetSameSiteCookies(origin, value) + .then(_ => { + return new Promise((resolve, reject) => { + var f = document.createElement('form'); + f.action = target + "/cookies/resources/postToParent.py"; + f.target = "_blank"; + f.method = "GET"; + f.rel = "opener"; + + // If |target| contains a `redir` parameter, extract it, and add it + // to the form so it doesn't get dropped in the submission. + var url = new URL(f.action); + if (url.pathname == "/cookies/resources/redirectWithCORSHeaders.py") { + var i = document.createElement("input"); + i.name = "location"; + i.type="hidden"; + i.value = url.searchParams.get("location"); + f.appendChild(i); + } + + var msgHandler = e => { + window.removeEventListener("message", msgHandler); + e.source.close(); + try { + verifySameSiteCookieState(expectedStatus, value, e.data, DomSameSiteStatus.SAME_SITE); + resolve("Popup received the cookie."); + } catch (e) { + reject(e); + } + }; + window.addEventListener("message", msgHandler); + document.body.appendChild(f); + f.submit(); + }); + }); + }, title); + } + + // No redirect: + create_test(SECURE_ORIGIN, SECURE_ORIGIN, SameSiteStatus.STRICT, "Same-host top-level form GETs are strictly same-site"); + create_test(SECURE_SUBDOMAIN_ORIGIN, SECURE_SUBDOMAIN_ORIGIN, SameSiteStatus.STRICT, "Subdomain top-level form GETs are strictly same-site"); + create_test(SECURE_CROSS_SITE_ORIGIN, SECURE_CROSS_SITE_ORIGIN, SameSiteStatus.LAX, "Cross-site top-level form GETs are laxly same-site"); + + // Redirect from {same-host,subdomain,cross-site} to same-host: + create_test(SECURE_ORIGIN, redirectTo(SECURE_ORIGIN, SECURE_ORIGIN), SameSiteStatus.STRICT, "Same-host redirecting to same-host top-level form GETs are strictly same-site"); + create_test(SECURE_ORIGIN, redirectTo(SECURE_SUBDOMAIN_ORIGIN, SECURE_ORIGIN), SameSiteStatus.STRICT, "Subdomain redirecting to same-host top-level form GETs are strictly same-site"); + create_test(SECURE_ORIGIN, redirectTo(SECURE_CROSS_SITE_ORIGIN, SECURE_ORIGIN), SameSiteStatus.LAX, "Cross-site redirecting to same-host top-level form GETs are laxly same-site"); + + // Redirect from {same-host,subdomain,cross-site} to same-host: + create_test(SECURE_SUBDOMAIN_ORIGIN, redirectTo(SECURE_ORIGIN, SECURE_SUBDOMAIN_ORIGIN), SameSiteStatus.STRICT, "Same-host redirecting to subdomain top-level form GETs are strictly same-site"); + create_test(SECURE_SUBDOMAIN_ORIGIN, redirectTo(SECURE_SUBDOMAIN_ORIGIN, SECURE_SUBDOMAIN_ORIGIN), SameSiteStatus.STRICT, "Subdomain redirecting to subdomain top-level form GETs are strictly same-site"); + create_test(SECURE_SUBDOMAIN_ORIGIN, redirectTo(SECURE_CROSS_SITE_ORIGIN, SECURE_SUBDOMAIN_ORIGIN), SameSiteStatus.LAX, "Cross-site redirecting to subdomain top-level form GETs are laxly same-site"); + + // Redirect from {same-host,subdomain,cross-site} to cross-site: + create_test(SECURE_CROSS_SITE_ORIGIN, redirectTo(SECURE_ORIGIN, SECURE_CROSS_SITE_ORIGIN), SameSiteStatus.LAX, "Same-host redirecting to cross-site top-level form GETs are laxly same-site"); + create_test(SECURE_CROSS_SITE_ORIGIN, redirectTo(SECURE_SUBDOMAIN_ORIGIN, SECURE_CROSS_SITE_ORIGIN), SameSiteStatus.LAX, "Subdomain redirecting to cross-site top-level form GETs are laxly same-site"); + create_test(SECURE_CROSS_SITE_ORIGIN, redirectTo(SECURE_CROSS_SITE_ORIGIN, SECURE_CROSS_SITE_ORIGIN), SameSiteStatus.LAX, "Cross-site redirecting to cross-site top-level form GETs are laxly same-site"); +</script> diff --git a/testing/web-platform/tests/cookies/samesite/form-post-blank-reload.https.html b/testing/web-platform/tests/cookies/samesite/form-post-blank-reload.https.html new file mode 100644 index 0000000000..cdbb89ace5 --- /dev/null +++ b/testing/web-platform/tests/cookies/samesite/form-post-blank-reload.https.html @@ -0,0 +1,56 @@ +<!DOCTYPE html> +<meta charset="utf-8"/> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/cookies/resources/cookie-helper.sub.js"></script> +<script> + // This test creates a form whose submission POSTs to the page postToParent.py + // (on the specified origin) in a popup window. The popup sends a postMessage + // event back to its opener (i.e., here) with the cookies it received, which + // we verify against expectedStatus. Then, the test sends a message to the + // popup, telling it to reload itself via window.location.reload(). Again, + // the popup posts a message back here with the cookies it received. These + // cookies are verified against expectedStatusReload. + function create_test(origin, target, expectedStatus, expectedStatusReload, title) { + promise_test(t => { + var value = "" + Math.random(); + return resetSameSiteCookies(origin, value) + .then(_ => { + return new Promise((resolve, reject) => { + var f = document.createElement('form'); + f.action = target + "/cookies/resources/postToParent.py"; + f.target = "_blank"; + f.method = "POST"; + f.rel = "opener"; + + var reloaded = false; + var msgHandler = e => { + try { + verifySameSiteCookieState(reloaded ? expectedStatusReload : expectedStatus, value, e.data, DomSameSiteStatus.SAME_SITE); + } catch (e) { + reject(e); + } + + if (reloaded) { + window.removeEventListener("message", msgHandler); + e.source.close(); + resolve("Popup received the cookie."); + } else { + reloaded = true; + e.source.postMessage("reload", "*"); + } + }; + window.addEventListener("message", msgHandler); + + document.body.appendChild(f); + f.submit(); + }); + }); + }, title); + } + + // The reload status is always strictly same-site because this is a site-initiated reload, as opposed to a reload triggered by a user interface element. + create_test(SECURE_ORIGIN, SECURE_ORIGIN, SameSiteStatus.STRICT, SameSiteStatus.STRICT, "Reloaded same-host top-level form POSTs are strictly same-site"); + create_test(SECURE_SUBDOMAIN_ORIGIN, SECURE_SUBDOMAIN_ORIGIN, SameSiteStatus.STRICT, SameSiteStatus.STRICT, "Reloaded subdomain top-level form POSTs are strictly same-site"); + create_test(SECURE_CROSS_SITE_ORIGIN, SECURE_CROSS_SITE_ORIGIN, SameSiteStatus.CROSS_SITE, SameSiteStatus.STRICT, "Reloaded cross-site top-level form POSTs are strictly same-site"); +</script> diff --git a/testing/web-platform/tests/cookies/samesite/form-post-blank.https.html b/testing/web-platform/tests/cookies/samesite/form-post-blank.https.html new file mode 100644 index 0000000000..947853893d --- /dev/null +++ b/testing/web-platform/tests/cookies/samesite/form-post-blank.https.html @@ -0,0 +1,57 @@ +<!DOCTYPE html> +<meta charset="utf-8"/> +<meta name="timeout" content="long"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/cookies/resources/cookie-helper.sub.js"></script> +<script> + function create_test(origin, target, expectedStatus, title) { + promise_test(t => { + var value = "" + Math.random(); + return resetSameSiteCookies(origin, value) + .then(_ => { + return new Promise((resolve, reject) => { + var f = document.createElement('form'); + f.action = target + "/cookies/resources/postToParent.py"; + f.target = "_blank"; + f.method = "POST"; + f.rel = "opener"; + + var msgHandler = e => { + window.removeEventListener("message", msgHandler); + e.source.close(); + try { + verifySameSiteCookieState(expectedStatus, value, e.data, DomSameSiteStatus.SAME_SITE); + resolve("Popup received the cookie."); + } catch (e) { + reject(e); + } + }; + window.addEventListener("message", msgHandler); + document.body.appendChild(f); + f.submit(); + }); + }); + }, title); + } + + // No redirect: + create_test(SECURE_ORIGIN, SECURE_ORIGIN, SameSiteStatus.STRICT, "Same-host top-level form POSTs are strictly same-site"); + create_test(SECURE_SUBDOMAIN_ORIGIN, SECURE_SUBDOMAIN_ORIGIN, SameSiteStatus.STRICT, "Subdomain top-level form POSTs are strictly same-site"); + create_test(SECURE_CROSS_SITE_ORIGIN, SECURE_CROSS_SITE_ORIGIN, SameSiteStatus.CROSS_SITE, "Cross-site top-level form POSTs are cross-site"); + + // Redirect from {same-host,subdomain,cross-site} to same-host: + create_test(SECURE_ORIGIN, redirectTo(SECURE_ORIGIN, SECURE_ORIGIN), SameSiteStatus.STRICT, "Same-host redirecting to same-host top-level form POSTs are strictly same-site"); + create_test(SECURE_ORIGIN, redirectTo(SECURE_SUBDOMAIN_ORIGIN, SECURE_ORIGIN), SameSiteStatus.STRICT, "Subdomain redirecting to same-host top-level form POSTs are strictly same-site"); + create_test(SECURE_ORIGIN, redirectTo(SECURE_CROSS_SITE_ORIGIN, SECURE_ORIGIN), SameSiteStatus.CROSS_SITE, "Cross-site redirecting to same-host top-level form POSTs are cross-site"); + + // Redirect from {same-host,subdomain,cross-site} to same-host: + create_test(SECURE_SUBDOMAIN_ORIGIN, redirectTo(SECURE_ORIGIN, SECURE_SUBDOMAIN_ORIGIN), SameSiteStatus.STRICT, "Same-host redirecting to subdomain top-level form POSTs are strictly same-site"); + create_test(SECURE_SUBDOMAIN_ORIGIN, redirectTo(SECURE_SUBDOMAIN_ORIGIN, SECURE_SUBDOMAIN_ORIGIN), SameSiteStatus.STRICT, "Subdomain redirecting to subdomain top-level form POSTs are strictly same-site"); + create_test(SECURE_SUBDOMAIN_ORIGIN, redirectTo(SECURE_CROSS_SITE_ORIGIN, SECURE_SUBDOMAIN_ORIGIN), SameSiteStatus.CROSS_SITE, "Cross-site redirecting to subdomain top-level form POSTs are cross-site"); + + // Redirect from {same-host,subdomain,cross-site} to cross-site: + create_test(SECURE_CROSS_SITE_ORIGIN, redirectTo(SECURE_ORIGIN, SECURE_CROSS_SITE_ORIGIN), SameSiteStatus.CROSS_SITE, "Same-host redirecting to cross-site top-level form POSTs are cross-site"); + create_test(SECURE_CROSS_SITE_ORIGIN, redirectTo(SECURE_SUBDOMAIN_ORIGIN, SECURE_CROSS_SITE_ORIGIN), SameSiteStatus.CROSS_SITE, "Subdomain redirecting to cross-site top-level form POSTs are cross-site"); + create_test(SECURE_CROSS_SITE_ORIGIN, redirectTo(SECURE_CROSS_SITE_ORIGIN, SECURE_CROSS_SITE_ORIGIN), SameSiteStatus.CROSS_SITE, "Cross-site redirecting to cross-site top-level form POSTs are cross-site"); +</script> diff --git a/testing/web-platform/tests/cookies/samesite/get_all_cookies-default-samesite.html b/testing/web-platform/tests/cookies/samesite/get_all_cookies-default-samesite.html new file mode 100644 index 0000000000..3b1606b7f8 --- /dev/null +++ b/testing/web-platform/tests/cookies/samesite/get_all_cookies-default-samesite.html @@ -0,0 +1,20 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>TestDriver get_all_cookies method w/ default SameSite</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-vendor.js"></script> +<script> +promise_test(async t => { + await test_driver.delete_all_cookies(); + t.add_cleanup(test_driver.delete_all_cookies); + document.cookie = "test0=0"; + const cookies = await test_driver.get_all_cookies(); + assert_equals(cookies.length, 1); + const cookie = cookies[0]; + assert_equals(cookie["name"], "test0"); + assert_equals(cookie["value"], "0"); + assert_equals(cookie["sameSite"], "Lax"); +}, "Get all cookies w/ default SameSite"); +</script> diff --git a/testing/web-platform/tests/cookies/samesite/get_named_cookie-default-samesite.html b/testing/web-platform/tests/cookies/samesite/get_named_cookie-default-samesite.html new file mode 100644 index 0000000000..2ee5d0f29c --- /dev/null +++ b/testing/web-platform/tests/cookies/samesite/get_named_cookie-default-samesite.html @@ -0,0 +1,18 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>TestDriver get_named_cookie method w/ default SameSite</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-vendor.js"></script> +<script> +promise_test(async t => { + await test_driver.delete_all_cookies(); + t.add_cleanup(test_driver.delete_all_cookies); + document.cookie = "test0=0"; + const cookie = await test_driver.get_named_cookie("test0"); + assert_equals(cookie["name"], "test0"); + assert_equals(cookie["value"], "0"); + assert_equals(cookie["sameSite"], "Lax"); +}, "Get Named cookie w/ default SameSite"); +</script> diff --git a/testing/web-platform/tests/cookies/samesite/iframe-reload.https.html b/testing/web-platform/tests/cookies/samesite/iframe-reload.https.html new file mode 100644 index 0000000000..d1916a805c --- /dev/null +++ b/testing/web-platform/tests/cookies/samesite/iframe-reload.https.html @@ -0,0 +1,55 @@ +<!DOCTYPE html> +<meta charset="utf-8"/> +<meta name="timeout" content="long"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/cookies/resources/cookie-helper.sub.js"></script> +<!-- We're appending an <iframe> to the document's body, so execute tests after we have a body --> +<body> +<script> + // This test creates an iframe with postToParent.py on the specified origin, + // which sends a postMessage event with the cookies it received back to the + // parent (i.e., here). Upon receiving the message, the test verifies that the + // correct cookies were sent to the iframe, and posts a message back to the + // iframe telling it to reload itself. Upon reload, the iframe sends a + // postMessage event back to the test with the cookies it received, which are + // again verified. + function create_test(origin, target, expectedStatus, expectedDomStatus, title) { + promise_test(t => { + var value = "" + Math.random(); + return resetSameSiteCookies(origin, value) + .then(_ => { + return new Promise((resolve, reject) => { + var iframe = document.createElement("iframe"); + iframe.onerror = _ => reject("IFrame could not be loaded."); + + var reloaded = false; + var msgHandler = e => { + try { + verifySameSiteCookieState(expectedStatus, value, e.data, expectedDomStatus); + } catch (e) { + reject(e); + } + + if (reloaded) { + window.removeEventListener("message", msgHandler); + document.body.removeChild(iframe); + resolve("IFrame received the cookie."); + } else { + reloaded = true; + e.source.postMessage("reload", "*"); + } + }; + window.addEventListener("message", msgHandler); + + iframe.src = target + "/cookies/resources/postToParent.py"; + document.body.appendChild(iframe); + }); + }); + }, title); + } + + create_test(SECURE_ORIGIN, SECURE_ORIGIN, SameSiteStatus.STRICT, DomSameSiteStatus.SAME_SITE, "Reloaded same-host fetches are strictly same-site"); + create_test(SECURE_SUBDOMAIN_ORIGIN, SECURE_SUBDOMAIN_ORIGIN, SameSiteStatus.STRICT, DomSameSiteStatus.SAME_SITE, "Reloaded subdomain fetches are strictly same-site"); + create_test(SECURE_CROSS_SITE_ORIGIN, SECURE_CROSS_SITE_ORIGIN, SameSiteStatus.CROSS_SITE, DomSameSiteStatus.CROSS_SITE, "Reloaded cross-site fetches are cross-site"); +</script> diff --git a/testing/web-platform/tests/cookies/samesite/iframe.document.https.html b/testing/web-platform/tests/cookies/samesite/iframe.document.https.html new file mode 100644 index 0000000000..2d276dbcaa --- /dev/null +++ b/testing/web-platform/tests/cookies/samesite/iframe.document.https.html @@ -0,0 +1,57 @@ +<!DOCTYPE html> +<meta charset="utf-8"/> +<meta name="timeout" content="long"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/cookies/resources/cookie-helper.sub.js"></script> +<!-- We're appending an <iframe> to the document's body, so execute tests after we have a body --> +<body> +<script> + function create_test(target, expectedDomStatus, title) { + promise_test(async t => { + let cookieValue = await new Promise((resolve, reject) => { + var iframe = document.createElement("iframe"); + + window.onmessage = t.step_func(e => { + if (e.source == iframe.contentWindow) { + document.body.removeChild(iframe); + resolve(e.data.value); + } + }); + + iframe.src = target + "/cookies/samesite/resources/iframe.document.html"; + document.body.appendChild(iframe); + }); + + await new Promise((resolve, reject) => { + var iframe = document.createElement("iframe"); + + window.onmessage = t.step_func(e => { + if (e.source == iframe.contentWindow) { + // Cleanup, then verify cookie state: + document.body.removeChild(iframe); + + const cookies = e.data; + assert_equals(cookies["dc_samesite_none"], cookieValue, "SameSite=none cookies can be set via document.cookies even by cross-origin documents"); + + if (expectedDomStatus === DomSameSiteStatus.SAME_SITE) { + assert_equals(cookies["dc_samesite_lax"], cookieValue, "SameSite=lax cookies can be set via document.cookies by same-site documents"); + assert_equals(cookies["dc_samesite_strict"], cookieValue, "SameSite=strict cookies can be set via document.cookies by same-site documents"); + } else if (expectedDomStatus === DomSameSiteStatus.CROSS_SITE) { + assert_not_equals(cookies["dc_samesite_lax"], cookieValue, "SameSite=lax cookies can be set via document.cookies by same-site documents"); + assert_not_equals(cookies["dc_samesite_strict"], cookieValue, "SameSite=strict cookies can be set via document.cookies by same-site documents"); + } + + resolve(); + } + }); + + iframe.src = target + "/cookies/resources/postToParent.py"; + document.body.appendChild(iframe); + }); + }, title); + } + + create_test(SECURE_ORIGIN, DomSameSiteStatus.SAME_SITE, "Same-site iframes can set lax/strict cookies via document.cookie"); + create_test(SECURE_CROSS_SITE_ORIGIN, DomSameSiteStatus.CROSS_SITE, "Cross-site iframe cannot set lax/strict cookies via document.cookie"); +</script> diff --git a/testing/web-platform/tests/cookies/samesite/iframe.https.html b/testing/web-platform/tests/cookies/samesite/iframe.https.html new file mode 100644 index 0000000000..31b34ccf55 --- /dev/null +++ b/testing/web-platform/tests/cookies/samesite/iframe.https.html @@ -0,0 +1,79 @@ +<!DOCTYPE html> +<meta charset="utf-8"/> +<meta name="timeout" content="long"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/cookies/resources/cookie-helper.sub.js"></script> +<!-- We're appending an <iframe> to the document's body, so execute tests after we have a body --> +<body> +<script> + // This test creates an iframe with postToParent.py on the specified origin, + // which sends a postMessage event with the cookies it received back to the + // parent (i.e., here). Upon receiving the message, the test verifies that the + // correct cookies were sent to the iframe. + function create_test(origin, target, expectedStatus, expectedDomStatus, title) { + promise_test(t => { + var value = "" + Math.random(); + return resetSameSiteCookies(origin, value) + .then(_ => { + return new Promise((resolve, reject) => { + var iframe = document.createElement("iframe"); + iframe.onerror = _ => reject("IFrame could not be loaded."); + + var msgHandler = e => { + if (e.source == iframe.contentWindow) { + // Cleanup, then verify cookie state: + document.body.removeChild(iframe); + window.removeEventListener("message", msgHandler); + try { + verifySameSiteCookieState(expectedStatus, value, e.data, expectedDomStatus); + resolve(); + } catch(e) { + reject(e); + } + } + }; + window.addEventListener("message", msgHandler); + + iframe.src = target + "/cookies/resources/postToParent.py"; + document.body.appendChild(iframe); + }); + }); + }, title); + } + + // No redirect: + create_test(SECURE_ORIGIN, SECURE_ORIGIN, SameSiteStatus.STRICT, DomSameSiteStatus.SAME_SITE, "Same-host fetches are strictly same-site"); + create_test(SECURE_SUBDOMAIN_ORIGIN, SECURE_SUBDOMAIN_ORIGIN, SameSiteStatus.STRICT, DomSameSiteStatus.SAME_SITE, "Subdomain fetches are strictly same-site"); + create_test(SECURE_CROSS_SITE_ORIGIN, SECURE_CROSS_SITE_ORIGIN, SameSiteStatus.CROSS_SITE, DomSameSiteStatus.CROSS_SITE, "Cross-site fetches are cross-site"); + + // Redirect from {same-host,subdomain,cross-site} to same-host: + create_test(SECURE_ORIGIN, redirectTo(SECURE_ORIGIN, SECURE_ORIGIN), SameSiteStatus.STRICT, DomSameSiteStatus.SAME_SITE, "Same-host redirecting to same-host fetches are strictly same-site"); + create_test(SECURE_ORIGIN, redirectTo(SECURE_SUBDOMAIN_ORIGIN, SECURE_ORIGIN), SameSiteStatus.STRICT, DomSameSiteStatus.SAME_SITE, "Subdomain redirecting to same-host fetches are strictly same-site"); + create_test(SECURE_ORIGIN, redirectTo(SECURE_CROSS_SITE_ORIGIN, SECURE_ORIGIN), SameSiteStatus.CROSS_SITE, DomSameSiteStatus.SAME_SITE, "Cross-site redirecting to same-host fetches are cross-site"); + + // Redirect from {same-host,subdomain,cross-site} to subdomain: + create_test(SECURE_SUBDOMAIN_ORIGIN, redirectTo(SECURE_ORIGIN, SECURE_SUBDOMAIN_ORIGIN), SameSiteStatus.STRICT, DomSameSiteStatus.SAME_SITE, "Same-host redirecting to subdomain fetches are strictly same-site"); + create_test(SECURE_SUBDOMAIN_ORIGIN, redirectTo(SECURE_SUBDOMAIN_ORIGIN, SECURE_SUBDOMAIN_ORIGIN), SameSiteStatus.STRICT, DomSameSiteStatus.SAME_SITE, "Subdomain redirecting to subdomain fetches are strictly same-site"); + create_test(SECURE_SUBDOMAIN_ORIGIN, redirectTo(SECURE_CROSS_SITE_ORIGIN, SECURE_SUBDOMAIN_ORIGIN), SameSiteStatus.CROSS_SITE, DomSameSiteStatus.SAME_SITE, "Cross-site redirecting to subdomain fetches are cross-site"); + + // Redirect from {same-host,subdomain,cross-site} to cross-site: + create_test(SECURE_CROSS_SITE_ORIGIN, redirectTo(SECURE_ORIGIN, SECURE_CROSS_SITE_ORIGIN), SameSiteStatus.CROSS_SITE, DomSameSiteStatus.CROSS_SITE, "Same-host redirecting to cross-site fetches are cross-site"); + create_test(SECURE_CROSS_SITE_ORIGIN, redirectTo(SECURE_SUBDOMAIN_ORIGIN, SECURE_CROSS_SITE_ORIGIN), SameSiteStatus.CROSS_SITE, DomSameSiteStatus.CROSS_SITE, "Subdomain redirecting to cross-site fetches are cross-site"); + create_test(SECURE_CROSS_SITE_ORIGIN, redirectTo(SECURE_CROSS_SITE_ORIGIN, SECURE_CROSS_SITE_ORIGIN), SameSiteStatus.CROSS_SITE, DomSameSiteStatus.CROSS_SITE, "Cross-site redirecting to cross-site fetches are cross-site"); + + // Navigate from {same-host,subdomain,cross-site} to same-host: + create_test(SECURE_ORIGIN, navigateTo(SECURE_ORIGIN, SECURE_ORIGIN), SameSiteStatus.STRICT, DomSameSiteStatus.SAME_SITE, "Same-host navigating to same-host fetches are strictly same-site"); + create_test(SECURE_ORIGIN, navigateTo(SECURE_SUBDOMAIN_ORIGIN, SECURE_ORIGIN), SameSiteStatus.STRICT, DomSameSiteStatus.SAME_SITE, "Subdomain navigating to same-host fetches are strictly same-site"); + create_test(SECURE_ORIGIN, navigateTo(SECURE_CROSS_SITE_ORIGIN, SECURE_ORIGIN), SameSiteStatus.CROSS_SITE, DomSameSiteStatus.SAME_SITE, "Cross-site navigating to same-host fetches are cross-site"); + + // Navigate from {same-host,subdomain,cross-site} to subdomain: + create_test(SECURE_SUBDOMAIN_ORIGIN, navigateTo(SECURE_ORIGIN, SECURE_SUBDOMAIN_ORIGIN), SameSiteStatus.STRICT, DomSameSiteStatus.SAME_SITE, "Same-host navigating to subdomain fetches are strictly same-site"); + create_test(SECURE_SUBDOMAIN_ORIGIN, navigateTo(SECURE_SUBDOMAIN_ORIGIN, SECURE_SUBDOMAIN_ORIGIN), SameSiteStatus.STRICT, DomSameSiteStatus.SAME_SITE, "Subdomain navigating to subdomain fetches are strictly same-site"); + create_test(SECURE_SUBDOMAIN_ORIGIN, navigateTo(SECURE_CROSS_SITE_ORIGIN, SECURE_SUBDOMAIN_ORIGIN), SameSiteStatus.CROSS_SITE, DomSameSiteStatus.SAME_SITE, "Cross-site navigating to subdomain fetches are cross-site-site"); + + // Navigate from {same-host,subdomain,cross-site} to cross-site: + create_test(SECURE_CROSS_SITE_ORIGIN, navigateTo(SECURE_ORIGIN, SECURE_CROSS_SITE_ORIGIN), SameSiteStatus.CROSS_SITE, DomSameSiteStatus.CROSS_SITE, "Same-host navigating to cross-site fetches are cross-site"); + create_test(SECURE_CROSS_SITE_ORIGIN, navigateTo(SECURE_SUBDOMAIN_ORIGIN, SECURE_CROSS_SITE_ORIGIN), SameSiteStatus.CROSS_SITE, DomSameSiteStatus.CROSS_SITE, "Subdomain navigating to cross-site fetches are cross-site"); + create_test(SECURE_CROSS_SITE_ORIGIN, navigateTo(SECURE_CROSS_SITE_ORIGIN, SECURE_CROSS_SITE_ORIGIN), SameSiteStatus.CROSS_SITE, DomSameSiteStatus.CROSS_SITE, "Cross-site navigating to cross-site fetches are cross-site"); +</script> diff --git a/testing/web-platform/tests/cookies/samesite/img.https.html b/testing/web-platform/tests/cookies/samesite/img.https.html new file mode 100644 index 0000000000..1ddc8d99ad --- /dev/null +++ b/testing/web-platform/tests/cookies/samesite/img.https.html @@ -0,0 +1,75 @@ +<!DOCTYPE html> +<meta charset="utf-8"/> +<meta name="timeout" content="long"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/cookies/resources/cookie-helper.sub.js"></script> +<script> + function assert_cookie_present(origin, name, value) { + return new Promise((resolve, reject) => { + var img = document.createElement("img"); + img.onload = _ => resolve("'" + name + "=" + value + "' present on " + origin); + img.onerror = _ => reject("'" + name + "=" + value + "' not present on " + origin); + + // We need to URL encode the destination path/query if we're redirecting: + if (origin.match(/\/redir/)) + img.src = origin + encodeURIComponent("/cookies/resources/imgIfMatch.py?name=" + name + "&value=" + value); + else + img.src = origin + "/cookies/resources/imgIfMatch.py?name=" + name + "&value=" + value; + }); + } + + function assert_cookie_absent(origin, name, value) { + return new Promise((resolve, reject) => { + var img = document.createElement("img"); + img.onload = _ => reject("'" + name + "=" + value + "' present on " + origin); + img.onerror = _ => resolve("'" + name + "=" + value + "' not present on " + origin); + + // We need to URL encode the destination path/query if we're redirecting: + if (origin.match(/\/redir/)) + img.src = origin + encodeURIComponent("/cookies/resources/imgIfMatch.py?name=" + name + "&value=" + value); + else + img.src = origin + "/cookies/resources/imgIfMatch.py?name=" + name + "&value=" + value; + }); + } + + function create_test(origin, target, expectedStatus, title) { + promise_test(t => { + var value = "" + Math.random(); + return resetSameSiteCookies(origin, value) + .then(_ => { + var asserts = [assert_cookie_present(target, "samesite_none", value), + expectedStatus == SameSiteStatus.STRICT ? + assert_cookie_present(target, "samesite_strict", value) : + assert_cookie_absent(target, "samesite_strict", value), + expectedStatus == SameSiteStatus.CROSS_SITE ? + assert_cookie_absent(target, "samesite_lax", value) : + assert_cookie_present(target, "samesite_lax", value), + expectedStatus == SameSiteStatus.CROSS_SITE ? + assert_cookie_absent(target, "samesite_unspecified", value) : + assert_cookie_present(target, "samesite_unspecified", value)]; + return Promise.all(asserts); + }); + }, title); + } + + // No redirect: + create_test(SECURE_ORIGIN, SECURE_ORIGIN, SameSiteStatus.STRICT, "Same-host images are strictly same-site"); + create_test(SECURE_SUBDOMAIN_ORIGIN, SECURE_SUBDOMAIN_ORIGIN, SameSiteStatus.STRICT, "Subdomain images are strictly same-site"); + create_test(SECURE_CROSS_SITE_ORIGIN, SECURE_CROSS_SITE_ORIGIN, SameSiteStatus.CROSS_SITE, "Cross-site images are cross-site"); + + // Redirect from {same-host,subdomain,cross-site} to same-host: + create_test(SECURE_ORIGIN, redirectTo(SECURE_ORIGIN, SECURE_ORIGIN), SameSiteStatus.STRICT, "Same-host redirecting to same-host images are strictly same-site"); + create_test(SECURE_ORIGIN, redirectTo(SECURE_SUBDOMAIN_ORIGIN, SECURE_ORIGIN), SameSiteStatus.STRICT, "Subdomain redirecting to same-host images are strictly same-site"); + create_test(SECURE_ORIGIN, redirectTo(SECURE_CROSS_SITE_ORIGIN, SECURE_ORIGIN), SameSiteStatus.CROSS_SITE, "Cross-site redirecting to same-host images are cross-site"); + + // Redirect from {same-host,subdomain,cross-site} to same-host: + create_test(SECURE_SUBDOMAIN_ORIGIN, redirectTo(SECURE_ORIGIN, SECURE_SUBDOMAIN_ORIGIN), SameSiteStatus.STRICT, "Same-host redirecting to subdomain images are strictly same-site"); + create_test(SECURE_SUBDOMAIN_ORIGIN, redirectTo(SECURE_SUBDOMAIN_ORIGIN, SECURE_SUBDOMAIN_ORIGIN), SameSiteStatus.STRICT, "Subdomain redirecting to subdomain images are strictly same-site"); + create_test(SECURE_SUBDOMAIN_ORIGIN, redirectTo(SECURE_CROSS_SITE_ORIGIN, SECURE_SUBDOMAIN_ORIGIN), SameSiteStatus.CROSS_SITE, "Cross-site redirecting to subdomain images are cross-site"); + + // Redirect from {same-host,subdomain,cross-site} to cross-site: + create_test(SECURE_CROSS_SITE_ORIGIN, redirectTo(SECURE_ORIGIN, SECURE_CROSS_SITE_ORIGIN), SameSiteStatus.CROSS_SITE, "Same-host redirecting to cross-site images are cross-site"); + create_test(SECURE_CROSS_SITE_ORIGIN, redirectTo(SECURE_SUBDOMAIN_ORIGIN, SECURE_CROSS_SITE_ORIGIN), SameSiteStatus.CROSS_SITE, "Subdomain redirecting to cross-site images are cross-site"); + create_test(SECURE_CROSS_SITE_ORIGIN, redirectTo(SECURE_CROSS_SITE_ORIGIN, SECURE_CROSS_SITE_ORIGIN), SameSiteStatus.CROSS_SITE, "Cross-site redirecting to cross-site images are cross-site"); +</script> diff --git a/testing/web-platform/tests/cookies/samesite/multiple-samesite-attributes.https.html b/testing/web-platform/tests/cookies/samesite/multiple-samesite-attributes.https.html new file mode 100644 index 0000000000..d32a39639d --- /dev/null +++ b/testing/web-platform/tests/cookies/samesite/multiple-samesite-attributes.https.html @@ -0,0 +1,92 @@ +<!DOCTYPE html> +<meta charset="utf-8"/> +<meta name="timeout" content="long"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/cookies/resources/cookie-helper.sub.js"></script> +<script> + function assert_cookie_present(origin, name, value) { + return new Promise((resolve, reject) => { + var img = document.createElement("img"); + img.onload = _ => resolve("'" + name + "=" + value + "' present on " + origin); + img.onerror = _ => reject("'" + name + "=" + value + "' not present on " + origin); + + // We need to URL encode the destination path/query if we're redirecting: + if (origin.match(/\/redir/)) + img.src = origin + encodeURIComponent("/cookies/resources/imgIfMatch.py?name=" + name + "&value=" + value); + else + img.src = origin + "/cookies/resources/imgIfMatch.py?name=" + name + "&value=" + value; + }); + } + + function assert_cookie_absent(origin, name, value) { + return new Promise((resolve, reject) => { + var img = document.createElement("img"); + img.onload = _ => reject("'" + name + "=" + value + "' present on " + origin); + img.onerror = _ => resolve("'" + name + "=" + value + "' not present on " + origin); + + // We need to URL encode the destination path/query if we're redirecting: + if (origin.match(/\/redir/)) + img.src = origin + encodeURIComponent("/cookies/resources/imgIfMatch.py?name=" + name + "&value=" + value); + else + img.src = origin + "/cookies/resources/imgIfMatch.py?name=" + name + "&value=" + value; + }); + } + + function create_test(origin, target, expectedStatus, title) { + promise_test(t => { + var value = "" + Math.random(); + return resetSameSiteMultiAttributeCookies(origin, value) + .then(_ => { + var asserts = [ + assert_cookie_present(target, "samesite_unsupported_none", value), + assert_cookie_present(target, "samesite_lax_none", value), + expectedStatus == SameSiteStatus.STRICT ? + assert_cookie_present(target, "samesite_unsupported_strict", value) : + assert_cookie_absent(target, "samesite_unsupported_strict", value), + expectedStatus == SameSiteStatus.STRICT ? + assert_cookie_present(target, "samesite_lax_strict", value) : + assert_cookie_absent(target, "samesite_lax_strict", value), + expectedStatus == SameSiteStatus.CROSS_SITE ? + assert_cookie_absent(target, "samesite_unsupported_lax", value) : + assert_cookie_present(target, "samesite_unsupported_lax", value), + expectedStatus == SameSiteStatus.CROSS_SITE ? + assert_cookie_absent(target, "samesite_strict_lax", value) : + assert_cookie_present(target, "samesite_strict_lax", value), + expectedStatus == SameSiteStatus.CROSS_SITE ? + assert_cookie_absent(target, "samesite_none_unsupported", value) : + assert_cookie_present(target, "samesite_none_unsupported", value), + expectedStatus == SameSiteStatus.CROSS_SITE ? + assert_cookie_absent(target, "samesite_lax_unsupported", value) : + assert_cookie_present(target, "samesite_lax_unsupported", value), + expectedStatus == SameSiteStatus.CROSS_SITE ? + assert_cookie_absent(target, "samesite_strict_unsupported", value) : + assert_cookie_present(target, "samesite_strict_unsupported", value), + expectedStatus == SameSiteStatus.CROSS_SITE ? + assert_cookie_absent(target, "samesite_unsupported", value) : + assert_cookie_present(target, "samesite_unsupported", value)]; + return Promise.all(asserts); + }); + }, title); + } + + // No redirect: + create_test(SECURE_ORIGIN, SECURE_ORIGIN, SameSiteStatus.STRICT, "Same-host images are strictly same-site"); + create_test(SECURE_SUBDOMAIN_ORIGIN, SECURE_SUBDOMAIN_ORIGIN, SameSiteStatus.STRICT, "Subdomain images are strictly same-site"); + create_test(SECURE_CROSS_SITE_ORIGIN, SECURE_CROSS_SITE_ORIGIN, SameSiteStatus.CROSS_SITE, "Cross-site images are cross-site"); + + // Redirect from {same-host,subdomain,cross-site} to same-host: + create_test(SECURE_ORIGIN, redirectTo(SECURE_ORIGIN, SECURE_ORIGIN), SameSiteStatus.STRICT, "Same-host redirecting to same-host images are strictly same-site"); + create_test(SECURE_ORIGIN, redirectTo(SECURE_SUBDOMAIN_ORIGIN, SECURE_ORIGIN), SameSiteStatus.STRICT, "Subdomain redirecting to same-host images are strictly same-site"); + create_test(SECURE_ORIGIN, redirectTo(SECURE_CROSS_SITE_ORIGIN, SECURE_ORIGIN), SameSiteStatus.CROSS_SITE, "Cross-site redirecting to same-host images are cross-site"); + + // Redirect from {same-host,subdomain,cross-site} to same-host: + create_test(SECURE_SUBDOMAIN_ORIGIN, redirectTo(SECURE_ORIGIN, SECURE_SUBDOMAIN_ORIGIN), SameSiteStatus.STRICT, "Same-host redirecting to subdomain images are strictly same-site"); + create_test(SECURE_SUBDOMAIN_ORIGIN, redirectTo(SECURE_SUBDOMAIN_ORIGIN, SECURE_SUBDOMAIN_ORIGIN), SameSiteStatus.STRICT, "Subdomain redirecting to subdomain images are strictly same-site"); + create_test(SECURE_SUBDOMAIN_ORIGIN, redirectTo(SECURE_CROSS_SITE_ORIGIN, SECURE_SUBDOMAIN_ORIGIN), SameSiteStatus.CROSS_SITE, "Cross-site redirecting to subdomain images are cross-site"); + + // Redirect from {same-host,subdomain,cross-site} to cross-site: + create_test(SECURE_CROSS_SITE_ORIGIN, redirectTo(SECURE_ORIGIN, SECURE_CROSS_SITE_ORIGIN), SameSiteStatus.CROSS_SITE, "Same-host redirecting to cross-site images are cross-site"); + create_test(SECURE_CROSS_SITE_ORIGIN, redirectTo(SECURE_SUBDOMAIN_ORIGIN, SECURE_CROSS_SITE_ORIGIN), SameSiteStatus.CROSS_SITE, "Subdomain redirecting to cross-site images are cross-site"); + create_test(SECURE_CROSS_SITE_ORIGIN, redirectTo(SECURE_CROSS_SITE_ORIGIN, SECURE_CROSS_SITE_ORIGIN), SameSiteStatus.CROSS_SITE, "Cross-site redirecting to cross-site images are cross-site"); +</script> diff --git a/testing/web-platform/tests/cookies/samesite/resources/echo-cookies.html b/testing/web-platform/tests/cookies/samesite/resources/echo-cookies.html new file mode 100644 index 0000000000..a1b29b9b03 --- /dev/null +++ b/testing/web-platform/tests/cookies/samesite/resources/echo-cookies.html @@ -0,0 +1,8 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<script> + if (window.opener) + window.opener.postMessage({ type: 'COOKIES_SET', cookies: document.cookie }, '*'); + if (window.parent !== window) + window.parent.postMessage({ type: 'FRAME_COOKIES_SET', cookies: document.cookie }, '*'); +</script> diff --git a/testing/web-platform/tests/cookies/samesite/resources/iframe-navigate-report.html b/testing/web-platform/tests/cookies/samesite/resources/iframe-navigate-report.html new file mode 100644 index 0000000000..98ea469fda --- /dev/null +++ b/testing/web-platform/tests/cookies/samesite/resources/iframe-navigate-report.html @@ -0,0 +1,3 @@ +<!DOCTYPE html> +<iframe src="/cookies/resources/postToParent.py"> +</iframe> diff --git a/testing/web-platform/tests/cookies/samesite/resources/iframe-subresource-report.html b/testing/web-platform/tests/cookies/samesite/resources/iframe-subresource-report.html new file mode 100644 index 0000000000..1dceb4e436 --- /dev/null +++ b/testing/web-platform/tests/cookies/samesite/resources/iframe-subresource-report.html @@ -0,0 +1,14 @@ +<!DOCTYPE html> +<head> +<script src="/cookies/resources/cookie-helper.sub.js"></script> +<script> +function reportSubresourceCookies() { + credFetch(SECURE_ORIGIN + "/cookies/resources/list.py") + .then(r => r.json()) + .then(cookies => { cookies.type = "COOKIES"; + target = window.opener ? window.opener : window.parent; + target.postMessage(cookies, "*");}); +} +</script> +</head> +<body onload="reportSubresourceCookies()"> diff --git a/testing/web-platform/tests/cookies/samesite/resources/iframe.document.html b/testing/web-platform/tests/cookies/samesite/resources/iframe.document.html new file mode 100644 index 0000000000..7026beb7e8 --- /dev/null +++ b/testing/web-platform/tests/cookies/samesite/resources/iframe.document.html @@ -0,0 +1,8 @@ +<!DOCTYPE html> +<script> + var value = "" + Math.random(); + document.cookie = `dc_samesite_strict=${value}; secure; sameSite=strict; path=/`; + document.cookie = `dc_samesite_lax=${value}; secure; sameSite=lax; path=/`; + document.cookie = `dc_samesite_none=${value}; secure; sameSite=none; path=/`; + parent.postMessage({value}, "*"); +</script> diff --git a/testing/web-platform/tests/cookies/samesite/resources/navigate-iframe.html b/testing/web-platform/tests/cookies/samesite/resources/navigate-iframe.html new file mode 100644 index 0000000000..98ad6264fa --- /dev/null +++ b/testing/web-platform/tests/cookies/samesite/resources/navigate-iframe.html @@ -0,0 +1,26 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<script src="/cookies/resources/cookie-helper.sub.js"></script> +<script> + window.addEventListener('load', function() { + window.opener.postMessage({ type: 'LOADED' }, '*'); + }); + + window.addEventListener('message', function(e) { + if (SECURE_ORIGIN !== window.location.origin) + return; + + if (e.data.type === "initialize-iframe") + window.frames[0].location = e.data.url; + if (e.data.type === "navigate-iframe") + window.frames[0].postMessage({ type: 'navigate', url: e.data.url }, '*'); + + // Relay messages sent by the subframe to the opener. + if (e.data.type === 'FRAME_READY') + window.opener.postMessage({ type: 'FRAME_READY' }, '*'); + + if (e.data.type === 'FRAME_COOKIES_SET') + window.opener.postMessage({ type: 'FRAME_COOKIES_SET', cookies: e.data.cookies }, '*'); + }); +</script> +<iframe></iframe> diff --git a/testing/web-platform/tests/cookies/samesite/resources/navigate.html b/testing/web-platform/tests/cookies/samesite/resources/navigate.html new file mode 100644 index 0000000000..88de6dff92 --- /dev/null +++ b/testing/web-platform/tests/cookies/samesite/resources/navigate.html @@ -0,0 +1,25 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<script src="/cookies/resources/cookie-helper.sub.js"></script> +<script> + window.addEventListener('load', function() { + if (window.opener) + window.opener.postMessage({ type: 'READY' }, '*'); + if (window.parent !== window) + window.parent.postMessage({ type: 'FRAME_READY' }, '*'); + }); + + window.addEventListener('message', function(e) { + if (e.data.type === "navigate") { + window.location = e.data.url; + } + + if (e.data.type === "post-form") { + var f = document.createElement('form'); + f.action = e.data.url; + f.method = "POST"; + document.body.appendChild(f); + f.submit(); + } + }); +</script> diff --git a/testing/web-platform/tests/cookies/samesite/resources/puppet.html b/testing/web-platform/tests/cookies/samesite/resources/puppet.html new file mode 100644 index 0000000000..6d36132d3a --- /dev/null +++ b/testing/web-platform/tests/cookies/samesite/resources/puppet.html @@ -0,0 +1,32 @@ +<!DOCTYPE html> +<script src="/cookies/resources/cookie-helper.sub.js"></script> +<script> + // Helper to either set or clear some cookies on its own origin, or + // (potentially) cross-site on SECURE_ORIGIN. + window.onmessage = e => { + var originToUse = SECURE_ORIGIN; + if (e.data.useOwnOrigin) + originToUse = self.origin; + + if (e.data.type === "set") { + credFetch(originToUse + "/cookies/resources/setSameSite.py?" + e.data.value) + .then(_ => { + e.source.postMessage({ + type: "set-complete", + value: e.data.value + }, "*"); + }); + } + + if (e.data.type === "drop") { + credFetch(originToUse + "/cookies/resources/dropSameSite.py") + .then(_ => { + e.source.postMessage({type: "drop-complete"}, "*"); + }); + } + }; + + window.opener.postMessage({ + type: "READY" + }, "*"); +</script> diff --git a/testing/web-platform/tests/cookies/samesite/sandbox-iframe-nested.https.html b/testing/web-platform/tests/cookies/samesite/sandbox-iframe-nested.https.html new file mode 100644 index 0000000000..310f86a446 --- /dev/null +++ b/testing/web-platform/tests/cookies/samesite/sandbox-iframe-nested.https.html @@ -0,0 +1,28 @@ +<!DOCTYPE html> +<head> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <script src="/cookies/resources/cookie-helper.sub.js"></script> +</head> +<body onload="doTests()"> + <iframe id="if" sandbox="allow-scripts"> + </iframe> + <script> + function doTests() { + promise_test(async function(t) { + var value = "" + Math.random(); + await resetSameSiteCookies(SECURE_ORIGIN, value); + var child = document.getElementById("if"); + child.src = SECURE_ORIGIN + "/cookies/samesite/resources/iframe-navigate-report.html"; + + // the iframe nested inside if should post COOKIES to here. + var e = await wait_for_message("COOKIES"); + // Not testing unspecified here as to not depend on the presence or + // absence of upcoming change of behavior. + assert_cookie(SECURE_ORIGIN, e.data, "samesite_lax", value, false); + assert_cookie(SECURE_ORIGIN, e.data, "samesite_strict", value, false); + assert_cookie(SECURE_ORIGIN, e.data, "samesite_none", value, true); + }, "SameSite cookies with intervening sandboxed iframe and navigation"); + } + </script> +</body> diff --git a/testing/web-platform/tests/cookies/samesite/sandbox-iframe-subresource.https.html b/testing/web-platform/tests/cookies/samesite/sandbox-iframe-subresource.https.html new file mode 100644 index 0000000000..417089ef57 --- /dev/null +++ b/testing/web-platform/tests/cookies/samesite/sandbox-iframe-subresource.https.html @@ -0,0 +1,28 @@ +<!DOCTYPE html> +<head> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <script src="/cookies/resources/cookie-helper.sub.js"></script> +</head> +<body onload="doTests()"> + <iframe id="if" sandbox="allow-scripts"> + </iframe> + <script> + function doTests() { + promise_test(async function(t) { + var value = "" + Math.random(); + await resetSameSiteCookies(SECURE_ORIGIN, value); + var child = document.getElementById("if"); + child.src = SECURE_ORIGIN + "/cookies/samesite/resources/iframe-subresource-report.html"; + + // the iframe nested inside if should post COOKIES to here. + var e = await wait_for_message("COOKIES"); + // Not testing unspecified here as to not depend on the presence or + // absence of upcoming change of behavior. + assert_cookie(SECURE_ORIGIN, e.data, "samesite_lax", value, false); + assert_cookie(SECURE_ORIGIN, e.data, "samesite_strict", value, false); + assert_cookie(SECURE_ORIGIN, e.data, "samesite_none", value, true); + }, "SameSite cookies with intervening sandboxed iframe and subresources"); + } + </script> +</body> diff --git a/testing/web-platform/tests/cookies/samesite/setcookie-lax.https.html b/testing/web-platform/tests/cookies/samesite/setcookie-lax.https.html new file mode 100644 index 0000000000..f2094af693 --- /dev/null +++ b/testing/web-platform/tests/cookies/samesite/setcookie-lax.https.html @@ -0,0 +1,32 @@ +<!DOCTYPE html> +<meta charset="utf-8"/> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/cookies/resources/cookie-helper.sub.js"></script> +<script> + promise_test(async function(t) { + let w = window.open(SECURE_ORIGIN + "/cookies/samesite/resources/puppet.html"); + await wait_for_message("READY", SECURE_ORIGIN); + let random = "" + Math.random(); + w.postMessage({type: "set", value: random}, "*"); + let e = await wait_for_message("set-complete", SECURE_ORIGIN) + assert_dom_cookie("samesite_strict", e.data.value, true); + assert_dom_cookie("samesite_lax", e.data.value, true); + assert_dom_cookie("samesite_none", e.data.value, true); + assert_dom_cookie("samesite_unspecified", e.data.value, true); + w.close(); + }, "Same-site window should be able to set `SameSite=Lax` or `SameSite=Strict` cookies."); + + promise_test(async function(t) { + let w = window.open(SECURE_CROSS_SITE_ORIGIN + "/cookies/samesite/resources/puppet.html"); + await wait_for_message("READY", SECURE_CROSS_SITE_ORIGIN); + let random = "" + Math.random(); + w.postMessage({type: "set", value: random}, "*"); + let e = await wait_for_message("set-complete", SECURE_CROSS_SITE_ORIGIN); + assert_dom_cookie("samesite_strict", e.data.value, false); + assert_dom_cookie("samesite_lax", e.data.value, false); + assert_dom_cookie("samesite_none", e.data.value, true); + assert_dom_cookie("samesite_unspecified", e.data.value, false); + w.close(); + }, "Cross-site window shouldn't be able to set `SameSite=Lax` or `SameSite=Strict` cookies."); +</script> diff --git a/testing/web-platform/tests/cookies/samesite/setcookie-navigation.https.html b/testing/web-platform/tests/cookies/samesite/setcookie-navigation.https.html new file mode 100644 index 0000000000..2dbc5526bc --- /dev/null +++ b/testing/web-platform/tests/cookies/samesite/setcookie-navigation.https.html @@ -0,0 +1,81 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<meta name="timeout" content="long"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/cookies/resources/cookie-helper.sub.js"></script> +<script> + // Asserts that cookies are present or not present (according to `expectation`) + // in the cookie string `cookies` with the correct names and value. + function assert_cookies_present(cookies, value, expected_cookie_names, expectation) { + for (name of expected_cookie_names) { + let re = new RegExp("(?:^|; )" + name + "=" + value + "(?:$|;)"); + let assertion = expectation ? assert_true : assert_false; + assertion(re.test(cookies), "`" + name + "=" + value + "` in cookies"); + } + } + + // Navigate from ORIGIN to |origin_to|, expecting the navigation to set SameSite + // cookies on |origin_to|. + function navigate_test(method, origin_to, title) { + promise_test(async function(t) { + // The cookies don't need to be cleared on each run because |value| is + // a new random value on each run, so on each run we are overwriting and + // checking for a cookie with a different random value. + let value = "" + Math.random(); + let url_from = SECURE_ORIGIN + "/cookies/samesite/resources/navigate.html"; + let url_to = origin_to + "/cookies/resources/setSameSite.py?" + value; + var w = window.open(url_from); + await wait_for_message('READY', SECURE_ORIGIN); + assert_equals(SECURE_ORIGIN, window.origin); + assert_equals(SECURE_ORIGIN, w.origin); + let command = (method === "POST") ? "post-form" : "navigate"; + w.postMessage({ type: command, url: url_to }, "*"); + let message = await wait_for_message('COOKIES_SET', origin_to); + let samesite_cookie_names = ['samesite_strict', 'samesite_lax', 'samesite_none', 'samesite_unspecified']; + assert_cookies_present(message.data.cookies, value, samesite_cookie_names, true); + w.close(); + }, title); + } + + // Opens a page on origin SECURE_ORIGIN containing an iframe on `iframe_origin_from`, + // then navigates that iframe to `iframe_origin_to`. Expects that navigation to set + // some subset of SameSite cookies. + function navigate_iframe_test(iframe_origin_from, iframe_origin_to, cross_site, title) { + promise_test(async function(t) { + // The cookies don't need to be cleared on each run because |value| is + // a new random value on each run, so on each run we are overwriting and + // checking for a cookie with a different random value. + let value = "" + Math.random(); + let parent_url = SECURE_ORIGIN + "/cookies/samesite/resources/navigate-iframe.html"; + let iframe_url_from = iframe_origin_from + "/cookies/samesite/resources/navigate.html"; + let iframe_url_to = iframe_origin_to + "/cookies/resources/setSameSite.py?" + value; + var w = window.open(parent_url); + await wait_for_message('LOADED', SECURE_ORIGIN); + assert_equals(SECURE_ORIGIN, window.origin); + assert_equals(SECURE_ORIGIN, w.origin); + // Navigate the frame to its starting location. + w.postMessage({ type: 'initialize-iframe', url: iframe_url_from }, '*'); + await wait_for_message('FRAME_READY', SECURE_ORIGIN); + // Have the frame navigate itself, possibly cross-site. + w.postMessage({ type: 'navigate-iframe', url: iframe_url_to }, '*'); + let message = await wait_for_message('FRAME_COOKIES_SET', SECURE_ORIGIN); + // Check for the proper cookies. + let samesite_none_cookies = ['samesite_none']; + let samesite_cookies = ['samesite_strict', 'samesite_lax', 'samesite_unspecified']; + assert_cookies_present(message.data.cookies, value, samesite_none_cookies, true); + assert_cookies_present(message.data.cookies, value, samesite_cookies, !cross_site); + w.close(); + }, title); + } + + navigate_test("GET", SECURE_ORIGIN, "Same-site top-level navigation should be able to set SameSite=* cookies."); + navigate_test("GET", SECURE_CROSS_SITE_ORIGIN, "Cross-site top-level navigation should be able to set SameSite=* cookies."); + navigate_test("POST", SECURE_ORIGIN, "Same-site top-level POST should be able to set SameSite=* cookies."); + navigate_test("POST", SECURE_CROSS_SITE_ORIGIN, "Cross-site top-level POST should be able to set SameSite=* cookies."); + + navigate_iframe_test(SECURE_ORIGIN, SECURE_ORIGIN, false, "Same-site to same-site iframe navigation should be able to set SameSite=* cookies."); + navigate_iframe_test(SECURE_CROSS_SITE_ORIGIN, SECURE_ORIGIN, true, "Cross-site to same-site iframe navigation should only be able to set SameSite=None cookies."); + navigate_iframe_test(SECURE_ORIGIN, SECURE_CROSS_SITE_ORIGIN, true, "Same-site to cross-site-site iframe navigation should only be able to set SameSite=None cookies."); + navigate_iframe_test(SECURE_CROSS_SITE_ORIGIN, SECURE_CROSS_SITE_ORIGIN, true, "Cross-site to cross-site iframe navigation should only be able to set SameSite=None cookies."); +</script> diff --git a/testing/web-platform/tests/cookies/samesite/window-open-reload.https.html b/testing/web-platform/tests/cookies/samesite/window-open-reload.https.html new file mode 100644 index 0000000000..32076c7c97 --- /dev/null +++ b/testing/web-platform/tests/cookies/samesite/window-open-reload.https.html @@ -0,0 +1,52 @@ +<!DOCTYPE html> +<meta charset="utf-8"/> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/cookies/resources/cookie-helper.sub.js"></script> +<script> + // This test opens a popup window to postToParent.py (on the specified + // origin). The popup sends a postMessage event back to its opener + // (i.e., here) with the cookies it received, which we verify against + // expectedStatus. Then, the test sends a message to the popup, telling it to + // reload itself via window.location.reload(). Again, the popup posts a + // message back here with the cookies it received. These cookies are verified + // against expectedStatusReload. + function create_test(origin, target, expectedStatus, expectedStatusReload, title) { + promise_test(t => { + var value = "" + Math.random(); + return resetSameSiteCookies(origin, value) + .then(_ => { + return new Promise((resolve, reject) => { + var w = window.open(target + "/cookies/resources/postToParent.py"); + + var reloaded = false; + var msgHandler = e => { + try { + verifySameSiteCookieState(reloaded ? expectedStatusReload : expectedStatus, value, e.data, DomSameSiteStatus.SAME_SITE); + } catch (e) { + reject(e); + } + + if (reloaded) { + window.removeEventListener("message", msgHandler); + w.close(); + resolve("Popup received the cookie."); + } else { + reloaded = true; + w.postMessage("reload", "*"); + } + }; + window.addEventListener("message", msgHandler); + + if (!w) + reject("Popup could not be opened (did you allow the test site in your popup blocker?)."); + }); + }); + }, title); + } + + // The reload status is always strictly same-site because this is a site-initiated reload, as opposed to a reload triggered by a user interface element. + create_test(SECURE_ORIGIN, SECURE_ORIGIN, SameSiteStatus.STRICT, SameSiteStatus.STRICT, "Reloaded same-host auxiliary navigations are strictly same-site."); + create_test(SECURE_SUBDOMAIN_ORIGIN, SECURE_SUBDOMAIN_ORIGIN, SameSiteStatus.STRICT, SameSiteStatus.STRICT, "Reloaded subdomain auxiliary navigations are strictly same-site."); + create_test(SECURE_CROSS_SITE_ORIGIN, SECURE_CROSS_SITE_ORIGIN, SameSiteStatus.LAX, SameSiteStatus.STRICT, "Reloaded cross-site auxiliary navigations are strictly same-site"); +</script> diff --git a/testing/web-platform/tests/cookies/samesite/window-open.https.html b/testing/web-platform/tests/cookies/samesite/window-open.https.html new file mode 100644 index 0000000000..be4225046a --- /dev/null +++ b/testing/web-platform/tests/cookies/samesite/window-open.https.html @@ -0,0 +1,54 @@ +<!DOCTYPE html> +<meta charset="utf-8"/> +<meta name="timeout" content="long"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/cookies/resources/cookie-helper.sub.js"></script> +<script> + function create_test(origin, target, expectedStatus, title) { + promise_test(t => { + var value = "" + Math.random(); + return resetSameSiteCookies(origin, value) + .then(_ => { + return new Promise((resolve, reject) => { + var w = window.open(target + "/cookies/resources/postToParent.py"); + + var msgHandler = e => { + window.removeEventListener("message", msgHandler); + w.close(); + try { + verifySameSiteCookieState(expectedStatus, value, e.data, DomSameSiteStatus.SAME_SITE); + resolve("Popup received the cookie."); + } catch (e) { + reject(e); + } + }; + window.addEventListener("message", msgHandler); + + if (!w) + reject("Popup could not be opened (did you allow the test site in your popup blocker?)."); + }); + }); + }, title); + } + + // No redirect: + create_test(SECURE_ORIGIN, SECURE_ORIGIN, SameSiteStatus.STRICT, "Same-host auxiliary navigations are strictly same-site"); + create_test(SECURE_SUBDOMAIN_ORIGIN, SECURE_SUBDOMAIN_ORIGIN, SameSiteStatus.STRICT, "Subdomain auxiliary navigations are strictly same-site"); + create_test(SECURE_CROSS_SITE_ORIGIN, SECURE_CROSS_SITE_ORIGIN, SameSiteStatus.LAX, "Cross-site auxiliary navigations are laxly same-site"); + + // Redirect from {same-host,subdomain,cross-site} to same-host: + create_test(SECURE_ORIGIN, redirectTo(SECURE_ORIGIN, SECURE_ORIGIN), SameSiteStatus.STRICT, "Same-host redirecting to same-host auxiliary navigations are strictly same-site"); + create_test(SECURE_ORIGIN, redirectTo(SECURE_SUBDOMAIN_ORIGIN, SECURE_ORIGIN), SameSiteStatus.STRICT, "Subdomain redirecting to same-host auxiliary navigations are strictly same-site"); + create_test(SECURE_ORIGIN, redirectTo(SECURE_CROSS_SITE_ORIGIN, SECURE_ORIGIN), SameSiteStatus.LAX, "Cross-site redirecting to same-host auxiliary navigations are laxly same-site"); + + // Redirect from {same-host,subdomain,cross-site} to same-host: + create_test(SECURE_SUBDOMAIN_ORIGIN, redirectTo(SECURE_ORIGIN, SECURE_SUBDOMAIN_ORIGIN), SameSiteStatus.STRICT, "Same-host redirecting to subdomain auxiliary navigations are strictly same-site"); + create_test(SECURE_SUBDOMAIN_ORIGIN, redirectTo(SECURE_SUBDOMAIN_ORIGIN, SECURE_SUBDOMAIN_ORIGIN), SameSiteStatus.STRICT, "Subdomain redirecting to subdomain auxiliary navigations are strictly same-site"); + create_test(SECURE_SUBDOMAIN_ORIGIN, redirectTo(SECURE_CROSS_SITE_ORIGIN, SECURE_SUBDOMAIN_ORIGIN), SameSiteStatus.LAX, "Cross-site redirecting to subdomain auxiliary navigations are laxly same-site"); + + // Redirect from {same-host,subdomain,cross-site} to cross-site: + create_test(SECURE_CROSS_SITE_ORIGIN, redirectTo(SECURE_ORIGIN, SECURE_CROSS_SITE_ORIGIN), SameSiteStatus.LAX, "Same-host redirecting to cross-site auxiliary navigations are laxly same-site"); + create_test(SECURE_CROSS_SITE_ORIGIN, redirectTo(SECURE_SUBDOMAIN_ORIGIN, SECURE_CROSS_SITE_ORIGIN), SameSiteStatus.LAX, "Subdomain redirecting to cross-site auxiliary navigations are laxly same-site"); + create_test(SECURE_CROSS_SITE_ORIGIN, redirectTo(SECURE_CROSS_SITE_ORIGIN, SECURE_CROSS_SITE_ORIGIN), SameSiteStatus.LAX, "Cross-site redirecting to cross-site auxiliary navigations are laxly same-site"); +</script> diff --git a/testing/web-platform/tests/cookies/schemeful-same-site/resources/navigateToInsecurePostToParent.html b/testing/web-platform/tests/cookies/schemeful-same-site/resources/navigateToInsecurePostToParent.html new file mode 100644 index 0000000000..b81b722bf6 --- /dev/null +++ b/testing/web-platform/tests/cookies/schemeful-same-site/resources/navigateToInsecurePostToParent.html @@ -0,0 +1,6 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<script src="/cookies/resources/cookie-helper.sub.js"></script> +<script> + window.location = INSECURE_ORIGIN + "/cookies/resources/postToParent.py"; +</script> diff --git a/testing/web-platform/tests/cookies/schemeful-same-site/schemeful-iframe-subresource.tentative.html b/testing/web-platform/tests/cookies/schemeful-same-site/schemeful-iframe-subresource.tentative.html new file mode 100644 index 0000000000..13397d241a --- /dev/null +++ b/testing/web-platform/tests/cookies/schemeful-same-site/schemeful-iframe-subresource.tentative.html @@ -0,0 +1,28 @@ +<!DOCTYPE html> +<head> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <script src="/cookies/resources/cookie-helper.sub.js"></script> +</head> +<body onload="doTests()"> + <iframe id="if"> + </iframe> + <script> + function doTests() { + promise_test(async function(t) { + var value = "" + Math.random(); + await resetSameSiteCookies(SECURE_ORIGIN, value); + var child = document.getElementById("if"); + child.src = SECURE_ORIGIN + "/cookies/samesite/resources/iframe-subresource-report.html"; + + // the iframe nested inside if should post COOKIES to here. + var e = await wait_for_message("COOKIES"); + // Cross-scheme iframes should be cross-site and thus the subresources + // shouldn't get Lax or Strict cookies. + assert_cookie(SECURE_ORIGIN, e.data, "samesite_lax", value, false); + assert_cookie(SECURE_ORIGIN, e.data, "samesite_strict", value, false); + assert_cookie(SECURE_ORIGIN, e.data, "samesite_none", value, true); + }, "SameSite cookies with intervening cross-scheme iframe and subresources"); + } + </script> +</body> diff --git a/testing/web-platform/tests/cookies/schemeful-same-site/schemeful-navigation.tentative.html b/testing/web-platform/tests/cookies/schemeful-same-site/schemeful-navigation.tentative.html new file mode 100644 index 0000000000..5ead2a54c7 --- /dev/null +++ b/testing/web-platform/tests/cookies/schemeful-same-site/schemeful-navigation.tentative.html @@ -0,0 +1,42 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<meta name="timeout" content="long"> +<meta name="variant" content=""> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/cookies/resources/cookie-helper.sub.js"></script> +<script> + function schemeful_navigation_test(target, expectedSameSiteStatus, title) { + promise_test(async function(t) { + let value = "" + Math.random(); + document.cookie = `samesite_strict=${value}; sameSite=strict; path=/`; + document.cookie = `samesite_lax=${value}; sameSite=lax; path=/`; + + let url = target + "/cookies/schemeful-same-site/resources/navigateToInsecurePostToParent.html"; + + await new Promise((resolve, reject) => { + window.onmessage = t.step_func(e => { + if (e.source == window.open("", "testwindow" + value)) { + e.source.close(); + const cookies = e.data; + + assert_equals(cookies["samesite_lax"], value, "SameSite=lax cookies can be sent in both cases"); + if (expectedSameSiteStatus === SameSiteStatus.STRICT) { + assert_equals(cookies["samesite_strict"], value, "SameSite=strict cookies can be sent to same-scheme navigations"); + } else if (expectedSameSiteStatus === SameSiteStatus.LAX) { + assert_not_equals(cookies["samesite_strict"], value, "SameSite=strict cookies cannot be sent to cross-scheme navigations"); + } + + resolve(); + } + else {reject();} + }); + + var w = window.open(url, "testwindow" + value); + }); + + },title);} + + schemeful_navigation_test(INSECURE_ORIGIN, SameSiteStatus.STRICT, "Navigate same-scheme"); + schemeful_navigation_test(SECURE_ORIGIN, SameSiteStatus.LAX, "Navigate cross-scheme"); +</script> diff --git a/testing/web-platform/tests/cookies/schemeful-same-site/schemeful-subresource.tentative.html b/testing/web-platform/tests/cookies/schemeful-same-site/schemeful-subresource.tentative.html new file mode 100644 index 0000000000..4ba9286c25 --- /dev/null +++ b/testing/web-platform/tests/cookies/schemeful-same-site/schemeful-subresource.tentative.html @@ -0,0 +1,49 @@ +<!DOCTYPE html> +<meta charset="utf-8"/> +<meta name="timeout" content="long"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/cookies/resources/cookie-helper.sub.js"></script> +<!-- We're appending an <iframe> to the document's body, so execute tests after we have a body --> +<body> +<script> + function create_test(target, expectedDomStatus, title) { + promise_test(async t => { + var cookieValue = "" + Math.random(); + document.cookie = `dc_samesite_strict=${cookieValue}; sameSite=strict; path=/`; + document.cookie = `dc_samesite_lax=${cookieValue}; sameSite=lax; path=/`; + // SameSite=None requires `Secure` which complicates the test and we don't + // need it, so don't add it. + + await new Promise((resolve, reject) => { + var iframe = document.createElement("iframe"); + + window.onmessage = t.step_func(e => { + if (e.source == iframe.contentWindow) { + // Cleanup, then verify cookie state: + document.body.removeChild(iframe); + + const cookies = e.data; + + if (expectedDomStatus === DomSameSiteStatus.SAME_SITE) { + assert_equals(cookies["dc_samesite_lax"], cookieValue, "SameSite=lax cookies can be sent to same-scheme subresources"); + assert_equals(cookies["dc_samesite_strict"], cookieValue, "SameSite=strict cookies can be sent to same-scheme subresources"); + } else if (expectedDomStatus === DomSameSiteStatus.CROSS_SITE) { + assert_not_equals(cookies["dc_samesite_lax"], cookieValue, "SameSite=lax cookies cannot be sent to cross-scheme subresources"); + assert_not_equals(cookies["dc_samesite_strict"], cookieValue, "SameSite=strict cookies cannot be sent to cross-scheme subresources"); + } + + resolve(); + } + }); + + iframe.src = target + "/cookies/resources/postToParent.py"; + document.body.appendChild(iframe); + }); + }, title); + } + + // Test that cross-scheme subresources (iframes in this case) are cross-site. + create_test(INSECURE_ORIGIN, DomSameSiteStatus.SAME_SITE, "Same-scheme subresources can send lax/strict cookies"); + create_test(SECURE_ORIGIN, DomSameSiteStatus.CROSS_SITE, "Cross-scheme subresources cannot sent lax/strict cookies"); +</script> diff --git a/testing/web-platform/tests/cookies/schemeful-same-site/schemeful-websockets.sub.tentative.html b/testing/web-platform/tests/cookies/schemeful-same-site/schemeful-websockets.sub.tentative.html new file mode 100644 index 0000000000..7095eee21e --- /dev/null +++ b/testing/web-platform/tests/cookies/schemeful-same-site/schemeful-websockets.sub.tentative.html @@ -0,0 +1,57 @@ +<!doctype html> +<html> +<head> + <meta charset=utf-8> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <script src="/cookies/resources/testharness-helpers.js"></script> + <script src="/cookies/resources/cookie-helper.sub.js"></script> +</head> +<body> +<div id=log></div> +<script> + promise_test(async function (t) { + var value = "" + Math.random(); + document.cookie = `schemeful_same_site_websockets_strict=${value}; sameSite=strict; path=/`; + document.cookie = `schemeful_same_site_websockets_lax=${value}; sameSite=lax; path=/`; + await credFetch(SECURE_ORIGIN + "/cookies/resources/setSameSiteNone.py?" + value) + t.add_cleanup(async function() { + await credFetch(origin + "/cookies/resources/drop.py?name=" + "schemeful_same_site_websockets_strict"); + await credFetch(origin + "/cookies/resources/drop.py?name=" + "schemeful_same_site_websockets_lax"); + await credFetch(SECURE_ORIGIN + "/cookies/resources/dropSameSiteNone.py"); + }); + + var ws = new WebSocket("ws://{{host}}:{{ports[ws][0]}}/echo-cookie"); + return new Promise((resolve, reject) => { + ws.onclose = t.step_func_done(function () { + assert_unreached("'close' should not fire before 'open'."); + }); + ws.onmessage = t.step_func(function (e) { + ws.onclose = null; + ws.close(); + // Same-scheme WebSockets should get Lax and Strict cookies. + var strictRegex = new RegExp("schemeful_same_site_websockets_strict=" + value); + var laxRegex = new RegExp("schemeful_same_site_websockets_lax=" + value); + assert_regexp_match(e.data, strictRegex, "Same-scheme strict"); + assert_regexp_match(e.data, laxRegex, "Same-scheme strict"); + + var ws2 = new WebSocket("wss://{{host}}:{{ports[wss][0]}}/echo-cookie"); + ws2.onclose = t.step_func_done(function () { + assert_unreached("'close' should not fire before 'open'."); + }); + ws2.onmessage = t.step_func(function (e2) { + ws2.onclose = null; + ws2.close(); + // Cross-scheme WebSockets should only get samesite_none. + var noneRegex = new RegExp("samesite_none_secure=" + value); + assert_regexp_match(e2.data, noneRegex, "Cross-scheme none"); + assert_false(strictRegex.test(e2.data), "Cross-scheme strict"); + assert_false(laxRegex.test(e2.data), "Cross-scheme lax"); + resolve(); + }); + }); + }); + }, "Cross-scheme WebSockets are cross-site"); +</script> +</body> +</html> diff --git a/testing/web-platform/tests/cookies/secure/set-from-dom.https.sub.html b/testing/web-platform/tests/cookies/secure/set-from-dom.https.sub.html new file mode 100644 index 0000000000..46997db18a --- /dev/null +++ b/testing/web-platform/tests/cookies/secure/set-from-dom.https.sub.html @@ -0,0 +1,47 @@ +<!doctype html> +<html> +<head> + <meta charset=utf-8> + <title>Set 'secure' cookie from `document.cookie` on a secure page</title> + <meta name=help href="https://tools.ietf.org/html/draft-west-leave-secure-cookies-alone"> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <script src="/cookies/resources/testharness-helpers.js"></script> +</head> +<body> +<div id=log></div> +<script> + var tests = [ + [ + "'secure' cookie visible in `document.cookie`", + function () { + document.cookie = "secure_from_secure_dom=1; secure; path=/"; + assert_not_equals(document.cookie.match(/secure_from_secure_dom=1/), null); + this.done(); + } + ], + [ + "'secure' cookie visible in HTTP request", + function () { + document.cookie = "secure_from_secure_dom=1; secure; path=/"; + assert_not_equals(document.cookie.match(/secure_from_secure_dom=1/), null); + fetch("https://{{host}}:{{ports[https][0]}}/cookies/resources/echo-json.py", + { "credentials": "include" }) + .then(this.step_func(function (r) { + return r.json(); + })) + .then(this.step_func_done(function (j) { + assert_equals(j["secure_from_secure_dom"], "secure_from_secure_dom=1"); + })); + } + ] + ]; + + function clearKnownCookie() { + document.cookie = "secure_from_secure_dom=0; Secure; expires=Thu, 01 Jan 1970 00:00:01 GMT; path=/"; + } + + executeTestsSerially(tests, clearKnownCookie, clearKnownCookie); +</script> +</body> +</html> diff --git a/testing/web-platform/tests/cookies/secure/set-from-dom.sub.html b/testing/web-platform/tests/cookies/secure/set-from-dom.sub.html new file mode 100644 index 0000000000..91aa8fff3b --- /dev/null +++ b/testing/web-platform/tests/cookies/secure/set-from-dom.sub.html @@ -0,0 +1,47 @@ +<!doctype html> +<html> +<head> + <meta charset=utf-8> + <title>Set 'secure' cookie from `document.cookie` on a non-secure page</title> + <meta name=help href="https://tools.ietf.org/html/draft-west-leave-secure-cookies-alone"> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <script src="/cookies/resources/testharness-helpers.js"></script> +</head> +<body> +<div id=log></div> +<script> + var tests = [ + [ + "'secure' cookie not set in `document.cookie`", + function () { + var originalCookie = document.cookie; + document.cookie = "secure_from_nonsecure_dom=1; secure; path=/"; + assert_equals(document.cookie, originalCookie); + this.done(); + } + ], + [ + "'secure' cookie not sent in HTTP request", + function () { + document.cookie = "secure_from_nonsecure_dom=1; secure; path=/"; + fetch("https://{{host}}:{{ports[https][0]}}/cookies/resources/echo-json.py", { "credentials": "include" }) + .then(this.step_func(function (r) { + return r.json(); + })) + .then(this.step_func_done(function (j) { + assert_equals(j["secure_from_nonsecure_dom"], undefined); + })); + } + ] + ]; + + function clearKnownCookie() { + document.cookie = "secure_from_nonsecure_dom=0; Secure; expires=Thu, 01 Jan 1970 00:00:01 GMT; path=/"; + } + + executeTestsSerially(tests, clearKnownCookie, clearKnownCookie); +</script> +</body> +</html> + diff --git a/testing/web-platform/tests/cookies/secure/set-from-http.https.sub.html b/testing/web-platform/tests/cookies/secure/set-from-http.https.sub.html new file mode 100644 index 0000000000..6024c5b7f6 --- /dev/null +++ b/testing/web-platform/tests/cookies/secure/set-from-http.https.sub.html @@ -0,0 +1,36 @@ +<!doctype html> +<html> +<head> + <meta charset=utf-8> + <title>Set 'secure' cookie from `Set-Cookie` HTTP header on a secure page</title> + <meta name=help href="https://tools.ietf.org/html/draft-west-leave-secure-cookies-alone"> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <script src="/cookies/resources/testharness-helpers.js"></script> +</head> +<body> +<div id=log></div> +<script> + function clearKnownCookie() { + document.cookie = "secure_from_secure_http=0; Secure; expires=Thu, 01 Jan 1970 00:00:01 GMT; path=/"; + } + + test(function () { + assert_not_equals(document.cookie.match(/secure_from_secure_http=1/), null); + }, "'secure' cookie present in `document.cookie`"); + + promise_test(function (t) { + t.add_cleanup(clearKnownCookie); + return fetch("https://{{host}}:{{ports[https][0]}}/cookies/resources/echo-json.py", + { "credentials": "include" }) + .then(function (r) { + return r.json(); + }) + .then(function (j) { + assert_equals(j["secure_from_secure_http"], "secure_from_secure_http=1"); + }); + }, "'secure' cookie sent in HTTP request"); +</script> +</body> +</html> + diff --git a/testing/web-platform/tests/cookies/secure/set-from-http.https.sub.html.headers b/testing/web-platform/tests/cookies/secure/set-from-http.https.sub.html.headers new file mode 100644 index 0000000000..f4c9147fac --- /dev/null +++ b/testing/web-platform/tests/cookies/secure/set-from-http.https.sub.html.headers @@ -0,0 +1,5 @@ +Expires: Mon, 26 Jul 1997 05:00:00 GMT +Cache-Control: no-store, no-cache, must-revalidate +Cache-Control: post-check=0, pre-check=0, false +Pragma: no-cache +Set-Cookie: secure_from_secure_http=1; Secure; Path=/ diff --git a/testing/web-platform/tests/cookies/secure/set-from-http.sub.html b/testing/web-platform/tests/cookies/secure/set-from-http.sub.html new file mode 100644 index 0000000000..c80cc34101 --- /dev/null +++ b/testing/web-platform/tests/cookies/secure/set-from-http.sub.html @@ -0,0 +1,36 @@ +<!doctype html> +<html> +<head> + <meta charset=utf-8> + <title>Set 'secure' cookie from `Set-Cookie` HTTP header on a non-secure page</title> + <meta name=help href="https://tools.ietf.org/html/draft-west-leave-secure-cookies-alone"> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <script src="/cookies/resources/testharness-helpers.js"></script> +</head> +<body> +<div id=log></div> +<script> + function clearKnownCookie() { + document.cookie = "secure_from_nonsecure_http=0; Secure; expires=Thu, 01 Jan 1970 00:00:01 GMT; path=/"; + } + + test(function () { + assert_equals(document.cookie.match(/secure_from_nonsecure_http=1/), null); + }, "'secure' cookie not present in `document.cookie`"); + + promise_test(function (t) { + t.add_cleanup(clearKnownCookie); + return fetch("https://{{host}}:{{ports[https][0]}}/cookies/resources/echo-json.py", + { "credentials": "include" }) + .then(function (r) { + return r.json(); + }) + .then(function (j) { + assert_equals(j["secure_from_nonsecure_http"], undefined); + }); + }, "'secure' cookie not sent in HTTP request"); +</script> +</body> +</html> + diff --git a/testing/web-platform/tests/cookies/secure/set-from-http.sub.html.headers b/testing/web-platform/tests/cookies/secure/set-from-http.sub.html.headers new file mode 100644 index 0000000000..57a45167f0 --- /dev/null +++ b/testing/web-platform/tests/cookies/secure/set-from-http.sub.html.headers @@ -0,0 +1,5 @@ +Expires: Mon, 26 Jul 1997 05:00:00 GMT +Cache-Control: no-store, no-cache, must-revalidate +Cache-Control: post-check=0, pre-check=0, false +Pragma: no-cache +Set-Cookie: secure_from_nonsecure_http=1; Secure; Path=/ diff --git a/testing/web-platform/tests/cookies/secure/set-from-ws.sub.html b/testing/web-platform/tests/cookies/secure/set-from-ws.sub.html new file mode 100644 index 0000000000..b12504450e --- /dev/null +++ b/testing/web-platform/tests/cookies/secure/set-from-ws.sub.html @@ -0,0 +1,45 @@ +<!doctype html> +<html> +<head> + <meta charset=utf-8> + <title>Set 'secure' cookie from `Set-Cookie` HTTP header on a non-secure WebSocket</title> + <meta name=help href="https://tools.ietf.org/html/draft-west-leave-secure-cookies-alone"> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <script src="/cookies/resources/testharness-helpers.js"></script> +</head> +<body> +<div id=log></div> +<script> + function clearKnownCookie() { + document.cookie = "ws_test_secure_from_nonsecure=0; Secure; expires=Thu, 01 Jan 1970 00:00:01 GMT; path=/"; + } + + async_test(function (t) { + t.add_cleanup(clearKnownCookie); + assert_equals(document.cookie.match(/ws_test_secure_from_nonsecure=/), null); + + clearKnownCookie(); + var ws = new WebSocket("ws://{{host}}:{{ports[ws][0]}}/set-cookie-secure?secure_from_nonsecure"); + ws.onclose = t.step_func_done(function () { + assert_unreached("'close' should not fire before 'open'."); + }); + ws.onopen = t.step_func(function (e) { + ws.onclose = null; + ws.close(); + assert_false(/ws_test_secure_from_nonsecure=test/.test(document.cookie)); + + var ws2 = new WebSocket("wss://{{host}}:{{ports[wss][0]}}/echo-cookie"); + ws2.onclose = t.step_func_done(function () { + assert_unreached("'close' should not fire before 'open'."); + }); + ws2.onmessage = t.step_func_done(function (e) { + ws2.onclose = null; + ws2.close(); + assert_false(/ws_test_secure_from_nonsecure=test/.test(e.data)); + }); + }); + }, "'secure' cookie not sent in WSS request when set from WS"); +</script> +</body> +</html> diff --git a/testing/web-platform/tests/cookies/secure/set-from-wss.https.sub.html b/testing/web-platform/tests/cookies/secure/set-from-wss.https.sub.html new file mode 100644 index 0000000000..c5e8b385d0 --- /dev/null +++ b/testing/web-platform/tests/cookies/secure/set-from-wss.https.sub.html @@ -0,0 +1,44 @@ +<!doctype html> +<html> +<head> + <meta charset=utf-8> + <title>Set 'secure' cookie from `Set-Cookie` HTTP header on a secure WebSocket</title> + <meta name=help href="https://tools.ietf.org/html/draft-west-leave-secure-cookies-alone"> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <script src="/cookies/resources/testharness-helpers.js"></script> +</head> +<body> +<div id=log></div> +<script> + function clearKnownCookie() { + document.cookie = "ws_test_secure_from_secure=0; Secure; expires=Thu, 01 Jan 1970 00:00:01 GMT; path=/"; + } + + async_test(function (t) { + t.add_cleanup(clearKnownCookie); + assert_equals(document.cookie.match(/ws_test_secure_from_secure=/), null); + + clearKnownCookie(); + var ws = new WebSocket("wss://{{host}}:{{ports[wss][0]}}/set-cookie-secure?secure_from_secure"); + ws.onclose = t.step_func_done(function () { + assert_unreached("'close' should not fire before 'open'."); + }); + ws.onopen = t.step_func(function (e) { + ws.onclose = null; + ws.close(); + assert_regexp_match(document.cookie, /ws_test_secure_from_secure=test/); + var ws2 = new WebSocket("wss://{{host}}:{{ports[wss][0]}}/echo-cookie"); + ws2.onclose = t.step_func_done(function () { + assert_unreached("'close' should not fire before 'open'."); + }); + ws2.onmessage = t.step_func_done(function (e) { + ws2.onclose = null; + ws2.close(); + assert_regexp_match(e.data, /ws_test_secure_from_secure=test/); + }); + }); + }, "'secure' cookie not sent in HTTP request"); +</script> +</body> +</html> diff --git a/testing/web-platform/tests/cookies/size/attributes.www.sub.html b/testing/web-platform/tests/cookies/size/attributes.www.sub.html new file mode 100644 index 0000000000..2500daef96 --- /dev/null +++ b/testing/web-platform/tests/cookies/size/attributes.www.sub.html @@ -0,0 +1,121 @@ +<!doctype html> +<html> + +<head> + <meta charset=utf-8> + <title>Test cookie attribute size restrictions</title> + <meta name=help href="https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis#section-5.4"> + <meta name="timeout" content="long"> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <script src="/resources/testdriver.js"></script> + <script src="/resources/testdriver-vendor.js"></script> + <script src="/cookies/resources/cookie-test.js"></script> +</head> + +<body> + <div id=log></div> + <script> + const host = "{{host}}"; + const attrSizeTests = [ + { + cookie: `test=1; path=/cookies/size; path=/cookies/siz${"e".repeat(1024)}`, + expected: "test=1", + name: "Too long path attribute (>1024 bytes) is ignored; previous valid path wins.", + defaultPath: false, + }, + { + cookie: `test=2; path=/cookies/siz${"e".repeat(1024)}; path=/cookies/size`, + expected: "test=2", + name: "Too long path attribute (>1024 bytes) is ignored; next valid path wins.", + defaultPath: false, + }, + { + // Look for the cookie using the default path to ensure that it + // doesn't show up if the path attribute actually takes effect. + cookie: `test=3; path=/${"a".repeat(1023)};`, + expected: "", + name: "Max size path attribute (1024 bytes) is not ignored", + }, + { + // Look for the cookie using the default path to ensure that it + // shows up if the path is ignored. + cookie: `test=4; path=/${"a".repeat(1024)};`, + expected: "test=4", + name: "Too long path attribute (>1024 bytes) is ignored", + }, + { + // This page opens on the www subdomain, so we set domain to {{host}} + // to see if anything works as expected. Using a valid domain other + // than ${host} will cause the cookie to fail to be set. + + // NOTE: the domain we use for testing here is technically invalid per + // the RFCs that define the format of domain names, but currently + // neither RFC6265bis or the major browsers enforce those restrictions + // when parsing cookie domain attributes. If that changes, update these + // tests. + cookie: `test=5; domain=${host}; domain=${"a".repeat(1024)}.com`, + expected: "test=5", + name: "Too long domain attribute (>1024 bytes) is ignored; previous valid domain wins.", + }, + { + cookie: `test=6; domain=${"a".repeat(1024)}.com; domain=${host}`, + expected: "test=6", + name: "Too long domain attribute (>1024 bytes) is ignored; next valid domain wins.", + }, + { + cookie: `test=7; domain=${"a".repeat(1020)}.com;`, + expected: "", + name: "Max size domain attribute (1024 bytes) is not ignored" + }, + { + cookie: `test=8; domain=${"a".repeat(1021)}.com;`, + expected: "test=8", + name: "Too long domain attribute (>1024 bytes) is ignored" + }, + { + cookie: cookieStringWithNameAndValueLengths(2048, 2048) + + `; domain=${"a".repeat(1020)}.com; domain=${host}`, + expected: cookieStringWithNameAndValueLengths(2048, 2048), + name: "Set cookie with max size name/value pair and max size attribute value", + }, + { + // RFC6265bis doesn't specify a maximum size of the entire Set-Cookie + // header, although some browsers do + cookie: cookieStringWithNameAndValueLengths(2048, 2048) + + `; domain=${"a".repeat(1020)}.com` + + `; domain=${"a".repeat(1020)}.com` + + `; domain=${"a".repeat(1020)}.com` + + `; domain=${"a".repeat(1020)}.com; domain=${host}`, + expected: cookieStringWithNameAndValueLengths(2048, 2048), + name: "Set cookie with max size name/value pair and multiple max size attributes (>8k bytes total)", + }, + { + cookie: `test=11; max-age=${"1".repeat(1024)};`, + expected: "test=11", + name: "Max length Max-Age attribute value (1024 bytes) doesn't cause cookie rejection" + }, + { + cookie: `test=12; max-age=${"1".repeat(1025)};`, + expected: "test=12", + name: "Too long Max-Age attribute value (>1024 bytes) doesn't cause cookie rejection" + }, + { + cookie: `test=13; max-age=-${"1".repeat(1023)};`, + expected: "", + name: "Max length negative Max-Age attribute value (1024 bytes) doesn't get ignored" + }, + { + cookie: `test=14; max-age=-${"1".repeat(1024)};`, + expected: "test=14", + name: "Too long negative Max-Age attribute value (>1024 bytes) gets ignored" + }, + ]; + + for (const test of attrSizeTests) { + httpCookieTest(test.cookie, test.expected, test.name, test.defaultPath); + } + </script> +</body> + +</html> diff --git a/testing/web-platform/tests/cookies/size/name-and-value.html b/testing/web-platform/tests/cookies/size/name-and-value.html new file mode 100644 index 0000000000..b387bd2d54 --- /dev/null +++ b/testing/web-platform/tests/cookies/size/name-and-value.html @@ -0,0 +1,83 @@ +<!doctype html> +<html> + +<head> + <meta charset=utf-8> + <title>Test cookie name size restrictions</title> + <meta name=help href="https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis#section-5.4"> + <meta name="timeout" content="long"> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <script src="/resources/testdriver.js"></script> + <script src="/resources/testdriver-vendor.js"></script> + <script src="/cookies/resources/cookie-test.js"></script> +</head> + +<body> + <div id=log></div> + <script> + const nameAndValueSizeTests = [ + { + cookie: cookieStringWithNameAndValueLengths(2048, 2048), + expected: cookieStringWithNameAndValueLengths(2048, 2048), + name: "Set max-size cookie with largest possible name and value (4096 bytes)", + }, + { + cookie: cookieStringWithNameAndValueLengths(4097, 1), + expected: "", + name: "Ignore cookie with name larger than 4096 and 1 byte value", + }, + { + cookie: cookieStringWithNameAndValueLengths(4096, 0), + expected: cookieStringWithNameAndValueLengths(4096, 0), + name: "Set max-size value-less cookie", + }, + { + cookie: cookieStringWithNameAndValueLengths(4097, 0), + expected: "", + name: "Ignore value-less cookie with name larger than 4096 bytes", + }, + { + cookie: cookieStringWithNameAndValueLengths(1, 4095), + expected: cookieStringWithNameAndValueLengths(1, 4095), + name: "Set max-size cookie with largest possible value (4095 bytes)", + }, + { + cookie: cookieStringWithNameAndValueLengths(1, 4096), + expected: "", + name: "Ignore named cookie (with non-zero length) and value larger than 4095 bytes", + }, + { + cookie: cookieStringWithNameAndValueLengths(4096, 1), + expected: "", + name: "Ignore named cookie with length larger than 4095 bytes, and a non-zero value", + }, + { + cookie: cookieStringWithNameAndValueLengths(0, 4096), + expected: cookieStringWithNameAndValueLengths(0, 4096).slice(1), // it won't come back with leading = + name: "Set max-size name-less cookie", + }, + { + cookie: cookieStringWithNameAndValueLengths(0, 4097), + expected: "", + name: "Ignore name-less cookie with value larger than 4096 bytes", + }, + { + cookie: cookieStringWithNameAndValueLengths(0, 4097).slice(1), // slice off leading = + expected: "", + name: "Ignore name-less cookie (without leading =) with value larger than 4096 bytes", + }, + { + cookie: cookieStringWithNameAndValueLengths(2048, 2048) + '; Max-Age:43110;', + expected: cookieStringWithNameAndValueLengths(2048, 2048), + name: "Set max-size cookie that also has an attribute", + }, + ]; + + for (const test of nameAndValueSizeTests) { + httpCookieTest(test.cookie, test.expected, test.name); + } + </script> +</body> + +</html> diff --git a/testing/web-platform/tests/cookies/value/value-ctl.html b/testing/web-platform/tests/cookies/value/value-ctl.html new file mode 100644 index 0000000000..5a24064f43 --- /dev/null +++ b/testing/web-platform/tests/cookies/value/value-ctl.html @@ -0,0 +1,64 @@ +<!doctype html> +<html> + <head> + <meta charset=utf-8> + <title>Test cookie value parsing with control characters</title> + <meta name=help href="https://tools.ietf.org/html/rfc6265#section-5.2"> + <meta name="timeout" content="long"> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <script src="/resources/testdriver.js"></script> + <script src="/resources/testdriver-vendor.js"></script> + <script src="/cookies/resources/cookie-test.js"></script> + </head> + <body> + <div id=log></div> + <script> + // Tests for control characters (CTLs) in a cookie's value. + // CTLs are defined by RFC 5234 to be %x00-1F / %x7F. + const CTLS = getCtlCharacters(); + + // All CTLs, with the exception of %x09 (the tab character), should + // cause the cookie to be rejected. + for (const ctl of CTLS) { + if (ctl.code === 0x09) { + domCookieTest( + `test=${ctl.code}${ctl.chr}value`, + `test=${ctl.code}${ctl.chr}value`, + `Cookie with %x${ctl.code.toString(16)} in value is accepted (DOM).`); + } else { + domCookieTest( + `test=${ctl.code}${ctl.chr}value`, + '', + `Cookie with %x${ctl.code.toString(16)} in value is rejected (DOM).`); + } + } + + // Note that per RFC 9110, %x00, %x0A, and %x0D characters in the HTTP + // header MUST either cause the HTTP message to be rejected or be + // replaced with %x20 (space) characters. Both cases will result in a + // passing test here. For more info, see: + // https://www.rfc-editor.org/rfc/rfc9110.html#section-5.5 + for (const ctl of CTLS) { + if (ctl.code === 0x09) { + httpCookieTest( + `test=${ctl.code}${ctl.chr}value`, + `test=${ctl.code}${ctl.chr}value`, + `Cookie with %x${ctl.code.toString(16)} in value is accepted (HTTP).`); + } else if (ctl.code === 0x00 || ctl.code === 0x0A || ctl.code === 0x0D) { + httpCookieTest( + `test${ctl.code}${ctl.chr}name=${ctl.code}`, + `test${ctl.code} name=${ctl.code}`, + `Cookie with %x${ctl.code.toString(16)} in name is rejected or modified (HTTP).`, + /* defaultPath */ true, /* allowFetchFailure */ true); + } else { + httpCookieTest( + `test=${ctl.code}${ctl.chr}value`, + '', + `Cookie with %x${ctl.code.toString(16)} in value is rejected (HTTP).`); + } + } + + </script> + </body> +</html> diff --git a/testing/web-platform/tests/cookies/value/value.html b/testing/web-platform/tests/cookies/value/value.html new file mode 100644 index 0000000000..14292cece9 --- /dev/null +++ b/testing/web-platform/tests/cookies/value/value.html @@ -0,0 +1,170 @@ +<!doctype html> +<html> + <head> + <meta charset=utf-8> + <title>Test cookie value parsing</title> + <meta name=help href="https://tools.ietf.org/html/rfc6265#section-5.2"> + <meta name="timeout" content="long"> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <script src="/resources/testdriver.js"></script> + <script src="/resources/testdriver-vendor.js"></script> + <script src="/cookies/resources/cookie-test.js"></script> + </head> + <body> + <div id=log></div> + <script> + // TODO: there is more to test here, these tests capture the old + // ported http-state tests. Feel free to delete this comment when more + // are added, or these are split up into logical groups. + const valueTests = [ + { + cookie: "test=1, baz=qux", + expected: "test=1, baz=qux", + name: "Set value containing a comma", + }, + { + cookie: 'test="2, baz=qux"', + expected: 'test="2, baz=qux"', + name: "Set quoted value containing a comma", + }, + { + cookie: 'test="3zz;pp" ; ;', + expected: 'test="3zz', + name: "Ignore values after semicolon", + }, + { + cookie: 'test="4zz ;', + expected: 'test="4zz', + name: "Ignore whitespace at the end of value", + }, + { + cookie: 'test="5zzz " "ppp" ;', + expected: 'test="5zzz " "ppp"', + name: "Set value including quotes and whitespace up until semicolon", + }, + { + cookie: 'test=6A"B ;', + expected: 'test=6A"B', + name: "Set value with a single quote excluding whitespace" + }, + { + cookie: "test7", + expected: "test7", + name: "Set nameless cookie to its value", + }, + { + cookie: '"test8\"HHH"', + expected: '"test8\"HHH"', + name: "Set nameless cookie to its value with an escaped quote", + }, + { + cookie: 'test="9', + expected: 'test="9', + name: "Set value with unbalanced leading quote", + }, + { + cookie: "=test10", + expected: "test10", + name: "Set nameless cookie followed by '=' to its value", + }, + { + // 4 + 2 + 4090 = 4096 + cookie: `test=11${"a".repeat(4090)}`, + expected: `test=11${"a".repeat(4090)}`, + name: "Set cookie with large name + value ( = 4kb)", + }, + { + // 4 + 2 + 4091 = 4097 + cookie: `test=12${"a".repeat(4091)}`, + expected: "", + name: "Ignore cookie with large name + value ( > 4kb)", + }, + { + cookie: `test=13\nZYX`, + expected: "test=13", + name: "Set cookie but ignore value after LF", + }, + { + cookie: 'test="14 " ;', + expected: 'test="14 "', + name: "Set cookie ignoring whitespace after value endquote", + }, + { + cookie: "test=15 ;", + expected: "test=15", + name: "Ignore whitespace and ; after value", + }, + { + cookie: "test= 16", + expected: "test=16", + name: "Ignore whitespace preceding value", + }, + { + cookie: 'test="17"', + expected: 'test="17"', + name: "Set cookie with quotes in value", + }, + { + cookie: 'test=" 18 "', + expected: 'test=" 18 "', + name: "Set cookie keeping whitespace inside quoted value", + }, + { + cookie: 'test="19;wow"', + expected: 'test="19', + name: "Set cookie value ignoring characters after semicolon", + }, + { + cookie: 'test="20=20"', + expected: 'test="20=20"', + name: "Set cookie with another = inside quoted value", + }, + { + cookie: "test = 21 ; ttt", + expected: "test=21", + name: "Set cookie ignoring whitespace surrounding value and characters after first semicolon", + }, + { + cookie: ["testA=22", "test22=", "testB=22"], + expected: "testA=22; test22=; testB=22", + name: "Set valueless cookie, given `Set-Cookie: test22=`", + }, + { + cookie: "test=%32%33", + expected: "test=%32%33", + name: "URL-encoded cookie value is not decoded", + }, + { + cookie: "test24==", + expected: "test24==", + name: "Set cookie with value set to =", + }, + { + cookie: 'test=25=25', + expected: 'test=25=25', + name: "Set cookie with one = inside an unquoted value", + }, + { + cookie: 'test=26=26=26', + expected: 'test=26=26=26', + name: "Set cookie with two = inside an unquoted value", + }, + { + cookie: 'test=27 test', + expected: 'test=27 test', + name: "Set cookie with a space character in the value", + }, + { + cookie: ' test test28 ;', + expected: 'test test28', + name: "Set a nameless cookie with a space character in the value", + }, + ]; + + for (const test of valueTests) { + httpCookieTest(test.cookie, test.expected, test.name, test.defaultPath); + } + </script> + </body> +</html> |