use std::collections::HashMap;
use std::fmt;
#[doc(hidden)]
pub const REDACTED: &str = "***";
#[doc(hidden)]
pub const SENSITIVE_HEADERS: [&str; 7] = [
"authorization",
"proxy-authorization",
"cookie",
"set-cookie",
"x-api-key",
"x-amz-security-token",
"authentication-info",
];
pub(crate) fn is_sensitive_header(name: &str) -> bool {
SENSITIVE_HEADERS
.iter()
.any(|sensitive| name.eq_ignore_ascii_case(sensitive))
}
pub(crate) struct RedactedValues<'a>(pub(crate) &'a HashMap<String, String>);
impl fmt::Debug for RedactedValues<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_map().entries(self.0.keys().map(|key| (key, REDACTED))).finish()
}
}
pub(crate) struct RedactedHeaders<'a>(pub(crate) &'a HashMap<String, String>);
impl fmt::Debug for RedactedHeaders<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_map()
.entries(self.0.iter().map(|(name, value)| {
let value = if is_sensitive_header(name) {
REDACTED
} else {
value.as_str()
};
(name, value)
}))
.finish()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn redacted_headers_hide_only_sensitive_values_in_any_case() {
let map = HashMap::from([
("Cookie".to_owned(), "sid=s3cr3t".to_owned()),
("PROXY-AUTHORIZATION".to_owned(), "Basic dXNlcjpwdw==".to_owned()),
("accept".to_owned(), "text/html".to_owned()),
]);
let debug = format!("{:?}", RedactedHeaders(&map));
assert!(
!debug.contains("s3cr3t") && !debug.contains("dXNlcjpwdw"),
"got {debug}"
);
assert!(debug.contains(r#""Cookie": "***""#), "got {debug}");
assert!(debug.contains(r#""PROXY-AUTHORIZATION": "***""#), "got {debug}");
assert!(debug.contains(r#""accept": "text/html""#), "got {debug}");
}
}