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 @@ + + + + Tests cross-origin request with non-CORS-safelisted last-event-id header + + + + + + + + diff --git a/xhr/resources/access-control-basic-cors-safelisted-request-headers.py b/xhr/resources/access-control-basic-cors-safelisted-request-headers.py index 46523a905a9f70..946e44e4fa68b5 100644 --- a/xhr/resources/access-control-basic-cors-safelisted-request-headers.py +++ b/xhr/resources/access-control-basic-cors-safelisted-request-headers.py @@ -11,6 +11,6 @@ def main(request, response): response.headers.set(b"Access-Control-Allow-Credentials", b"true") response.headers.set(b"Access-Control-Allow-Origin", request.headers.get(b"origin")) - for header in [b"Accept", b"Accept-Language", b"Content-Language", b"Content-Type"]: + for header in [b"Accept", b"Accept-Language", b"Content-Language", b"Content-Type", b"Last-Event-ID"]: value = request.headers.get(header) response.content += isomorphic_decode(header) + u": " + (isomorphic_decode(value) if value else u"") + u'\n'