diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
commit | 26a029d407be480d791972afb5975cf62c9360a6 (patch) | |
tree | f435a8308119effd964b339f76abb83a57c29483 /testing/web-platform/tests/cookies/resources | |
parent | Initial commit. (diff) | |
download | firefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz firefox-26a029d407be480d791972afb5975cf62c9360a6.zip |
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'testing/web-platform/tests/cookies/resources')
26 files changed, 1057 insertions, 0 deletions
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(); +} |