aws_smithy_http_server/instrumentation/sensitivity/
headers.rs1use std::fmt::{Debug, Display, Error, Formatter};
9
10use http::{header::HeaderName, HeaderMap};
11
12use crate::instrumentation::MakeFmt;
13
14use super::Sensitive;
15
16#[derive(Debug, Default, PartialEq, Eq)]
18pub struct HeaderMarker {
19    pub value: bool,
21    pub key_suffix: Option<usize>,
23}
24
25pub struct SensitiveHeaders<'a, F> {
50    headers: &'a HeaderMap,
51    marker: F,
52}
53
54impl<'a, F> SensitiveHeaders<'a, F> {
55    pub fn new(headers: &'a HeaderMap, marker: F) -> Self {
57        Self { headers, marker }
58    }
59}
60
61struct ThenDebug<'a>(&'a str, Sensitive<&'a str>);
63
64impl Debug for ThenDebug<'_> {
65    #[inline]
66    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
67        write!(f, "\"{}{}\"", self.0, self.1)
68    }
69}
70
71enum OrFmt<Left, Right> {
73    Left(Left),
74    Right(Right),
75}
76
77impl<Left, Right> Debug for OrFmt<Left, Right>
78where
79    Left: Debug,
80    Right: Debug,
81{
82    #[inline]
83    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
84        match self {
85            Self::Left(left) => left.fmt(f),
86            Self::Right(right) => right.fmt(f),
87        }
88    }
89}
90
91impl<Left, Right> Display for OrFmt<Left, Right>
92where
93    Left: Display,
94    Right: Display,
95{
96    #[inline]
97    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
98        match self {
99            Self::Left(left) => left.fmt(f),
100            Self::Right(right) => right.fmt(f),
101        }
102    }
103}
104
105impl<'a, F> Debug for SensitiveHeaders<'a, F>
106where
107    F: Fn(&'a HeaderName) -> HeaderMarker,
108{
109    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
110        let iter = self.headers.iter().map(|(key, value)| {
111            let HeaderMarker {
112                value: value_sensitive,
113                key_suffix,
114            } = (self.marker)(key);
115
116            let key = if let Some(key_suffix) = key_suffix {
117                let key_str = key.as_str();
118                OrFmt::Left(ThenDebug(&key_str[..key_suffix], Sensitive(&key_str[key_suffix..])))
119            } else {
120                OrFmt::Right(key)
121            };
122
123            let value = if value_sensitive {
124                OrFmt::Left(Sensitive(value))
125            } else {
126                OrFmt::Right(value)
127            };
128
129            (key, value)
130        });
131
132        f.debug_map().entries(iter).finish()
133    }
134}
135
136#[derive(Clone)]
138pub struct MakeHeaders<F>(pub(crate) F);
139
140impl<F> Debug for MakeHeaders<F> {
141    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
142        f.debug_tuple("MakeHeaders").field(&"...").finish()
143    }
144}
145
146impl<'a, F> MakeFmt<&'a HeaderMap> for MakeHeaders<F>
147where
148    F: Clone,
149{
150    type Target = SensitiveHeaders<'a, F>;
151
152    fn make(&self, source: &'a HeaderMap) -> Self::Target {
153        SensitiveHeaders::new(source, self.0.clone())
154    }
155}
156#[cfg(test)]
157mod tests {
158    use http::{header::HeaderName, HeaderMap, HeaderValue};
159
160    use super::*;
161
162    struct TestDebugMap([(&'static str, &'static str); 4]);
164
165    impl Debug for TestDebugMap {
166        fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
167            f.debug_map().entries(self.0).finish()
168        }
169    }
170
171    const HEADER_MAP: [(&str, &str); 4] = [
172        ("name-a", "value-a"),
173        ("name-b", "value-b"),
174        ("prefix-a-x", "value-c"),
175        ("prefix-b-y", "value-d"),
176    ];
177
178    fn to_header_map<I>(values: I) -> HeaderMap
179    where
180        I: IntoIterator<Item = (&'static str, &'static str)>,
181    {
182        values
183            .into_iter()
184            .map(|(key, value)| (HeaderName::from_static(key), HeaderValue::from_static(value)))
185            .collect()
186    }
187
188    #[test]
189    fn mark_none() {
190        let original: HeaderMap = to_header_map(HEADER_MAP);
191
192        let output = SensitiveHeaders::new(&original, |_| HeaderMarker::default());
193        assert_eq!(format!("{:?}", output), format!("{:?}", original));
194    }
195
196    #[cfg(not(feature = "unredacted-logging"))]
197    const ALL_VALUES_HEADER_MAP: [(&str, &str); 4] = [
198        ("name-a", "{redacted}"),
199        ("name-b", "{redacted}"),
200        ("prefix-a-x", "{redacted}"),
201        ("prefix-b-y", "{redacted}"),
202    ];
203    #[cfg(feature = "unredacted-logging")]
204    const ALL_VALUES_HEADER_MAP: [(&str, &str); 4] = HEADER_MAP;
205
206    #[test]
207    fn mark_all_values() {
208        let original: HeaderMap = to_header_map(HEADER_MAP);
209        let expected = TestDebugMap(ALL_VALUES_HEADER_MAP);
210
211        let output = SensitiveHeaders::new(&original, |_| HeaderMarker {
212            value: true,
213            key_suffix: None,
214        });
215        assert_eq!(format!("{:?}", output), format!("{:?}", expected));
216    }
217
218    #[cfg(not(feature = "unredacted-logging"))]
219    const NAME_A_HEADER_MAP: [(&str, &str); 4] = [
220        ("name-a", "{redacted}"),
221        ("name-b", "value-b"),
222        ("prefix-a-x", "value-c"),
223        ("prefix-b-y", "value-d"),
224    ];
225    #[cfg(feature = "unredacted-logging")]
226    const NAME_A_HEADER_MAP: [(&str, &str); 4] = HEADER_MAP;
227
228    #[test]
229    fn mark_name_a_values() {
230        let original: HeaderMap = to_header_map(HEADER_MAP);
231        let expected = TestDebugMap(NAME_A_HEADER_MAP);
232
233        let output = SensitiveHeaders::new(&original, |name| HeaderMarker {
234            value: name == "name-a",
235            key_suffix: None,
236        });
237        assert_eq!(format!("{:?}", output), format!("{:?}", expected));
238    }
239
240    #[cfg(not(feature = "unredacted-logging"))]
241    const PREFIX_A_HEADER_MAP: [(&str, &str); 4] = [
242        ("name-a", "value-a"),
243        ("name-b", "value-b"),
244        ("prefix-a{redacted}", "value-c"),
245        ("prefix-b-y", "value-d"),
246    ];
247    #[cfg(feature = "unredacted-logging")]
248    const PREFIX_A_HEADER_MAP: [(&str, &str); 4] = HEADER_MAP;
249
250    #[test]
251    fn mark_prefix_a_values() {
252        let original: HeaderMap = to_header_map(HEADER_MAP);
253        let expected = TestDebugMap(PREFIX_A_HEADER_MAP);
254
255        let prefix = "prefix-a";
256        let output = SensitiveHeaders::new(&original, |name: &HeaderName| HeaderMarker {
257            value: false,
258            key_suffix: if name.as_str().starts_with(prefix) {
259                Some(prefix.len())
260            } else {
261                None
262            },
263        });
264        assert_eq!(format!("{:?}", output), format!("{:?}", expected));
265    }
266}