use core::fmt;
const CREDENTIAL_FIELDS: &[&[u8]] = &[
b"authorization",
b"proxy-authorization",
b"cookie",
b"set-cookie",
];
pub(crate) fn is_credential(name: &[u8]) -> bool {
CREDENTIAL_FIELDS
.iter()
.any(|c| c.eq_ignore_ascii_case(name))
}
pub(crate) struct FieldValue<'a> {
pub(crate) name: &'a [u8],
pub(crate) value: &'a [u8],
}
impl fmt::Debug for FieldValue<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if is_credential(self.name) {
return write!(f, "<redacted {} bytes>", self.value.len());
}
match core::str::from_utf8(self.value) {
Ok(s) => fmt::Debug::fmt(s, f),
Err(_) => fmt::Debug::fmt(self.value, f),
}
}
}
pub(crate) struct FieldName<'a>(pub(crate) &'a [u8]);
impl fmt::Debug for FieldName<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match core::str::from_utf8(self.0) {
Ok(s) => fmt::Debug::fmt(s, f),
Err(_) => fmt::Debug::fmt(self.0, f),
}
}
}
pub(crate) struct SerializedFields<'a>(pub(crate) &'a [u8]);
impl fmt::Debug for SerializedFields<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_list()
.entries(
serialized_fields(self.0)
.map(|(name, value)| (FieldName(name), FieldValue { name, value })),
)
.finish()
}
}
pub(crate) fn serialized_fields(fields: &[u8]) -> impl Iterator<Item = (&[u8], &[u8])> {
fields.split(|&b| b == b'\n').filter_map(|line| {
let line = line.strip_suffix(b"\r")?;
let colon = line.iter().position(|&b| b == b':')?;
Some((&line[..colon], line.get(colon + 2..).unwrap_or(&[])))
})
}
#[cfg(test)]
mod tests {
use alloc::format;
use super::*;
#[test]
fn test_is_credential_any_case_matches_the_four_fields() {
for name in [
&b"Authorization"[..],
b"PROXY-AUTHORIZATION",
b"cookie",
b"Set-Cookie",
] {
assert!(is_credential(name), "{name:?}");
}
for name in [&b"cookies"[..], b"x-authorization", b"", b"set-cookie2"] {
assert!(!is_credential(name), "{name:?}");
}
}
#[test]
fn test_field_value_credential_prints_length_only() {
let v = FieldValue {
name: b"Cookie",
value: b"sid=s3cr3t",
};
assert_eq!(format!("{v:?}"), "<redacted 10 bytes>");
let v = FieldValue {
name: b"accept",
value: b"text/html",
};
assert_eq!(format!("{v:?}"), "\"text/html\"");
let v = FieldValue {
name: b"x",
value: b"\xff",
};
assert_eq!(format!("{v:?}"), "[255]");
}
#[test]
fn test_serialized_fields_debug_redacts_and_keeps_order() {
let shown = format!(
"{:?}",
SerializedFields(b"set-cookie: a=1\r\nx-id: 7\r\nauthorization: Basic eDp5\r\n")
);
assert_eq!(
shown,
"[(\"set-cookie\", <redacted 3 bytes>), (\"x-id\", \"7\"), (\"authorization\", <redacted 10 bytes>)]"
);
}
}