const NEVER_FORWARD: &[&str] = &[
"connection",
"keep-alive",
"proxy-authenticate",
"proxy-authorization",
"te",
"trailer",
"transfer-encoding",
"upgrade",
"host",
"content-length",
"accept-encoding",
];
fn is_never_forwarded(name: &str) -> bool {
NEVER_FORWARD.iter().any(|h| name.eq_ignore_ascii_case(h))
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct ForwardPolicy {
pub enabled: bool,
pub deny: Vec<String>,
}
impl ForwardPolicy {
#[must_use]
pub fn pass_all() -> Self {
Self {
enabled: true,
deny: Vec::new(),
}
}
#[must_use]
pub fn forward_set(&self, client: &[(String, String)]) -> Vec<(String, String)> {
if !self.enabled {
return Vec::new();
}
client
.iter()
.filter(|(name, _)| !is_never_forwarded(name))
.filter(|(name, _)| !self.deny.iter().any(|d| d.eq_ignore_ascii_case(name)))
.cloned()
.collect()
}
}
#[cfg(test)]
mod tests {
use super::*;
fn raw() -> Vec<(String, String)> {
vec![
("Authorization".to_owned(), "Bearer s3cret".to_owned()),
("X-Tenant".to_owned(), "acme".to_owned()),
("traceparent".to_owned(), "00-abc-def-01".to_owned()),
("b3".to_owned(), "abc-def-1".to_owned()),
("Connection".to_owned(), "keep-alive".to_owned()),
("Host".to_owned(), "client.local".to_owned()),
("Content-Length".to_owned(), "42".to_owned()),
("Accept-Encoding".to_owned(), "gzip".to_owned()),
]
}
fn names(set: &[(String, String)]) -> Vec<String> {
set.iter().map(|(k, _)| k.to_ascii_lowercase()).collect()
}
#[test]
fn pass_all_forwards_client_headers_minus_hop_by_hop_and_framing() {
let set = ForwardPolicy::pass_all().forward_set(&raw());
let n = names(&set);
assert!(n.contains(&"authorization".to_owned()));
assert!(n.contains(&"x-tenant".to_owned()));
assert!(n.contains(&"traceparent".to_owned()));
assert!(n.contains(&"b3".to_owned()));
assert!(!n.contains(&"connection".to_owned()));
assert!(!n.contains(&"host".to_owned()));
assert!(!n.contains(&"content-length".to_owned()));
assert!(!n.contains(&"accept-encoding".to_owned()));
}
#[test]
fn the_deny_list_drops_named_headers_case_insensitively() {
let policy = ForwardPolicy {
enabled: true,
deny: vec!["AUTHORIZATION".to_owned()],
};
let n = names(&policy.forward_set(&raw()));
assert!(!n.contains(&"authorization".to_owned()), "denied: {n:?}");
assert!(n.contains(&"x-tenant".to_owned()), "others still pass");
}
#[test]
fn disabled_forwards_nothing() {
let policy = ForwardPolicy {
enabled: false,
deny: Vec::new(),
};
assert!(policy.forward_set(&raw()).is_empty());
}
}