| // META: timeout=long |
| // META: script=/common/get-host-info.sub.js |
| // META: script=/common/utils.js |
| // META: script=beacon-common.sub.js |
| |
| 'use strict'; |
| |
| const {HTTPS_ORIGIN, ORIGIN, HTTPS_REMOTE_ORIGIN} = get_host_info(); |
| |
| // As /common/redirect.py is not under our control, let's make sure that |
| // it doesn't support CORS. |
| parallelPromiseTest(async (t) => { |
| const destination = `${HTTPS_REMOTE_ORIGIN}/common/text-plain.txt` + |
| `?pipe=header(access-control-allow-origin,*)`; |
| const redirect = `${HTTPS_REMOTE_ORIGIN}/common/redirect.py` + |
| `?location=${encodeURIComponent(destination)}`; |
| |
| // Fetching `destination` is fine because it supports CORS. |
| await fetch(destination); |
| |
| // Fetching redirect.py should fail because it doesn't support CORS. |
| await promise_rejects_js(t, TypeError, fetch(redirect)); |
| }, '/common/redirect.py does not support CORS'); |
| |
| for (const type of [STRING, ARRAYBUFFER, FORM, BLOB]) { |
| parallelPromiseTest(async (t) => { |
| const iframe = document.createElement('iframe'); |
| document.body.appendChild(iframe); |
| t.add_cleanup(() => iframe.remove()); |
| |
| const payload = makePayload(SMALL, type); |
| const id = token(); |
| // As we use "no-cors" for CORS-safelisted requests, the redirect is |
| // processed without an error while the request is cross-origin and the |
| // redirect handler doesn't support CORS. |
| const destination = |
| `${HTTPS_REMOTE_ORIGIN}/beacon/resources/beacon.py?cmd=store&id=${id}`; |
| const url = `${HTTPS_REMOTE_ORIGIN}/common/redirect.py` + |
| `?status=307&location=${encodeURIComponent(destination)}`; |
| |
| assert_true(iframe.contentWindow.navigator.sendBeacon(url, payload)); |
| iframe.remove(); |
| |
| await waitForResult(id); |
| }, `cross-origin, CORS-safelisted: type = ${type}`); |
| } |
| |
| parallelPromiseTest(async (t) => { |
| const iframe = document.createElement('iframe'); |
| document.body.appendChild(iframe); |
| t.add_cleanup(() => iframe.remove()); |
| |
| const payload = makePayload(SMALL, BLOB, 'application/octet-stream'); |
| const id = token(); |
| const destination = |
| `${HTTPS_REMOTE_ORIGIN}/beacon/resources/beacon.py?cmd=store&id=${id}`; |
| const url = `${HTTPS_REMOTE_ORIGIN}/common/redirect.py` + |
| `?status=307&location=${encodeURIComponent(destination)}`; |
| assert_true(iframe.contentWindow.navigator.sendBeacon(url, payload)); |
| iframe.remove(); |
| |
| // The beacon is rejected during redirect handling because /common/redirect.py |
| // doesn't support CORS. |
| |
| await new Promise((resolve) => step_timeout(resolve, 3000)); |
| const res = await fetch(`/beacon/resources/beacon.py?cmd=stat&id=${id}`); |
| assert_equals((await res.json()).length, 0); |
| }, `cross-origin, non-CORS-safelisted: failure case (with redirect)`); |
| |
| parallelPromiseTest(async (t) => { |
| const iframe = document.createElement('iframe'); |
| document.body.appendChild(iframe); |
| t.add_cleanup(() => iframe.remove()); |
| |
| const payload = makePayload(SMALL, BLOB, 'application/octet-stream'); |
| const id = token(); |
| const url = |
| `${HTTPS_REMOTE_ORIGIN}/beacon/resources/beacon.py?cmd=store&id=${id}`; |
| assert_true(iframe.contentWindow.navigator.sendBeacon(url, payload)); |
| iframe.remove(); |
| |
| // The beacon is rejected during preflight handling. |
| await waitForResult(id, /*expectedError=*/ 'Preflight not expected.'); |
| }, `cross-origin, non-CORS-safelisted: failure case (without redirect)`); |
| |
| for (const credentials of [false, true]) { |
| parallelPromiseTest(async (t) => { |
| const iframe = document.createElement('iframe'); |
| document.body.appendChild(iframe); |
| t.add_cleanup(() => iframe.remove()); |
| |
| const payload = makePayload(SMALL, BLOB, 'application/octet-stream'); |
| const id = token(); |
| let url = `${HTTPS_REMOTE_ORIGIN}/beacon/resources/beacon.py` + |
| `?cmd=store&id=${id}&preflightExpected&origin=${ORIGIN}`; |
| if (credentials) { |
| url += `&credentials=true`; |
| } |
| assert_true(iframe.contentWindow.navigator.sendBeacon(url, payload)); |
| iframe.remove(); |
| |
| // We need access-control-allow-credentials in the preflight response. This |
| // shows that the request's credentials mode is 'include'. |
| if (credentials) { |
| const result = await waitForResult(id); |
| assert_equals(result.type, 'application/octet-stream'); |
| } else { |
| await new Promise((resolve) => step_timeout(resolve, 3000)); |
| const res = await fetch(`/beacon/resources/beacon.py?cmd=stat&id=${id}`); |
| assert_equals((await res.json()).length, 0); |
| } |
| }, `cross-origin, non-CORS-safelisted[credentials=${credentials}]`); |
| } |
| |
| parallelPromiseTest(async (t) => { |
| const iframe = document.createElement('iframe'); |
| document.body.appendChild(iframe); |
| t.add_cleanup(() => iframe.remove()); |
| |
| const payload = makePayload(SMALL, BLOB, 'application/octet-stream'); |
| const id = token(); |
| const destination = `${HTTPS_REMOTE_ORIGIN}/beacon/resources/beacon.py` + |
| `?cmd=store&id=${id}&preflightExpected&origin=${ORIGIN}&credentials=true`; |
| const url = `${HTTPS_REMOTE_ORIGIN}/fetch/api/resources/redirect.py` + |
| `?redirect_status=307&allow_headers=content-type` + |
| `&location=${encodeURIComponent(destination)}`; |
| assert_true(iframe.contentWindow.navigator.sendBeacon(url, payload)); |
| iframe.remove(); |
| |
| const result = await waitForResult(id); |
| assert_equals(result.type, 'application/octet-stream'); |
| }, `cross-origin, non-CORS-safelisted success-case (with redirect)`); |