From 5ba352017725d95bcf1992ea20aadba2db113e46 Mon Sep 17 00:00:00 2001 From: Luam Souza Date: Fri, 18 Sep 2026 12:39:45 -0300 Subject: [PATCH] fix(emulate): correct Firefox 147+ Accept-Language q-value Firefox 147 changed how Accept-Language q-values are generated: the list used to divide 1.0 across its entries, giving q=0.5 for a two-entry list, and now decrements by 0.1 per entry, giving q=0.9. See Bugzilla 2000765 (target milestone 147 Branch), landed in mozilla-firefox 0e050ae5116d. The Firefox 147 through 152 profiles still emitted the pre-147 value. Profiles below 147 are unchanged. Co-Authored-By: Claude Opus 5 --- crates/wreq-util/src/emulate/macros.rs | 29 ++++------ .../wreq-util/src/emulate/profile/firefox.rs | 12 ++-- .../src/emulate/profile/firefox/header.rs | 22 +++++++ crates/wreq-util/tests/emulate_firefox.rs | 58 +++++++++++++++++++ 4 files changed, 98 insertions(+), 23 deletions(-) diff --git a/crates/wreq-util/src/emulate/macros.rs b/crates/wreq-util/src/emulate/macros.rs index 0a8ca27..f045273 100644 --- a/crates/wreq-util/src/emulate/macros.rs +++ b/crates/wreq-util/src/emulate/macros.rs @@ -151,32 +151,27 @@ macro_rules! header_firefox_sec_fetch { macro_rules! header_firefox_accept { ($headers:expr) => { - $headers.insert( - ACCEPT, - HeaderValue::from_static( - "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", - ), - ); - $headers.insert(ACCEPT_LANGUAGE, HeaderValue::from_static("en-US,en;q=0.5")); - #[cfg(feature = "emulation-compression")] - $headers.insert( - ACCEPT_ENCODING, - HeaderValue::from_static("gzip, deflate, br"), - ); + header_firefox_accept!(@build $headers, "en-US,en;q=0.5", "gzip, deflate, br"); }; (zstd, $headers:expr) => { + header_firefox_accept!(@build $headers, "en-US,en;q=0.5", "gzip, deflate, br, zstd"); + }; + // Firefox 147 changed the generated q-value from dividing 1.0 across the list to + // decrementing by 0.1 per entry, so a two-entry list became q=0.9 (Bugzilla 2000765, + // landed in 0e050ae5116d). + (zstd_q09, $headers:expr) => { + header_firefox_accept!(@build $headers, "en-US,en;q=0.9", "gzip, deflate, br, zstd"); + }; + (@build $headers:expr, $accept_language:expr, $accept_encoding:expr) => { $headers.insert( ACCEPT, HeaderValue::from_static( "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", ), ); - $headers.insert(ACCEPT_LANGUAGE, HeaderValue::from_static("en-US,en;q=0.5")); + $headers.insert(ACCEPT_LANGUAGE, HeaderValue::from_static($accept_language)); #[cfg(feature = "emulation-compression")] - $headers.insert( - ACCEPT_ENCODING, - HeaderValue::from_static("gzip, deflate, br, zstd"), - ); + $headers.insert(ACCEPT_ENCODING, HeaderValue::from_static($accept_encoding)); }; } diff --git a/crates/wreq-util/src/emulate/profile/firefox.rs b/crates/wreq-util/src/emulate/profile/firefox.rs index 6924fb4..74cf164 100644 --- a/crates/wreq-util/src/emulate/profile/firefox.rs +++ b/crates/wreq-util/src/emulate/profile/firefox.rs @@ -380,7 +380,7 @@ mod_generator!( mod_generator!( ff147, ff135::build_emulation, - header_initializer_with_zstd, + header_initializer_with_zstd_q09, [ ( Windows, @@ -408,7 +408,7 @@ mod_generator!( mod_generator!( ff148, ff135::build_emulation, - header_initializer_with_zstd, + header_initializer_with_zstd_q09, [ ( Windows, @@ -436,7 +436,7 @@ mod_generator!( mod_generator!( ff149, ff135::build_emulation, - header_initializer_with_zstd, + header_initializer_with_zstd_q09, [ ( Windows, @@ -465,7 +465,7 @@ mod_generator!( ff150, tls_options!(4, CIPHER_LIST_3, CURVES_2, KEY_SHARES_2), http2_options!(1), - header_initializer_with_zstd, + header_initializer_with_zstd_q09, [ ( Windows, @@ -493,7 +493,7 @@ mod_generator!( mod_generator!( ff151, ff150::build_emulation, - header_initializer_with_zstd, + header_initializer_with_zstd_q09, [ ( Windows, @@ -521,7 +521,7 @@ mod_generator!( mod_generator!( ff152, ff150::build_emulation, - header_initializer_with_zstd, + header_initializer_with_zstd_q09, [ ( Windows, diff --git a/crates/wreq-util/src/emulate/profile/firefox/header.rs b/crates/wreq-util/src/emulate/profile/firefox/header.rs index 10223e0..2627180 100644 --- a/crates/wreq-util/src/emulate/profile/firefox/header.rs +++ b/crates/wreq-util/src/emulate/profile/firefox/header.rs @@ -35,3 +35,25 @@ pub fn header_initializer_with_zstd(ua: &'static str) -> HeaderMap { ); headers } + +/// Firefox 147 and later. Identical to [`header_initializer_with_zstd`] apart from the +/// `Accept-Language` q-value, which Firefox changed in 147 (Bugzilla 2000765). +pub fn header_initializer_with_zstd_q09(ua: &'static str) -> HeaderMap { + let mut headers = HeaderMap::new(); + header_firefox_ua!(headers, ua); + header_firefox_accept!(zstd_q09, headers); + headers.insert( + HeaderName::from_static("upgrade-insecure-requests"), + HeaderValue::from_static("1"), + ); + header_firefox_sec_fetch!(headers); + headers.insert( + HeaderName::from_static("priority"), + HeaderValue::from_static("u=0, i"), + ); + headers.insert( + HeaderName::from_static("te"), + HeaderValue::from_static("trailers"), + ); + headers +} diff --git a/crates/wreq-util/tests/emulate_firefox.rs b/crates/wreq-util/tests/emulate_firefox.rs index fb0dd15..29cf391 100644 --- a/crates/wreq-util/tests/emulate_firefox.rs +++ b/crates/wreq-util/tests/emulate_firefox.rs @@ -111,3 +111,61 @@ test_emulation!( ["t13d1617h2_86a278354501_3cbfd9057e0d"], "6ea73faa8fc5aac76bded7bd238f6433" ); + +/// Firefox 147 changed the generated `Accept-Language` q-value: the list used to divide 1.0 +/// across its entries, giving `q=0.5` for two, and now decrements by 0.1 per entry, giving +/// `q=0.9`. See Bugzilla 2000765, landed in mozilla-firefox 0e050ae5116d. +/// +/// Every profile is listed rather than a range, so adding one is a deliberate choice about +/// which side of 147 it falls on. +#[test] +fn firefox_accept_language_matches_the_profile_version() { + use wreq::IntoEmulation; + use wreq_util::{Platform, Profile}; + + const PRE_147: &[(&str, Profile)] = &[ + ("Firefox109", Emulation::Firefox109), + ("Firefox117", Emulation::Firefox117), + ("Firefox128", Emulation::Firefox128), + ("Firefox133", Emulation::Firefox133), + ("Firefox135", Emulation::Firefox135), + ("FirefoxPrivate135", Emulation::FirefoxPrivate135), + ("FirefoxAndroid135", Emulation::FirefoxAndroid135), + ("Firefox136", Emulation::Firefox136), + ("FirefoxPrivate136", Emulation::FirefoxPrivate136), + ("Firefox139", Emulation::Firefox139), + ("Firefox142", Emulation::Firefox142), + ("Firefox143", Emulation::Firefox143), + ("Firefox144", Emulation::Firefox144), + ("Firefox145", Emulation::Firefox145), + ("Firefox146", Emulation::Firefox146), + ]; + const POST_147: &[(&str, Profile)] = &[ + ("Firefox147", Emulation::Firefox147), + ("Firefox148", Emulation::Firefox148), + ("Firefox149", Emulation::Firefox149), + ("Firefox150", Emulation::Firefox150), + ("Firefox151", Emulation::Firefox151), + ("Firefox152", Emulation::Firefox152), + ]; + + fn accept_language(profile: Profile) -> String { + Emulation::builder() + .profile(profile) + .platform(Platform::Linux) + .build() + .into_emulation() + .headers + .get("accept-language") + .and_then(|value| value.to_str().ok()) + .unwrap_or_default() + .to_owned() + } + + for (name, profile) in PRE_147 { + assert_eq!("en-US,en;q=0.5", accept_language(*profile), "{name}"); + } + for (name, profile) in POST_147 { + assert_eq!("en-US,en;q=0.9", accept_language(*profile), "{name}"); + } +}