net: derive sec-ch-ua client hints from the UA on the default HTTP path
The non-stealth navigation path hardcoded sec-ch-ua-platform "Linux" and a fixed sec-ch-ua using Chrome 149's GREASE token, regardless of the selected profile. With the default Windows Chrome 143 profile that meant a Windows UA and navigator.userAgentData traveling with a Linux sec-ch-ua-platform header and mismatched brands, a cross-layer inconsistency a site can check at a glance. Derive both headers from the profile User-Agent using Chromium's per-major GREASE algorithm, so the HTTP client hints agree with the JS surfaces.
This commit is contained in:
1 parent
3eb28da5c6
commit
f7b3ff5d75
1 file changed
+45
-2
@@ -273,6 +273,46 @@ pub struct ObscuraHttpClient {
|
||||
pub allow_private_network: bool,
|
||||
}
|
||||
|
||||
/// Derive the sec-ch-ua and sec-ch-ua-platform client-hint header values from a
|
||||
/// User-Agent string, using Chromium's per-major-version GREASE algorithm so
|
||||
/// the non-stealth HTTP path agrees with navigator.userAgentData instead of
|
||||
/// shipping a fixed Linux/Chrome-145 hint that contradicts a Windows profile.
|
||||
fn chrome_client_hints(ua: &str) -> (String, String) {
|
||||
let major: usize = ua
|
||||
.split("Chrome/")
|
||||
.nth(1)
|
||||
.and_then(|s| s.split('.').next())
|
||||
.and_then(|s| s.parse::<usize>().ok())
|
||||
.unwrap_or(145);
|
||||
const GREASE_CHARS: [char; 11] = [' ', '(', ':', '-', '.', '/', ')', ';', '=', '?', '_'];
|
||||
const GREASE_VER: [&str; 3] = ["8", "99", "24"];
|
||||
const PERMS: [[usize; 3]; 6] = [[0, 1, 2], [0, 2, 1], [1, 0, 2], [1, 2, 0], [2, 0, 1], [2, 1, 0]];
|
||||
let grease_brand = format!(
|
||||
"Not{}A{}Brand",
|
||||
GREASE_CHARS[major % 11],
|
||||
GREASE_CHARS[(major + 1) % 11]
|
||||
);
|
||||
let brands = [
|
||||
(grease_brand, GREASE_VER[major % 3].to_string()),
|
||||
("Chromium".to_string(), major.to_string()),
|
||||
("Google Chrome".to_string(), major.to_string()),
|
||||
];
|
||||
let p = PERMS[major % 6];
|
||||
let sec_ch_ua = p
|
||||
.iter()
|
||||
.map(|&i| format!("\"{}\";v=\"{}\"", brands[i].0, brands[i].1))
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ");
|
||||
let platform = if ua.contains("Windows NT") {
|
||||
"\"Windows\""
|
||||
} else if ua.contains("Macintosh") {
|
||||
"\"macOS\""
|
||||
} else {
|
||||
"\"Linux\""
|
||||
};
|
||||
(sec_ch_ua, platform.to_string())
|
||||
}
|
||||
|
||||
impl ObscuraHttpClient {
|
||||
pub fn new() -> Self {
|
||||
Self::with_cookie_jar(Arc::new(CookieJar::new()))
|
||||
@@ -419,9 +459,11 @@ impl ObscuraHttpClient {
|
||||
reqwest::header::ACCEPT_LANGUAGE,
|
||||
HeaderValue::from_static("en-US,en;q=0.9"),
|
||||
);
|
||||
let (sec_ch_ua, sec_ch_ua_platform) = chrome_client_hints(&ua);
|
||||
headers.insert(
|
||||
HeaderName::from_static("sec-ch-ua"),
|
||||
HeaderValue::from_static("\"Chromium\";v=\"145\", \"Not;A=Brand\";v=\"24\", \"Google Chrome\";v=\"145\""),
|
||||
HeaderValue::from_str(&sec_ch_ua)
|
||||
.unwrap_or_else(|_| HeaderValue::from_static("\"Not:A-Brand\";v=\"99\", \"Google Chrome\";v=\"145\", \"Chromium\";v=\"145\"")),
|
||||
);
|
||||
headers.insert(
|
||||
HeaderName::from_static("sec-ch-ua-mobile"),
|
||||
@@ -429,7 +471,8 @@ impl ObscuraHttpClient {
|
||||
);
|
||||
headers.insert(
|
||||
HeaderName::from_static("sec-ch-ua-platform"),
|
||||
HeaderValue::from_static("\"Linux\""),
|
||||
HeaderValue::from_str(&sec_ch_ua_platform)
|
||||
.unwrap_or_else(|_| HeaderValue::from_static("\"Windows\"")),
|
||||
);
|
||||
headers.insert(
|
||||
HeaderName::from_static("sec-fetch-dest"),
|
||||
|
||||
Reference in new issue
Block a user