scrapling_fetch/
fingerprint.rs1use std::collections::HashMap;
19
20#[derive(Debug, Clone, Copy, PartialEq, Eq)]
26pub enum OsName {
27 Linux,
29 MacOs,
31 Windows,
33}
34
35pub fn detect_os() -> OsName {
38 if cfg!(target_os = "macos") {
39 OsName::MacOs
40 } else if cfg!(target_os = "windows") {
41 OsName::Windows
42 } else {
43 OsName::Linux
44 }
45}
46
47const CHROME_VERSION: u32 = 145;
48const FIREFOX_VERSION: u32 = 142;
49const EDGE_VERSION: u32 = 140;
50
51fn platform_string(os: OsName, include_rv: bool) -> &'static str {
52 match (os, include_rv) {
53 (OsName::Windows, false) => "Windows NT 10.0; Win64; x64",
54 (OsName::MacOs, false) => "Macintosh; Intel Mac OS X 10_15_7",
55 (OsName::Linux, false) => "X11; Linux x86_64",
56 (OsName::Windows, true) => "Windows NT 10.0; Win64; x64; rv:142.0",
57 (OsName::MacOs, true) => "Macintosh; Intel Mac OS X 10.15; rv:142.0",
58 (OsName::Linux, true) => "X11; Linux x86_64; rv:142.0",
59 }
60}
61
62fn sec_ch_platform(os: OsName) -> &'static str {
63 match os {
64 OsName::Windows => "\"Windows\"",
65 OsName::MacOs => "\"macOS\"",
66 OsName::Linux => "\"Linux\"",
67 }
68}
69
70#[derive(Debug, Clone, Copy, PartialEq, Eq)]
71enum BrowserKind {
72 Chrome,
73 Firefox,
74 Edge,
75}
76
77impl BrowserKind {
78 fn user_agent(self, os: OsName) -> String {
79 match self {
80 Self::Chrome => format!(
81 "Mozilla/5.0 ({}) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/{CHROME_VERSION}.0.0.0 Safari/537.36",
82 platform_string(os, false)
83 ),
84 Self::Firefox => format!(
85 "Mozilla/5.0 ({}) Gecko/20100101 Firefox/{FIREFOX_VERSION}.0",
86 platform_string(os, true)
87 ),
88 Self::Edge => format!(
89 "Mozilla/5.0 ({}) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/{CHROME_VERSION}.0.0.0 Safari/537.36 Edg/{EDGE_VERSION}.0.0.0",
90 platform_string(os, false)
91 ),
92 }
93 }
94
95 fn sec_ch_ua(self) -> String {
96 match self {
97 Self::Edge => format!(
98 "\"Microsoft Edge\";v=\"{EDGE_VERSION}\", \"Chromium\";v=\"{CHROME_VERSION}\", \"Not-A.Brand\";v=\"99\""
99 ),
100 Self::Chrome => format!(
101 "\"Google Chrome\";v=\"{CHROME_VERSION}\", \"Chromium\";v=\"{CHROME_VERSION}\", \"Not-A.Brand\";v=\"99\""
102 ),
103 Self::Firefox => String::new(),
104 }
105 }
106
107 fn random() -> Self {
108 use rand::Rng;
109 const CHOICES: [BrowserKind; 3] =
110 [BrowserKind::Chrome, BrowserKind::Firefox, BrowserKind::Edge];
111 CHOICES[rand::thread_rng().gen_range(0..CHOICES.len())]
112 }
113}
114
115pub fn chrome_user_agent(os: OsName) -> String {
119 BrowserKind::Chrome.user_agent(os)
120}
121
122pub fn generate_headers(browser_mode: bool) -> HashMap<String, String> {
130 let os = detect_os();
131 let browser = if browser_mode {
132 BrowserKind::Chrome
133 } else {
134 BrowserKind::random()
135 };
136
137 let mut headers = HashMap::from([
138 ("User-Agent".into(), browser.user_agent(os)),
139 ("Accept".into(), "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8".into()),
140 ("Accept-Language".into(), "en-US,en;q=0.9".into()),
141 ("Accept-Encoding".into(), "gzip, deflate, br".into()),
142 ("Upgrade-Insecure-Requests".into(), "1".into()),
143 ]);
144
145 if matches!(browser, BrowserKind::Chrome | BrowserKind::Edge) {
146 headers.insert("Sec-Ch-Ua-Platform".into(), sec_ch_platform(os).into());
147 headers.insert("Sec-Fetch-Site".into(), "none".into());
148 headers.insert("Sec-Fetch-Mode".into(), "navigate".into());
149 headers.insert("Sec-Fetch-User".into(), "?1".into());
150 headers.insert("Sec-Fetch-Dest".into(), "document".into());
151 headers.insert("Sec-Ch-Ua".into(), browser.sec_ch_ua());
152 headers.insert("Sec-Ch-Ua-Mobile".into(), "?0".into());
153 }
154
155 headers
156}
157
158pub fn default_user_agent() -> String {
162 chrome_user_agent(detect_os())
163}