script: Ensure EventSource interprets non-200 response codes as failure (#36853)

Spec has updated so that all responses that aren't 200 shall fail the
event source connection.

Testing: Covered by
`tests/wpt/tests/eventsource/request-status-error.window.js`

---------

Signed-off-by: Keith Yeung <kungfukeith11@gmail.com>
This commit is contained in:
Keith Yeung 2025-05-05 20:04:46 +08:00 committed by GitHub
parent b3980dc2ee
commit 3936b1d22b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 33 additions and 39 deletions

View file

@ -11,6 +11,7 @@ use std::time::Duration;
use content_security_policy as csp; use content_security_policy as csp;
use dom_struct::dom_struct; use dom_struct::dom_struct;
use headers::ContentType; use headers::ContentType;
use http::StatusCode;
use http::header::{self, HeaderName, HeaderValue}; use http::header::{self, HeaderName, HeaderValue};
use ipc_channel::ipc; use ipc_channel::ipc;
use ipc_channel::router::ROUTER; use ipc_channel::router::ROUTER;
@ -351,6 +352,11 @@ impl FetchResponseListener for EventSourceContext {
_ => unsafe_, _ => unsafe_,
}, },
}; };
// Step 15.3 if res's status is not 200, or if res's `Content-Type` is not
// `text/event-stream`, then fail the connection.
if meta.status.code() != StatusCode::OK {
return self.fail_the_connection();
}
let mime = match meta.content_type { let mime = match meta.content_type {
None => return self.fail_the_connection(), None => return self.fail_the_connection(),
Some(ct) => <ContentType as Into<Mime>>::into(ct.into_inner()), Some(ct) => <ContentType as Into<Mime>>::into(ct.into_inner()),
@ -359,12 +365,15 @@ impl FetchResponseListener for EventSourceContext {
return self.fail_the_connection(); return self.fail_the_connection();
} }
self.origin = meta.final_url.origin().ascii_serialization(); self.origin = meta.final_url.origin().ascii_serialization();
// Step 15.4 announce the connection and interpret res's body line by line.
self.announce_the_connection(); self.announce_the_connection();
}, },
Err(_) => { Err(_) => {
// The spec advises failing here if reconnecting would be // Step 15.2 if res is a network error, then reestablish the connection, unless
// "futile", with no more specific advice; WPT tests // the user agent knows that to be futile, in which case the user agent may
// consider a non-http(s) scheme to be futile. // fail the connection.
// WPT tests consider a non-http(s) scheme to be futile.
match self.event_source.root().url.scheme() { match self.event_source.root().url.scheme() {
"http" | "https" => self.reestablish_the_connection(), "http" | "https" => self.reestablish_the_connection(),
_ => self.fail_the_connection(), _ => self.fail_the_connection(),
@ -415,12 +424,14 @@ impl FetchResponseListener for EventSourceContext {
fn process_response_eof( fn process_response_eof(
&mut self, &mut self,
_: RequestId, _: RequestId,
_response: Result<ResourceFetchTiming, NetworkError>, response: Result<ResourceFetchTiming, NetworkError>,
) { ) {
if self.incomplete_utf8.take().is_some() { if self.incomplete_utf8.take().is_some() {
self.parse("\u{FFFD}".chars(), CanGc::note()); self.parse("\u{FFFD}".chars(), CanGc::note());
} }
self.reestablish_the_connection(); if response.is_ok() {
self.reestablish_the_connection();
}
} }
fn resource_timing_mut(&mut self) -> &mut ResourceFetchTiming { fn resource_timing_mut(&mut self) -> &mut ResourceFetchTiming {
@ -537,29 +548,35 @@ impl EventSourceMethods<crate::DomTypeHolder> for EventSource {
event_source_init: &EventSourceInit, event_source_init: &EventSourceInit,
) -> Fallible<DomRoot<EventSource>> { ) -> Fallible<DomRoot<EventSource>> {
// TODO: Step 2 relevant settings object // TODO: Step 2 relevant settings object
// Step 3 // Step 3 Let urlRecord be the result of encoding-parsing a URL given url,
// relative to settings.
let base_url = global.api_base_url(); let base_url = global.api_base_url();
let url_record = match base_url.join(&url) { let url_record = match base_url.join(&url) {
Ok(u) => u, Ok(u) => u,
// Step 4 // Step 4 If urlRecord is failure, then throw a "SyntaxError" DOMException.
Err(_) => return Err(Error::Syntax), Err(_) => return Err(Error::Syntax),
}; };
// Step 1, 5 // Step 1 Let ev be a new EventSource object.
let ev = EventSource::new( let ev = EventSource::new(
global, global,
proto, proto,
// Step 5 Set ev's url to urlRecord.
url_record.clone(), url_record.clone(),
event_source_init.withCredentials, event_source_init.withCredentials,
can_gc, can_gc,
); );
global.track_event_source(&ev); global.track_event_source(&ev);
// Steps 6-7
let cors_attribute_state = if event_source_init.withCredentials { let cors_attribute_state = if event_source_init.withCredentials {
// Step 7 If the value of eventSourceInitDict's withCredentials member is true,
// then set corsAttributeState to Use Credentials and set ev's withCredentials
// attribute to true.
CorsSettings::UseCredentials CorsSettings::UseCredentials
} else { } else {
// Step 6 Let corsAttributeState be Anonymous.
CorsSettings::Anonymous CorsSettings::Anonymous
}; };
// Step 8 // Step 8 Let request be the result of creating a potential-CORS request
// given urlRecord, the empty string, and corsAttributeState.
// TODO: Step 9 set request's client settings // TODO: Step 9 set request's client settings
let mut request = create_a_potential_cors_request( let mut request = create_a_potential_cors_request(
global.webview_id(), global.webview_id(),
@ -575,17 +592,18 @@ impl EventSourceMethods<crate::DomTypeHolder> for EventSource {
.origin(global.origin().immutable().clone()) .origin(global.origin().immutable().clone())
.pipeline_id(Some(global.pipeline_id())); .pipeline_id(Some(global.pipeline_id()));
// Step 10 // Step 10 User agents may set (`Accept`, `text/event-stream`) in request's header list.
// TODO(eijebong): Replace once typed headers allow it // TODO(eijebong): Replace once typed headers allow it
request.headers.insert( request.headers.insert(
header::ACCEPT, header::ACCEPT,
HeaderValue::from_static("text/event-stream"), HeaderValue::from_static("text/event-stream"),
); );
// Step 11 // Step 11 Set request's cache mode to "no-store".
request.cache_mode = CacheMode::NoStore; request.cache_mode = CacheMode::NoStore;
// Step 12 // Step 13 Set ev's request to request.
*ev.request.borrow_mut() = Some(request.clone()); *ev.request.borrow_mut() = Some(request.clone());
// Step 14 // Step 14 Let processEventSourceEndOfBody given response res be the following step:
// if res is not a network error, then reestablish the connection.
let (action_sender, action_receiver) = ipc::channel().unwrap(); let (action_sender, action_receiver) = ipc::channel().unwrap();
let context = EventSourceContext { let context = EventSourceContext {
incomplete_utf8: None, incomplete_utf8: None,
@ -622,7 +640,7 @@ impl EventSourceMethods<crate::DomTypeHolder> for EventSource {
FetchChannels::ResponseMsg(action_sender), FetchChannels::ResponseMsg(action_sender),
)) ))
.unwrap(); .unwrap();
// Step 13 // Step 16 Return ev.
Ok(ev) Ok(ev)
} }

View file

@ -1,3 +0,0 @@
[eventsource-close.window.html]
[EventSource: close(), test events]
expected: FAIL

View file

@ -1,21 +0,0 @@
[request-status-error.window.html]
[EventSource: incorrect HTTP status code (204)]
expected: FAIL
[EventSource: incorrect HTTP status code (205)]
expected: FAIL
[EventSource: incorrect HTTP status code (210)]
expected: FAIL
[EventSource: incorrect HTTP status code (299)]
expected: FAIL
[EventSource: incorrect HTTP status code (404)]
expected: FAIL
[EventSource: incorrect HTTP status code (410)]
expected: FAIL
[EventSource: incorrect HTTP status code (503)]
expected: FAIL