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<'a> Debug for ThenDebug<'a> {
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}