use super::*;
fn preset_xhr() -> Vec<HeaderPair> {
vec![
("sec-ch-ua".into(), "chrome".into()),
("sec-ch-ua-mobile".into(), "?0".into()),
("sec-ch-ua-platform".into(), "\"Windows\"".into()),
("user-agent".into(), "Mozilla/5.0".into()),
("accept".into(), "application/json".into()),
("sec-fetch-site".into(), "same-origin".into()),
("sec-fetch-mode".into(), "cors".into()),
("sec-fetch-dest".into(), "empty".into()),
("accept-encoding".into(), "gzip".into()),
("accept-language".into(), "en-US".into()),
]
}
fn never_sensitive(_: &str) -> bool {
false
}
#[test]
fn plain_unknown_header_appends_at_end() {
let mut headers = preset_xhr();
let mut extra = HeaderList::new();
drop(extra.set("x-custom", "val"));
apply_extra_headers(&mut headers, &extra, false, &never_sensitive);
assert_eq!(headers.last().unwrap().0, "x-custom");
}
#[test]
fn plain_known_header_rides_inferred_anchor() {
let mut headers = preset_xhr();
let mut extra = HeaderList::new();
drop(extra.set("authorization", "Bearer t"));
apply_extra_headers(&mut headers, &extra, false, &never_sensitive);
let ua = headers.iter().position(|(k, _)| k == "user-agent").unwrap();
assert_eq!(headers[ua + 1].0, "authorization");
}
#[test]
fn anchored_header_splices_after_anchor() {
let mut headers = preset_xhr();
let mut extra = HeaderList::new();
drop(extra.append_anchored(HeaderAnchor::AfterUserAgent, "x-extra-6", "6"));
apply_extra_headers(&mut headers, &extra, false, &never_sensitive);
let ua = headers.iter().position(|(k, _)| k == "user-agent").unwrap();
assert_eq!(headers[ua + 1].0, "x-extra-6");
}
#[test]
fn multiple_anchored_same_anchor_preserve_caller_order() {
let mut headers = preset_xhr();
let mut extra = HeaderList::new();
drop(extra.append_anchored(HeaderAnchor::AfterCchUaMobile, "x-a0", "0"));
drop(extra.append_anchored(HeaderAnchor::AfterCchUaMobile, "x-b", "b"));
drop(extra.append_anchored(HeaderAnchor::AfterCchUaMobile, "x-a", "a"));
apply_extra_headers(&mut headers, &extra, false, &never_sensitive);
let names: Vec<&str> = headers.iter().map(|(k, _)| k.as_ref()).collect();
let mobile = names.iter().position(|&n| n == "sec-ch-ua-mobile").unwrap();
assert_eq!(&names[mobile + 1..mobile + 4], &["x-a0", "x-b", "x-a"]);
}
#[test]
fn preset_owned_name_replaced_in_place() {
let mut headers = preset_xhr();
let original_ua_idx = headers.iter().position(|(k, _)| k == "user-agent").unwrap();
let mut extra = HeaderList::new();
drop(extra.set("user-agent", "custom-agent"));
apply_extra_headers(&mut headers, &extra, false, &never_sensitive);
assert_eq!(headers[original_ua_idx].0, "user-agent");
assert_eq!(headers[original_ua_idx].1, "custom-agent");
}
#[test]
fn anchored_headers_interleave_at_each_anchor() {
let mut headers = vec![
("sec-ch-ua".into(), "chrome".into()),
("sec-ch-ua-mobile".into(), "?0".into()),
("sec-ch-ua-platform".into(), "\"Windows\"".into()),
("user-agent".into(), "Mozilla/5.0".into()),
("accept".into(), "*/*".into()),
("content-type".into(), "application/json".into()),
("origin".into(), "https://api.example.com".into()),
("accept-encoding".into(), "gzip".into()),
];
let mut extra = HeaderList::new();
drop(extra.append_anchored(HeaderAnchor::AfterCchUaPlatform, "x-extra-5", "5"));
drop(extra.append_anchored(HeaderAnchor::AfterCchUa, "x-extra-1", "1"));
drop(extra.append_anchored(HeaderAnchor::AfterCchUaMobile, "x-extra-2", "2"));
drop(extra.append_anchored(HeaderAnchor::AfterCchUaMobile, "x-extra-3", "3"));
drop(extra.append_anchored(HeaderAnchor::AfterCchUaMobile, "x-extra-4", "4"));
drop(extra.append_anchored(HeaderAnchor::AfterUserAgent, "x-extra-6", "6"));
drop(extra.append_anchored(HeaderAnchor::AfterContentType, "x-extra-7", "7"));
apply_extra_headers(&mut headers, &extra, false, &never_sensitive);
let names: Vec<&str> = headers.iter().map(|(k, _)| k.as_ref()).collect();
assert_eq!(
names,
vec![
"sec-ch-ua",
"x-extra-1",
"sec-ch-ua-mobile",
"x-extra-2",
"x-extra-3",
"x-extra-4",
"sec-ch-ua-platform",
"x-extra-5",
"user-agent",
"x-extra-6",
"accept",
"content-type",
"x-extra-7",
"origin",
"accept-encoding",
]
);
}
#[test]
fn before_anchor_inserts_before_target() {
let mut headers = preset_xhr();
let mut extra = HeaderList::new();
drop(extra.append_anchored(HeaderAnchor::BeforeAcceptEncoding, "x-last-chance", "v"));
apply_extra_headers(&mut headers, &extra, false, &never_sensitive);
let ae_idx = headers
.iter()
.position(|(k, _)| k == "accept-encoding")
.unwrap();
assert_eq!(headers[ae_idx - 1].0, "x-last-chance");
}
#[test]
fn client_hints_lead_the_client_hint_block_in_push_order() {
let mut headers = preset_xhr();
let mut extra = HeaderList::new();
drop(extra.append_anchored(HeaderAnchor::BeforeCchUa, "device-memory", "8"));
drop(extra.append_anchored(HeaderAnchor::BeforeCchUa, "dpr", "1"));
apply_extra_headers(&mut headers, &extra, false, &never_sensitive);
let names: Vec<&str> = headers.iter().take(3).map(|(k, _)| k.as_ref()).collect();
assert_eq!(names, ["device-memory", "dpr", "sec-ch-ua"]);
}
#[test]
fn sensitive_stripped_on_cross_origin_redirect() {
let mut headers = preset_xhr();
let mut extra = HeaderList::new();
drop(extra.set("authorization", "Bearer t"));
drop(extra.set("x-benign", "ok"));
let sensitive = |name: &str| name.eq_ignore_ascii_case("authorization");
apply_extra_headers(&mut headers, &extra, true, &sensitive);
assert!(!headers.iter().any(|(k, _)| k == "authorization"));
assert!(headers.iter().any(|(k, _)| k == "x-benign"));
}
#[test]
fn anchor_absent_falls_back_to_end() {
let mut headers = vec![
("sec-ch-ua".into(), "chrome".into()),
("user-agent".into(), "Mozilla/5.0".into()),
("accept".into(), "text/html".into()),
("accept-encoding".into(), "gzip".into()),
];
let mut extra = HeaderList::new();
drop(extra.append_anchored(HeaderAnchor::AfterContentType, "x-d", "d"));
apply_extra_headers(&mut headers, &extra, false, &never_sensitive);
assert_eq!(headers.last().unwrap().0, "x-d");
}