Skip to main content

specter/
headers.rs

1//! Browser header presets for HTTP requests.
2//!
3//! Supported Chrome versions: 142, 143, 144, 145, 146, 147, 148
4//! Supported Firefox versions: 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, ESR 115, ESR 128, ESR 140
5
6use crate::cookie::CookieJar;
7use bytes::{Bytes, BytesMut};
8use http::HeaderMap;
9use std::sync::{Arc, OnceLock};
10
11/// Chrome 142 browser headers for page navigation.
12pub fn chrome_142_headers() -> Vec<(&'static str, &'static str)> {
13    vec![
14        (
15            "User-Agent",
16            "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",
17        ),
18        (
19            "Accept",
20            "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8",
21        ),
22        ("Accept-Language", "en-US,en;q=0.9"),
23        ("Accept-Encoding", "gzip, deflate, br, zstd"),
24        ("Sec-Fetch-Dest", "document"),
25        ("Sec-Fetch-Mode", "navigate"),
26        ("Sec-Fetch-Site", "none"),
27        ("Sec-Fetch-User", "?1"),
28        (
29            "Sec-Ch-Ua",
30            r#""Chromium";v="142", "Google Chrome";v="142", "Not_A Brand";v="99""#,
31        ),
32        ("Sec-Ch-Ua-Mobile", "?0"),
33        ("Sec-Ch-Ua-Platform", r#""macOS""#),
34        ("Sec-Ch-Ua-Arch", r#""arm64""#),
35        ("Sec-Ch-Ua-Bitness", r#""64""#),
36        (
37            "Sec-Ch-Ua-Full-Version-List",
38            r#""Chromium";v="142.0.7444.176", "Google Chrome";v="142.0.7444.176", "Not_A Brand";v="99.0.0.0""#,
39        ),
40        ("Sec-Ch-Ua-Model", r#""""#),
41        ("Sec-Ch-Ua-Platform-Version", r#""15.5.0""#),
42        ("Sec-Ch-Ua-Wow64", "?0"),
43        ("Upgrade-Insecure-Requests", "1"),
44        ("Connection", "keep-alive"),
45    ]
46}
47
48/// Chrome 142 headers for AJAX/API requests.
49/// Extended Client Hints are typically only sent on navigation requests,
50/// not on AJAX/API requests unless explicitly requested by the server.
51pub fn chrome_142_ajax_headers() -> Vec<(&'static str, &'static str)> {
52    vec![
53        (
54            "User-Agent",
55            "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",
56        ),
57        ("Accept", "application/json, text/plain, */*"),
58        ("Accept-Language", "en-US,en;q=0.9"),
59        ("Accept-Encoding", "gzip, deflate, br, zstd"),
60        ("Content-Type", "application/json"),
61        ("Sec-Fetch-Dest", "empty"),
62        ("Sec-Fetch-Mode", "cors"),
63        ("Sec-Fetch-Site", "same-origin"),
64        (
65            "Sec-Ch-Ua",
66            r#""Chromium";v="142", "Google Chrome";v="142", "Not_A Brand";v="99""#,
67        ),
68        ("Sec-Ch-Ua-Mobile", "?0"),
69        ("Sec-Ch-Ua-Platform", r#""macOS""#),
70        ("Connection", "keep-alive"),
71    ]
72}
73
74/// Chrome 142 headers for form submissions.
75pub fn chrome_142_form_headers() -> Vec<(&'static str, &'static str)> {
76    vec![
77        (
78            "User-Agent",
79            "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",
80        ),
81        (
82            "Accept",
83            "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8",
84        ),
85        ("Accept-Language", "en-US,en;q=0.9"),
86        ("Accept-Encoding", "gzip, deflate, br, zstd"),
87        ("Content-Type", "application/x-www-form-urlencoded"),
88        ("Sec-Fetch-Dest", "document"),
89        ("Sec-Fetch-Mode", "navigate"),
90        ("Sec-Fetch-Site", "same-origin"),
91        (
92            "Sec-Ch-Ua",
93            r#""Chromium";v="142", "Google Chrome";v="142", "Not_A Brand";v="99""#,
94        ),
95        ("Sec-Ch-Ua-Mobile", "?0"),
96        ("Sec-Ch-Ua-Platform", r#""macOS""#),
97        ("Sec-Ch-Ua-Arch", r#""arm64""#),
98        ("Sec-Ch-Ua-Bitness", r#""64""#),
99        (
100            "Sec-Ch-Ua-Full-Version-List",
101            r#""Chromium";v="142.0.7444.176", "Google Chrome";v="142.0.7444.176", "Not_A Brand";v="99.0.0.0""#,
102        ),
103        ("Sec-Ch-Ua-Model", r#""""#),
104        ("Sec-Ch-Ua-Platform-Version", r#""15.5.0""#),
105        ("Sec-Ch-Ua-Wow64", "?0"),
106        ("Upgrade-Insecure-Requests", "1"),
107        ("Connection", "keep-alive"),
108    ]
109}
110
111/// Chrome 143 browser headers for page navigation.
112pub fn chrome_143_headers() -> Vec<(&'static str, &'static str)> {
113    vec![
114        (
115            "User-Agent",
116            "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",
117        ),
118        (
119            "Accept",
120            "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8",
121        ),
122        ("Accept-Language", "en-US,en;q=0.9"),
123        ("Accept-Encoding", "gzip, deflate, br, zstd"),
124        ("Sec-Fetch-Dest", "document"),
125        ("Sec-Fetch-Mode", "navigate"),
126        ("Sec-Fetch-Site", "none"),
127        ("Sec-Fetch-User", "?1"),
128        (
129            "Sec-Ch-Ua",
130            r#""Google Chrome";v="143", "Chromium";v="143", "Not A(Brand";v="24""#,
131        ),
132        ("Sec-Ch-Ua-Mobile", "?0"),
133        ("Sec-Ch-Ua-Platform", r#""macOS""#),
134        ("Sec-Ch-Ua-Arch", r#""arm64""#),
135        ("Sec-Ch-Ua-Bitness", r#""64""#),
136        (
137            "Sec-Ch-Ua-Full-Version-List",
138            r#""Google Chrome";v="143.0.7499.193", "Chromium";v="143.0.7499.193", "Not A(Brand";v="24.0.0.0""#,
139        ),
140        ("Sec-Ch-Ua-Model", r#""""#),
141        ("Sec-Ch-Ua-Platform-Version", r#""15.5.0""#),
142        ("Sec-Ch-Ua-Wow64", "?0"),
143        ("Upgrade-Insecure-Requests", "1"),
144        ("Connection", "keep-alive"),
145    ]
146}
147
148/// Chrome 143 headers for AJAX/API requests.
149pub fn chrome_143_ajax_headers() -> Vec<(&'static str, &'static str)> {
150    vec![
151        (
152            "User-Agent",
153            "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",
154        ),
155        ("Accept", "application/json, text/plain, */*"),
156        ("Accept-Language", "en-US,en;q=0.9"),
157        ("Accept-Encoding", "gzip, deflate, br, zstd"),
158        ("Content-Type", "application/json"),
159        ("Sec-Fetch-Dest", "empty"),
160        ("Sec-Fetch-Mode", "cors"),
161        ("Sec-Fetch-Site", "same-origin"),
162        (
163            "Sec-Ch-Ua",
164            r#""Google Chrome";v="143", "Chromium";v="143", "Not A(Brand";v="24""#,
165        ),
166        ("Sec-Ch-Ua-Mobile", "?0"),
167        ("Sec-Ch-Ua-Platform", r#""macOS""#),
168        ("Connection", "keep-alive"),
169    ]
170}
171
172/// Chrome 143 headers for form submissions.
173pub fn chrome_143_form_headers() -> Vec<(&'static str, &'static str)> {
174    vec![
175        (
176            "User-Agent",
177            "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",
178        ),
179        (
180            "Accept",
181            "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8",
182        ),
183        ("Accept-Language", "en-US,en;q=0.9"),
184        ("Accept-Encoding", "gzip, deflate, br, zstd"),
185        ("Content-Type", "application/x-www-form-urlencoded"),
186        ("Sec-Fetch-Dest", "document"),
187        ("Sec-Fetch-Mode", "navigate"),
188        ("Sec-Fetch-Site", "same-origin"),
189        (
190            "Sec-Ch-Ua",
191            r#""Google Chrome";v="143", "Chromium";v="143", "Not A(Brand";v="24""#,
192        ),
193        ("Sec-Ch-Ua-Mobile", "?0"),
194        ("Sec-Ch-Ua-Platform", r#""macOS""#),
195        ("Sec-Ch-Ua-Arch", r#""arm64""#),
196        ("Sec-Ch-Ua-Bitness", r#""64""#),
197        (
198            "Sec-Ch-Ua-Full-Version-List",
199            r#""Google Chrome";v="143.0.7499.193", "Chromium";v="143.0.7499.193", "Not A(Brand";v="24.0.0.0""#,
200        ),
201        ("Sec-Ch-Ua-Model", r#""""#),
202        ("Sec-Ch-Ua-Platform-Version", r#""15.5.0""#),
203        ("Sec-Ch-Ua-Wow64", "?0"),
204        ("Upgrade-Insecure-Requests", "1"),
205        ("Connection", "keep-alive"),
206    ]
207}
208
209/// Chrome 144 browser headers for page navigation.
210pub fn chrome_144_headers() -> Vec<(&'static str, &'static str)> {
211    vec![
212        (
213            "User-Agent",
214            "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",
215        ),
216        (
217            "Accept",
218            "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8",
219        ),
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        (
227            "Sec-Ch-Ua",
228            r#""Not(A:Brand";v="8", "Chromium";v="144", "Google Chrome";v="144""#,
229        ),
230        ("Sec-Ch-Ua-Mobile", "?0"),
231        ("Sec-Ch-Ua-Platform", r#""macOS""#),
232        ("Sec-Ch-Ua-Arch", r#""arm64""#),
233        ("Sec-Ch-Ua-Bitness", r#""64""#),
234        (
235            "Sec-Ch-Ua-Full-Version-List",
236            r#""Not(A:Brand";v="8.0.0.0", "Chromium";v="144.0.7559.133", "Google Chrome";v="144.0.7559.133""#,
237        ),
238        ("Sec-Ch-Ua-Model", r#""""#),
239        ("Sec-Ch-Ua-Platform-Version", r#""15.5.0""#),
240        ("Sec-Ch-Ua-Wow64", "?0"),
241        ("Upgrade-Insecure-Requests", "1"),
242        ("Connection", "keep-alive"),
243    ]
244}
245
246/// Chrome 144 headers for AJAX/API requests.
247pub fn chrome_144_ajax_headers() -> Vec<(&'static str, &'static str)> {
248    vec![
249        (
250            "User-Agent",
251            "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",
252        ),
253        ("Accept", "application/json, text/plain, */*"),
254        ("Accept-Language", "en-US,en;q=0.9"),
255        ("Accept-Encoding", "gzip, deflate, br, zstd"),
256        ("Content-Type", "application/json"),
257        ("Sec-Fetch-Dest", "empty"),
258        ("Sec-Fetch-Mode", "cors"),
259        ("Sec-Fetch-Site", "same-origin"),
260        (
261            "Sec-Ch-Ua",
262            r#""Not(A:Brand";v="8", "Chromium";v="144", "Google Chrome";v="144""#,
263        ),
264        ("Sec-Ch-Ua-Mobile", "?0"),
265        ("Sec-Ch-Ua-Platform", r#""macOS""#),
266        ("Connection", "keep-alive"),
267    ]
268}
269
270/// Chrome 144 headers for form submissions.
271pub fn chrome_144_form_headers() -> Vec<(&'static str, &'static str)> {
272    vec![
273        (
274            "User-Agent",
275            "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",
276        ),
277        (
278            "Accept",
279            "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8",
280        ),
281        ("Accept-Language", "en-US,en;q=0.9"),
282        ("Accept-Encoding", "gzip, deflate, br, zstd"),
283        ("Content-Type", "application/x-www-form-urlencoded"),
284        ("Sec-Fetch-Dest", "document"),
285        ("Sec-Fetch-Mode", "navigate"),
286        ("Sec-Fetch-Site", "same-origin"),
287        (
288            "Sec-Ch-Ua",
289            r#""Not(A:Brand";v="8", "Chromium";v="144", "Google Chrome";v="144""#,
290        ),
291        ("Sec-Ch-Ua-Mobile", "?0"),
292        ("Sec-Ch-Ua-Platform", r#""macOS""#),
293        ("Sec-Ch-Ua-Arch", r#""arm64""#),
294        ("Sec-Ch-Ua-Bitness", r#""64""#),
295        (
296            "Sec-Ch-Ua-Full-Version-List",
297            r#""Not(A:Brand";v="8.0.0.0", "Chromium";v="144.0.7559.133", "Google Chrome";v="144.0.7559.133""#,
298        ),
299        ("Sec-Ch-Ua-Model", r#""""#),
300        ("Sec-Ch-Ua-Platform-Version", r#""15.5.0""#),
301        ("Sec-Ch-Ua-Wow64", "?0"),
302        ("Upgrade-Insecure-Requests", "1"),
303        ("Connection", "keep-alive"),
304    ]
305}
306
307/// Chrome 145 browser headers for page navigation.
308pub fn chrome_145_headers() -> Vec<(&'static str, &'static str)> {
309    vec![
310        (
311            "User-Agent",
312            "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",
313        ),
314        (
315            "Accept",
316            "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8",
317        ),
318        ("Accept-Language", "en-US,en;q=0.9"),
319        ("Accept-Encoding", "gzip, deflate, br, zstd"),
320        ("Sec-Fetch-Dest", "document"),
321        ("Sec-Fetch-Mode", "navigate"),
322        ("Sec-Fetch-Site", "none"),
323        ("Sec-Fetch-User", "?1"),
324        (
325            "Sec-Ch-Ua",
326            r#""Not:A-Brand";v="99", "Google Chrome";v="145", "Chromium";v="145""#,
327        ),
328        ("Sec-Ch-Ua-Mobile", "?0"),
329        ("Sec-Ch-Ua-Platform", r#""macOS""#),
330        ("Sec-Ch-Ua-Arch", r#""arm64""#),
331        ("Sec-Ch-Ua-Bitness", r#""64""#),
332        (
333            "Sec-Ch-Ua-Full-Version-List",
334            r#""Not:A-Brand";v="99.0.0.0", "Google Chrome";v="145.0.7632.117", "Chromium";v="145.0.7632.117""#,
335        ),
336        ("Sec-Ch-Ua-Model", r#""""#),
337        ("Sec-Ch-Ua-Platform-Version", r#""15.5.0""#),
338        ("Sec-Ch-Ua-Wow64", "?0"),
339        ("Upgrade-Insecure-Requests", "1"),
340        ("Connection", "keep-alive"),
341    ]
342}
343
344/// Chrome 145 headers for AJAX/API requests.
345pub fn chrome_145_ajax_headers() -> Vec<(&'static str, &'static str)> {
346    vec![
347        (
348            "User-Agent",
349            "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",
350        ),
351        ("Accept", "application/json, text/plain, */*"),
352        ("Accept-Language", "en-US,en;q=0.9"),
353        ("Accept-Encoding", "gzip, deflate, br, zstd"),
354        ("Content-Type", "application/json"),
355        ("Sec-Fetch-Dest", "empty"),
356        ("Sec-Fetch-Mode", "cors"),
357        ("Sec-Fetch-Site", "same-origin"),
358        (
359            "Sec-Ch-Ua",
360            r#""Not:A-Brand";v="99", "Google Chrome";v="145", "Chromium";v="145""#,
361        ),
362        ("Sec-Ch-Ua-Mobile", "?0"),
363        ("Sec-Ch-Ua-Platform", r#""macOS""#),
364        ("Connection", "keep-alive"),
365    ]
366}
367
368/// Chrome 145 headers for form submissions.
369pub fn chrome_145_form_headers() -> Vec<(&'static str, &'static str)> {
370    vec![
371        (
372            "User-Agent",
373            "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",
374        ),
375        (
376            "Accept",
377            "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8",
378        ),
379        ("Accept-Language", "en-US,en;q=0.9"),
380        ("Accept-Encoding", "gzip, deflate, br, zstd"),
381        ("Content-Type", "application/x-www-form-urlencoded"),
382        ("Sec-Fetch-Dest", "document"),
383        ("Sec-Fetch-Mode", "navigate"),
384        ("Sec-Fetch-Site", "same-origin"),
385        (
386            "Sec-Ch-Ua",
387            r#""Not:A-Brand";v="99", "Google Chrome";v="145", "Chromium";v="145""#,
388        ),
389        ("Sec-Ch-Ua-Mobile", "?0"),
390        ("Sec-Ch-Ua-Platform", r#""macOS""#),
391        ("Sec-Ch-Ua-Arch", r#""arm64""#),
392        ("Sec-Ch-Ua-Bitness", r#""64""#),
393        (
394            "Sec-Ch-Ua-Full-Version-List",
395            r#""Not:A-Brand";v="99.0.0.0", "Google Chrome";v="145.0.7632.117", "Chromium";v="145.0.7632.117""#,
396        ),
397        ("Sec-Ch-Ua-Model", r#""""#),
398        ("Sec-Ch-Ua-Platform-Version", r#""15.5.0""#),
399        ("Sec-Ch-Ua-Wow64", "?0"),
400        ("Upgrade-Insecure-Requests", "1"),
401        ("Connection", "keep-alive"),
402    ]
403}
404
405/// Chrome 146 browser headers for page navigation.
406pub fn chrome_146_headers() -> Vec<(&'static str, &'static str)> {
407    vec![
408        (
409            "User-Agent",
410            "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",
411        ),
412        (
413            "Accept",
414            "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8",
415        ),
416        ("Accept-Language", "en-US,en;q=0.9"),
417        ("Accept-Encoding", "gzip, deflate, br, zstd"),
418        ("Sec-Fetch-Dest", "document"),
419        ("Sec-Fetch-Mode", "navigate"),
420        ("Sec-Fetch-Site", "none"),
421        ("Sec-Fetch-User", "?1"),
422        (
423            "Sec-Ch-Ua",
424            r#""Chromium";v="146", "Not-A.Brand";v="24", "Google Chrome";v="146""#,
425        ),
426        ("Sec-Ch-Ua-Mobile", "?0"),
427        ("Sec-Ch-Ua-Platform", r#""macOS""#),
428        ("Sec-Ch-Ua-Arch", r#""arm64""#),
429        ("Sec-Ch-Ua-Bitness", r#""64""#),
430        (
431            "Sec-Ch-Ua-Full-Version-List",
432            r#""Chromium";v="146.0.7680.165", "Not-A.Brand";v="24.0.0.0", "Google Chrome";v="146.0.7680.165""#,
433        ),
434        ("Sec-Ch-Ua-Model", r#""""#),
435        ("Sec-Ch-Ua-Platform-Version", r#""15.5.0""#),
436        ("Sec-Ch-Ua-Wow64", "?0"),
437        ("Upgrade-Insecure-Requests", "1"),
438        ("Connection", "keep-alive"),
439    ]
440}
441
442/// Chrome 146 headers for AJAX/API requests.
443pub fn chrome_146_ajax_headers() -> Vec<(&'static str, &'static str)> {
444    vec![
445        (
446            "User-Agent",
447            "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",
448        ),
449        ("Accept", "application/json, text/plain, */*"),
450        ("Accept-Language", "en-US,en;q=0.9"),
451        ("Accept-Encoding", "gzip, deflate, br, zstd"),
452        ("Content-Type", "application/json"),
453        ("Sec-Fetch-Dest", "empty"),
454        ("Sec-Fetch-Mode", "cors"),
455        ("Sec-Fetch-Site", "same-origin"),
456        (
457            "Sec-Ch-Ua",
458            r#""Chromium";v="146", "Not-A.Brand";v="24", "Google Chrome";v="146""#,
459        ),
460        ("Sec-Ch-Ua-Mobile", "?0"),
461        ("Sec-Ch-Ua-Platform", r#""macOS""#),
462        ("Connection", "keep-alive"),
463    ]
464}
465
466/// Chrome 146 headers for form submissions.
467pub fn chrome_146_form_headers() -> Vec<(&'static str, &'static str)> {
468    vec![
469        (
470            "User-Agent",
471            "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",
472        ),
473        (
474            "Accept",
475            "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8",
476        ),
477        ("Accept-Language", "en-US,en;q=0.9"),
478        ("Accept-Encoding", "gzip, deflate, br, zstd"),
479        ("Content-Type", "application/x-www-form-urlencoded"),
480        ("Sec-Fetch-Dest", "document"),
481        ("Sec-Fetch-Mode", "navigate"),
482        ("Sec-Fetch-Site", "same-origin"),
483        (
484            "Sec-Ch-Ua",
485            r#""Chromium";v="146", "Not-A.Brand";v="24", "Google Chrome";v="146""#,
486        ),
487        ("Sec-Ch-Ua-Mobile", "?0"),
488        ("Sec-Ch-Ua-Platform", r#""macOS""#),
489        ("Sec-Ch-Ua-Arch", r#""arm64""#),
490        ("Sec-Ch-Ua-Bitness", r#""64""#),
491        (
492            "Sec-Ch-Ua-Full-Version-List",
493            r#""Chromium";v="146.0.7680.165", "Not-A.Brand";v="24.0.0.0", "Google Chrome";v="146.0.7680.165""#,
494        ),
495        ("Sec-Ch-Ua-Model", r#""""#),
496        ("Sec-Ch-Ua-Platform-Version", r#""15.5.0""#),
497        ("Sec-Ch-Ua-Wow64", "?0"),
498        ("Upgrade-Insecure-Requests", "1"),
499        ("Connection", "keep-alive"),
500    ]
501}
502
503/// Chrome 147 browser headers for page navigation.
504pub fn chrome_147_headers() -> Vec<(&'static str, &'static str)> {
505    vec![
506        (
507            "User-Agent",
508            "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/147.0.0.0 Safari/537.36",
509        ),
510        (
511            "Accept",
512            "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8",
513        ),
514        ("Accept-Language", "en-US,en;q=0.9"),
515        ("Accept-Encoding", "gzip, deflate, br, zstd"),
516        ("Sec-Fetch-Dest", "document"),
517        ("Sec-Fetch-Mode", "navigate"),
518        ("Sec-Fetch-Site", "none"),
519        ("Sec-Fetch-User", "?1"),
520        (
521            "Sec-Ch-Ua",
522            r#""Google Chrome";v="147", "Not.A/Brand";v="8", "Chromium";v="147""#,
523        ),
524        ("Sec-Ch-Ua-Mobile", "?0"),
525        ("Sec-Ch-Ua-Platform", r#""macOS""#),
526        ("Sec-Ch-Ua-Arch", r#""arm64""#),
527        ("Sec-Ch-Ua-Bitness", r#""64""#),
528        (
529            "Sec-Ch-Ua-Full-Version-List",
530            r#""Google Chrome";v="147.0.7727.138", "Not.A/Brand";v="8.0.0.0", "Chromium";v="147.0.7727.138""#,
531        ),
532        ("Sec-Ch-Ua-Model", r#""""#),
533        ("Sec-Ch-Ua-Platform-Version", r#""15.5.0""#),
534        ("Sec-Ch-Ua-Wow64", "?0"),
535        ("Upgrade-Insecure-Requests", "1"),
536        ("Connection", "keep-alive"),
537    ]
538}
539
540/// Chrome 147 headers for AJAX/API requests.
541pub fn chrome_147_ajax_headers() -> Vec<(&'static str, &'static str)> {
542    vec![
543        (
544            "User-Agent",
545            "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/147.0.0.0 Safari/537.36",
546        ),
547        ("Accept", "application/json, text/plain, */*"),
548        ("Accept-Language", "en-US,en;q=0.9"),
549        ("Accept-Encoding", "gzip, deflate, br, zstd"),
550        ("Content-Type", "application/json"),
551        ("Sec-Fetch-Dest", "empty"),
552        ("Sec-Fetch-Mode", "cors"),
553        ("Sec-Fetch-Site", "same-origin"),
554        (
555            "Sec-Ch-Ua",
556            r#""Google Chrome";v="147", "Not.A/Brand";v="8", "Chromium";v="147""#,
557        ),
558        ("Sec-Ch-Ua-Mobile", "?0"),
559        ("Sec-Ch-Ua-Platform", r#""macOS""#),
560        ("Connection", "keep-alive"),
561    ]
562}
563
564/// Chrome 147 headers for form submissions.
565pub fn chrome_147_form_headers() -> Vec<(&'static str, &'static str)> {
566    vec![
567        (
568            "User-Agent",
569            "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/147.0.0.0 Safari/537.36",
570        ),
571        (
572            "Accept",
573            "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8",
574        ),
575        ("Accept-Language", "en-US,en;q=0.9"),
576        ("Accept-Encoding", "gzip, deflate, br, zstd"),
577        ("Content-Type", "application/x-www-form-urlencoded"),
578        ("Sec-Fetch-Dest", "document"),
579        ("Sec-Fetch-Mode", "navigate"),
580        ("Sec-Fetch-Site", "same-origin"),
581        (
582            "Sec-Ch-Ua",
583            r#""Google Chrome";v="147", "Not.A/Brand";v="8", "Chromium";v="147""#,
584        ),
585        ("Sec-Ch-Ua-Mobile", "?0"),
586        ("Sec-Ch-Ua-Platform", r#""macOS""#),
587        ("Sec-Ch-Ua-Arch", r#""arm64""#),
588        ("Sec-Ch-Ua-Bitness", r#""64""#),
589        (
590            "Sec-Ch-Ua-Full-Version-List",
591            r#""Google Chrome";v="147.0.7727.138", "Not.A/Brand";v="8.0.0.0", "Chromium";v="147.0.7727.138""#,
592        ),
593        ("Sec-Ch-Ua-Model", r#""""#),
594        ("Sec-Ch-Ua-Platform-Version", r#""15.5.0""#),
595        ("Sec-Ch-Ua-Wow64", "?0"),
596        ("Upgrade-Insecure-Requests", "1"),
597        ("Connection", "keep-alive"),
598    ]
599}
600
601/// Chrome 148 browser headers for page navigation.
602pub fn chrome_148_headers() -> Vec<(&'static str, &'static str)> {
603    vec![
604        (
605            "User-Agent",
606            "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36",
607        ),
608        (
609            "Accept",
610            "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8",
611        ),
612        ("Accept-Language", "en-US,en;q=0.9"),
613        ("Accept-Encoding", "gzip, deflate, br, zstd"),
614        ("Sec-Fetch-Dest", "document"),
615        ("Sec-Fetch-Mode", "navigate"),
616        ("Sec-Fetch-Site", "none"),
617        ("Sec-Fetch-User", "?1"),
618        (
619            "Sec-Ch-Ua",
620            r#""Chromium";v="148", "Google Chrome";v="148", "Not/A)Brand";v="99""#,
621        ),
622        ("Sec-Ch-Ua-Mobile", "?0"),
623        ("Sec-Ch-Ua-Platform", r#""macOS""#),
624        ("Sec-Ch-Ua-Arch", r#""arm64""#),
625        ("Sec-Ch-Ua-Bitness", r#""64""#),
626        (
627            "Sec-Ch-Ua-Full-Version-List",
628            r#""Chromium";v="148.0.7778.179", "Google Chrome";v="148.0.7778.179", "Not/A)Brand";v="99.0.0.0""#,
629        ),
630        ("Sec-Ch-Ua-Model", r#""""#),
631        ("Sec-Ch-Ua-Platform-Version", r#""15.5.0""#),
632        ("Sec-Ch-Ua-Wow64", "?0"),
633        ("Upgrade-Insecure-Requests", "1"),
634        ("Connection", "keep-alive"),
635    ]
636}
637
638/// Chrome 148 headers for AJAX/API requests.
639pub fn chrome_148_ajax_headers() -> Vec<(&'static str, &'static str)> {
640    vec![
641        (
642            "User-Agent",
643            "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36",
644        ),
645        ("Accept", "application/json, text/plain, */*"),
646        ("Accept-Language", "en-US,en;q=0.9"),
647        ("Accept-Encoding", "gzip, deflate, br, zstd"),
648        ("Content-Type", "application/json"),
649        ("Sec-Fetch-Dest", "empty"),
650        ("Sec-Fetch-Mode", "cors"),
651        ("Sec-Fetch-Site", "same-origin"),
652        (
653            "Sec-Ch-Ua",
654            r#""Chromium";v="148", "Google Chrome";v="148", "Not/A)Brand";v="99""#,
655        ),
656        ("Sec-Ch-Ua-Mobile", "?0"),
657        ("Sec-Ch-Ua-Platform", r#""macOS""#),
658        ("Connection", "keep-alive"),
659    ]
660}
661
662/// Chrome 148 headers for form submissions.
663pub fn chrome_148_form_headers() -> Vec<(&'static str, &'static str)> {
664    vec![
665        (
666            "User-Agent",
667            "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36",
668        ),
669        (
670            "Accept",
671            "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8",
672        ),
673        ("Accept-Language", "en-US,en;q=0.9"),
674        ("Accept-Encoding", "gzip, deflate, br, zstd"),
675        ("Content-Type", "application/x-www-form-urlencoded"),
676        ("Sec-Fetch-Dest", "document"),
677        ("Sec-Fetch-Mode", "navigate"),
678        ("Sec-Fetch-Site", "same-origin"),
679        (
680            "Sec-Ch-Ua",
681            r#""Chromium";v="148", "Google Chrome";v="148", "Not/A)Brand";v="99""#,
682        ),
683        ("Sec-Ch-Ua-Mobile", "?0"),
684        ("Sec-Ch-Ua-Platform", r#""macOS""#),
685        ("Sec-Ch-Ua-Arch", r#""arm64""#),
686        ("Sec-Ch-Ua-Bitness", r#""64""#),
687        (
688            "Sec-Ch-Ua-Full-Version-List",
689            r#""Chromium";v="148.0.7778.179", "Google Chrome";v="148.0.7778.179", "Not/A)Brand";v="99.0.0.0""#,
690        ),
691        ("Sec-Ch-Ua-Model", r#""""#),
692        ("Sec-Ch-Ua-Platform-Version", r#""15.5.0""#),
693        ("Sec-Ch-Ua-Wow64", "?0"),
694        ("Upgrade-Insecure-Requests", "1"),
695        ("Connection", "keep-alive"),
696    ]
697}
698
699/// Add Cookie header from jar.
700pub fn with_cookies(base: impl Into<Headers>, url: &str, jar: &CookieJar) -> Headers {
701    let base = base.into();
702    let mut builder = HeadersBuilder::from_headers(&base);
703    builder.remove("cookie");
704    if let Some(cookie_header) = jar.build_cookie_header(url) {
705        builder.insert("Cookie", cookie_header);
706    }
707    builder.build()
708}
709
710/// Add Origin header.
711pub fn with_origin(headers: Headers, origin: &str) -> Headers {
712    let mut builder = HeadersBuilder::from_headers(&headers);
713    builder.remove("origin");
714    builder.insert("Origin", origin);
715    builder.build()
716}
717
718/// Add Referer header.
719pub fn with_referer(headers: Headers, referer: &str) -> Headers {
720    let mut builder = HeadersBuilder::from_headers(&headers);
721    builder.remove("referer");
722    builder.insert("Referer", referer);
723    builder.build()
724}
725
726/// Convert owned headers to references.
727pub fn headers_as_refs(headers: &Headers) -> Vec<(&str, &str)> {
728    headers.as_refs()
729}
730
731/// Convert static headers to owned.
732pub fn headers_to_owned(headers: Vec<(&'static str, &'static str)>) -> Vec<(String, String)> {
733    headers
734        .into_iter()
735        .map(|(k, v)| (k.to_string(), v.to_string()))
736        .collect()
737}
738
739/// Byte span into a contiguous header buffer.
740#[derive(Debug, Clone, Copy, PartialEq, Eq)]
741pub struct HeaderSpan {
742    name_start: u32,
743    name_len: u32,
744    value_start: u32,
745    value_len: u32,
746}
747
748impl HeaderSpan {
749    fn name<'a>(&self, buf: &'a [u8]) -> &'a [u8] {
750        let start = self.name_start as usize;
751        &buf[start..start + self.name_len as usize]
752    }
753
754    fn value<'a>(&self, buf: &'a [u8]) -> &'a [u8] {
755        let start = self.value_start as usize;
756        &buf[start..start + self.value_len as usize]
757    }
758}
759
760#[inline]
761fn name_eq_ignore_ascii_case(buf: &[u8], span: &HeaderSpan, name: &[u8]) -> bool {
762    let header_name = span.name(buf);
763    header_name.len() == name.len()
764        && header_name
765            .iter()
766            .zip(name)
767            .all(|(a, b)| a.eq_ignore_ascii_case(b))
768}
769
770fn push_header_bytes(buf: &mut BytesMut, spans: &mut Vec<HeaderSpan>, name: &[u8], value: &[u8]) {
771    let name_start = buf.len() as u32;
772    buf.extend_from_slice(name);
773    let name_len = name.len() as u32;
774    let value_start = buf.len() as u32;
775    buf.extend_from_slice(value);
776    let value_len = value.len() as u32;
777    spans.push(HeaderSpan {
778        name_start,
779        name_len,
780        value_start,
781        value_len,
782    });
783}
784
785/// Mutable builder for byte-spanned headers.
786#[derive(Debug, Default)]
787pub struct HeadersBuilder {
788    buf: BytesMut,
789    spans: Vec<HeaderSpan>,
790}
791
792impl HeadersBuilder {
793    pub fn new() -> Self {
794        Self::default()
795    }
796
797    pub fn with_capacity(header_count: usize, byte_capacity: usize) -> Self {
798        Self {
799            buf: BytesMut::with_capacity(byte_capacity),
800            spans: Vec::with_capacity(header_count),
801        }
802    }
803
804    pub fn from_headers(headers: &Headers) -> Self {
805        let mut builder = Self::with_capacity(headers.len(), headers.buf.len());
806        for (name, value) in headers.iter_bytes() {
807            builder.push(name, value);
808        }
809        builder
810    }
811
812    pub fn len(&self) -> usize {
813        self.spans.len()
814    }
815
816    pub fn is_empty(&self) -> bool {
817        self.spans.is_empty()
818    }
819
820    pub fn push(&mut self, name: impl AsRef<[u8]>, value: impl AsRef<[u8]>) {
821        push_header_bytes(
822            &mut self.buf,
823            &mut self.spans,
824            name.as_ref(),
825            value.as_ref(),
826        );
827    }
828
829    pub fn append(&mut self, name: impl AsRef<[u8]>, value: impl AsRef<[u8]>) {
830        self.push(name, value);
831    }
832
833    pub fn insert(&mut self, name: impl AsRef<[u8]>, value: impl AsRef<[u8]>) {
834        let name = name.as_ref();
835        self.spans
836            .retain(|span| !name_eq_ignore_ascii_case(&self.buf, span, name));
837        self.push(name, value);
838    }
839
840    pub fn remove(&mut self, name: &str) -> Option<Vec<Vec<u8>>> {
841        let name = name.as_bytes();
842        let mut removed = Vec::new();
843        self.spans.retain(|span| {
844            if name_eq_ignore_ascii_case(&self.buf, span, name) {
845                removed.push(span.value(&self.buf).to_vec());
846                false
847            } else {
848                true
849            }
850        });
851        if removed.is_empty() {
852            None
853        } else {
854            Some(removed)
855        }
856    }
857
858    pub fn get(&self, name: &str) -> Option<&[u8]> {
859        let name = name.as_bytes();
860        self.spans
861            .iter()
862            .find(|span| name_eq_ignore_ascii_case(&self.buf, span, name))
863            .map(|span| span.value(&self.buf))
864    }
865
866    pub fn get_all(&self, name: &str) -> Vec<&[u8]> {
867        let name = name.as_bytes();
868        self.spans
869            .iter()
870            .filter_map(|span| {
871                if name_eq_ignore_ascii_case(&self.buf, span, name) {
872                    Some(span.value(&self.buf))
873                } else {
874                    None
875                }
876            })
877            .collect()
878    }
879
880    pub fn contains(&self, name: &str) -> bool {
881        self.get(name).is_some()
882    }
883
884    pub fn iter(&self) -> impl Iterator<Item = (&[u8], &[u8])> + '_ {
885        self.spans
886            .iter()
887            .map(|span| (span.name(&self.buf), span.value(&self.buf)))
888    }
889
890    pub fn build(self) -> Headers {
891        Headers {
892            buf: self.buf.freeze(),
893            spans: Arc::new(self.spans),
894        }
895    }
896}
897
898/// Ordered headers for requests and responses.
899///
900/// This preserves insertion order for fingerprinting while providing
901/// convenient lookup and mutation helpers.
902///
903/// Storage: `Bytes` for the byte buffer (refcounted, cheap to share across
904/// clones) plus `Arc<Vec<HeaderSpan>>` for the per-header spans (refcounted
905/// vec, cheap to share AND cheap to mutate in place when unshared via
906/// `Arc::make_mut`). Mutations call `unshared_storage()` once to obtain
907/// uniquely-owned `BytesMut` + `Vec<HeaderSpan>`; the unshared path is
908/// allocation-free, paying at most one buffer copy the first time a shared
909/// `Headers` is mutated after a clone.
910#[derive(Debug, Clone, Default, PartialEq, Eq)]
911pub struct Headers {
912    buf: Bytes,
913    spans: Arc<Vec<HeaderSpan>>,
914}
915
916impl Headers {
917    pub fn new() -> Self {
918        Self::default()
919    }
920
921    pub fn from_vec(headers: Vec<(String, String)>) -> Self {
922        let mut builder = HeadersBuilder::with_capacity(headers.len(), headers.len() * 32);
923        for (name, value) in headers {
924            builder.push(name.as_bytes(), value.as_bytes());
925        }
926        builder.build()
927    }
928
929    pub fn from_static(headers: Vec<(&'static str, &'static str)>) -> Self {
930        let mut builder = HeadersBuilder::with_capacity(headers.len(), headers.len() * 32);
931        for (name, value) in headers {
932            builder.push(name.as_bytes(), value.as_bytes());
933        }
934        builder.build()
935    }
936
937    pub fn len(&self) -> usize {
938        self.spans.len()
939    }
940
941    pub fn is_empty(&self) -> bool {
942        self.spans.is_empty()
943    }
944
945    /// Replace any existing values for `name` with a single new entry.
946    #[inline]
947    pub fn insert(&mut self, name: impl Into<String>, value: impl Into<String>) {
948        let name = name.into();
949        let value = value.into();
950        self.with_mut(|buf, spans| {
951            spans.retain(|span| !name_eq_ignore_ascii_case(buf, span, name.as_bytes()));
952            push_header_bytes(buf, spans, name.as_bytes(), value.as_bytes());
953        });
954    }
955
956    /// Append `name: value` without removing any existing entries for `name`.
957    #[inline]
958    pub fn append(&mut self, name: impl Into<String>, value: impl Into<String>) {
959        let name = name.into();
960        let value = value.into();
961        self.with_mut(|buf, spans| {
962            push_header_bytes(buf, spans, name.as_bytes(), value.as_bytes());
963        });
964    }
965
966    /// Append without the dedup scan that `insert` performs. Caller must
967    /// guarantee `name` is not already present. Skips a linear scan over
968    /// existing spans on the per-request hot path.
969    #[inline]
970    pub fn insert_unique(&mut self, name: impl Into<String>, value: impl Into<String>) {
971        let name = name.into();
972        let value = value.into();
973        self.with_mut(|buf, spans| {
974            push_header_bytes(buf, spans, name.as_bytes(), value.as_bytes());
975        });
976    }
977
978    pub fn remove(&mut self, name: &str) -> Option<Vec<String>> {
979        let name_bytes = name.as_bytes();
980        let removed = self.with_mut(|buf, spans| {
981            let mut removed: Vec<String> = Vec::new();
982            spans.retain(|span| {
983                if name_eq_ignore_ascii_case(buf, span, name_bytes) {
984                    removed.push(String::from_utf8_lossy(span.value(buf)).into_owned());
985                    false
986                } else {
987                    true
988                }
989            });
990            removed
991        });
992        if removed.is_empty() {
993            None
994        } else {
995            Some(removed)
996        }
997    }
998
999    #[inline]
1000    pub fn get(&self, name: &str) -> Option<&str> {
1001        let name = name.as_bytes();
1002        self.spans
1003            .iter()
1004            .find(|span| name_eq_ignore_ascii_case(&self.buf, span, name))
1005            .and_then(|span| std::str::from_utf8(span.value(&self.buf)).ok())
1006    }
1007
1008    pub fn get_all(&self, name: &str) -> Vec<&str> {
1009        let name = name.as_bytes();
1010        self.spans
1011            .iter()
1012            .filter_map(|span| {
1013                if name_eq_ignore_ascii_case(&self.buf, span, name) {
1014                    std::str::from_utf8(span.value(&self.buf)).ok()
1015                } else {
1016                    None
1017                }
1018            })
1019            .collect()
1020    }
1021
1022    #[inline]
1023    pub fn contains(&self, name: &str) -> bool {
1024        let name = name.as_bytes();
1025        self.spans
1026            .iter()
1027            .any(|span| name_eq_ignore_ascii_case(&self.buf, span, name))
1028    }
1029
1030    #[inline]
1031    pub fn iter_bytes(&self) -> impl Iterator<Item = (&[u8], &[u8])> + '_ {
1032        self.spans
1033            .iter()
1034            .map(|span| (span.name(&self.buf), span.value(&self.buf)))
1035    }
1036
1037    pub fn iter(&self) -> impl Iterator<Item = (&str, &str)> + '_ {
1038        self.iter_bytes().filter_map(|(name, value)| {
1039            Some((
1040                std::str::from_utf8(name).ok()?,
1041                std::str::from_utf8(value).ok()?,
1042            ))
1043        })
1044    }
1045
1046    pub fn iter_ordered(&self) -> impl Iterator<Item = (&str, &str)> + '_ {
1047        self.iter()
1048    }
1049
1050    pub fn extend(&mut self, other: Headers) {
1051        // Materialise the source spans before borrowing self, since
1052        // `with_mut` takes a mutable borrow of self for the whole closure.
1053        let entries: Vec<(Bytes, Bytes)> = other
1054            .iter_bytes()
1055            .map(|(name, value)| (Bytes::copy_from_slice(name), Bytes::copy_from_slice(value)))
1056            .collect();
1057        self.with_mut(|buf, spans| {
1058            for (name, value) in &entries {
1059                push_header_bytes(buf, spans, name, value);
1060            }
1061        });
1062    }
1063
1064    /// Apply a closure to uniquely-owned `BytesMut + Vec<HeaderSpan>` and
1065    /// re-freeze the buffer on completion. First mutation after a clone
1066    /// pays one buffer copy; subsequent mutations on the same instance
1067    /// allocate nothing.
1068    #[inline]
1069    fn with_mut<R>(&mut self, f: impl FnOnce(&mut BytesMut, &mut Vec<HeaderSpan>) -> R) -> R {
1070        let buf = std::mem::take(&mut self.buf);
1071        let mut buf_mut = buf.try_into_mut().unwrap_or_else(|shared| {
1072            let mut owned = BytesMut::with_capacity(shared.len().max(64));
1073            owned.extend_from_slice(&shared);
1074            owned
1075        });
1076        let spans = Arc::make_mut(&mut self.spans);
1077        let result = f(&mut buf_mut, spans);
1078        self.buf = buf_mut.freeze();
1079        result
1080    }
1081
1082    pub fn as_refs(&self) -> Vec<(&str, &str)> {
1083        self.iter().collect()
1084    }
1085
1086    pub fn to_vec(&self) -> Vec<(String, String)> {
1087        self.iter()
1088            .map(|(name, value)| (name.to_string(), value.to_string()))
1089            .collect()
1090    }
1091
1092    pub fn into_inner(self) -> Vec<(String, String)> {
1093        self.to_vec()
1094    }
1095}
1096
1097impl From<Vec<(String, String)>> for Headers {
1098    fn from(value: Vec<(String, String)>) -> Self {
1099        Headers::from_vec(value)
1100    }
1101}
1102
1103impl From<&Headers> for Headers {
1104    fn from(value: &Headers) -> Self {
1105        value.clone()
1106    }
1107}
1108
1109impl From<&[(String, String)]> for Headers {
1110    fn from(value: &[(String, String)]) -> Self {
1111        let mut builder = HeadersBuilder::with_capacity(value.len(), value.len() * 32);
1112        for (name, value) in value {
1113            builder.push(name.as_bytes(), value.as_bytes());
1114        }
1115        builder.build()
1116    }
1117}
1118
1119impl From<&Vec<(String, String)>> for Headers {
1120    fn from(value: &Vec<(String, String)>) -> Self {
1121        Headers::from(value.as_slice())
1122    }
1123}
1124
1125impl<const N: usize> From<&[(String, String); N]> for Headers {
1126    fn from(value: &[(String, String); N]) -> Self {
1127        Headers::from(value.as_slice())
1128    }
1129}
1130
1131impl PartialEq<Vec<(String, String)>> for Headers {
1132    fn eq(&self, other: &Vec<(String, String)>) -> bool {
1133        self.to_vec() == *other
1134    }
1135}
1136
1137impl PartialEq<Headers> for Vec<(String, String)> {
1138    fn eq(&self, other: &Headers) -> bool {
1139        *self == other.to_vec()
1140    }
1141}
1142
1143impl From<HeaderMap> for Headers {
1144    fn from(map: HeaderMap) -> Self {
1145        let mut builder = HeadersBuilder::with_capacity(map.len(), map.len() * 32);
1146        for (name, value) in map.iter() {
1147            builder.append(name.as_str(), value.as_bytes());
1148        }
1149        builder.build()
1150    }
1151}
1152
1153impl From<HeadersBuilder> for Headers {
1154    fn from(builder: HeadersBuilder) -> Self {
1155        builder.build()
1156    }
1157}
1158
1159/// Ordered headers with JA4H fingerprint calculation.
1160///
1161/// JA4H (JA4 for HTTP) fingerprints HTTP clients based on:
1162/// - Header order
1163/// - Header names (normalized to lowercase)
1164///
1165/// This implementation is intentionally header-name/order based and does not
1166/// include header values in the hash. Firefox version differences that only
1167/// change the User-Agent value are not JA4H-distinguishable here.
1168///
1169/// This type preserves exact header order for fingerprint accuracy.
1170#[derive(Debug)]
1171pub struct OrderedHeaders {
1172    headers: Headers,
1173    cached_pairs: OnceLock<Arc<[(String, String)]>>,
1174}
1175
1176impl Clone for OrderedHeaders {
1177    fn clone(&self) -> Self {
1178        Self {
1179            headers: self.headers.clone(),
1180            cached_pairs: OnceLock::new(),
1181        }
1182    }
1183}
1184
1185static CHROME_NAVIGATION_HEADERS: OnceLock<OrderedHeaders> = OnceLock::new();
1186static FIREFOX_NAVIGATION_HEADERS: OnceLock<OrderedHeaders> = OnceLock::new();
1187
1188impl OrderedHeaders {
1189    /// Create new ordered headers.
1190    pub fn new(headers: Vec<(String, String)>) -> Self {
1191        Self {
1192            headers: Headers::from_vec(headers),
1193            cached_pairs: OnceLock::new(),
1194        }
1195    }
1196
1197    /// Create Chrome navigation headers with exact order.
1198    /// Uses Chrome 148 (latest implemented) by default.
1199    pub fn chrome_navigation() -> Self {
1200        CHROME_NAVIGATION_HEADERS
1201            .get_or_init(|| Self::new(headers_to_owned(chrome_148_headers())))
1202            .clone()
1203    }
1204
1205    /// Create Firefox navigation headers with exact order.
1206    /// Uses Firefox 151 (latest implemented release) by default.
1207    pub fn firefox_navigation() -> Self {
1208        FIREFOX_NAVIGATION_HEADERS
1209            .get_or_init(|| Self::new(headers_to_owned(firefox_151_headers())))
1210            .clone()
1211    }
1212
1213    /// Get headers as vector pairs (cached for stable references).
1214    pub fn headers(&self) -> &[(String, String)] {
1215        self.cached_pairs
1216            .get_or_init(|| {
1217                let pairs = self.headers.to_vec();
1218                Arc::from(pairs.into_boxed_slice())
1219            })
1220            .as_ref()
1221    }
1222
1223    /// Borrow the underlying byte-spanned headers.
1224    pub fn headers_ref(&self) -> &Headers {
1225        &self.headers
1226    }
1227
1228    /// Calculate JA4H fingerprint string.
1229    ///
1230    /// JA4H format: header_names|header_order_hash
1231    /// - header_names: comma-separated lowercase header names
1232    /// - header_order_hash: hash of header order
1233    pub fn ja4h_fingerprint(&self) -> String {
1234        use sha2::{Digest, Sha256};
1235
1236        let header_names: Vec<String> = self
1237            .headers
1238            .iter()
1239            .map(|(name, _)| name.to_lowercase())
1240            .collect();
1241
1242        let names_str = header_names.join(",");
1243
1244        let mut hasher = Sha256::new();
1245        hasher.update(names_str.as_bytes());
1246        let hash = hasher.finalize();
1247
1248        let hash_str: String = hash[..3].iter().map(|b| format!("{:02x}", b)).collect();
1249
1250        format!("{}|{}", names_str, hash_str)
1251    }
1252
1253    /// Add a header (preserves order).
1254    pub fn add(mut self, name: impl Into<String>, value: impl Into<String>) -> Self {
1255        self.headers.append(name, value);
1256        self
1257    }
1258
1259    /// Convert to vector of owned headers.
1260    pub fn into_vec(self) -> Vec<(String, String)> {
1261        self.headers.into_inner()
1262    }
1263}
1264
1265impl From<Vec<(String, String)>> for OrderedHeaders {
1266    fn from(headers: Vec<(String, String)>) -> Self {
1267        Self::new(headers)
1268    }
1269}
1270
1271impl From<OrderedHeaders> for Vec<(String, String)> {
1272    fn from(oh: OrderedHeaders) -> Self {
1273        oh.into_vec()
1274    }
1275}
1276
1277fn firefox_navigation_headers(ua: &'static str) -> Vec<(&'static str, &'static str)> {
1278    vec![
1279        ("User-Agent", ua),
1280        (
1281            "Accept",
1282            "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8",
1283        ),
1284        ("Accept-Language", "en-US,en;q=0.5"),
1285        ("Accept-Encoding", "gzip, deflate, br, zstd"),
1286        ("Sec-Fetch-Dest", "document"),
1287        ("Sec-Fetch-Mode", "navigate"),
1288        ("Sec-Fetch-Site", "none"),
1289        ("Sec-Fetch-User", "?1"),
1290        ("Upgrade-Insecure-Requests", "1"),
1291        ("Connection", "keep-alive"),
1292    ]
1293}
1294
1295fn firefox_ajax_headers(ua: &'static str) -> Vec<(&'static str, &'static str)> {
1296    vec![
1297        ("User-Agent", ua),
1298        ("Accept", "application/json, text/plain, */*"),
1299        ("Accept-Language", "en-US,en;q=0.5"),
1300        ("Accept-Encoding", "gzip, deflate, br, zstd"),
1301        ("Content-Type", "application/json"),
1302        ("Sec-Fetch-Dest", "empty"),
1303        ("Sec-Fetch-Mode", "cors"),
1304        ("Sec-Fetch-Site", "same-origin"),
1305        ("Connection", "keep-alive"),
1306    ]
1307}
1308
1309fn firefox_form_headers(ua: &'static str) -> Vec<(&'static str, &'static str)> {
1310    vec![
1311        ("User-Agent", ua),
1312        (
1313            "Accept",
1314            "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8",
1315        ),
1316        ("Accept-Language", "en-US,en;q=0.5"),
1317        ("Accept-Encoding", "gzip, deflate, br, zstd"),
1318        ("Content-Type", "application/x-www-form-urlencoded"),
1319        ("Sec-Fetch-Dest", "document"),
1320        ("Sec-Fetch-Mode", "navigate"),
1321        ("Sec-Fetch-Site", "same-origin"),
1322        ("Upgrade-Insecure-Requests", "1"),
1323        ("Connection", "keep-alive"),
1324    ]
1325}
1326
1327macro_rules! firefox_header_set {
1328    ($headers_fn:ident, $ajax_fn:ident, $form_fn:ident, $ua:literal, $label:literal) => {
1329        #[doc = concat!("Firefox ", $label, " browser headers for page navigation.")]
1330        #[doc = "Firefox does NOT send Sec-Ch-Ua headers (Client Hints)."]
1331        pub fn $headers_fn() -> Vec<(&'static str, &'static str)> {
1332            firefox_navigation_headers($ua)
1333        }
1334
1335        #[doc = concat!("Firefox ", $label, " headers for AJAX/API requests.")]
1336        pub fn $ajax_fn() -> Vec<(&'static str, &'static str)> {
1337            firefox_ajax_headers($ua)
1338        }
1339
1340        #[doc = concat!("Firefox ", $label, " headers for form submissions.")]
1341        pub fn $form_fn() -> Vec<(&'static str, &'static str)> {
1342            firefox_form_headers($ua)
1343        }
1344    };
1345}
1346
1347firefox_header_set!(
1348    firefox_133_headers,
1349    firefox_133_ajax_headers,
1350    firefox_133_form_headers,
1351    "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:133.0) Gecko/20100101 Firefox/133.0",
1352    "133"
1353);
1354firefox_header_set!(
1355    firefox_134_headers,
1356    firefox_134_ajax_headers,
1357    firefox_134_form_headers,
1358    "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:134.0) Gecko/20100101 Firefox/134.0",
1359    "134"
1360);
1361firefox_header_set!(
1362    firefox_135_headers,
1363    firefox_135_ajax_headers,
1364    firefox_135_form_headers,
1365    "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:135.0) Gecko/20100101 Firefox/135.0",
1366    "135"
1367);
1368firefox_header_set!(
1369    firefox_136_headers,
1370    firefox_136_ajax_headers,
1371    firefox_136_form_headers,
1372    "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:136.0) Gecko/20100101 Firefox/136.0",
1373    "136"
1374);
1375firefox_header_set!(
1376    firefox_137_headers,
1377    firefox_137_ajax_headers,
1378    firefox_137_form_headers,
1379    "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:137.0) Gecko/20100101 Firefox/137.0",
1380    "137"
1381);
1382firefox_header_set!(
1383    firefox_138_headers,
1384    firefox_138_ajax_headers,
1385    firefox_138_form_headers,
1386    "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:138.0) Gecko/20100101 Firefox/138.0",
1387    "138"
1388);
1389firefox_header_set!(
1390    firefox_139_headers,
1391    firefox_139_ajax_headers,
1392    firefox_139_form_headers,
1393    "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0",
1394    "139"
1395);
1396firefox_header_set!(
1397    firefox_140_headers,
1398    firefox_140_ajax_headers,
1399    firefox_140_form_headers,
1400    "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:140.0) Gecko/20100101 Firefox/140.0",
1401    "140"
1402);
1403firefox_header_set!(
1404    firefox_141_headers,
1405    firefox_141_ajax_headers,
1406    firefox_141_form_headers,
1407    "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:141.0) Gecko/20100101 Firefox/141.0",
1408    "141"
1409);
1410firefox_header_set!(
1411    firefox_142_headers,
1412    firefox_142_ajax_headers,
1413    firefox_142_form_headers,
1414    "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:142.0) Gecko/20100101 Firefox/142.0",
1415    "142"
1416);
1417firefox_header_set!(
1418    firefox_143_headers,
1419    firefox_143_ajax_headers,
1420    firefox_143_form_headers,
1421    "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:143.0) Gecko/20100101 Firefox/143.0",
1422    "143"
1423);
1424firefox_header_set!(
1425    firefox_144_headers,
1426    firefox_144_ajax_headers,
1427    firefox_144_form_headers,
1428    "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:144.0) Gecko/20100101 Firefox/144.0",
1429    "144"
1430);
1431firefox_header_set!(
1432    firefox_145_headers,
1433    firefox_145_ajax_headers,
1434    firefox_145_form_headers,
1435    "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:145.0) Gecko/20100101 Firefox/145.0",
1436    "145"
1437);
1438firefox_header_set!(
1439    firefox_146_headers,
1440    firefox_146_ajax_headers,
1441    firefox_146_form_headers,
1442    "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:146.0) Gecko/20100101 Firefox/146.0",
1443    "146"
1444);
1445firefox_header_set!(
1446    firefox_147_headers,
1447    firefox_147_ajax_headers,
1448    firefox_147_form_headers,
1449    "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:147.0) Gecko/20100101 Firefox/147.0",
1450    "147"
1451);
1452firefox_header_set!(
1453    firefox_148_headers,
1454    firefox_148_ajax_headers,
1455    firefox_148_form_headers,
1456    "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:148.0) Gecko/20100101 Firefox/148.0",
1457    "148"
1458);
1459firefox_header_set!(
1460    firefox_149_headers,
1461    firefox_149_ajax_headers,
1462    firefox_149_form_headers,
1463    "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:149.0) Gecko/20100101 Firefox/149.0",
1464    "149"
1465);
1466firefox_header_set!(
1467    firefox_150_headers,
1468    firefox_150_ajax_headers,
1469    firefox_150_form_headers,
1470    "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:150.0) Gecko/20100101 Firefox/150.0",
1471    "150"
1472);
1473firefox_header_set!(
1474    firefox_151_headers,
1475    firefox_151_ajax_headers,
1476    firefox_151_form_headers,
1477    "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:151.0) Gecko/20100101 Firefox/151.0",
1478    "151"
1479);
1480firefox_header_set!(
1481    firefox_esr_115_headers,
1482    firefox_esr_115_ajax_headers,
1483    firefox_esr_115_form_headers,
1484    "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:115.0) Gecko/20100101 Firefox/115.0",
1485    "115 ESR"
1486);
1487firefox_header_set!(
1488    firefox_esr_128_headers,
1489    firefox_esr_128_ajax_headers,
1490    firefox_esr_128_form_headers,
1491    "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:128.0) Gecko/20100101 Firefox/128.0",
1492    "128 ESR"
1493);
1494firefox_header_set!(
1495    firefox_esr_140_headers,
1496    firefox_esr_140_ajax_headers,
1497    firefox_esr_140_form_headers,
1498    "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:140.0) Gecko/20100101 Firefox/140.0",
1499    "140 ESR"
1500);