#![forbid(unsafe_code)]
#![deny(unreachable_patterns)]
#![warn(missing_docs)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum StealthProfile {
ChromeWindowsStable,
ChromeWindowsLegacy96,
ChromeMacStable,
EdgeWindowsStable,
Ie11Windows,
FirefoxLinux,
FirefoxWindows,
ChromeAndroid,
SafariIphone,
SafariIpad,
SafariMacStable,
ChromeLinux,
BraveWindows,
OperaWindows,
SamsungInternetAndroid,
}
pub const DEFAULT_STEALTH_PROFILE: StealthProfile = StealthProfile::ChromeWindowsStable;
pub const ALL_PROFILES: &[StealthProfile] = &[
DEFAULT_STEALTH_PROFILE,
StealthProfile::ChromeWindowsLegacy96,
StealthProfile::ChromeMacStable,
StealthProfile::EdgeWindowsStable,
StealthProfile::Ie11Windows,
StealthProfile::FirefoxLinux,
StealthProfile::FirefoxWindows,
StealthProfile::ChromeAndroid,
StealthProfile::SafariIphone,
StealthProfile::SafariIpad,
StealthProfile::SafariMacStable,
StealthProfile::ChromeLinux,
StealthProfile::BraveWindows,
StealthProfile::OperaWindows,
StealthProfile::SamsungInternetAndroid,
];
pub const ROTATION_PROFILES: &[StealthProfile] = &[
DEFAULT_STEALTH_PROFILE,
StealthProfile::ChromeMacStable,
StealthProfile::EdgeWindowsStable,
StealthProfile::FirefoxLinux,
StealthProfile::FirefoxWindows,
StealthProfile::ChromeAndroid,
StealthProfile::SafariIphone,
StealthProfile::SafariIpad,
StealthProfile::SafariMacStable,
StealthProfile::ChromeLinux,
StealthProfile::BraveWindows,
StealthProfile::OperaWindows,
StealthProfile::SamsungInternetAndroid,
];
#[must_use]
pub const fn profile_name(profile: StealthProfile) -> &'static str {
match profile {
StealthProfile::ChromeWindowsStable => "chrome",
StealthProfile::ChromeWindowsLegacy96 => "chrome-windows-legacy-96",
StealthProfile::ChromeMacStable => "chrome-macos",
StealthProfile::EdgeWindowsStable => "edge",
StealthProfile::Ie11Windows => "ie11-windows",
StealthProfile::FirefoxLinux => "firefox",
StealthProfile::FirefoxWindows => "firefox-windows",
StealthProfile::ChromeAndroid => "chrome-android",
StealthProfile::SafariIphone => "safari-iphone",
StealthProfile::SafariIpad => "safari-ipad",
StealthProfile::SafariMacStable => "safari",
StealthProfile::ChromeLinux => "chrome-linux",
StealthProfile::BraveWindows => "brave",
StealthProfile::OperaWindows => "opera",
StealthProfile::SamsungInternetAndroid => "samsung-internet",
}
}
#[must_use]
pub const fn profile_display_name(profile: StealthProfile) -> &'static str {
match profile {
StealthProfile::ChromeWindowsStable => "ChromeWindowsStable",
StealthProfile::ChromeWindowsLegacy96 => "ChromeWindowsLegacy96",
StealthProfile::ChromeMacStable => "ChromeMacStable",
StealthProfile::EdgeWindowsStable => "EdgeWindowsStable",
StealthProfile::Ie11Windows => "Ie11Windows",
StealthProfile::FirefoxLinux => "FirefoxLinux",
StealthProfile::FirefoxWindows => "FirefoxWindows",
StealthProfile::ChromeAndroid => "ChromeAndroid",
StealthProfile::SafariIphone => "SafariIphone",
StealthProfile::SafariIpad => "SafariIpad",
StealthProfile::SafariMacStable => "SafariMacStable",
StealthProfile::ChromeLinux => "ChromeLinux",
StealthProfile::BraveWindows => "BraveWindows",
StealthProfile::OperaWindows => "OperaWindows",
StealthProfile::SamsungInternetAndroid => "SamsungInternetAndroid",
}
}
#[must_use]
pub fn named_profile(name: &str) -> Option<StealthProfile> {
let normalized = name.trim().to_ascii_lowercase();
match normalized.as_str() {
"chrome"
| "chrome-windows"
| "chrome-win"
| "chrome_131_windows"
| "chrome_windows"
| "chromewindowsstable" => Some(StealthProfile::ChromeWindowsStable),
"chrome-windows-legacy-96"
| "chrome_96_windows"
| "chrome_windows_legacy_96"
| "chromewindowslegacy96" => Some(StealthProfile::ChromeWindowsLegacy96),
"chrome-macos" | "chrome-mac" | "chrome-osx" | "chrome_131_macos" | "chrome_mac"
| "chromemacstable" => Some(StealthProfile::ChromeMacStable),
"edge" | "edge-windows" | "edge_131" | "edge_windows" | "edgewindowsstable" => {
Some(StealthProfile::EdgeWindowsStable)
}
"ie11" | "ie" | "internet-explorer" | "ie11-windows" | "ie11_windows" | "ie11windows" => {
Some(StealthProfile::Ie11Windows)
}
"firefox" | "firefox-linux" | "firefox_133" | "firefox_linux" | "firefoxlinux" => {
Some(StealthProfile::FirefoxLinux)
}
"firefox-windows" | "firefox_windows" | "firefox_133_windows" | "firefoxwindows" => {
Some(StealthProfile::FirefoxWindows)
}
"chrome-android" | "chrome_android" | "android" | "chromeandroid" => {
Some(StealthProfile::ChromeAndroid)
}
"safari-iphone" | "safari_iphone" | "iphone" | "safariiphone" => {
Some(StealthProfile::SafariIphone)
}
"safari-ipad" | "safari_ipad" | "ipad" | "safariipad" => Some(StealthProfile::SafariIpad),
"safari" | "safari-mac" | "safari_17_5" | "safari_mac" | "safarimacstable" => {
Some(StealthProfile::SafariMacStable)
}
"chrome-linux" | "chrome_linux" | "chromelinux" => Some(StealthProfile::ChromeLinux),
"brave" | "brave-windows" | "brave_windows" | "bravewindows" => {
Some(StealthProfile::BraveWindows)
}
"opera" | "opera-windows" | "opera_windows" | "operawindows" => {
Some(StealthProfile::OperaWindows)
}
"samsung-internet" | "samsung_internet" | "samsung" | "samsunginternetandroid" => {
Some(StealthProfile::SamsungInternetAndroid)
}
_ => None,
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ProfileFacts {
pub user_agent: &'static str,
pub platform: &'static str,
pub languages: &'static [&'static str],
pub accept: &'static str,
pub accept_language: &'static str,
pub accept_encoding: &'static str,
pub mobile: bool,
pub screen_width: u32,
pub screen_height: u32,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum UserAgentBrowser {
Chrome,
Edge,
Firefox,
Safari,
InternetExplorer,
Opera,
SamsungInternet,
Unknown,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum UserAgentPlatform {
Android,
Ios,
MacOs,
Windows,
Linux,
Unknown,
}
impl UserAgentPlatform {
#[must_use]
pub const fn client_hint_value(self) -> Option<&'static str> {
match self {
Self::Android => Some("\"Android\""),
Self::Ios => Some("\"iOS\""),
Self::MacOs => Some("\"macOS\""),
Self::Windows => Some("\"Windows\""),
Self::Linux => Some("\"Linux\""),
Self::Unknown => None,
}
}
#[must_use]
pub const fn chrome_tls_label(self) -> Option<&'static str> {
match self {
Self::Android => Some("Android"),
Self::MacOs => Some("macOS"),
Self::Windows => Some("Windows"),
Self::Linux => Some("Linux"),
Self::Ios | Self::Unknown => None,
}
}
#[must_use]
pub const fn is_mobile(self) -> bool {
matches!(self, Self::Android | Self::Ios)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct UserAgentFacts {
pub browser: UserAgentBrowser,
pub platform: UserAgentPlatform,
pub browser_major_version: Option<u32>,
pub chromium_major_version: Option<u32>,
pub headless: bool,
pub inferred_profile: Option<StealthProfile>,
pub mobile: bool,
}
impl UserAgentFacts {
#[must_use]
pub const fn client_hint_platform_value(self) -> Option<&'static str> {
self.platform.client_hint_value()
}
#[must_use]
pub const fn client_hint_mobile_value(self) -> &'static str {
if self.mobile {
"?1"
} else {
"?0"
}
}
}
#[must_use]
pub fn user_agent_facts(user_agent: &str) -> UserAgentFacts {
let browser = user_agent_browser(user_agent);
let platform = user_agent_platform(user_agent);
let headless =
user_agent.contains("HeadlessChrome/") || user_agent.contains("HeadlessChromium/");
let mobile = platform.is_mobile() || user_agent.contains("Mobile");
let chromium_major_version = first_major_after(
user_agent,
&[
"HeadlessChrome/",
"HeadlessChromium/",
"Chrome/",
"Chromium/",
],
);
let browser_major_version = match browser {
UserAgentBrowser::Chrome => chromium_major_version,
UserAgentBrowser::Edge => major_after(user_agent, "Edg/"),
UserAgentBrowser::Firefox => major_after(user_agent, "Firefox/"),
UserAgentBrowser::Safari => major_after(user_agent, "Version/"),
UserAgentBrowser::InternetExplorer => {
major_after(user_agent, "MSIE ").or_else(|| major_after(user_agent, "rv:"))
}
UserAgentBrowser::Opera => major_after(user_agent, "OPR/"),
UserAgentBrowser::SamsungInternet => major_after(user_agent, "SamsungBrowser/"),
UserAgentBrowser::Unknown => None,
};
UserAgentFacts {
browser,
platform,
browser_major_version,
chromium_major_version,
headless,
inferred_profile: profile_from_user_agent_facts(
user_agent,
browser,
platform,
browser_major_version,
),
mobile,
}
}
#[must_use]
pub fn infer_profile_from_user_agent(user_agent: &str) -> Option<StealthProfile> {
user_agent_facts(user_agent).inferred_profile
}
fn user_agent_browser(user_agent: &str) -> UserAgentBrowser {
if user_agent.contains("Trident/") || user_agent.contains("MSIE ") {
UserAgentBrowser::InternetExplorer
} else if user_agent.contains("Edg/") {
UserAgentBrowser::Edge
} else if user_agent.contains("SamsungBrowser/") {
UserAgentBrowser::SamsungInternet
} else if user_agent.contains("OPR/") {
UserAgentBrowser::Opera
} else if user_agent.contains("Firefox/") {
UserAgentBrowser::Firefox
} else if user_agent.contains("HeadlessChrome/")
|| user_agent.contains("HeadlessChromium/")
|| user_agent.contains("Chrome/")
|| user_agent.contains("Chromium/")
{
UserAgentBrowser::Chrome
} else if user_agent.contains("Safari/") && user_agent.contains("Version/") {
UserAgentBrowser::Safari
} else {
UserAgentBrowser::Unknown
}
}
fn user_agent_platform(user_agent: &str) -> UserAgentPlatform {
if user_agent.contains("Android") {
UserAgentPlatform::Android
} else if user_agent.contains("iPhone") || user_agent.contains("iPad") {
UserAgentPlatform::Ios
} else if user_agent.contains("Macintosh") || user_agent.contains("Mac OS X") {
UserAgentPlatform::MacOs
} else if user_agent.contains("Windows") {
UserAgentPlatform::Windows
} else if user_agent.contains("Linux") || user_agent.contains("X11") {
UserAgentPlatform::Linux
} else {
UserAgentPlatform::Unknown
}
}
fn profile_from_user_agent_facts(
user_agent: &str,
browser: UserAgentBrowser,
platform: UserAgentPlatform,
browser_major_version: Option<u32>,
) -> Option<StealthProfile> {
match browser {
UserAgentBrowser::InternetExplorer => Some(StealthProfile::Ie11Windows),
UserAgentBrowser::Edge => Some(StealthProfile::EdgeWindowsStable),
UserAgentBrowser::Firefox => match platform {
UserAgentPlatform::Windows => Some(StealthProfile::FirefoxWindows),
_ => Some(StealthProfile::FirefoxLinux),
},
UserAgentBrowser::Safari => {
if user_agent.contains("iPhone") {
Some(StealthProfile::SafariIphone)
} else if user_agent.contains("iPad") {
Some(StealthProfile::SafariIpad)
} else {
Some(StealthProfile::SafariMacStable)
}
}
UserAgentBrowser::SamsungInternet => Some(StealthProfile::SamsungInternetAndroid),
UserAgentBrowser::Opera => Some(StealthProfile::OperaWindows),
UserAgentBrowser::Chrome => match platform {
UserAgentPlatform::Android => Some(StealthProfile::ChromeAndroid),
UserAgentPlatform::MacOs => Some(StealthProfile::ChromeMacStable),
UserAgentPlatform::Linux => Some(StealthProfile::ChromeLinux),
UserAgentPlatform::Windows => {
if browser_major_version.is_some_and(|major| major <= 96) {
Some(StealthProfile::ChromeWindowsLegacy96)
} else {
Some(StealthProfile::ChromeWindowsStable)
}
}
UserAgentPlatform::Ios | UserAgentPlatform::Unknown => None,
},
UserAgentBrowser::Unknown => None,
}
}
fn first_major_after(user_agent: &str, tokens: &[&str]) -> Option<u32> {
tokens
.iter()
.find_map(|token| major_after(user_agent, token))
}
fn major_after(user_agent: &str, token: &str) -> Option<u32> {
let rest = user_agent.split_once(token)?.1;
let end = rest
.find(|ch: char| ch == '.' || ch == ' ' || ch == ';' || ch == ')')
.unwrap_or(rest.len());
rest[..end].parse().ok()
}
pub const USER_AGENT_HEADER: &str = "user-agent";
pub const ACCEPT_HEADER: &str = "accept";
pub const ACCEPT_LANGUAGE_HEADER: &str = "accept-language";
pub const ACCEPT_ENCODING_HEADER: &str = "accept-encoding";
pub const UPGRADE_INSECURE_REQUESTS_HEADER: &str = "upgrade-insecure-requests";
pub const SEC_FETCH_DEST_HEADER: &str = "sec-fetch-dest";
pub const SEC_FETCH_MODE_HEADER: &str = "sec-fetch-mode";
pub const SEC_FETCH_SITE_HEADER: &str = "sec-fetch-site";
pub const SEC_FETCH_USER_HEADER: &str = "sec-fetch-user";
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum BrowserRequestKind {
Navigation,
SameOriginNavigation,
CrossSiteNavigation,
SameOriginFetch,
SameOriginModeFetch,
CrossSiteFetch,
ImageSubresource,
AudioSubresource,
}
#[must_use]
pub fn canonical_navigation_header_name(name: &str) -> &str {
match name {
USER_AGENT_HEADER => "User-Agent",
ACCEPT_HEADER => "Accept",
ACCEPT_LANGUAGE_HEADER => "Accept-Language",
ACCEPT_ENCODING_HEADER => "Accept-Encoding",
UPGRADE_INSECURE_REQUESTS_HEADER => "Upgrade-Insecure-Requests",
SEC_FETCH_DEST_HEADER => "Sec-Fetch-Dest",
SEC_FETCH_MODE_HEADER => "Sec-Fetch-Mode",
SEC_FETCH_SITE_HEADER => "Sec-Fetch-Site",
SEC_FETCH_USER_HEADER => "Sec-Fetch-User",
_ => name,
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct NavigationHeader {
pub name: &'static str,
pub value: &'static str,
}
const EMPTY_HEADER: NavigationHeader = NavigationHeader {
name: "",
value: "",
};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct BrowserRequestHeaders {
entries: [NavigationHeader; 9],
len: usize,
}
impl BrowserRequestHeaders {
#[must_use]
pub fn as_slice(&self) -> &[NavigationHeader] {
&self.entries[..self.len]
}
#[must_use]
pub const fn len(&self) -> usize {
self.len
}
#[must_use]
pub const fn is_empty(&self) -> bool {
self.len == 0
}
}
const WILDCARD_ACCEPT: &str = "*/*";
const UPGRADE_INSECURE_REQUESTS_VALUE: &str = "1";
const DOCUMENT_DEST_VALUE: &str = "document";
const EMPTY_DEST_VALUE: &str = "empty";
const IMAGE_DEST_VALUE: &str = "image";
const AUDIO_DEST_VALUE: &str = "audio";
const NAVIGATE_MODE_VALUE: &str = "navigate";
const CORS_MODE_VALUE: &str = "cors";
const SAME_ORIGIN_MODE_VALUE: &str = "same-origin";
const NO_CORS_MODE_VALUE: &str = "no-cors";
const NONE_SITE_VALUE: &str = "none";
const SAME_ORIGIN_SITE_VALUE: &str = "same-origin";
const CROSS_SITE_VALUE: &str = "cross-site";
const FETCH_USER_ACTIVATED_VALUE: &str = "?1";
const EN_US_EN: &[&str] = &["en-US", "en"];
pub const CHROMIUM_NAVIGATION_ACCEPT: &str = "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7";
pub const FIREFOX_NAVIGATION_ACCEPT: &str =
"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8";
pub const IE11_NAVIGATION_ACCEPT: &str = "text/html, application/xhtml+xml, */*";
pub const SAFARI_NAVIGATION_ACCEPT: &str =
"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8";
pub const DEFAULT_ACCEPT_LANGUAGE: &str = "en-US,en;q=0.9";
pub const FIREFOX_ACCEPT_LANGUAGE: &str = "en-US,en;q=0.5";
pub const DEFAULT_ACCEPT_ENCODING: &str = "gzip, deflate, br";
pub const LEGACY_ACCEPT_ENCODING: &str = "gzip, deflate";
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct HeaderProfile {
pub name: &'static str,
pub user_agent: &'static str,
pub accept: &'static str,
pub accept_language: &'static str,
pub accept_encoding: &'static str,
pub sec_fetch_site: &'static str,
pub sec_fetch_mode: &'static str,
pub sec_fetch_dest: &'static str,
}
impl HeaderProfile {
#[must_use]
pub const fn headers(self) -> [NavigationHeader; 4] {
[
NavigationHeader {
name: USER_AGENT_HEADER,
value: self.user_agent,
},
NavigationHeader {
name: ACCEPT_HEADER,
value: self.accept,
},
NavigationHeader {
name: ACCEPT_LANGUAGE_HEADER,
value: self.accept_language,
},
NavigationHeader {
name: ACCEPT_ENCODING_HEADER,
value: self.accept_encoding,
},
]
}
}
pub static PROFILES: &[HeaderProfile] = &[
browser_profile("chrome", StealthProfile::ChromeWindowsStable),
browser_profile("firefox", StealthProfile::FirefoxLinux),
browser_profile("safari", StealthProfile::SafariMacStable),
browser_profile("edge", StealthProfile::EdgeWindowsStable),
];
const DEFAULT_PROFILE: HeaderProfile = browser_profile("default", DEFAULT_STEALTH_PROFILE);
const fn browser_profile(name: &'static str, profile: StealthProfile) -> HeaderProfile {
let facts = profile_facts(profile);
HeaderProfile {
name,
user_agent: facts.user_agent,
accept: facts.accept,
accept_language: facts.accept_language,
accept_encoding: facts.accept_encoding,
sec_fetch_site: "none",
sec_fetch_mode: "navigate",
sec_fetch_dest: "document",
}
}
#[must_use]
pub fn get_profile(name: &str) -> Option<&'static HeaderProfile> {
PROFILES.iter().find(|profile| profile.name == name)
}
#[must_use]
pub fn rotate(index: usize) -> &'static HeaderProfile {
if PROFILES.is_empty() {
return &DEFAULT_PROFILE;
}
&PROFILES[index % PROFILES.len()]
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ProfileHardware {
pub screen_width: u32,
pub screen_height: u32,
pub color_depth: u8,
pub device_memory: u8,
pub hardware_concurrency: u8,
pub webgl_vendor: &'static str,
pub webgl_renderer: &'static str,
}
const CHROME_WINDOWS_HARDWARE: &[ProfileHardware] = &[
ProfileHardware {
screen_width: 1920,
screen_height: 1080,
color_depth: 24,
device_memory: 8,
hardware_concurrency: 8,
webgl_vendor: "Google Inc. (Intel)",
webgl_renderer:
"ANGLE (Intel, Intel(R) Iris(R) Xe Graphics Direct3D11 vs_5_0 ps_5_0, D3D11)",
},
ProfileHardware {
screen_width: 1920,
screen_height: 1080,
color_depth: 24,
device_memory: 16,
hardware_concurrency: 12,
webgl_vendor: "Google Inc. (NVIDIA)",
webgl_renderer: "ANGLE (NVIDIA, NVIDIA GeForce RTX 3060 Direct3D11 vs_5_0 ps_5_0, D3D11)",
},
ProfileHardware {
screen_width: 2560,
screen_height: 1440,
color_depth: 24,
device_memory: 16,
hardware_concurrency: 16,
webgl_vendor: "Google Inc. (AMD)",
webgl_renderer: "ANGLE (AMD, AMD Radeon RX 6700 XT Direct3D11 vs_5_0 ps_5_0, D3D11)",
},
ProfileHardware {
screen_width: 1366,
screen_height: 768,
color_depth: 24,
device_memory: 8,
hardware_concurrency: 8,
webgl_vendor: "Google Inc. (Intel)",
webgl_renderer: "ANGLE (Intel, Intel(R) UHD Graphics 630 Direct3D11 vs_5_0 ps_5_0, D3D11)",
},
ProfileHardware {
screen_width: 1920,
screen_height: 1080,
color_depth: 24,
device_memory: 32,
hardware_concurrency: 16,
webgl_vendor: "Google Inc. (NVIDIA)",
webgl_renderer: "ANGLE (NVIDIA, NVIDIA GeForce RTX 4070 Direct3D11 vs_5_0 ps_5_0, D3D11)",
},
];
const CHROME_WINDOWS_LEGACY_96_HARDWARE: &[ProfileHardware] = &[ProfileHardware {
screen_width: 1366,
screen_height: 768,
color_depth: 24,
device_memory: 8,
hardware_concurrency: 8,
webgl_vendor: "Google Inc. (Intel)",
webgl_renderer: "ANGLE (Intel, Intel(R) UHD Graphics 630 Direct3D11 vs_5_0 ps_5_0, D3D11)",
}];
const IE11_WINDOWS_HARDWARE: &[ProfileHardware] = &[ProfileHardware {
screen_width: 1366,
screen_height: 768,
color_depth: 24,
device_memory: 4,
hardware_concurrency: 4,
webgl_vendor: "Microsoft",
webgl_renderer: "Internet Explorer 11",
}];
const CHROME_MAC_HARDWARE: &[ProfileHardware] = &[ProfileHardware {
screen_width: 1728,
screen_height: 1117,
color_depth: 30,
device_memory: 16,
hardware_concurrency: 10,
webgl_vendor: "Google Inc. (Apple)",
webgl_renderer: "ANGLE (Apple, ANGLE Metal Renderer: Apple M1 Pro, Unspecified Version)",
}];
const EDGE_WINDOWS_HARDWARE: &[ProfileHardware] = &[ProfileHardware {
screen_width: 1920,
screen_height: 1080,
color_depth: 24,
device_memory: 8,
hardware_concurrency: 8,
webgl_vendor: "Google Inc. (Intel)",
webgl_renderer: "ANGLE (Intel, Intel(R) UHD Graphics Direct3D11 vs_5_0 ps_5_0, D3D11)",
}];
const FIREFOX_LINUX_HARDWARE: &[ProfileHardware] = &[ProfileHardware {
screen_width: 1920,
screen_height: 1080,
color_depth: 24,
device_memory: 8,
hardware_concurrency: 8,
webgl_vendor: "",
webgl_renderer: "",
}];
const FIREFOX_WINDOWS_HARDWARE: &[ProfileHardware] = &[ProfileHardware {
screen_width: 1920,
screen_height: 1080,
color_depth: 24,
device_memory: 8,
hardware_concurrency: 8,
webgl_vendor: "Google Inc. (Intel)",
webgl_renderer: "ANGLE (Intel, Intel(R) UHD Graphics Direct3D11 vs_5_0 ps_5_0, D3D11)",
}];
const CHROME_ANDROID_HARDWARE: &[ProfileHardware] = &[ProfileHardware {
screen_width: 412,
screen_height: 915,
color_depth: 24,
device_memory: 6,
hardware_concurrency: 8,
webgl_vendor: "Qualcomm",
webgl_renderer: "Adreno (TM) 740",
}];
const SAFARI_IPHONE_HARDWARE: &[ProfileHardware] = &[ProfileHardware {
screen_width: 390,
screen_height: 844,
color_depth: 24,
device_memory: 4,
hardware_concurrency: 6,
webgl_vendor: "Apple Inc.",
webgl_renderer: "Apple GPU",
}];
const SAFARI_IPAD_HARDWARE: &[ProfileHardware] = &[ProfileHardware {
screen_width: 1024,
screen_height: 1366,
color_depth: 24,
device_memory: 8,
hardware_concurrency: 8,
webgl_vendor: "Apple Inc.",
webgl_renderer: "Apple GPU",
}];
const SAFARI_MAC_HARDWARE: &[ProfileHardware] = &[ProfileHardware {
screen_width: 1728,
screen_height: 1117,
color_depth: 30,
device_memory: 16,
hardware_concurrency: 10,
webgl_vendor: "Apple Inc.",
webgl_renderer: "Apple M2",
}];
const CHROME_LINUX_HARDWARE: &[ProfileHardware] = &[
ProfileHardware {
screen_width: 1920,
screen_height: 1080,
color_depth: 24,
device_memory: 8,
hardware_concurrency: 8,
webgl_vendor: "Mesa",
webgl_renderer: "Mesa Intel(R) UHD Graphics 770 (ADL-S GT1)",
},
ProfileHardware {
screen_width: 1920,
screen_height: 1080,
color_depth: 24,
device_memory: 32,
hardware_concurrency: 8,
webgl_vendor: "Google Inc. (NVIDIA)",
webgl_renderer: "ANGLE (NVIDIA, NVIDIA GeForce GTX 1660 SUPER/PCIe/SSE2, OpenGL 4.5)",
},
];
const BRAVE_WINDOWS_HARDWARE: &[ProfileHardware] = &[ProfileHardware {
screen_width: 1920,
screen_height: 1080,
color_depth: 24,
device_memory: 8,
hardware_concurrency: 8,
webgl_vendor: "Brave",
webgl_renderer: "Brave",
}];
const OPERA_WINDOWS_HARDWARE: &[ProfileHardware] = &[ProfileHardware {
screen_width: 1920,
screen_height: 1080,
color_depth: 24,
device_memory: 8,
hardware_concurrency: 8,
webgl_vendor: "Google Inc. (Intel)",
webgl_renderer: "ANGLE (Intel, Intel(R) UHD Graphics 770 Direct3D11 vs_5_0 ps_5_0, D3D11)",
}];
const SAMSUNG_INTERNET_HARDWARE: &[ProfileHardware] = &[ProfileHardware {
screen_width: 412,
screen_height: 915,
color_depth: 24,
device_memory: 8,
hardware_concurrency: 8,
webgl_vendor: "Qualcomm",
webgl_renderer: "Adreno (TM) 750",
}];
#[must_use]
pub const fn profile_hardware_variants(profile: StealthProfile) -> &'static [ProfileHardware] {
match profile {
StealthProfile::ChromeWindowsStable => CHROME_WINDOWS_HARDWARE,
StealthProfile::ChromeWindowsLegacy96 => CHROME_WINDOWS_LEGACY_96_HARDWARE,
StealthProfile::ChromeMacStable => CHROME_MAC_HARDWARE,
StealthProfile::EdgeWindowsStable => EDGE_WINDOWS_HARDWARE,
StealthProfile::Ie11Windows => IE11_WINDOWS_HARDWARE,
StealthProfile::FirefoxLinux => FIREFOX_LINUX_HARDWARE,
StealthProfile::FirefoxWindows => FIREFOX_WINDOWS_HARDWARE,
StealthProfile::ChromeAndroid => CHROME_ANDROID_HARDWARE,
StealthProfile::SafariIphone => SAFARI_IPHONE_HARDWARE,
StealthProfile::SafariIpad => SAFARI_IPAD_HARDWARE,
StealthProfile::SafariMacStable => SAFARI_MAC_HARDWARE,
StealthProfile::ChromeLinux => CHROME_LINUX_HARDWARE,
StealthProfile::BraveWindows => BRAVE_WINDOWS_HARDWARE,
StealthProfile::OperaWindows => OPERA_WINDOWS_HARDWARE,
StealthProfile::SamsungInternetAndroid => SAMSUNG_INTERNET_HARDWARE,
}
}
#[must_use]
pub const fn profile_hardware(profile: StealthProfile) -> ProfileHardware {
profile_hardware_variants(profile)[0]
}
#[must_use]
pub const fn profile_hardware_at(profile: StealthProfile, index: usize) -> ProfileHardware {
let variants = profile_hardware_variants(profile);
variants[index % variants.len()]
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ProfileClientHintBrand {
pub brand: &'static str,
pub version: &'static str,
}
const NO_CLIENT_HINT_BRANDS: &[ProfileClientHintBrand] = &[];
const CHROMIUM_131_BRANDS: &[ProfileClientHintBrand] = &[
ProfileClientHintBrand {
brand: "Chromium",
version: "131",
},
ProfileClientHintBrand {
brand: "Google Chrome",
version: "131",
},
ProfileClientHintBrand {
brand: "Not?A_Brand",
version: "99",
},
];
const CHROMIUM_96_BRANDS: &[ProfileClientHintBrand] = &[
ProfileClientHintBrand {
brand: "Chromium",
version: "96",
},
ProfileClientHintBrand {
brand: "Google Chrome",
version: "96",
},
ProfileClientHintBrand {
brand: "Not?A_Brand",
version: "99",
},
];
const EDGE_131_BRANDS: &[ProfileClientHintBrand] = &[
ProfileClientHintBrand {
brand: "Chromium",
version: "131",
},
ProfileClientHintBrand {
brand: "Microsoft Edge",
version: "131",
},
ProfileClientHintBrand {
brand: "Not?A_Brand",
version: "99",
},
];
const BRAVE_131_BRANDS: &[ProfileClientHintBrand] = &[
ProfileClientHintBrand {
brand: "Brave",
version: "131",
},
ProfileClientHintBrand {
brand: "Chromium",
version: "131",
},
ProfileClientHintBrand {
brand: "Not?A_Brand",
version: "99",
},
];
const OPERA_116_BRANDS: &[ProfileClientHintBrand] = &[
ProfileClientHintBrand {
brand: "Chromium",
version: "131",
},
ProfileClientHintBrand {
brand: "Opera",
version: "116",
},
ProfileClientHintBrand {
brand: "Not?A_Brand",
version: "99",
},
];
const SAMSUNG_INTERNET_26_BRANDS: &[ProfileClientHintBrand] = &[
ProfileClientHintBrand {
brand: "Samsung Internet",
version: "26",
},
ProfileClientHintBrand {
brand: "Chromium",
version: "126",
},
ProfileClientHintBrand {
brand: "Not?A_Brand",
version: "99",
},
];
#[must_use]
pub const fn profile_navigator_vendor(profile: StealthProfile) -> &'static str {
match profile {
StealthProfile::ChromeWindowsStable
| StealthProfile::ChromeWindowsLegacy96
| StealthProfile::ChromeMacStable
| StealthProfile::EdgeWindowsStable
| StealthProfile::ChromeAndroid
| StealthProfile::ChromeLinux
| StealthProfile::BraveWindows
| StealthProfile::OperaWindows
| StealthProfile::SamsungInternetAndroid => "Google Inc.",
StealthProfile::SafariIphone
| StealthProfile::SafariIpad
| StealthProfile::SafariMacStable => "Apple Computer, Inc.",
StealthProfile::Ie11Windows
| StealthProfile::FirefoxLinux
| StealthProfile::FirefoxWindows => "",
}
}
#[must_use]
pub const fn profile_client_hint_brands(
profile: StealthProfile,
) -> &'static [ProfileClientHintBrand] {
match profile {
StealthProfile::ChromeWindowsStable
| StealthProfile::ChromeMacStable
| StealthProfile::ChromeAndroid
| StealthProfile::ChromeLinux => CHROMIUM_131_BRANDS,
StealthProfile::ChromeWindowsLegacy96 => CHROMIUM_96_BRANDS,
StealthProfile::EdgeWindowsStable => EDGE_131_BRANDS,
StealthProfile::BraveWindows => BRAVE_131_BRANDS,
StealthProfile::OperaWindows => OPERA_116_BRANDS,
StealthProfile::SamsungInternetAndroid => SAMSUNG_INTERNET_26_BRANDS,
StealthProfile::Ie11Windows
| StealthProfile::FirefoxLinux
| StealthProfile::FirefoxWindows
| StealthProfile::SafariIphone
| StealthProfile::SafariIpad
| StealthProfile::SafariMacStable => NO_CLIENT_HINT_BRANDS,
}
}
#[must_use]
pub const fn profile_client_hint_platform(profile: StealthProfile) -> Option<&'static str> {
match profile {
StealthProfile::ChromeWindowsStable
| StealthProfile::ChromeWindowsLegacy96
| StealthProfile::EdgeWindowsStable
| StealthProfile::BraveWindows
| StealthProfile::OperaWindows => Some("Windows"),
StealthProfile::ChromeMacStable => Some("macOS"),
StealthProfile::ChromeAndroid | StealthProfile::SamsungInternetAndroid => Some("Android"),
StealthProfile::ChromeLinux => Some("Linux"),
StealthProfile::Ie11Windows
| StealthProfile::FirefoxLinux
| StealthProfile::FirefoxWindows
| StealthProfile::SafariIphone
| StealthProfile::SafariIpad
| StealthProfile::SafariMacStable => None,
}
}
pub const CHROME_WINDOWS_STABLE_USER_AGENT: &str =
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 \
(KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36";
pub const CHROME_WINDOWS_LEGACY_96_USER_AGENT: &str = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) \
AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36";
pub const FIREFOX_WINDOWS_STABLE_USER_AGENT: &str =
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:133.0) Gecko/20100101 Firefox/133.0";
pub const IE11_WINDOWS_USER_AGENT: &str =
"Mozilla/5.0 (Windows NT 6.3; Trident/7.0; rv:11.0) like Gecko";
#[must_use]
pub const fn profile_facts(profile: StealthProfile) -> ProfileFacts {
match profile {
StealthProfile::ChromeWindowsStable => ProfileFacts {
user_agent: CHROME_WINDOWS_STABLE_USER_AGENT,
platform: "Win32",
languages: EN_US_EN,
accept: CHROMIUM_NAVIGATION_ACCEPT,
accept_language: DEFAULT_ACCEPT_LANGUAGE,
accept_encoding: DEFAULT_ACCEPT_ENCODING,
mobile: false,
screen_width: 1920,
screen_height: 1080,
},
StealthProfile::ChromeWindowsLegacy96 => ProfileFacts {
user_agent: CHROME_WINDOWS_LEGACY_96_USER_AGENT,
platform: "Win32",
languages: EN_US_EN,
accept: CHROMIUM_NAVIGATION_ACCEPT,
accept_language: DEFAULT_ACCEPT_LANGUAGE,
accept_encoding: DEFAULT_ACCEPT_ENCODING,
mobile: false,
screen_width: 1366,
screen_height: 768,
},
StealthProfile::ChromeMacStable => ProfileFacts {
user_agent: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 \
(KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36",
platform: "MacIntel",
languages: EN_US_EN,
accept: CHROMIUM_NAVIGATION_ACCEPT,
accept_language: DEFAULT_ACCEPT_LANGUAGE,
accept_encoding: DEFAULT_ACCEPT_ENCODING,
mobile: false,
screen_width: 1728,
screen_height: 1117,
},
StealthProfile::EdgeWindowsStable => ProfileFacts {
user_agent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 \
(KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36 Edg/131.0.0.0",
platform: "Win32",
languages: EN_US_EN,
accept: CHROMIUM_NAVIGATION_ACCEPT,
accept_language: DEFAULT_ACCEPT_LANGUAGE,
accept_encoding: DEFAULT_ACCEPT_ENCODING,
mobile: false,
screen_width: 1920,
screen_height: 1080,
},
StealthProfile::Ie11Windows => ProfileFacts {
user_agent: IE11_WINDOWS_USER_AGENT,
platform: "Win32",
languages: EN_US_EN,
accept: IE11_NAVIGATION_ACCEPT,
accept_language: DEFAULT_ACCEPT_LANGUAGE,
accept_encoding: LEGACY_ACCEPT_ENCODING,
mobile: false,
screen_width: 1366,
screen_height: 768,
},
StealthProfile::FirefoxLinux => ProfileFacts {
user_agent: "Mozilla/5.0 (X11; Linux x86_64; rv:133.0) Gecko/20100101 Firefox/133.0",
platform: "Linux x86_64",
languages: EN_US_EN,
accept: FIREFOX_NAVIGATION_ACCEPT,
accept_language: FIREFOX_ACCEPT_LANGUAGE,
accept_encoding: DEFAULT_ACCEPT_ENCODING,
mobile: false,
screen_width: 1920,
screen_height: 1080,
},
StealthProfile::FirefoxWindows => ProfileFacts {
user_agent: FIREFOX_WINDOWS_STABLE_USER_AGENT,
platform: "Win32",
languages: EN_US_EN,
accept: FIREFOX_NAVIGATION_ACCEPT,
accept_language: FIREFOX_ACCEPT_LANGUAGE,
accept_encoding: DEFAULT_ACCEPT_ENCODING,
mobile: false,
screen_width: 1920,
screen_height: 1080,
},
StealthProfile::ChromeAndroid => ProfileFacts {
user_agent: "Mozilla/5.0 (Linux; Android 14; Pixel 8) AppleWebKit/537.36 \
(KHTML, like Gecko) Chrome/131.0.0.0 Mobile Safari/537.36",
platform: "Linux armv8l",
languages: EN_US_EN,
accept: CHROMIUM_NAVIGATION_ACCEPT,
accept_language: DEFAULT_ACCEPT_LANGUAGE,
accept_encoding: DEFAULT_ACCEPT_ENCODING,
mobile: true,
screen_width: 412,
screen_height: 915,
},
StealthProfile::SafariIphone => ProfileFacts {
user_agent: "Mozilla/5.0 (iPhone; CPU iPhone OS 17_5 like Mac OS X) \
AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.5 \
Mobile/15E148 Safari/604.1",
platform: "iPhone",
languages: EN_US_EN,
accept: SAFARI_NAVIGATION_ACCEPT,
accept_language: DEFAULT_ACCEPT_LANGUAGE,
accept_encoding: DEFAULT_ACCEPT_ENCODING,
mobile: true,
screen_width: 390,
screen_height: 844,
},
StealthProfile::SafariIpad => ProfileFacts {
user_agent: "Mozilla/5.0 (iPad; CPU OS 17_5 like Mac OS X) AppleWebKit/605.1.15 \
(KHTML, like Gecko) Version/17.5 Mobile/15E148 Safari/604.1",
platform: "iPad",
languages: EN_US_EN,
accept: SAFARI_NAVIGATION_ACCEPT,
accept_language: DEFAULT_ACCEPT_LANGUAGE,
accept_encoding: DEFAULT_ACCEPT_ENCODING,
mobile: true,
screen_width: 1024,
screen_height: 1366,
},
StealthProfile::SafariMacStable => ProfileFacts {
user_agent: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) \
AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.5 Safari/605.1.15",
platform: "MacIntel",
languages: EN_US_EN,
accept: SAFARI_NAVIGATION_ACCEPT,
accept_language: DEFAULT_ACCEPT_LANGUAGE,
accept_encoding: DEFAULT_ACCEPT_ENCODING,
mobile: false,
screen_width: 1728,
screen_height: 1117,
},
StealthProfile::ChromeLinux => ProfileFacts {
user_agent: "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 \
(KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36",
platform: "Linux x86_64",
languages: EN_US_EN,
accept: CHROMIUM_NAVIGATION_ACCEPT,
accept_language: DEFAULT_ACCEPT_LANGUAGE,
accept_encoding: DEFAULT_ACCEPT_ENCODING,
mobile: false,
screen_width: 1920,
screen_height: 1080,
},
StealthProfile::BraveWindows => ProfileFacts {
user_agent: CHROME_WINDOWS_STABLE_USER_AGENT,
platform: "Win32",
languages: EN_US_EN,
accept: CHROMIUM_NAVIGATION_ACCEPT,
accept_language: DEFAULT_ACCEPT_LANGUAGE,
accept_encoding: DEFAULT_ACCEPT_ENCODING,
mobile: false,
screen_width: 1920,
screen_height: 1080,
},
StealthProfile::OperaWindows => ProfileFacts {
user_agent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 \
(KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36 OPR/116.0.0.0",
platform: "Win32",
languages: EN_US_EN,
accept: CHROMIUM_NAVIGATION_ACCEPT,
accept_language: DEFAULT_ACCEPT_LANGUAGE,
accept_encoding: DEFAULT_ACCEPT_ENCODING,
mobile: false,
screen_width: 1920,
screen_height: 1080,
},
StealthProfile::SamsungInternetAndroid => ProfileFacts {
user_agent: "Mozilla/5.0 (Linux; Android 14; SM-S928B) AppleWebKit/537.36 \
(KHTML, like Gecko) SamsungBrowser/26.0 Chrome/126.0.0.0 \
Mobile Safari/537.36",
platform: "Linux armv8l",
languages: EN_US_EN,
accept: CHROMIUM_NAVIGATION_ACCEPT,
accept_language: DEFAULT_ACCEPT_LANGUAGE,
accept_encoding: DEFAULT_ACCEPT_ENCODING,
mobile: true,
screen_width: 412,
screen_height: 915,
},
}
}
#[must_use]
pub const fn default_profile_facts() -> ProfileFacts {
profile_facts(DEFAULT_STEALTH_PROFILE)
}
#[must_use]
pub const fn profile_user_agent(profile: StealthProfile) -> &'static str {
profile_facts(profile).user_agent
}
#[must_use]
pub const fn default_profile_user_agent() -> &'static str {
default_profile_facts().user_agent
}
#[must_use]
pub const fn profile_navigation_headers(profile: StealthProfile) -> [NavigationHeader; 3] {
let facts = profile_facts(profile);
[
NavigationHeader {
name: USER_AGENT_HEADER,
value: facts.user_agent,
},
NavigationHeader {
name: ACCEPT_HEADER,
value: facts.accept,
},
NavigationHeader {
name: ACCEPT_LANGUAGE_HEADER,
value: facts.accept_language,
},
]
}
#[must_use]
pub const fn default_profile_navigation_headers() -> [NavigationHeader; 3] {
profile_navigation_headers(DEFAULT_STEALTH_PROFILE)
}
#[must_use]
pub const fn profile_browser_headers(profile: StealthProfile) -> [NavigationHeader; 4] {
let facts = profile_facts(profile);
[
NavigationHeader {
name: USER_AGENT_HEADER,
value: facts.user_agent,
},
NavigationHeader {
name: ACCEPT_HEADER,
value: facts.accept,
},
NavigationHeader {
name: ACCEPT_LANGUAGE_HEADER,
value: facts.accept_language,
},
NavigationHeader {
name: ACCEPT_ENCODING_HEADER,
value: facts.accept_encoding,
},
]
}
#[must_use]
pub const fn default_profile_browser_headers() -> [NavigationHeader; 4] {
profile_browser_headers(DEFAULT_STEALTH_PROFILE)
}
#[must_use]
pub const fn profile_request_headers(
profile: StealthProfile,
kind: BrowserRequestKind,
) -> BrowserRequestHeaders {
profile_request_headers_inner(profile, kind, true)
}
#[must_use]
pub const fn default_profile_request_headers(kind: BrowserRequestKind) -> BrowserRequestHeaders {
profile_request_headers(DEFAULT_STEALTH_PROFILE, kind)
}
#[must_use]
pub const fn profile_request_headers_without_compression(
profile: StealthProfile,
kind: BrowserRequestKind,
) -> BrowserRequestHeaders {
profile_request_headers_inner(profile, kind, false)
}
#[must_use]
pub const fn default_profile_request_headers_without_compression(
kind: BrowserRequestKind,
) -> BrowserRequestHeaders {
profile_request_headers_without_compression(DEFAULT_STEALTH_PROFILE, kind)
}
const fn profile_request_headers_inner(
profile: StealthProfile,
kind: BrowserRequestKind,
include_compression: bool,
) -> BrowserRequestHeaders {
let facts = profile_facts(profile);
let surface = request_surface_facts(kind, facts.accept);
if surface.upgrade_insecure_requests {
if include_compression {
BrowserRequestHeaders {
entries: [
NavigationHeader {
name: USER_AGENT_HEADER,
value: facts.user_agent,
},
NavigationHeader {
name: ACCEPT_HEADER,
value: surface.accept,
},
NavigationHeader {
name: ACCEPT_LANGUAGE_HEADER,
value: facts.accept_language,
},
NavigationHeader {
name: ACCEPT_ENCODING_HEADER,
value: facts.accept_encoding,
},
NavigationHeader {
name: UPGRADE_INSECURE_REQUESTS_HEADER,
value: UPGRADE_INSECURE_REQUESTS_VALUE,
},
NavigationHeader {
name: SEC_FETCH_DEST_HEADER,
value: surface.dest,
},
NavigationHeader {
name: SEC_FETCH_MODE_HEADER,
value: surface.mode,
},
NavigationHeader {
name: SEC_FETCH_SITE_HEADER,
value: surface.site,
},
NavigationHeader {
name: SEC_FETCH_USER_HEADER,
value: surface.fetch_user,
},
],
len: 9,
}
} else {
BrowserRequestHeaders {
entries: [
NavigationHeader {
name: USER_AGENT_HEADER,
value: facts.user_agent,
},
NavigationHeader {
name: ACCEPT_HEADER,
value: surface.accept,
},
NavigationHeader {
name: ACCEPT_LANGUAGE_HEADER,
value: facts.accept_language,
},
NavigationHeader {
name: UPGRADE_INSECURE_REQUESTS_HEADER,
value: UPGRADE_INSECURE_REQUESTS_VALUE,
},
NavigationHeader {
name: SEC_FETCH_DEST_HEADER,
value: surface.dest,
},
NavigationHeader {
name: SEC_FETCH_MODE_HEADER,
value: surface.mode,
},
NavigationHeader {
name: SEC_FETCH_SITE_HEADER,
value: surface.site,
},
NavigationHeader {
name: SEC_FETCH_USER_HEADER,
value: surface.fetch_user,
},
EMPTY_HEADER,
],
len: 8,
}
}
} else if include_compression {
BrowserRequestHeaders {
entries: [
NavigationHeader {
name: USER_AGENT_HEADER,
value: facts.user_agent,
},
NavigationHeader {
name: ACCEPT_HEADER,
value: surface.accept,
},
NavigationHeader {
name: ACCEPT_LANGUAGE_HEADER,
value: facts.accept_language,
},
NavigationHeader {
name: ACCEPT_ENCODING_HEADER,
value: facts.accept_encoding,
},
NavigationHeader {
name: SEC_FETCH_DEST_HEADER,
value: surface.dest,
},
NavigationHeader {
name: SEC_FETCH_MODE_HEADER,
value: surface.mode,
},
NavigationHeader {
name: SEC_FETCH_SITE_HEADER,
value: surface.site,
},
EMPTY_HEADER,
EMPTY_HEADER,
],
len: 7,
}
} else {
BrowserRequestHeaders {
entries: [
NavigationHeader {
name: USER_AGENT_HEADER,
value: facts.user_agent,
},
NavigationHeader {
name: ACCEPT_HEADER,
value: surface.accept,
},
NavigationHeader {
name: ACCEPT_LANGUAGE_HEADER,
value: facts.accept_language,
},
NavigationHeader {
name: SEC_FETCH_DEST_HEADER,
value: surface.dest,
},
NavigationHeader {
name: SEC_FETCH_MODE_HEADER,
value: surface.mode,
},
NavigationHeader {
name: SEC_FETCH_SITE_HEADER,
value: surface.site,
},
EMPTY_HEADER,
EMPTY_HEADER,
EMPTY_HEADER,
],
len: 6,
}
}
}
struct RequestSurfaceFacts {
accept: &'static str,
dest: &'static str,
mode: &'static str,
site: &'static str,
upgrade_insecure_requests: bool,
fetch_user: &'static str,
}
const fn request_surface_facts(
kind: BrowserRequestKind,
navigation_accept: &'static str,
) -> RequestSurfaceFacts {
match kind {
BrowserRequestKind::Navigation => RequestSurfaceFacts {
accept: navigation_accept,
dest: DOCUMENT_DEST_VALUE,
mode: NAVIGATE_MODE_VALUE,
site: NONE_SITE_VALUE,
upgrade_insecure_requests: true,
fetch_user: FETCH_USER_ACTIVATED_VALUE,
},
BrowserRequestKind::SameOriginNavigation => RequestSurfaceFacts {
accept: navigation_accept,
dest: DOCUMENT_DEST_VALUE,
mode: NAVIGATE_MODE_VALUE,
site: SAME_ORIGIN_SITE_VALUE,
upgrade_insecure_requests: true,
fetch_user: FETCH_USER_ACTIVATED_VALUE,
},
BrowserRequestKind::CrossSiteNavigation => RequestSurfaceFacts {
accept: navigation_accept,
dest: DOCUMENT_DEST_VALUE,
mode: NAVIGATE_MODE_VALUE,
site: CROSS_SITE_VALUE,
upgrade_insecure_requests: true,
fetch_user: FETCH_USER_ACTIVATED_VALUE,
},
BrowserRequestKind::SameOriginFetch => RequestSurfaceFacts {
accept: WILDCARD_ACCEPT,
dest: EMPTY_DEST_VALUE,
mode: CORS_MODE_VALUE,
site: SAME_ORIGIN_SITE_VALUE,
upgrade_insecure_requests: false,
fetch_user: "",
},
BrowserRequestKind::SameOriginModeFetch => RequestSurfaceFacts {
accept: WILDCARD_ACCEPT,
dest: EMPTY_DEST_VALUE,
mode: SAME_ORIGIN_MODE_VALUE,
site: SAME_ORIGIN_SITE_VALUE,
upgrade_insecure_requests: false,
fetch_user: "",
},
BrowserRequestKind::CrossSiteFetch => RequestSurfaceFacts {
accept: WILDCARD_ACCEPT,
dest: EMPTY_DEST_VALUE,
mode: CORS_MODE_VALUE,
site: CROSS_SITE_VALUE,
upgrade_insecure_requests: false,
fetch_user: "",
},
BrowserRequestKind::ImageSubresource => RequestSurfaceFacts {
accept: WILDCARD_ACCEPT,
dest: IMAGE_DEST_VALUE,
mode: NO_CORS_MODE_VALUE,
site: CROSS_SITE_VALUE,
upgrade_insecure_requests: false,
fetch_user: "",
},
BrowserRequestKind::AudioSubresource => RequestSurfaceFacts {
accept: WILDCARD_ACCEPT,
dest: AUDIO_DEST_VALUE,
mode: NO_CORS_MODE_VALUE,
site: CROSS_SITE_VALUE,
upgrade_insecure_requests: false,
fetch_user: "",
},
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn chrome_windows_const_matches_profile() {
assert_eq!(
profile_user_agent(StealthProfile::ChromeWindowsStable),
CHROME_WINDOWS_STABLE_USER_AGENT
);
}
#[test]
fn firefox_windows_const_matches_profile() {
assert_eq!(
profile_user_agent(StealthProfile::FirefoxWindows),
FIREFOX_WINDOWS_STABLE_USER_AGENT
);
}
#[test]
fn firefox_windows_stays_windows_and_firefox() {
let ua = profile_user_agent(StealthProfile::FirefoxWindows);
assert!(ua.contains("Windows NT"));
assert!(ua.contains("Firefox/133.0"));
assert!(!ua.contains("Chrome/"));
}
#[test]
fn profile_facts_match_user_agent_api() {
for profile in ALL_PROFILES {
assert_eq!(
profile_facts(*profile).user_agent,
profile_user_agent(*profile)
);
assert_eq!(profile_facts(*profile).languages[0], "en-US");
assert!(!profile_facts(*profile).accept.is_empty());
assert!(!profile_facts(*profile).accept_language.is_empty());
let expected_encoding = match profile {
&StealthProfile::Ie11Windows => LEGACY_ACCEPT_ENCODING,
_ => DEFAULT_ACCEPT_ENCODING,
};
assert_eq!(profile_facts(*profile).accept_encoding, expected_encoding);
}
}
#[test]
fn default_profile_accessors_delegate_to_default_profile() {
assert_eq!(
default_profile_facts(),
profile_facts(DEFAULT_STEALTH_PROFILE)
);
assert_eq!(
default_profile_user_agent(),
profile_user_agent(DEFAULT_STEALTH_PROFILE)
);
assert_eq!(
default_profile_navigation_headers(),
profile_navigation_headers(DEFAULT_STEALTH_PROFILE)
);
assert_eq!(
default_profile_browser_headers(),
profile_browser_headers(DEFAULT_STEALTH_PROFILE)
);
assert_eq!(
default_profile_request_headers(BrowserRequestKind::Navigation),
profile_request_headers(DEFAULT_STEALTH_PROFILE, BrowserRequestKind::Navigation)
);
assert_eq!(
default_profile_request_headers_without_compression(BrowserRequestKind::Navigation),
profile_request_headers_without_compression(
DEFAULT_STEALTH_PROFILE,
BrowserRequestKind::Navigation
)
);
}
#[test]
fn profile_names_and_aliases_are_canonical() {
for profile in ALL_PROFILES {
assert_eq!(named_profile(profile_name(*profile)), Some(*profile));
assert_eq!(
named_profile(profile_display_name(*profile)),
Some(*profile)
);
}
assert_eq!(
named_profile("chrome-win"),
Some(StealthProfile::ChromeWindowsStable)
);
assert_eq!(
named_profile("chrome-osx"),
Some(StealthProfile::ChromeMacStable)
);
assert_eq!(named_profile("ie11"), Some(StealthProfile::Ie11Windows));
assert_eq!(named_profile("unknown"), None);
}
#[test]
fn user_agent_facts_parse_chrome_windows() {
let facts = user_agent_facts(profile_user_agent(StealthProfile::ChromeWindowsStable));
assert_eq!(facts.browser, UserAgentBrowser::Chrome);
assert_eq!(facts.platform, UserAgentPlatform::Windows);
assert_eq!(facts.browser_major_version, Some(131));
assert_eq!(facts.chromium_major_version, Some(131));
assert_eq!(
facts.inferred_profile,
Some(StealthProfile::ChromeWindowsStable)
);
assert_eq!(facts.client_hint_platform_value(), Some("\"Windows\""));
assert_eq!(facts.client_hint_mobile_value(), "?0");
assert_eq!(facts.platform.chrome_tls_label(), Some("Windows"));
}
#[test]
fn user_agent_facts_parse_mobile_and_safari_profiles() {
let android = user_agent_facts(profile_user_agent(StealthProfile::ChromeAndroid));
assert_eq!(android.platform, UserAgentPlatform::Android);
assert_eq!(
android.inferred_profile,
Some(StealthProfile::ChromeAndroid)
);
assert_eq!(android.client_hint_mobile_value(), "?1");
assert_eq!(android.platform.chrome_tls_label(), Some("Android"));
let iphone = user_agent_facts(profile_user_agent(StealthProfile::SafariIphone));
assert_eq!(iphone.browser, UserAgentBrowser::Safari);
assert_eq!(iphone.platform, UserAgentPlatform::Ios);
assert_eq!(iphone.inferred_profile, Some(StealthProfile::SafariIphone));
assert_eq!(iphone.platform.chrome_tls_label(), None);
}
#[test]
fn user_agent_facts_parse_chromium_vendor_profiles() {
let edge = user_agent_facts(profile_user_agent(StealthProfile::EdgeWindowsStable));
assert_eq!(edge.browser, UserAgentBrowser::Edge);
assert_eq!(edge.browser_major_version, Some(131));
assert_eq!(edge.chromium_major_version, Some(131));
assert_eq!(
edge.inferred_profile,
Some(StealthProfile::EdgeWindowsStable)
);
let opera = user_agent_facts(profile_user_agent(StealthProfile::OperaWindows));
assert_eq!(opera.browser, UserAgentBrowser::Opera);
assert_eq!(opera.browser_major_version, Some(116));
assert_eq!(opera.chromium_major_version, Some(131));
assert_eq!(opera.inferred_profile, Some(StealthProfile::OperaWindows));
let samsung = user_agent_facts(profile_user_agent(StealthProfile::SamsungInternetAndroid));
assert_eq!(samsung.browser, UserAgentBrowser::SamsungInternet);
assert_eq!(samsung.browser_major_version, Some(26));
assert_eq!(samsung.chromium_major_version, Some(126));
assert_eq!(
samsung.inferred_profile,
Some(StealthProfile::SamsungInternetAndroid)
);
}
#[test]
fn user_agent_facts_parse_firefox_ie_and_legacy_chrome() {
let firefox = user_agent_facts(profile_user_agent(StealthProfile::FirefoxWindows));
assert_eq!(firefox.browser, UserAgentBrowser::Firefox);
assert_eq!(firefox.platform, UserAgentPlatform::Windows);
assert_eq!(firefox.browser_major_version, Some(133));
assert_eq!(firefox.chromium_major_version, None);
assert_eq!(
firefox.inferred_profile,
Some(StealthProfile::FirefoxWindows)
);
let ie = user_agent_facts(profile_user_agent(StealthProfile::Ie11Windows));
assert_eq!(ie.browser, UserAgentBrowser::InternetExplorer);
assert_eq!(ie.platform, UserAgentPlatform::Windows);
assert_eq!(ie.browser_major_version, Some(11));
assert_eq!(ie.inferred_profile, Some(StealthProfile::Ie11Windows));
let legacy = user_agent_facts(profile_user_agent(StealthProfile::ChromeWindowsLegacy96));
assert_eq!(legacy.browser_major_version, Some(96));
assert_eq!(
legacy.inferred_profile,
Some(StealthProfile::ChromeWindowsLegacy96)
);
}
#[test]
fn user_agent_facts_reject_unknown_and_flags_headless() {
let unknown = user_agent_facts("curl/8.0");
assert_eq!(unknown.browser, UserAgentBrowser::Unknown);
assert_eq!(unknown.platform, UserAgentPlatform::Unknown);
assert_eq!(unknown.inferred_profile, None);
assert_eq!(unknown.browser_major_version, None);
let headless = user_agent_facts(
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 \
(KHTML, like Gecko) HeadlessChrome/134.0.0.0 Safari/537.36",
);
assert!(headless.headless);
assert_eq!(headless.browser, UserAgentBrowser::Chrome);
assert_eq!(headless.chromium_major_version, Some(134));
assert_eq!(headless.inferred_profile, Some(StealthProfile::ChromeLinux));
}
#[test]
fn rotation_profiles_exclude_legacy_personas() {
assert!(!ROTATION_PROFILES.contains(&StealthProfile::ChromeWindowsLegacy96));
assert!(!ROTATION_PROFILES.contains(&StealthProfile::Ie11Windows));
assert!(ROTATION_PROFILES.contains(&StealthProfile::ChromeWindowsStable));
}
#[test]
fn default_stealth_profile_is_catalogued_and_rotatable() {
assert!(ALL_PROFILES.contains(&DEFAULT_STEALTH_PROFILE));
assert!(ROTATION_PROFILES.contains(&DEFAULT_STEALTH_PROFILE));
assert_eq!(
named_profile(profile_name(DEFAULT_STEALTH_PROFILE)),
Some(DEFAULT_STEALTH_PROFILE)
);
}
#[test]
fn profile_hardware_defaults_share_profile_screen_facts() {
for profile in ALL_PROFILES {
let facts = profile_facts(*profile);
let variants = profile_hardware_variants(*profile);
let hardware = profile_hardware(*profile);
assert!(
!variants.is_empty(),
"{profile:?} must expose at least one hardware tuple"
);
assert_eq!(hardware.screen_width, facts.screen_width);
assert_eq!(hardware.screen_height, facts.screen_height);
assert!(hardware.color_depth > 0);
assert!(hardware.device_memory > 0);
assert!(hardware.hardware_concurrency > 0);
assert_eq!(
hardware.webgl_vendor.is_empty(),
hardware.webgl_renderer.is_empty(),
"{profile:?} half-spoofed WebGL adapter (vendor/renderer emptiness disagree)"
);
}
}
#[test]
fn browser_surface_metadata_tracks_profile_family() {
for profile in ALL_PROFILES {
let vendor = profile_navigator_vendor(*profile);
let brands = profile_client_hint_brands(*profile);
let client_hint_platform = profile_client_hint_platform(*profile);
match profile {
StealthProfile::FirefoxLinux
| StealthProfile::FirefoxWindows
| StealthProfile::Ie11Windows => {
assert_eq!(vendor, "");
assert!(brands.is_empty(), "{profile:?} should not expose UA-CH");
assert_eq!(client_hint_platform, None);
}
StealthProfile::SafariIphone
| StealthProfile::SafariIpad
| StealthProfile::SafariMacStable => {
assert_eq!(vendor, "Apple Computer, Inc.");
assert!(brands.is_empty(), "{profile:?} should not expose UA-CH");
assert_eq!(client_hint_platform, None);
}
_ => {
assert_eq!(vendor, "Google Inc.");
assert!(!brands.is_empty(), "{profile:?} missing UA-CH brands");
assert!(
client_hint_platform.is_some(),
"{profile:?} missing Sec-CH-UA-Platform"
);
assert!(
brands
.iter()
.any(|brand| brand.brand == "Not?A_Brand" && brand.version == "99"),
"{profile:?} missing GREASE brand"
);
}
}
}
}
#[test]
fn ie11_profile_keeps_legacy_http_shape() {
let facts = profile_facts(StealthProfile::Ie11Windows);
assert_eq!(facts.user_agent, IE11_WINDOWS_USER_AGENT);
assert!(facts.user_agent.contains("Trident/7.0"));
assert!(!facts.user_agent.contains("Chrome/"));
assert_eq!(facts.platform, "Win32");
assert_eq!(facts.accept, IE11_NAVIGATION_ACCEPT);
assert_eq!(facts.accept_encoding, LEGACY_ACCEPT_ENCODING);
assert!(!facts.accept_encoding.contains("br"));
}
#[test]
fn chrome_legacy_96_profile_keeps_legacy_chromium_shape() {
let facts = profile_facts(StealthProfile::ChromeWindowsLegacy96);
assert_eq!(facts.user_agent, CHROME_WINDOWS_LEGACY_96_USER_AGENT);
assert!(facts.user_agent.contains("Chrome/96.0.4664.110"));
assert_eq!(facts.platform, "Win32");
assert_eq!(facts.accept, CHROMIUM_NAVIGATION_ACCEPT);
assert_eq!(facts.accept_encoding, DEFAULT_ACCEPT_ENCODING);
assert_eq!(facts.screen_width, 1366);
assert_eq!(facts.screen_height, 768);
}
#[test]
fn common_browser_profiles_delegate_to_profile_facts() {
for (name, profile) in [
("chrome", StealthProfile::ChromeWindowsStable),
("firefox", StealthProfile::FirefoxLinux),
("safari", StealthProfile::SafariMacStable),
("edge", StealthProfile::EdgeWindowsStable),
] {
let entry = get_profile(name).expect("profile should exist");
let facts = profile_facts(profile);
assert_eq!(entry.user_agent, facts.user_agent, "{name} UA drifted");
assert_eq!(entry.accept, facts.accept, "{name} Accept drifted");
assert_eq!(
entry.accept_language, facts.accept_language,
"{name} Accept-Language drifted"
);
assert_eq!(
entry.accept_encoding, facts.accept_encoding,
"{name} Accept-Encoding drifted"
);
}
}
#[test]
fn common_browser_profile_rotation_matches_legacy_contract() {
let first = rotate(0);
assert_eq!(first.name, "chrome");
assert_eq!(rotate(PROFILES.len()), first);
assert_ne!(rotate(1).name, first.name);
assert!(get_profile("unknown").is_none());
}
#[test]
fn navigation_headers_delegate_to_profile_facts() {
let facts = profile_facts(StealthProfile::ChromeWindowsStable);
assert_eq!(
profile_navigation_headers(StealthProfile::ChromeWindowsStable),
[
NavigationHeader {
name: USER_AGENT_HEADER,
value: facts.user_agent,
},
NavigationHeader {
name: ACCEPT_HEADER,
value: facts.accept,
},
NavigationHeader {
name: ACCEPT_LANGUAGE_HEADER,
value: facts.accept_language,
},
]
);
}
#[test]
fn browser_profile_headers_include_legacy_accept_encoding() {
let profile = rotate(0);
assert_eq!(
profile.headers(),
[
NavigationHeader {
name: USER_AGENT_HEADER,
value: profile.user_agent,
},
NavigationHeader {
name: ACCEPT_HEADER,
value: profile.accept,
},
NavigationHeader {
name: ACCEPT_LANGUAGE_HEADER,
value: profile.accept_language,
},
NavigationHeader {
name: ACCEPT_ENCODING_HEADER,
value: profile.accept_encoding,
},
]
);
}
#[test]
fn browser_headers_delegate_to_profile_facts_with_accept_encoding() {
for profile in ALL_PROFILES {
let facts = profile_facts(*profile);
assert_eq!(
profile_browser_headers(*profile),
[
NavigationHeader {
name: USER_AGENT_HEADER,
value: facts.user_agent,
},
NavigationHeader {
name: ACCEPT_HEADER,
value: facts.accept,
},
NavigationHeader {
name: ACCEPT_LANGUAGE_HEADER,
value: facts.accept_language,
},
NavigationHeader {
name: ACCEPT_ENCODING_HEADER,
value: facts.accept_encoding,
},
]
);
}
}
#[test]
fn navigation_request_headers_include_fetch_metadata() {
let facts = profile_facts(StealthProfile::ChromeWindowsStable);
let headers = profile_request_headers_without_compression(
StealthProfile::ChromeWindowsStable,
BrowserRequestKind::Navigation,
);
assert_eq!(headers.len(), 8);
assert_eq!(
headers.as_slice(),
&[
NavigationHeader {
name: USER_AGENT_HEADER,
value: facts.user_agent,
},
NavigationHeader {
name: ACCEPT_HEADER,
value: facts.accept,
},
NavigationHeader {
name: ACCEPT_LANGUAGE_HEADER,
value: facts.accept_language,
},
NavigationHeader {
name: UPGRADE_INSECURE_REQUESTS_HEADER,
value: "1",
},
NavigationHeader {
name: SEC_FETCH_DEST_HEADER,
value: "document",
},
NavigationHeader {
name: SEC_FETCH_MODE_HEADER,
value: "navigate",
},
NavigationHeader {
name: SEC_FETCH_SITE_HEADER,
value: "none",
},
NavigationHeader {
name: SEC_FETCH_USER_HEADER,
value: "?1",
},
]
);
}
#[test]
fn same_origin_fetch_request_headers_are_not_navigation_shaped() {
let facts = profile_facts(StealthProfile::ChromeWindowsStable);
let headers = profile_request_headers_without_compression(
StealthProfile::ChromeWindowsStable,
BrowserRequestKind::SameOriginFetch,
);
assert_eq!(headers.len(), 6);
assert_eq!(
headers.as_slice(),
&[
NavigationHeader {
name: USER_AGENT_HEADER,
value: facts.user_agent,
},
NavigationHeader {
name: ACCEPT_HEADER,
value: "*/*",
},
NavigationHeader {
name: ACCEPT_LANGUAGE_HEADER,
value: facts.accept_language,
},
NavigationHeader {
name: SEC_FETCH_DEST_HEADER,
value: "empty",
},
NavigationHeader {
name: SEC_FETCH_MODE_HEADER,
value: "cors",
},
NavigationHeader {
name: SEC_FETCH_SITE_HEADER,
value: "same-origin",
},
]
);
}
#[test]
fn same_origin_navigation_uses_document_surface_with_same_origin_site() {
let facts = profile_facts(StealthProfile::ChromeWindowsStable);
let headers = profile_request_headers_without_compression(
StealthProfile::ChromeWindowsStable,
BrowserRequestKind::SameOriginNavigation,
);
let by_name = |name: &str| {
headers
.as_slice()
.iter()
.find(|header| header.name == name)
.map(|header| header.value)
};
assert_eq!(headers.len(), 8);
assert_eq!(by_name(ACCEPT_HEADER), Some(facts.accept));
assert_eq!(by_name(UPGRADE_INSECURE_REQUESTS_HEADER), Some("1"));
assert_eq!(by_name(SEC_FETCH_DEST_HEADER), Some("document"));
assert_eq!(by_name(SEC_FETCH_MODE_HEADER), Some("navigate"));
assert_eq!(by_name(SEC_FETCH_SITE_HEADER), Some("same-origin"));
assert_eq!(by_name(SEC_FETCH_USER_HEADER), Some("?1"));
}
#[test]
fn cross_site_fetch_request_headers_are_cors_cross_site() {
let headers = profile_request_headers_without_compression(
StealthProfile::ChromeWindowsStable,
BrowserRequestKind::CrossSiteFetch,
);
let by_name = |name: &str| {
headers
.as_slice()
.iter()
.find(|header| header.name == name)
.map(|header| header.value)
};
assert_eq!(headers.len(), 6);
assert_eq!(by_name(ACCEPT_HEADER), Some("*/*"));
assert_eq!(by_name(SEC_FETCH_DEST_HEADER), Some("empty"));
assert_eq!(by_name(SEC_FETCH_MODE_HEADER), Some("cors"));
assert_eq!(by_name(SEC_FETCH_SITE_HEADER), Some("cross-site"));
assert_eq!(by_name(UPGRADE_INSECURE_REQUESTS_HEADER), None);
assert_eq!(by_name(SEC_FETCH_USER_HEADER), None);
}
#[test]
fn same_origin_mode_fetch_request_headers_preserve_explicit_fetch_mode() {
let headers = profile_request_headers_without_compression(
StealthProfile::ChromeWindowsStable,
BrowserRequestKind::SameOriginModeFetch,
);
let by_name = |name: &str| {
headers
.as_slice()
.iter()
.find(|header| header.name == name)
.map(|header| header.value)
};
assert_eq!(headers.len(), 6);
assert_eq!(by_name(ACCEPT_HEADER), Some("*/*"));
assert_eq!(by_name(SEC_FETCH_DEST_HEADER), Some("empty"));
assert_eq!(by_name(SEC_FETCH_MODE_HEADER), Some("same-origin"));
assert_eq!(by_name(SEC_FETCH_SITE_HEADER), Some("same-origin"));
assert_eq!(by_name(SEC_FETCH_USER_HEADER), None);
}
#[test]
fn image_request_headers_are_image_subresource_shaped() {
let headers = profile_request_headers_without_compression(
StealthProfile::ChromeWindowsStable,
BrowserRequestKind::ImageSubresource,
);
let by_name = |name: &str| {
headers
.as_slice()
.iter()
.find(|header| header.name == name)
.map(|header| header.value)
};
assert_eq!(headers.len(), 6);
assert_eq!(by_name(ACCEPT_HEADER), Some("*/*"));
assert_eq!(by_name(SEC_FETCH_DEST_HEADER), Some("image"));
assert_eq!(by_name(SEC_FETCH_MODE_HEADER), Some("no-cors"));
assert_eq!(by_name(SEC_FETCH_SITE_HEADER), Some("cross-site"));
assert_eq!(by_name(ACCEPT_ENCODING_HEADER), None);
assert_eq!(by_name(SEC_FETCH_USER_HEADER), None);
}
#[test]
fn audio_request_headers_are_media_subresource_shaped() {
let headers = profile_request_headers_without_compression(
StealthProfile::ChromeWindowsStable,
BrowserRequestKind::AudioSubresource,
);
let by_name = |name: &str| {
headers
.as_slice()
.iter()
.find(|header| header.name == name)
.map(|header| header.value)
};
assert_eq!(headers.len(), 6);
assert_eq!(by_name(ACCEPT_HEADER), Some("*/*"));
assert_eq!(by_name(SEC_FETCH_DEST_HEADER), Some("audio"));
assert_eq!(by_name(SEC_FETCH_MODE_HEADER), Some("no-cors"));
assert_eq!(by_name(SEC_FETCH_SITE_HEADER), Some("cross-site"));
assert_eq!(by_name(ACCEPT_ENCODING_HEADER), None);
assert_eq!(by_name(SEC_FETCH_USER_HEADER), None);
}
#[test]
fn compressed_request_headers_include_accept_encoding() {
let facts = profile_facts(StealthProfile::ChromeWindowsStable);
let headers = profile_request_headers(
StealthProfile::ChromeWindowsStable,
BrowserRequestKind::Navigation,
);
let by_name = |name: &str| {
headers
.as_slice()
.iter()
.find(|header| header.name == name)
.map(|header| header.value)
};
assert_eq!(headers.len(), 9);
assert_eq!(by_name(ACCEPT_ENCODING_HEADER), Some(facts.accept_encoding));
}
#[test]
fn canonical_navigation_header_names_use_browser_casing() {
assert_eq!(
canonical_navigation_header_name(USER_AGENT_HEADER),
"User-Agent"
);
assert_eq!(canonical_navigation_header_name(ACCEPT_HEADER), "Accept");
assert_eq!(
canonical_navigation_header_name(ACCEPT_LANGUAGE_HEADER),
"Accept-Language"
);
assert_eq!(
canonical_navigation_header_name(ACCEPT_ENCODING_HEADER),
"Accept-Encoding"
);
assert_eq!(
canonical_navigation_header_name(SEC_FETCH_MODE_HEADER),
"Sec-Fetch-Mode"
);
assert_eq!(
canonical_navigation_header_name("x-custom-header"),
"x-custom-header"
);
}
}