From ecebb7028232a55b60161521f9a624ce093c23e1 Mon Sep 17 00:00:00 2001 From: youdie006 Date: Tue, 18 Aug 2026 09:00:02 +0900 Subject: [PATCH] fix: Access-Control-Request-Headers must join header names without whitespace AccessControlRequestHeaders::from_iter collected HeaderNames into the shared FlatCsv, whose FromIterator joins with a comma+space. Per the Fetch CORS-preflight spec the Access-Control-Request-Headers value is a comma-joined list with no whitespace (it does not use combine), and WPT checks this strictly, so the typed API emitted 'accept-language, date' instead of 'accept-language,date'. Give AccessControlRequestHeaders its own FromIterator that joins the names with a plain ',' and wraps the result in a HeaderValue, instead of delegating to FlatCsv's ', ' joiner. The change is localized: shared FlatCsv is untouched, so other list headers (Vary, Allow, ...) keep emitting ', '. Fixes #207 --- src/common/access_control_request_headers.rs | 37 ++++++++++++++++++-- 1 file changed, 34 insertions(+), 3 deletions(-) diff --git a/src/common/access_control_request_headers.rs b/src/common/access_control_request_headers.rs index 3192c826..66e7e686 100644 --- a/src/common/access_control_request_headers.rs +++ b/src/common/access_control_request_headers.rs @@ -53,8 +53,23 @@ impl FromIterator for AccessControlRequestHeaders { where I: IntoIterator, { - let flat = iter.into_iter().map(HeaderValue::from).collect(); - AccessControlRequestHeaders(flat) + // Per the Fetch spec's CORS-preflight fetch, the value of + // `Access-Control-Request-Headers` joins header names with a plain + // `,` and no whitespace. This differs from the general list `combine` + // (`, `) that `FlatCsv`'s `FromIterator` performs, so join the names + // here directly rather than delegating to `FlatCsv`. Keeping this + // local leaves other `FlatCsv`-backed headers (Vary, Allow, ...) + // emitting `, ` as before. + let mut names = String::new(); + for name in iter { + if !names.is_empty() { + names.push(','); + } + names.push_str(name.as_str()); + } + let value = HeaderValue::from_bytes(names.as_bytes()) + .expect("header names joined by `,` are a valid header value"); + AccessControlRequestHeaders(value.into()) } } @@ -83,7 +98,23 @@ mod tests { let headers = test_encode(req_headers); assert_eq!( headers["access-control-request-headers"], - "cache-control, if-range" + "cache-control,if-range" + ); + } + + #[test] + fn from_iter_no_space_between_names() { + // Per the Fetch spec the preflight `Access-Control-Request-Headers` + // value must join header names with `,` only, no whitespace. + let req_headers: AccessControlRequestHeaders = + vec![::http::header::ACCEPT_LANGUAGE, ::http::header::DATE] + .into_iter() + .collect(); + + let headers = test_encode(req_headers); + assert_eq!( + headers["access-control-request-headers"], + "accept-language,date" ); } }