diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:22:09 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:22:09 +0000 |
commit | 43a97878ce14b72f0981164f87f2e35e14151312 (patch) | |
tree | 620249daf56c0258faa40cbdcf9cfba06de2a846 /testing/web-platform/tests/cookie-store/resources | |
parent | Initial commit. (diff) | |
download | firefox-upstream.tar.xz firefox-upstream.zip |
Adding upstream version 110.0.1.upstream/110.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'testing/web-platform/tests/cookie-store/resources')
6 files changed, 420 insertions, 0 deletions
diff --git a/testing/web-platform/tests/cookie-store/resources/always_changing_sw.sub.js b/testing/web-platform/tests/cookie-store/resources/always_changing_sw.sub.js new file mode 100644 index 0000000000..9fdf99848f --- /dev/null +++ b/testing/web-platform/tests/cookie-store/resources/always_changing_sw.sub.js @@ -0,0 +1,6 @@ +// This script changes every time it is fetched. + +// When used as a service worker script, this causes the Service Worker to be +// updated on every ServiceWorkerRegistration.update() call. + +// The following bytes change on every fetch: {{uuid()}} diff --git a/testing/web-platform/tests/cookie-store/resources/cookie-test-helpers.js b/testing/web-platform/tests/cookie-store/resources/cookie-test-helpers.js new file mode 100644 index 0000000000..178947ad6e --- /dev/null +++ b/testing/web-platform/tests/cookie-store/resources/cookie-test-helpers.js @@ -0,0 +1,226 @@ +'use strict'; + +// TODO(jsbell): Once ServiceWorker is supported, add arbitrary path coverage. +const kPath = location.pathname.replace(/[^/]+$/, ''); + +// True when running in a document context as opposed to a worker context +const kHasDocument = typeof document !== 'undefined'; + +// True when running on unsecured 'http:' rather than secured 'https:'. +const kIsUnsecured = location.protocol !== 'https:'; + +const kCookieHelperCgi = 'resources/cookie_helper.py'; + +// Approximate async equivalent to the document.cookie getter but with +// important differences: optional additional getAll arguments are +// forwarded, and an empty cookie jar returns undefined. +// +// This is intended primarily for verification against expected cookie +// jar contents. It should produce more readable messages using +// assert_equals in failing cases than assert_object_equals would +// using parsed cookie jar contents and also allows expectations to be +// written more compactly. +async function getCookieString(...args) { + const cookies = await cookieStore.getAll(...args); + return cookies.length + ? cookies.map(({name, value}) => + (name ? (name + '=') : '') + value).join('; ') + : undefined; +} + +// Approximate async equivalent to the document.cookie getter but from +// the server's point of view. Returns UTF-8 interpretation. Allows +// sub-path to be specified. +// +// Unlike document.cookie, this returns undefined when no cookies are +// present. +async function getCookieStringHttp(extraPath = null) { + const url = + kCookieHelperCgi + ((extraPath == null) ? '' : ('/' + extraPath)); + const response = await fetch(url, { credentials: 'include' }); + const text = await response.text(); + assert_equals( + response.ok, + true, + 'CGI should have succeeded in getCookieStringHttp\n' + text); + assert_equals( + response.headers.get('content-type'), + 'text/plain; charset=utf-8', + 'CGI did not return UTF-8 text in getCookieStringHttp'); + if (text === '') + return undefined; + assert_equals( + text.indexOf('cookie='), + 0, + 'CGI response did not begin with "cookie=" and was not empty: ' + text); + return decodeURIComponent(text.replace(/^cookie=/, '')); +} + +// Approximate async equivalent to the document.cookie getter but from +// the server's point of view. Returns binary string +// interpretation. Allows sub-path to be specified. +// +// Unlike document.cookie, this returns undefined when no cookies are +// present. +async function getCookieBinaryHttp(extraPath = null) { + const url = + kCookieHelperCgi + + ((extraPath == null) ? + '' : + ('/' + extraPath)) + '?charset=iso-8859-1'; + const response = await fetch(url, { credentials: 'include' }); + const text = await response.text(); + assert_equals( + response.ok, + true, + 'CGI should have succeeded in getCookieBinaryHttp\n' + text); + assert_equals( + response.headers.get('content-type'), + 'text/plain; charset=iso-8859-1', + 'CGI did not return ISO 8859-1 text in getCookieBinaryHttp'); + if (text === '') + return undefined; + assert_equals( + text.indexOf('cookie='), + 0, + 'CGI response did not begin with "cookie=" and was not empty: ' + text); + return unescape(text.replace(/^cookie=/, '')); +} + +// Approximate async equivalent to the document.cookie setter but from +// the server's point of view. +async function setCookieStringHttp(setCookie) { + const encodedSetCookie = encodeURIComponent(setCookie); + const url = kCookieHelperCgi; + const headers = new Headers(); + headers.set( + 'content-type', + 'application/x-www-form-urlencoded; charset=utf-8'); + const response = await fetch( + url, + { + credentials: 'include', + method: 'POST', + headers: headers, + body: 'set-cookie=' + encodedSetCookie, + }); + const text = await response.text(); + assert_equals( + response.ok, + true, + 'CGI should have succeeded in setCookieStringHttp set-cookie: ' + + setCookie + '\n' + text); + assert_equals( + response.headers.get('content-type'), + 'text/plain; charset=utf-8', + 'CGI did not return UTF-8 text in setCookieStringHttp'); + assert_equals( + text, + 'set-cookie=' + encodedSetCookie, + 'CGI did not faithfully echo the set-cookie value'); +} + +// Approximate async equivalent to the document.cookie setter but from +// the server's point of view. This version sets a binary cookie rather +// than a UTF-8 one. +async function setCookieBinaryHttp(setCookie) { + const encodedSetCookie = escape(setCookie).split('/').join('%2F'); + const url = kCookieHelperCgi + '?charset=iso-8859-1'; + const headers = new Headers(); + headers.set( + 'content-type', + 'application/x-www-form-urlencoded; charset=iso-8859-1'); + const response = await fetch(url, { + credentials: 'include', + method: 'POST', + headers: headers, + body: 'set-cookie=' + encodedSetCookie + }); + const text = await response.text(); + assert_equals( + response.ok, + true, + 'CGI should have succeeded in setCookieBinaryHttp set-cookie: ' + + setCookie + '\n' + text); + assert_equals( + response.headers.get('content-type'), + 'text/plain; charset=iso-8859-1', + 'CGI did not return Latin-1 text in setCookieBinaryHttp'); + assert_equals( + text, + 'set-cookie=' + encodedSetCookie, + 'CGI did not faithfully echo the set-cookie value'); +} + +// Async document.cookie getter; converts '' to undefined which loses +// information in the edge case where a single ''-valued anonymous +// cookie is visible. +async function getCookieStringDocument() { + if (!kHasDocument) + throw 'document.cookie not available in this context'; + return String(document.cookie || '') || undefined; +} + +// Async document.cookie setter +async function setCookieStringDocument(setCookie) { + if (!kHasDocument) + throw 'document.cookie not available in this context'; + document.cookie = setCookie; +} + +// Observe the next 'change' event on the cookieStore. Typical usage: +// +// const eventPromise = observeNextCookieChangeEvent(); +// await /* something that modifies cookies */ +// await verifyCookieChangeEvent( +// eventPromise, {changed: [{name: 'name', value: 'value'}]}); +// +function observeNextCookieChangeEvent() { + return new Promise(resolve => { + cookieStore.addEventListener('change', e => resolve(e), {once: true}); + }); +} + +async function verifyCookieChangeEvent(eventPromise, expected, description) { + description = description ? description + ': ' : ''; + expected = Object.assign({changed:[], deleted:[]}, expected); + const event = await eventPromise; + assert_equals(event.changed.length, expected.changed.length, + description + 'number of changed cookies'); + for (let i = 0; i < event.changed.length; ++i) { + assert_equals(event.changed[i].name, expected.changed[i].name, + description + 'changed cookie name'); + assert_equals(event.changed[i].value, expected.changed[i].value, + description + 'changed cookie value'); + } + assert_equals(event.deleted.length, expected.deleted.length, + description + 'number of deleted cookies'); + for (let i = 0; i < event.deleted.length; ++i) { + assert_equals(event.deleted[i].name, expected.deleted[i].name, + description + 'deleted cookie name'); + assert_equals(event.deleted[i].value, expected.deleted[i].value, + description + 'deleted cookie value'); + } +} + +// Helper function for promise_test with cookies; cookies +// named in these tests are cleared before/after the test +// body function is executed. +async function cookie_test(func, description) { + + // Wipe cookies used by tests before and after the test. + async function deleteAllCookies() { + (await cookieStore.getAll()).forEach(({name, value}) => { + cookieStore.delete(name); + }); + } + + return promise_test(async t => { + await deleteAllCookies(); + try { + return await func(t); + } finally { + await deleteAllCookies(); + } + }, description); +} diff --git a/testing/web-platform/tests/cookie-store/resources/cookie_helper.py b/testing/web-platform/tests/cookie-store/resources/cookie_helper.py new file mode 100644 index 0000000000..71dd8b82ee --- /dev/null +++ b/testing/web-platform/tests/cookie-store/resources/cookie_helper.py @@ -0,0 +1,84 @@ +# -*- coding: utf-8 -*- + +# Active wptserve handler for cookie operations. +# +# This must support the following requests: +# +# - GET with the following query parameters: +# - charset: (optional) character set for response (default: utf-8) +# A cookie: request header (if present) is echoed in the body with a +# cookie= prefix followed by the urlencoded bytes from the header. +# Used to inspect the cookie jar from an HTTP request header context. +# - POST with form-data in the body and the following query-or-form parameters: +# - set-cookie: (optional; repeated) echoed in the set-cookie: response +# header and also echoed in the body with a set-cookie= prefix +# followed by the urlencoded bytes from the parameter; multiple occurrences +# are CRLF-delimited. +# Used to set cookies from an HTTP response header context. +# +# The response has 200 status and content-type: text/plain; charset=<charset> +import encodings, re + +from urllib.parse import parse_qs, quote + +from wptserve.utils import isomorphic_decode, isomorphic_encode + +# NOTE: These are intentionally very lax to permit testing +DISALLOWED_IN_COOKIE_NAME_RE = re.compile(br'[;\0-\x1f\x7f]') +DISALLOWED_IN_HEADER_RE = re.compile(br'[\0-\x1f\x7f]') + +# Ensure common charset names do not end up with different +# capitalization or punctuation +CHARSET_OVERRIDES = { + encodings.codecs.lookup(charset).name: charset + for charset in (u'utf-8', u'iso-8859-1',) +} + +def quote_str(cookie_str): + return isomorphic_encode(quote(isomorphic_decode(cookie_str), u'', encoding=u'iso-8859-1')) + +def parse_qs_str(query_str): + args = parse_qs(isomorphic_decode(query_str), keep_blank_values=True, encoding=u'iso-8859-1') + binary_args = {} + for key, val in args.items(): + binary_args[isomorphic_encode(key)] = [isomorphic_encode(x) for x in val] + return binary_args + +def main(request, response): + assert request.method in ( + u'GET', + u'POST', + ), u'request method was neither GET nor POST: %r' % request.method + qd = (isomorphic_encode(request.url).split(b'#')[0].split(b'?', 1) + [b''])[1] + if request.method == u'POST': + qd += b'&' + request.body + args = parse_qs_str(qd) + + charset = encodings.codecs.lookup([isomorphic_decode(x) for x in args.get(b'charset', [u'utf-8'])][-1]).name + charset = CHARSET_OVERRIDES.get(charset, charset) + headers = [(b'content-type', b'text/plain; charset=' + isomorphic_encode(charset))] + body = [] + if request.method == u'POST': + for set_cookie in args.get(b'set-cookie', []): + if b'=' in set_cookie.split(b';', 1)[0]: + name, rest = set_cookie.split(b'=', 1) + assert re.search( + DISALLOWED_IN_COOKIE_NAME_RE, + name + ) is None, b'name had disallowed characters: %r' % name + else: + rest = set_cookie + assert re.search( + DISALLOWED_IN_HEADER_RE, + rest + ) is None, b'rest had disallowed characters: %r' % rest + headers.append((b'set-cookie', set_cookie)) + body.append(b'set-cookie=' + quote_str(set_cookie)) + + else: + cookie = request.headers.get(b'cookie') + if cookie is not None: + body.append(b'cookie=' + quote_str(cookie)) + body = b'\r\n'.join(body) + headers.append((b'content-length', len(body))) + return 200, headers, body diff --git a/testing/web-platform/tests/cookie-store/resources/empty_sw.js b/testing/web-platform/tests/cookie-store/resources/empty_sw.js new file mode 100644 index 0000000000..2b0ae22612 --- /dev/null +++ b/testing/web-platform/tests/cookie-store/resources/empty_sw.js @@ -0,0 +1 @@ +// Empty service worker script. diff --git a/testing/web-platform/tests/cookie-store/resources/helper_iframe.sub.html b/testing/web-platform/tests/cookie-store/resources/helper_iframe.sub.html new file mode 100644 index 0000000000..9017eace44 --- /dev/null +++ b/testing/web-platform/tests/cookie-store/resources/helper_iframe.sub.html @@ -0,0 +1,31 @@ +<!doctype html> +<meta charset='utf-8'> +<link rel='author' href='jarrydg@chromium.org' title='Jarryd Goodman'> +<script> + 'use strict'; + + // Writing a cookie: + // Input: { cookieToSet: { name: 'cookie-name', value: 'cookie-value' } } + // Response: "Cookie has been set" + // + // Read a cookie. + // Command: { existingCookieName: 'cookie-name' } + // Response: Result of cookieStore.get('cookie-name'): + // { frameCookie: { name: 'cookie-name', value: 'cookie-value' } } + window.addEventListener('message', async function (event) { + const { opname } = event.data; + if (opname === 'set-cookie') { + const { name, value } = event.data + await cookieStore.set({ + name, + value, + domain: '{{host}}', + }); + event.source.postMessage('Cookie has been set', event.origin); + } else if (opname === 'get-cookie') { + const { name } = event.data + const frameCookie = await cookieStore.get(name); + event.source.postMessage({frameCookie}, event.origin); + } + }); +</script> diff --git a/testing/web-platform/tests/cookie-store/resources/helpers.js b/testing/web-platform/tests/cookie-store/resources/helpers.js new file mode 100644 index 0000000000..8d5dddef65 --- /dev/null +++ b/testing/web-platform/tests/cookie-store/resources/helpers.js @@ -0,0 +1,72 @@ +/** + * Promise based helper function who's return promise will resolve + * once the iframe src has been loaded + * @param {string} url the url to set the iframe src + * @param {test} t a test object to add a cleanup function to + * @return {Promise} when resolved, will return the iframe + */ +self.createIframe = (url, t) => new Promise(resolve => { + const iframe = document.createElement('iframe'); + iframe.addEventListener('load', () => {resolve(iframe);}, {once: true}); + iframe.src = url; + document.documentElement.appendChild(iframe); + t.add_cleanup(() => iframe.remove()); +}); + +/** + * @description - Function unregisters any service workers in this scope + * and then creates a new registration. The function returns + * a promise that resolves when the registered service worker + * becomes activated. The resolved promise yields the + * service worker registration + * @param {testCase} t - test case to add cleanup functions to + */ +self.createServiceWorker = async (t, sw_registration_name, scope_url) => { + let registration = await navigator.serviceWorker.getRegistration(scope_url); + if (registration) + await registration.unregister(); + + registration = await navigator.serviceWorker.register(sw_registration_name, + {scope_url}); + t.add_cleanup(() => registration.unregister()); + + return new Promise(resolve => { + const serviceWorker = registration.installing || registration.active || + registration.waiting; + serviceWorker.addEventListener('statechange', event => { + if (event.target.state === 'activated') { + resolve(serviceWorker); + } + }); + }) +} + +/** + * Function that will return a promise that resolves when a message event + * is fired. Returns a promise that resolves to the message that was received + */ +self.waitForMessage = () => new Promise(resolve => { + window.addEventListener('message', event => { + resolve(event.data); + }, {once: true}); +}); + +/** + * Sends a message via MessageChannel and waits for the response + * @param {*} message + * @returns {Promise} resolves with the response payload + */ +self.sendMessageOverChannel = (message, target) => { + return new Promise(function(resolve, reject) { + const messageChannel = new MessageChannel(); + messageChannel.port1.onmessage = event => { + if (event.data.error) { + reject(event.data.error); + } else { + resolve(event.data); + } + }; + + target.postMessage(message, [messageChannel.port2]); + }) +}; |