From 43da9b8907ef0a4b7c0b5b07756a9daa84e6ae17 Mon Sep 17 00:00:00 2001
From: Espen Hovlandsdal
Date: Mon, 18 Nov 2024 20:19:17 -0800
Subject: [PATCH 1/5] Add `Last-Event-ID` to CORS-safelisted headers
The EventSource API does _not_ run any preflight request when specifying
the `Last-Event-ID` header, and so `fetch` requests should also allow
this header to be set manually.
See https://github.com/whatwg/fetch/issues/568 for more information
---
cors/cors-safelisted-request-header.any.js | 9 +++++++++
fetch/api/cors/cors-no-preflight.any.js | 2 ++
fetch/api/cors/resources/not-cors-safelisted.json | 2 ++
3 files changed, 13 insertions(+)
diff --git a/cors/cors-safelisted-request-header.any.js b/cors/cors-safelisted-request-header.any.js
index a0a0417d74b3db..d02110c79ac956 100644
--- a/cors/cors-safelisted-request-header.any.js
+++ b/cors/cors-safelisted-request-header.any.js
@@ -69,3 +69,12 @@ function safelist(headers, expectPreflight = false) {
].forEach(([value, preflight = false]) => {
safelist({"range": value}, preflight);
});
+
+[
+ ["abc123"],
+ ["\"", true],
+ ["e5p3n<3k0k0s", true],
+ ["012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678", true],
+].forEach(([value, preflight = false]) => {
+ safelist({"last-event-id": value}, preflight);
+});
\ No newline at end of file
diff --git a/fetch/api/cors/cors-no-preflight.any.js b/fetch/api/cors/cors-no-preflight.any.js
index 7a0269aae4ec3d..e3063e75a18ad7 100644
--- a/fetch/api/cors/cors-no-preflight.any.js
+++ b/fetch/api/cors/cors-no-preflight.any.js
@@ -39,3 +39,5 @@ corsNoPreflight("Cross domain [GET] [Content-Type: multipart/form-data]", host_i
corsNoPreflight("Cross domain [GET] [Content-Type: text/plain]", host_info.HTTP_REMOTE_ORIGIN, "GET" , "Content-Type", "text/plain");
corsNoPreflight("Cross domain [GET] [Content-Type: text/plain;charset=utf-8]", host_info.HTTP_REMOTE_ORIGIN, "GET" , "Content-Type", "text/plain;charset=utf-8");
corsNoPreflight("Cross domain [GET] [Content-Type: Text/Plain;charset=utf-8]", host_info.HTTP_REMOTE_ORIGIN, "GET" , "Content-Type", "Text/Plain;charset=utf-8");
+corsNoPreflight("Cross domain [GET] [Last-Event-ID: evt-14]", host_info.HTTP_REMOTE_ORIGIN, "GET", "Last-Event-ID", "evt-14");
+corsNoPreflight("Cross domain [GET] [Last-Event-ID: EvT-15]", host_info.HTTP_REMOTE_ORIGIN, "GET", "Last-Event-ID", "EvT-15");
diff --git a/fetch/api/cors/resources/not-cors-safelisted.json b/fetch/api/cors/resources/not-cors-safelisted.json
index 945dc0f93ba4a3..4bee8548b62d75 100644
--- a/fetch/api/cors/resources/not-cors-safelisted.json
+++ b/fetch/api/cors/resources/not-cors-safelisted.json
@@ -8,6 +8,8 @@
["content-language", "@"],
["content-type", "text/html"],
["content-type", "text/plain; long=0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901"],
+ ["last-event-id", "\""],
+ ["last-event-id", "012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678"],
["range", "bytes 0-"],
["test", "hi"]
]
From 2d86695898c9441af14adc85886325c0abe66a2a Mon Sep 17 00:00:00 2001
From: Espen Hovlandsdal
Date: Wed, 20 Nov 2024 13:55:12 -0800
Subject: [PATCH 2/5] Add tests for CORS-unsafe `Last-Event-ID` in EventSource
---
...entsource-cross-origin-preflight.window.js | 33 +++++++
.../resources/cors-unsafe-last-event-id.py | 96 +++++++++++++++++++
2 files changed, 129 insertions(+)
create mode 100644 eventsource/eventsource-cross-origin-preflight.window.js
create mode 100644 eventsource/resources/cors-unsafe-last-event-id.py
diff --git a/eventsource/eventsource-cross-origin-preflight.window.js b/eventsource/eventsource-cross-origin-preflight.window.js
new file mode 100644
index 00000000000000..01be076bf13659
--- /dev/null
+++ b/eventsource/eventsource-cross-origin-preflight.window.js
@@ -0,0 +1,33 @@
+// META: title=EventSource: cross-origin preflight
+// META: script=/common/utils.js
+
+const crossdomain = location.href.replace('://', '://élève.').replace(/\/[^\/]*$/, '/')
+const origin = location.origin.replace('://', '://xn--lve-6lad.')
+
+;[
+ ['safe `last-event-id` (no preflight)', 'safe'],
+ ['unsafe `last-event-id` (too long)', 'long'],
+ ['unsafe `last-event-id` (unsafe characters)', 'unsafe']
+].forEach(([name, fixture]) => {
+ async_test(document.title + ' - ' + name).step(function() {
+ const uuid = token()
+ const url = crossdomain + 'resources/cors-unsafe-last-event-id.py?fixture=' + fixture + '&token=' + uuid
+
+ const source = new EventSource(url)
+
+ // Make sure to close the EventSource after the test is done.
+ this.add_cleanup(() => source.close())
+
+ // 1. Event will be a `message` with `id` set to a CORS-safe value, then disconnects.
+ source.addEventListener('message', this.step_func(e => assert_equals(e.data, fixture)))
+
+ // 2. Will emit either `success` or `failure` event. We expect `success`,
+ // which is the case if `last-event-id` is set to the same value as received above,
+ // and a preflight request has been sent for the unsafe `last-event-id` headers.
+ source.addEventListener('success', this.step_func_done())
+ source.addEventListener('failure', (evt) => {
+ this.step(() => assert_unreached(evt.data))
+ this.done()
+ })
+ })
+})
diff --git a/eventsource/resources/cors-unsafe-last-event-id.py b/eventsource/resources/cors-unsafe-last-event-id.py
new file mode 100644
index 00000000000000..2b431d0ff30e5a
--- /dev/null
+++ b/eventsource/resources/cors-unsafe-last-event-id.py
@@ -0,0 +1,96 @@
+from datetime import datetime
+
+# Beyond the 128-byte limit for `Last-Event-ID`
+long_string = b"a" * 255
+
+# A regular, safe `Last-Event-ID` value
+safe_id_value = b"abc"
+
+# CORS-unsafe request-header byte 0x3C (`<`) in `Last-Event-ID`
+unsafe_id_value = b"e5p3n<3k0k0s"
+
+def main(request, response):
+ origin = request.headers.get(b"Origin")
+ cors_request_headers = request.headers.get(b"Access-Control-Request-Headers")
+
+ # Allow any CORS origin
+ if origin is not None:
+ response.headers.set(b"Access-Control-Allow-Origin", origin)
+
+ # Allow any CORS request headers
+ if cors_request_headers is not None:
+ response.headers.set(b"Access-Control-Allow-Headers", cors_request_headers)
+
+ # Expect a `token` in the query string
+ if b"token" not in request.GET:
+ headers = [(b"Content-Type", b"text/plain")]
+ return 400, headers, b"ERROR: `token` query parameter!"
+
+ # Expect a `fixture` in the query string
+ if b"fixture" not in request.GET:
+ headers = [(b"Content-Type", b"text/plain")]
+ return 400, headers, b"ERROR: `fixture` query parameter!"
+
+ # Prepare state
+ fixture = request.GET.first(b"fixture")
+ token = request.GET.first(b"token")
+ last_event_id = request.headers.get(b"Last-Event-ID", b"")
+ expect_preflight = fixture == b"unsafe" or fixture == b"long"
+
+ # Preflight handling
+ if request.method == u"OPTIONS":
+ # The first request (without any `Last-Event-ID` header) should _never_ be a
+ # preflight request, since it should be considered a "safe" request.
+ # If we _do_ send a preflight for these requests, error early.
+ if last_event_id == b"":
+ headers = [(b"Content-Type", b"text/plain")]
+ return 400, headers, b"ERROR: No Last-Event-ID header in preflight!"
+
+ # We keep track of the different "tokens" we see, in order to tell whether or not
+ # a client has done a preflight request. If the "stash" does not contain a token,
+ # no preflight request was made.
+ request.server.stash.put(token, cors_request_headers)
+
+ # We can return with an empty body on preflight requests
+ return b""
+
+ # This will be a SSE endpoint
+ response.headers.set(b"Content-Type", b"text/event-stream")
+ response.headers.set(b"Cache-Control", b"no-store")
+
+ # If we do not have a `Last-Event-ID` header, we're on the initial request
+ # Respond with the fixture corresponding to the `fixture` query parameter
+ if last_event_id == b"":
+ if fixture == b"safe":
+ return b"id: " + safe_id_value + b"\nretry: 200\ndata: safe\n\n"
+ if fixture == b"unsafe":
+ return b"id: " + unsafe_id_value + b"\nretry: 200\ndata: unsafe\n\n"
+ if fixture == b"long":
+ return b"id: " + long_string + b"\nretry: 200\ndata: long\n\n"
+ return b"event: failure\ndata: unknown fixture\n\n"
+
+ # If we have a `Last-Event-ID` header, we're on a reconnect.
+ # If fixture is "unsafe", eg requires a preflight, check to see that we got one.
+ preflight_headers = request.server.stash.take(token)
+ saw_preflight = preflight_headers is not None
+ if saw_preflight and not expect_preflight:
+ return b"event: failure\ndata: saw preflight, did not expect one\n\n"
+ elif not saw_preflight and expect_preflight:
+ return b"event: failure\ndata: expected preflight, did not get one\n\n"
+
+ if saw_preflight and preflight_headers.lower() != b"last-event-id":
+ data = b"preflight `access-control-request-headers` was not `last-event-id`"
+ return b"event: failure\ndata: " + data + b"\n\n"
+
+ # Expect to have the same ID in the header as the one we sent.
+ expected = b""
+ if fixture == b"safe":
+ expected = safe_id_value
+ elif fixture == b"unsafe":
+ expected = unsafe_id_value
+ elif fixture == b"long":
+ expected = long_string
+
+ event = last_event_id == expected and b"success" or b"failure"
+ data = b"got " + last_event_id + b", expected " + expected
+ return b"event: " + event + b"\ndata: " + data + b"\n\n"
From f3d9094a2de1b48e1a23ae3b445b214552aed341 Mon Sep 17 00:00:00 2001
From: Espen Hovlandsdal
Date: Tue, 26 Nov 2024 07:47:17 -0800
Subject: [PATCH 3/5] Refactor to address code review comments
---
cors/cors-safelisted-request-header.any.js | 2 +-
...entsource-cross-origin-preflight.window.js | 34 ++--
.../resources/cors-unsafe-last-event-id.py | 164 +++++++++---------
3 files changed, 101 insertions(+), 99 deletions(-)
diff --git a/cors/cors-safelisted-request-header.any.js b/cors/cors-safelisted-request-header.any.js
index d02110c79ac956..05ff22576c5a6d 100644
--- a/cors/cors-safelisted-request-header.any.js
+++ b/cors/cors-safelisted-request-header.any.js
@@ -77,4 +77,4 @@ function safelist(headers, expectPreflight = false) {
["012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678", true],
].forEach(([value, preflight = false]) => {
safelist({"last-event-id": value}, preflight);
-});
\ No newline at end of file
+});
diff --git a/eventsource/eventsource-cross-origin-preflight.window.js b/eventsource/eventsource-cross-origin-preflight.window.js
index 01be076bf13659..61f9a3ef1f6ca8 100644
--- a/eventsource/eventsource-cross-origin-preflight.window.js
+++ b/eventsource/eventsource-cross-origin-preflight.window.js
@@ -1,33 +1,35 @@
// META: title=EventSource: cross-origin preflight
// META: script=/common/utils.js
-const crossdomain = location.href.replace('://', '://élève.').replace(/\/[^\/]*$/, '/')
-const origin = location.origin.replace('://', '://xn--lve-6lad.')
+const crossdomain = location.href.replace('://', '://élève.').replace(/\/[^\/]*$/, '/');
+const origin = location.origin.replace('://', '://xn--lve-6lad.');
-;[
+[
['safe `last-event-id` (no preflight)', 'safe'],
['unsafe `last-event-id` (too long)', 'long'],
['unsafe `last-event-id` (unsafe characters)', 'unsafe']
].forEach(([name, fixture]) => {
- async_test(document.title + ' - ' + name).step(function() {
- const uuid = token()
- const url = crossdomain + 'resources/cors-unsafe-last-event-id.py?fixture=' + fixture + '&token=' + uuid
+ async_test(document.title + ' - ' + name).step(function () {
+ const uuid = token();
+ const url = crossdomain + 'resources/cors-unsafe-last-event-id.py?fixture=' + fixture + '&token=' + uuid;
- const source = new EventSource(url)
+ const source = new EventSource(url);
// Make sure to close the EventSource after the test is done.
- this.add_cleanup(() => source.close())
+ this.add_cleanup(() => source.close());
// 1. Event will be a `message` with `id` set to a CORS-safe value, then disconnects.
- source.addEventListener('message', this.step_func(e => assert_equals(e.data, fixture)))
+ source.addEventListener(
+ 'message',
+ this.step_func((evt) => assert_equals(evt.data, fixture)),
+ );
// 2. Will emit either `success` or `failure` event. We expect `success`,
// which is the case if `last-event-id` is set to the same value as received above,
// and a preflight request has been sent for the unsafe `last-event-id` headers.
- source.addEventListener('success', this.step_func_done())
- source.addEventListener('failure', (evt) => {
- this.step(() => assert_unreached(evt.data))
- this.done()
- })
- })
-})
+ source.addEventListener('success', this.step_func_done());
+ source.addEventListener('failure', this.step_func_done((evt) => {
+ assert_unreached(evt.data);
+ }));
+ });
+});
diff --git a/eventsource/resources/cors-unsafe-last-event-id.py b/eventsource/resources/cors-unsafe-last-event-id.py
index 2b431d0ff30e5a..9d24f342cdc54d 100644
--- a/eventsource/resources/cors-unsafe-last-event-id.py
+++ b/eventsource/resources/cors-unsafe-last-event-id.py
@@ -10,87 +10,87 @@
unsafe_id_value = b"e5p3n<3k0k0s"
def main(request, response):
- origin = request.headers.get(b"Origin")
- cors_request_headers = request.headers.get(b"Access-Control-Request-Headers")
-
- # Allow any CORS origin
- if origin is not None:
- response.headers.set(b"Access-Control-Allow-Origin", origin)
-
- # Allow any CORS request headers
- if cors_request_headers is not None:
- response.headers.set(b"Access-Control-Allow-Headers", cors_request_headers)
-
- # Expect a `token` in the query string
- if b"token" not in request.GET:
- headers = [(b"Content-Type", b"text/plain")]
- return 400, headers, b"ERROR: `token` query parameter!"
-
- # Expect a `fixture` in the query string
- if b"fixture" not in request.GET:
- headers = [(b"Content-Type", b"text/plain")]
- return 400, headers, b"ERROR: `fixture` query parameter!"
-
- # Prepare state
- fixture = request.GET.first(b"fixture")
- token = request.GET.first(b"token")
- last_event_id = request.headers.get(b"Last-Event-ID", b"")
- expect_preflight = fixture == b"unsafe" or fixture == b"long"
-
- # Preflight handling
- if request.method == u"OPTIONS":
- # The first request (without any `Last-Event-ID` header) should _never_ be a
- # preflight request, since it should be considered a "safe" request.
- # If we _do_ send a preflight for these requests, error early.
+ origin = request.headers.get(b"Origin")
+ cors_request_headers = request.headers.get(b"Access-Control-Request-Headers")
+
+ # Allow any CORS origin
+ if origin is not None:
+ response.headers.set(b"Access-Control-Allow-Origin", origin)
+
+ # Allow any CORS request headers
+ if cors_request_headers is not None:
+ response.headers.set(b"Access-Control-Allow-Headers", cors_request_headers)
+
+ # Expect a `token` in the query string
+ if b"token" not in request.GET:
+ headers = [(b"Content-Type", b"text/plain")]
+ return 400, headers, b"ERROR: `token` query parameter!"
+
+ # Expect a `fixture` in the query string
+ if b"fixture" not in request.GET:
+ headers = [(b"Content-Type", b"text/plain")]
+ return 400, headers, b"ERROR: `fixture` query parameter!"
+
+ # Prepare state
+ fixture = request.GET.first(b"fixture")
+ token = request.GET.first(b"token")
+ last_event_id = request.headers.get(b"Last-Event-ID", b"")
+ expect_preflight = fixture == b"unsafe" or fixture == b"long"
+
+ # Preflight handling
+ if request.method == u"OPTIONS":
+ # The first request (without any `Last-Event-ID` header) should _never_ be a
+ # preflight request, since it should be considered a "safe" request.
+ # If we _do_ send a preflight for these requests, error early.
+ if last_event_id == b"":
+ headers = [(b"Content-Type", b"text/plain")]
+ return 400, headers, b"ERROR: No Last-Event-ID header in preflight!"
+
+ # We keep track of the different "tokens" we see, in order to tell whether or not
+ # a client has done a preflight request. If the "stash" does not contain a token,
+ # no preflight request was made.
+ request.server.stash.put(token, cors_request_headers)
+
+ # We can return with an empty body on preflight requests
+ return b""
+
+ # This will be a SSE endpoint
+ response.headers.set(b"Content-Type", b"text/event-stream")
+ response.headers.set(b"Cache-Control", b"no-store")
+
+ # If we do not have a `Last-Event-ID` header, we're on the initial request
+ # Respond with the fixture corresponding to the `fixture` query parameter
if last_event_id == b"":
- headers = [(b"Content-Type", b"text/plain")]
- return 400, headers, b"ERROR: No Last-Event-ID header in preflight!"
-
- # We keep track of the different "tokens" we see, in order to tell whether or not
- # a client has done a preflight request. If the "stash" does not contain a token,
- # no preflight request was made.
- request.server.stash.put(token, cors_request_headers)
-
- # We can return with an empty body on preflight requests
- return b""
-
- # This will be a SSE endpoint
- response.headers.set(b"Content-Type", b"text/event-stream")
- response.headers.set(b"Cache-Control", b"no-store")
-
- # If we do not have a `Last-Event-ID` header, we're on the initial request
- # Respond with the fixture corresponding to the `fixture` query parameter
- if last_event_id == b"":
+ if fixture == b"safe":
+ return b"id: " + safe_id_value + b"\nretry: 200\ndata: safe\n\n"
+ if fixture == b"unsafe":
+ return b"id: " + unsafe_id_value + b"\nretry: 200\ndata: unsafe\n\n"
+ if fixture == b"long":
+ return b"id: " + long_string + b"\nretry: 200\ndata: long\n\n"
+ return b"event: failure\ndata: unknown fixture\n\n"
+
+ # If we have a `Last-Event-ID` header, we're on a reconnect.
+ # If fixture is "unsafe", eg requires a preflight, check to see that we got one.
+ preflight_headers = request.server.stash.take(token)
+ saw_preflight = preflight_headers is not None
+ if saw_preflight and not expect_preflight:
+ return b"event: failure\ndata: saw preflight, did not expect one\n\n"
+ elif not saw_preflight and expect_preflight:
+ return b"event: failure\ndata: expected preflight, did not get one\n\n"
+
+ if saw_preflight and preflight_headers.lower() != b"last-event-id":
+ data = b"preflight `access-control-request-headers` was not `last-event-id`"
+ return b"event: failure\ndata: " + data + b"\n\n"
+
+ # Expect to have the same ID in the header as the one we sent.
+ expected = b""
if fixture == b"safe":
- return b"id: " + safe_id_value + b"\nretry: 200\ndata: safe\n\n"
- if fixture == b"unsafe":
- return b"id: " + unsafe_id_value + b"\nretry: 200\ndata: unsafe\n\n"
- if fixture == b"long":
- return b"id: " + long_string + b"\nretry: 200\ndata: long\n\n"
- return b"event: failure\ndata: unknown fixture\n\n"
-
- # If we have a `Last-Event-ID` header, we're on a reconnect.
- # If fixture is "unsafe", eg requires a preflight, check to see that we got one.
- preflight_headers = request.server.stash.take(token)
- saw_preflight = preflight_headers is not None
- if saw_preflight and not expect_preflight:
- return b"event: failure\ndata: saw preflight, did not expect one\n\n"
- elif not saw_preflight and expect_preflight:
- return b"event: failure\ndata: expected preflight, did not get one\n\n"
-
- if saw_preflight and preflight_headers.lower() != b"last-event-id":
- data = b"preflight `access-control-request-headers` was not `last-event-id`"
- return b"event: failure\ndata: " + data + b"\n\n"
-
- # Expect to have the same ID in the header as the one we sent.
- expected = b""
- if fixture == b"safe":
- expected = safe_id_value
- elif fixture == b"unsafe":
- expected = unsafe_id_value
- elif fixture == b"long":
- expected = long_string
-
- event = last_event_id == expected and b"success" or b"failure"
- data = b"got " + last_event_id + b", expected " + expected
- return b"event: " + event + b"\ndata: " + data + b"\n\n"
+ expected = safe_id_value
+ elif fixture == b"unsafe":
+ expected = unsafe_id_value
+ elif fixture == b"long":
+ expected = long_string
+
+ event = last_event_id == expected and b"success" or b"failure"
+ data = b"got " + last_event_id + b", expected " + expected
+ return b"event: " + event + b"\ndata: " + data + b"\n\n"
From b6f42a4f1fcf0d79669383a0c4f3247aa397dded Mon Sep 17 00:00:00 2001
From: Espen Hovlandsdal
Date: Thu, 20 Mar 2025 22:59:20 -0700
Subject: [PATCH 4/5] Fix bug causing preflights to fail
---
...entsource-cross-origin-preflight.window.js | 36 ++++++++++++-------
.../resources/cors-unsafe-last-event-id.py | 7 ----
2 files changed, 23 insertions(+), 20 deletions(-)
diff --git a/eventsource/eventsource-cross-origin-preflight.window.js b/eventsource/eventsource-cross-origin-preflight.window.js
index 61f9a3ef1f6ca8..8af219b7952249 100644
--- a/eventsource/eventsource-cross-origin-preflight.window.js
+++ b/eventsource/eventsource-cross-origin-preflight.window.js
@@ -1,17 +1,24 @@
// META: title=EventSource: cross-origin preflight
// META: script=/common/utils.js
-const crossdomain = location.href.replace('://', '://élève.').replace(/\/[^\/]*$/, '/');
-const origin = location.origin.replace('://', '://xn--lve-6lad.');
+const crossdomain = location.href
+ .replace("://", "://élève.")
+ .replace(/\/[^\/]*$/, "/");
+const origin = location.origin.replace("://", "://xn--lve-6lad.");
[
- ['safe `last-event-id` (no preflight)', 'safe'],
- ['unsafe `last-event-id` (too long)', 'long'],
- ['unsafe `last-event-id` (unsafe characters)', 'unsafe']
+ ["safe `last-event-id` (no preflight)", "safe"],
+ ["unsafe `last-event-id` (preflight because too long)", "long"],
+ ["unsafe `last-event-id` (preflight because unsafe characters)", "unsafe"],
].forEach(([name, fixture]) => {
- async_test(document.title + ' - ' + name).step(function () {
+ async_test(document.title + " - " + name).step(function () {
const uuid = token();
- const url = crossdomain + 'resources/cors-unsafe-last-event-id.py?fixture=' + fixture + '&token=' + uuid;
+ const url =
+ crossdomain +
+ "resources/cors-unsafe-last-event-id.py?fixture=" +
+ fixture +
+ "&token=" +
+ uuid;
const source = new EventSource(url);
@@ -20,16 +27,19 @@ const origin = location.origin.replace('://', '://xn--lve-6lad.');
// 1. Event will be a `message` with `id` set to a CORS-safe value, then disconnects.
source.addEventListener(
- 'message',
- this.step_func((evt) => assert_equals(evt.data, fixture)),
+ "message",
+ this.step_func((evt) => assert_equals(evt.data, fixture))
);
// 2. Will emit either `success` or `failure` event. We expect `success`,
// which is the case if `last-event-id` is set to the same value as received above,
// and a preflight request has been sent for the unsafe `last-event-id` headers.
- source.addEventListener('success', this.step_func_done());
- source.addEventListener('failure', this.step_func_done((evt) => {
- assert_unreached(evt.data);
- }));
+ source.addEventListener("success", this.step_func_done());
+ source.addEventListener(
+ "failure",
+ this.step_func_done((evt) => {
+ assert_unreached(evt.data);
+ })
+ );
});
});
diff --git a/eventsource/resources/cors-unsafe-last-event-id.py b/eventsource/resources/cors-unsafe-last-event-id.py
index 9d24f342cdc54d..01b5aacefbfd4d 100644
--- a/eventsource/resources/cors-unsafe-last-event-id.py
+++ b/eventsource/resources/cors-unsafe-last-event-id.py
@@ -39,13 +39,6 @@ def main(request, response):
# Preflight handling
if request.method == u"OPTIONS":
- # The first request (without any `Last-Event-ID` header) should _never_ be a
- # preflight request, since it should be considered a "safe" request.
- # If we _do_ send a preflight for these requests, error early.
- if last_event_id == b"":
- headers = [(b"Content-Type", b"text/plain")]
- return 400, headers, b"ERROR: No Last-Event-ID header in preflight!"
-
# We keep track of the different "tokens" we see, in order to tell whether or not
# a client has done a preflight request. If the "stash" does not contain a token,
# no preflight request was made.
From b4e66b1b37ecdd5d5fa588ccd4f493e72a5efad7 Mon Sep 17 00:00:00 2001
From: Espen Hovlandsdal
Date: Thu, 20 Mar 2025 23:00:12 -0700
Subject: [PATCH 5/5] Add tests for `Last-Event-ID` in XMLHttpRequest CORS
---
...-basic-cors-safelisted-request-headers.htm | 4 ++-
...asic-non-cors-safelisted-last-event-id.htm | 30 +++++++++++++++++++
...l-basic-cors-safelisted-request-headers.py | 2 +-
3 files changed, 34 insertions(+), 2 deletions(-)
create mode 100644 xhr/access-control-basic-non-cors-safelisted-last-event-id.htm
diff --git a/xhr/access-control-basic-cors-safelisted-request-headers.htm b/xhr/access-control-basic-cors-safelisted-request-headers.htm
index 56870493b4ed9d..27ac4a3c9adbda 100644
--- a/xhr/access-control-basic-cors-safelisted-request-headers.htm
+++ b/xhr/access-control-basic-cors-safelisted-request-headers.htm
@@ -17,6 +17,7 @@
xhr.setRequestHeader("Accept-Language", "ru");
xhr.setRequestHeader("Content-Language", "ru");
xhr.setRequestHeader("Content-Type", "text/plain");
+ xhr.setRequestHeader("Last-Event-ID", "abc123");
xhr.send();
@@ -24,7 +25,8 @@
"Accept: *\n" +
"Accept-Language: ru\n" +
"Content-Language: ru\n" +
- "Content-Type: text/plain\n");
+ "Content-Type: text/plain\n" +
+ "Last-Event-ID: abc123\n");
}, "Request with CORS-safelisted headers");
+
+
diff --git a/xhr/access-control-basic-non-cors-safelisted-last-event-id.htm b/xhr/access-control-basic-non-cors-safelisted-last-event-id.htm
new file mode 100644
index 00000000000000..bb77ee13d58d10
--- /dev/null
+++ b/xhr/access-control-basic-non-cors-safelisted-last-event-id.htm
@@ -0,0 +1,30 @@
+
+
+