Skip to main content

specter/
headers.rs

1//! Browser header presets for HTTP requests.
2//!
3//! Supported Chrome versions: 142, 143, 144, 145, 146
4//! Supported Firefox versions: 133
5
6use crate::cookie::CookieJar;
7use http::HeaderMap;
8
9/// Chrome 142 browser headers for page navigation.
10pub fn chrome_142_headers() -> Vec<(&'static str, &'static str)> {
11    vec![
12        ("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36"),
13        ("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8"),
14        ("Accept-Language", "en-US,en;q=0.9"),
15        ("Accept-Encoding", "gzip, deflate, br, zstd"),
16        ("Sec-Fetch-Dest", "document"),
17        ("Sec-Fetch-Mode", "navigate"),
18        ("Sec-Fetch-Site", "none"),
19        ("Sec-Fetch-User", "?1"),
20        ("Sec-Ch-Ua", r#""Chromium";v="142", "Google Chrome";v="142", "Not_A Brand";v="24""#),
21        ("Sec-Ch-Ua-Mobile", "?0"),
22        ("Sec-Ch-Ua-Platform", r#""macOS""#),
23        ("Sec-Ch-Ua-Arch", r#""arm64""#),
24        ("Sec-Ch-Ua-Bitness", r#""64""#),
25        ("Sec-Ch-Ua-Full-Version-List", r#""Chromium";v="142.0.6367.118", "Google Chrome";v="142.0.6367.118", "Not_A Brand";v="24.0.0.0""#),
26        ("Sec-Ch-Ua-Model", r#""""#),
27        ("Sec-Ch-Ua-Platform-Version", r#""15.5.0""#),
28        ("Sec-Ch-Ua-Wow64", "?0"),
29        ("Upgrade-Insecure-Requests", "1"),
30        ("Connection", "keep-alive"),
31    ]
32}
33
34/// Chrome 142 headers for AJAX/API requests.
35/// Extended Client Hints are typically only sent on navigation requests,
36/// not on AJAX/API requests unless explicitly requested by the server.
37pub fn chrome_142_ajax_headers() -> Vec<(&'static str, &'static str)> {
38    vec![
39        ("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36"),
40        ("Accept", "application/json, text/plain, */*"),
41        ("Accept-Language", "en-US,en;q=0.9"),
42        ("Accept-Encoding", "gzip, deflate, br, zstd"),
43        ("Content-Type", "application/json"),
44        ("Sec-Fetch-Dest", "empty"),
45        ("Sec-Fetch-Mode", "cors"),
46        ("Sec-Fetch-Site", "same-origin"),
47        ("Sec-Ch-Ua", r#""Chromium";v="142", "Google Chrome";v="142", "Not_A Brand";v="24""#),
48        ("Sec-Ch-Ua-Mobile", "?0"),
49        ("Sec-Ch-Ua-Platform", r#""macOS""#),
50        ("Connection", "keep-alive"),
51    ]
52}
53
54/// Chrome 142 headers for form submissions.
55pub fn chrome_142_form_headers() -> Vec<(&'static str, &'static str)> {
56    vec![
57        ("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36"),
58        ("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8"),
59        ("Accept-Language", "en-US,en;q=0.9"),
60        ("Accept-Encoding", "gzip, deflate, br, zstd"),
61        ("Content-Type", "application/x-www-form-urlencoded"),
62        ("Sec-Fetch-Dest", "document"),
63        ("Sec-Fetch-Mode", "navigate"),
64        ("Sec-Fetch-Site", "same-origin"),
65        ("Sec-Ch-Ua", r#""Chromium";v="142", "Google Chrome";v="142", "Not_A Brand";v="24""#),
66        ("Sec-Ch-Ua-Mobile", "?0"),
67        ("Sec-Ch-Ua-Platform", r#""macOS""#),
68        ("Sec-Ch-Ua-Arch", r#""arm64""#),
69        ("Sec-Ch-Ua-Bitness", r#""64""#),
70        ("Sec-Ch-Ua-Full-Version-List", r#""Chromium";v="142.0.6367.118", "Google Chrome";v="142.0.6367.118", "Not_A Brand";v="24.0.0.0""#),
71        ("Sec-Ch-Ua-Model", r#""""#),
72        ("Sec-Ch-Ua-Platform-Version", r#""15.5.0""#),
73        ("Sec-Ch-Ua-Wow64", "?0"),
74        ("Upgrade-Insecure-Requests", "1"),
75        ("Connection", "keep-alive"),
76    ]
77}
78
79/// Chrome 143 browser headers for page navigation.
80pub fn chrome_143_headers() -> Vec<(&'static str, &'static str)> {
81    vec![
82        ("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36"),
83        ("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8"),
84        ("Accept-Language", "en-US,en;q=0.9"),
85        ("Accept-Encoding", "gzip, deflate, br, zstd"),
86        ("Sec-Fetch-Dest", "document"),
87        ("Sec-Fetch-Mode", "navigate"),
88        ("Sec-Fetch-Site", "none"),
89        ("Sec-Fetch-User", "?1"),
90        ("Sec-Ch-Ua", r#""Google Chrome";v="143", "Chromium";v="143", "Not A(Brand";v="99""#),
91        ("Sec-Ch-Ua-Mobile", "?0"),
92        ("Sec-Ch-Ua-Platform", r#""macOS""#),
93        ("Sec-Ch-Ua-Arch", r#""arm64""#),
94        ("Sec-Ch-Ua-Bitness", r#""64""#),
95        ("Sec-Ch-Ua-Full-Version-List", r#""Google Chrome";v="143.0.7499.40", "Chromium";v="143.0.7499.40", "Not A(Brand";v="99.0.0.0""#),
96        ("Sec-Ch-Ua-Model", r#""""#),
97        ("Sec-Ch-Ua-Platform-Version", r#""15.5.0""#),
98        ("Sec-Ch-Ua-Wow64", "?0"),
99        ("Upgrade-Insecure-Requests", "1"),
100        ("Connection", "keep-alive"),
101    ]
102}
103
104/// Chrome 143 headers for AJAX/API requests.
105pub fn chrome_143_ajax_headers() -> Vec<(&'static str, &'static str)> {
106    vec![
107        ("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36"),
108        ("Accept", "application/json, text/plain, */*"),
109        ("Accept-Language", "en-US,en;q=0.9"),
110        ("Accept-Encoding", "gzip, deflate, br, zstd"),
111        ("Content-Type", "application/json"),
112        ("Sec-Fetch-Dest", "empty"),
113        ("Sec-Fetch-Mode", "cors"),
114        ("Sec-Fetch-Site", "same-origin"),
115        ("Sec-Ch-Ua", r#""Google Chrome";v="143", "Chromium";v="143", "Not A(Brand";v="99""#),
116        ("Sec-Ch-Ua-Mobile", "?0"),
117        ("Sec-Ch-Ua-Platform", r#""macOS""#),
118        ("Connection", "keep-alive"),
119    ]
120}
121
122/// Chrome 143 headers for form submissions.
123pub fn chrome_143_form_headers() -> Vec<(&'static str, &'static str)> {
124    vec![
125        ("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36"),
126        ("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8"),
127        ("Accept-Language", "en-US,en;q=0.9"),
128        ("Accept-Encoding", "gzip, deflate, br, zstd"),
129        ("Content-Type", "application/x-www-form-urlencoded"),
130        ("Sec-Fetch-Dest", "document"),
131        ("Sec-Fetch-Mode", "navigate"),
132        ("Sec-Fetch-Site", "same-origin"),
133        ("Sec-Ch-Ua", r#""Google Chrome";v="143", "Chromium";v="143", "Not A(Brand";v="99""#),
134        ("Sec-Ch-Ua-Mobile", "?0"),
135        ("Sec-Ch-Ua-Platform", r#""macOS""#),
136        ("Sec-Ch-Ua-Arch", r#""arm64""#),
137        ("Sec-Ch-Ua-Bitness", r#""64""#),
138        ("Sec-Ch-Ua-Full-Version-List", r#""Google Chrome";v="143.0.7499.40", "Chromium";v="143.0.7499.40", "Not A(Brand";v="99.0.0.0""#),
139        ("Sec-Ch-Ua-Model", r#""""#),
140        ("Sec-Ch-Ua-Platform-Version", r#""15.5.0""#),
141        ("Sec-Ch-Ua-Wow64", "?0"),
142        ("Upgrade-Insecure-Requests", "1"),
143        ("Connection", "keep-alive"),
144    ]
145}
146
147/// Chrome 144 browser headers for page navigation.
148pub fn chrome_144_headers() -> Vec<(&'static str, &'static str)> {
149    vec![
150        ("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/144.0.0.0 Safari/537.36"),
151        ("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8"),
152        ("Accept-Language", "en-US,en;q=0.9"),
153        ("Accept-Encoding", "gzip, deflate, br, zstd"),
154        ("Sec-Fetch-Dest", "document"),
155        ("Sec-Fetch-Mode", "navigate"),
156        ("Sec-Fetch-Site", "none"),
157        ("Sec-Fetch-User", "?1"),
158        ("Sec-Ch-Ua", r#""Not(A:Brand";v="8", "Chromium";v="144", "Google Chrome";v="144""#),
159        ("Sec-Ch-Ua-Mobile", "?0"),
160        ("Sec-Ch-Ua-Platform", r#""macOS""#),
161        ("Sec-Ch-Ua-Arch", r#""arm64""#),
162        ("Sec-Ch-Ua-Bitness", r#""64""#),
163        ("Sec-Ch-Ua-Full-Version-List", r#""Not(A:Brand";v="8.0.0.0", "Chromium";v="144.0.7559.133", "Google Chrome";v="144.0.7559.133""#),
164        ("Sec-Ch-Ua-Model", r#""""#),
165        ("Sec-Ch-Ua-Platform-Version", r#""15.5.0""#),
166        ("Sec-Ch-Ua-Wow64", "?0"),
167        ("Upgrade-Insecure-Requests", "1"),
168        ("Connection", "keep-alive"),
169    ]
170}
171
172/// Chrome 144 headers for AJAX/API requests.
173pub fn chrome_144_ajax_headers() -> Vec<(&'static str, &'static str)> {
174    vec![
175        ("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/144.0.0.0 Safari/537.36"),
176        ("Accept", "application/json, text/plain, */*"),
177        ("Accept-Language", "en-US,en;q=0.9"),
178        ("Accept-Encoding", "gzip, deflate, br, zstd"),
179        ("Content-Type", "application/json"),
180        ("Sec-Fetch-Dest", "empty"),
181        ("Sec-Fetch-Mode", "cors"),
182        ("Sec-Fetch-Site", "same-origin"),
183        ("Sec-Ch-Ua", r#""Not(A:Brand";v="8", "Chromium";v="144", "Google Chrome";v="144""#),
184        ("Sec-Ch-Ua-Mobile", "?0"),
185        ("Sec-Ch-Ua-Platform", r#""macOS""#),
186        ("Connection", "keep-alive"),
187    ]
188}
189
190/// Chrome 144 headers for form submissions.
191pub fn chrome_144_form_headers() -> Vec<(&'static str, &'static str)> {
192    vec![
193        ("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/144.0.0.0 Safari/537.36"),
194        ("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8"),
195        ("Accept-Language", "en-US,en;q=0.9"),
196        ("Accept-Encoding", "gzip, deflate, br, zstd"),
197        ("Content-Type", "application/x-www-form-urlencoded"),
198        ("Sec-Fetch-Dest", "document"),
199        ("Sec-Fetch-Mode", "navigate"),
200        ("Sec-Fetch-Site", "same-origin"),
201        ("Sec-Ch-Ua", r#""Not(A:Brand";v="8", "Chromium";v="144", "Google Chrome";v="144""#),
202        ("Sec-Ch-Ua-Mobile", "?0"),
203        ("Sec-Ch-Ua-Platform", r#""macOS""#),
204        ("Sec-Ch-Ua-Arch", r#""arm64""#),
205        ("Sec-Ch-Ua-Bitness", r#""64""#),
206        ("Sec-Ch-Ua-Full-Version-List", r#""Not(A:Brand";v="8.0.0.0", "Chromium";v="144.0.7559.133", "Google Chrome";v="144.0.7559.133""#),
207        ("Sec-Ch-Ua-Model", r#""""#),
208        ("Sec-Ch-Ua-Platform-Version", r#""15.5.0""#),
209        ("Sec-Ch-Ua-Wow64", "?0"),
210        ("Upgrade-Insecure-Requests", "1"),
211        ("Connection", "keep-alive"),
212    ]
213}
214
215/// Chrome 145 browser headers for page navigation.
216pub fn chrome_145_headers() -> Vec<(&'static str, &'static str)> {
217    vec![
218        ("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/145.0.0.0 Safari/537.36"),
219        ("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8"),
220        ("Accept-Language", "en-US,en;q=0.9"),
221        ("Accept-Encoding", "gzip, deflate, br, zstd"),
222        ("Sec-Fetch-Dest", "document"),
223        ("Sec-Fetch-Mode", "navigate"),
224        ("Sec-Fetch-Site", "none"),
225        ("Sec-Fetch-User", "?1"),
226        ("Sec-Ch-Ua", r#""Not:A-Brand";v="24", "Google Chrome";v="145", "Chromium";v="145""#),
227        ("Sec-Ch-Ua-Mobile", "?0"),
228        ("Sec-Ch-Ua-Platform", r#""macOS""#),
229        ("Sec-Ch-Ua-Arch", r#""arm64""#),
230        ("Sec-Ch-Ua-Bitness", r#""64""#),
231        ("Sec-Ch-Ua-Full-Version-List", r#""Not:A-Brand";v="24.0.0.0", "Google Chrome";v="145.0.7632.117", "Chromium";v="145.0.7632.117""#),
232        ("Sec-Ch-Ua-Model", r#""""#),
233        ("Sec-Ch-Ua-Platform-Version", r#""15.5.0""#),
234        ("Sec-Ch-Ua-Wow64", "?0"),
235        ("Upgrade-Insecure-Requests", "1"),
236        ("Connection", "keep-alive"),
237    ]
238}
239
240/// Chrome 145 headers for AJAX/API requests.
241pub fn chrome_145_ajax_headers() -> Vec<(&'static str, &'static str)> {
242    vec![
243        ("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/145.0.0.0 Safari/537.36"),
244        ("Accept", "application/json, text/plain, */*"),
245        ("Accept-Language", "en-US,en;q=0.9"),
246        ("Accept-Encoding", "gzip, deflate, br, zstd"),
247        ("Content-Type", "application/json"),
248        ("Sec-Fetch-Dest", "empty"),
249        ("Sec-Fetch-Mode", "cors"),
250        ("Sec-Fetch-Site", "same-origin"),
251        ("Sec-Ch-Ua", r#""Not:A-Brand";v="24", "Google Chrome";v="145", "Chromium";v="145""#),
252        ("Sec-Ch-Ua-Mobile", "?0"),
253        ("Sec-Ch-Ua-Platform", r#""macOS""#),
254        ("Connection", "keep-alive"),
255    ]
256}
257
258/// Chrome 145 headers for form submissions.
259pub fn chrome_145_form_headers() -> Vec<(&'static str, &'static str)> {
260    vec![
261        ("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/145.0.0.0 Safari/537.36"),
262        ("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8"),
263        ("Accept-Language", "en-US,en;q=0.9"),
264        ("Accept-Encoding", "gzip, deflate, br, zstd"),
265        ("Content-Type", "application/x-www-form-urlencoded"),
266        ("Sec-Fetch-Dest", "document"),
267        ("Sec-Fetch-Mode", "navigate"),
268        ("Sec-Fetch-Site", "same-origin"),
269        ("Sec-Ch-Ua", r#""Not:A-Brand";v="24", "Google Chrome";v="145", "Chromium";v="145""#),
270        ("Sec-Ch-Ua-Mobile", "?0"),
271        ("Sec-Ch-Ua-Platform", r#""macOS""#),
272        ("Sec-Ch-Ua-Arch", r#""arm64""#),
273        ("Sec-Ch-Ua-Bitness", r#""64""#),
274        ("Sec-Ch-Ua-Full-Version-List", r#""Not:A-Brand";v="24.0.0.0", "Google Chrome";v="145.0.7632.117", "Chromium";v="145.0.7632.117""#),
275        ("Sec-Ch-Ua-Model", r#""""#),
276        ("Sec-Ch-Ua-Platform-Version", r#""15.5.0""#),
277        ("Sec-Ch-Ua-Wow64", "?0"),
278        ("Upgrade-Insecure-Requests", "1"),
279        ("Connection", "keep-alive"),
280    ]
281}
282
283/// Chrome 146 browser headers for page navigation.
284pub fn chrome_146_headers() -> Vec<(&'static str, &'static str)> {
285    vec![
286        ("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"),
287        ("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8"),
288        ("Accept-Language", "en-US,en;q=0.9"),
289        ("Accept-Encoding", "gzip, deflate, br, zstd"),
290        ("Sec-Fetch-Dest", "document"),
291        ("Sec-Fetch-Mode", "navigate"),
292        ("Sec-Fetch-Site", "none"),
293        ("Sec-Fetch-User", "?1"),
294        ("Sec-Ch-Ua", r#""Chromium";v="146", "Not-A.Brand";v="99", "Google Chrome";v="146""#),
295        ("Sec-Ch-Ua-Mobile", "?0"),
296        ("Sec-Ch-Ua-Platform", r#""macOS""#),
297        ("Sec-Ch-Ua-Arch", r#""arm64""#),
298        ("Sec-Ch-Ua-Bitness", r#""64""#),
299        ("Sec-Ch-Ua-Full-Version-List", r#""Chromium";v="146.0.7680.165", "Not-A.Brand";v="99.0.0.0", "Google Chrome";v="146.0.7680.165""#),
300        ("Sec-Ch-Ua-Model", r#""""#),
301        ("Sec-Ch-Ua-Platform-Version", r#""15.5.0""#),
302        ("Sec-Ch-Ua-Wow64", "?0"),
303        ("Upgrade-Insecure-Requests", "1"),
304        ("Connection", "keep-alive"),
305    ]
306}
307
308/// Chrome 146 headers for AJAX/API requests.
309pub fn chrome_146_ajax_headers() -> Vec<(&'static str, &'static str)> {
310    vec![
311        ("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"),
312        ("Accept", "application/json, text/plain, */*"),
313        ("Accept-Language", "en-US,en;q=0.9"),
314        ("Accept-Encoding", "gzip, deflate, br, zstd"),
315        ("Content-Type", "application/json"),
316        ("Sec-Fetch-Dest", "empty"),
317        ("Sec-Fetch-Mode", "cors"),
318        ("Sec-Fetch-Site", "same-origin"),
319        ("Sec-Ch-Ua", r#""Chromium";v="146", "Not-A.Brand";v="99", "Google Chrome";v="146""#),
320        ("Sec-Ch-Ua-Mobile", "?0"),
321        ("Sec-Ch-Ua-Platform", r#""macOS""#),
322        ("Connection", "keep-alive"),
323    ]
324}
325
326/// Chrome 146 headers for form submissions.
327pub fn chrome_146_form_headers() -> Vec<(&'static str, &'static str)> {
328    vec![
329        ("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"),
330        ("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8"),
331        ("Accept-Language", "en-US,en;q=0.9"),
332        ("Accept-Encoding", "gzip, deflate, br, zstd"),
333        ("Content-Type", "application/x-www-form-urlencoded"),
334        ("Sec-Fetch-Dest", "document"),
335        ("Sec-Fetch-Mode", "navigate"),
336        ("Sec-Fetch-Site", "same-origin"),
337        ("Sec-Ch-Ua", r#""Chromium";v="146", "Not-A.Brand";v="99", "Google Chrome";v="146""#),
338        ("Sec-Ch-Ua-Mobile", "?0"),
339        ("Sec-Ch-Ua-Platform", r#""macOS""#),
340        ("Sec-Ch-Ua-Arch", r#""arm64""#),
341        ("Sec-Ch-Ua-Bitness", r#""64""#),
342        ("Sec-Ch-Ua-Full-Version-List", r#""Chromium";v="146.0.7680.165", "Not-A.Brand";v="99.0.0.0", "Google Chrome";v="146.0.7680.165""#),
343        ("Sec-Ch-Ua-Model", r#""""#),
344        ("Sec-Ch-Ua-Platform-Version", r#""15.5.0""#),
345        ("Sec-Ch-Ua-Wow64", "?0"),
346        ("Upgrade-Insecure-Requests", "1"),
347        ("Connection", "keep-alive"),
348    ]
349}
350
351/// Add Cookie header from jar.
352pub fn with_cookies(base: impl Into<Headers>, url: &str, jar: &CookieJar) -> Headers {
353    let mut headers: Headers = base.into();
354    headers.remove("cookie");
355    if let Some(cookie_header) = jar.build_cookie_header(url) {
356        headers.append("Cookie", cookie_header);
357    }
358    headers
359}
360
361/// Add Origin header.
362pub fn with_origin(mut headers: Headers, origin: &str) -> Headers {
363    headers.remove("origin");
364    headers.append("Origin", origin.to_string());
365    headers
366}
367
368/// Add Referer header.
369pub fn with_referer(mut headers: Headers, referer: &str) -> Headers {
370    headers.remove("referer");
371    headers.append("Referer", referer.to_string());
372    headers
373}
374
375/// Convert owned headers to references.
376pub fn headers_as_refs(headers: &Headers) -> Vec<(&str, &str)> {
377    headers.as_refs()
378}
379
380/// Convert static headers to owned.
381pub fn headers_to_owned(headers: Vec<(&'static str, &'static str)>) -> Vec<(String, String)> {
382    headers
383        .into_iter()
384        .map(|(k, v)| (k.to_string(), v.to_string()))
385        .collect()
386}
387
388/// Ordered headers for requests and responses.
389///
390/// This preserves insertion order for fingerprinting while providing
391/// convenient lookup and mutation helpers.
392#[derive(Debug, Clone, Default)]
393pub struct Headers {
394    headers: Vec<(String, String)>,
395}
396
397impl Headers {
398    pub fn new() -> Self {
399        Self::default()
400    }
401
402    pub fn from_vec(headers: Vec<(String, String)>) -> Self {
403        Self { headers }
404    }
405
406    pub fn from_static(headers: Vec<(&'static str, &'static str)>) -> Self {
407        Self {
408            headers: headers
409                .into_iter()
410                .map(|(k, v)| (k.to_string(), v.to_string()))
411                .collect(),
412        }
413    }
414
415    pub fn len(&self) -> usize {
416        self.headers.len()
417    }
418
419    pub fn is_empty(&self) -> bool {
420        self.headers.is_empty()
421    }
422
423    pub fn insert(&mut self, name: impl Into<String>, value: impl Into<String>) {
424        let name = name.into();
425        let name_lower = name.to_ascii_lowercase();
426        self.headers
427            .retain(|(k, _)| k.to_ascii_lowercase() != name_lower);
428        self.headers.push((name, value.into()));
429    }
430
431    pub fn append(&mut self, name: impl Into<String>, value: impl Into<String>) {
432        self.headers.push((name.into(), value.into()));
433    }
434
435    pub fn remove(&mut self, name: &str) -> Option<Vec<String>> {
436        let name_lower = name.to_ascii_lowercase();
437        let mut removed = Vec::new();
438        self.headers.retain(|(k, v)| {
439            if k.to_ascii_lowercase() == name_lower {
440                removed.push(v.clone());
441                false
442            } else {
443                true
444            }
445        });
446        if removed.is_empty() {
447            None
448        } else {
449            Some(removed)
450        }
451    }
452
453    pub fn get(&self, name: &str) -> Option<&str> {
454        let name_lower = name.to_ascii_lowercase();
455        self.headers.iter().find_map(|(k, v)| {
456            if k.to_ascii_lowercase() == name_lower {
457                Some(v.as_str())
458            } else {
459                None
460            }
461        })
462    }
463
464    pub fn get_all(&self, name: &str) -> Vec<&str> {
465        let name_lower = name.to_ascii_lowercase();
466        self.headers
467            .iter()
468            .filter_map(|(k, v)| {
469                if k.to_ascii_lowercase() == name_lower {
470                    Some(v.as_str())
471                } else {
472                    None
473                }
474            })
475            .collect()
476    }
477
478    pub fn contains(&self, name: &str) -> bool {
479        self.get(name).is_some()
480    }
481
482    pub fn iter(&self) -> impl Iterator<Item = (&str, &str)> {
483        self.headers.iter().map(|(k, v)| (k.as_str(), v.as_str()))
484    }
485
486    pub fn iter_ordered(&self) -> impl Iterator<Item = (&str, &str)> {
487        self.iter()
488    }
489
490    pub fn extend(&mut self, other: Headers) {
491        self.headers.extend(other.headers);
492    }
493
494    pub fn as_slice(&self) -> &[(String, String)] {
495        &self.headers
496    }
497
498    pub fn as_refs(&self) -> Vec<(&str, &str)> {
499        self.headers
500            .iter()
501            .map(|(k, v)| (k.as_str(), v.as_str()))
502            .collect()
503    }
504
505    pub fn to_vec(&self) -> Vec<(String, String)> {
506        self.headers.clone()
507    }
508}
509
510impl From<Vec<(String, String)>> for Headers {
511    fn from(value: Vec<(String, String)>) -> Self {
512        Headers::from_vec(value)
513    }
514}
515
516impl From<Vec<(&'static str, &'static str)>> for Headers {
517    fn from(value: Vec<(&'static str, &'static str)>) -> Self {
518        Headers::from_static(value)
519    }
520}
521
522impl From<HeaderMap> for Headers {
523    fn from(map: HeaderMap) -> Self {
524        let mut headers = Vec::new();
525        for (name, value) in map.iter() {
526            let value = value
527                .to_str()
528                .map(|s| s.to_string())
529                .unwrap_or_else(|_| String::from_utf8_lossy(value.as_bytes()).into_owned());
530            headers.push((name.as_str().to_string(), value));
531        }
532        Headers { headers }
533    }
534}
535
536/// Ordered headers with JA4H fingerprint calculation.
537///
538/// JA4H (JA4 for HTTP) fingerprints HTTP clients based on:
539/// - Header order
540/// - Header names (normalized to lowercase)
541/// - Header values (normalized)
542///
543/// This type preserves exact header order for fingerprint accuracy.
544#[derive(Debug, Clone)]
545pub struct OrderedHeaders {
546    headers: Vec<(String, String)>,
547}
548
549impl OrderedHeaders {
550    /// Create new ordered headers.
551    pub fn new(headers: Vec<(String, String)>) -> Self {
552        Self { headers }
553    }
554
555    /// Create Chrome navigation headers with exact order.
556    /// Uses Chrome 146 (latest) by default.
557    pub fn chrome_navigation() -> Self {
558        Self::new(headers_to_owned(chrome_146_headers()))
559    }
560
561    /// Create Firefox navigation headers with exact order.
562    pub fn firefox_navigation() -> Self {
563        Self::new(headers_to_owned(firefox_133_headers()))
564    }
565
566    /// Get headers as vector.
567    pub fn headers(&self) -> &[(String, String)] {
568        &self.headers
569    }
570
571    /// Calculate JA4H fingerprint string.
572    ///
573    /// JA4H format: header_names|header_order_hash
574    /// - header_names: comma-separated lowercase header names
575    /// - header_order_hash: hash of header order
576    pub fn ja4h_fingerprint(&self) -> String {
577        use sha2::{Digest, Sha256};
578
579        // Extract header names (lowercase) in order
580        let header_names: Vec<String> = self
581            .headers
582            .iter()
583            .map(|(name, _)| name.to_lowercase())
584            .collect();
585
586        // Create header names string
587        let names_str = header_names.join(",");
588
589        // Calculate hash of header order (using names for simplicity)
590        let mut hasher = Sha256::new();
591        hasher.update(names_str.as_bytes());
592        let hash = hasher.finalize();
593
594        // Use first 12 hex characters (24 bits) for fingerprint
595        let hash_str: String = hash[..3].iter().map(|b| format!("{:02x}", b)).collect();
596
597        format!("{}|{}", names_str, hash_str)
598    }
599
600    /// Add a header (preserves order).
601    pub fn add(mut self, name: impl Into<String>, value: impl Into<String>) -> Self {
602        self.headers.push((name.into(), value.into()));
603        self
604    }
605
606    /// Convert to vector of owned headers.
607    pub fn into_vec(self) -> Vec<(String, String)> {
608        self.headers
609    }
610}
611
612impl From<Vec<(String, String)>> for OrderedHeaders {
613    fn from(headers: Vec<(String, String)>) -> Self {
614        Self::new(headers)
615    }
616}
617
618impl From<OrderedHeaders> for Vec<(String, String)> {
619    fn from(oh: OrderedHeaders) -> Self {
620        oh.headers
621    }
622}
623
624/// Firefox 133 browser headers for page navigation.
625/// Firefox does NOT send Sec-Ch-Ua headers (Client Hints).
626pub fn firefox_133_headers() -> Vec<(&'static str, &'static str)> {
627    vec![
628        (
629            "User-Agent",
630            "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:133.0) Gecko/20100101 Firefox/133.0",
631        ),
632        (
633            "Accept",
634            "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8",
635        ),
636        ("Accept-Language", "en-US,en;q=0.5"),
637        ("Accept-Encoding", "gzip, deflate, br, zstd"),
638        ("Sec-Fetch-Dest", "document"),
639        ("Sec-Fetch-Mode", "navigate"),
640        ("Sec-Fetch-Site", "none"),
641        ("Sec-Fetch-User", "?1"),
642        ("Upgrade-Insecure-Requests", "1"),
643        ("Connection", "keep-alive"),
644    ]
645}
646
647/// Firefox 133 headers for AJAX/API requests.
648pub fn firefox_133_ajax_headers() -> Vec<(&'static str, &'static str)> {
649    vec![
650        (
651            "User-Agent",
652            "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:133.0) Gecko/20100101 Firefox/133.0",
653        ),
654        ("Accept", "application/json, text/plain, */*"),
655        ("Accept-Language", "en-US,en;q=0.5"),
656        ("Accept-Encoding", "gzip, deflate, br, zstd"),
657        ("Content-Type", "application/json"),
658        ("Sec-Fetch-Dest", "empty"),
659        ("Sec-Fetch-Mode", "cors"),
660        ("Sec-Fetch-Site", "same-origin"),
661        ("Connection", "keep-alive"),
662    ]
663}
664
665/// Firefox 133 headers for form submissions.
666pub fn firefox_133_form_headers() -> Vec<(&'static str, &'static str)> {
667    vec![
668        (
669            "User-Agent",
670            "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:133.0) Gecko/20100101 Firefox/133.0",
671        ),
672        (
673            "Accept",
674            "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8",
675        ),
676        ("Accept-Language", "en-US,en;q=0.5"),
677        ("Accept-Encoding", "gzip, deflate, br, zstd"),
678        ("Content-Type", "application/x-www-form-urlencoded"),
679        ("Sec-Fetch-Dest", "document"),
680        ("Sec-Fetch-Mode", "navigate"),
681        ("Sec-Fetch-Site", "same-origin"),
682        ("Upgrade-Insecure-Requests", "1"),
683        ("Connection", "keep-alive"),
684    ]
685}