1mod label;
9mod query;
10
11use std::fmt::{Debug, Display, Error, Formatter};
12
13use http::Uri;
14
15pub use label::*;
16pub use query::*;
17
18use crate::instrumentation::{MakeDisplay, MakeFmt, MakeIdentity};
19
20#[allow(missing_debug_implementations)]
25pub struct SensitiveUri<'a, P, Q> {
26 uri: &'a Uri,
27 make_path: P,
28 make_query: Q,
29}
30
31impl<'a> SensitiveUri<'a, MakeIdentity, MakeIdentity> {
32 pub fn new(uri: &'a Uri) -> Self {
34 Self {
35 uri,
36 make_path: MakeIdentity,
37 make_query: MakeIdentity,
38 }
39 }
40}
41
42impl<'a, P, Q> SensitiveUri<'a, P, Q> {
43 pub(crate) fn make_path<M>(self, make_path: M) -> SensitiveUri<'a, M, Q> {
44 SensitiveUri {
45 uri: self.uri,
46 make_path,
47 make_query: self.make_query,
48 }
49 }
50
51 pub(crate) fn make_query<M>(self, make_query: M) -> SensitiveUri<'a, P, M> {
52 SensitiveUri {
53 uri: self.uri,
54 make_path: self.make_path,
55 make_query,
56 }
57 }
58
59 pub fn label<F>(self, label_marker: F, greedy_label: Option<GreedyLabel>) -> SensitiveUri<'a, MakeLabel<F>, Q> {
63 self.make_path(MakeLabel {
64 label_marker,
65 greedy_label,
66 })
67 }
68
69 pub fn query<F>(self, marker: F) -> SensitiveUri<'a, P, MakeQuery<F>> {
73 self.make_query(MakeQuery(marker))
74 }
75}
76
77impl<'a, P, Q> Display for SensitiveUri<'a, P, Q>
78where
79 P: MakeDisplay<&'a str>,
80 Q: MakeDisplay<&'a str>,
81{
82 fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
83 if let Some(scheme) = self.uri.scheme() {
84 write!(f, "{scheme}://")?;
85 }
86
87 if let Some(authority) = self.uri.authority() {
88 write!(f, "{authority}")?;
89 }
90
91 let path = self.uri.path();
92 let path = self.make_path.make_display(path);
93 write!(f, "{path}")?;
94
95 if let Some(query) = self.uri.query() {
96 let query = self.make_query.make_display(query);
97 write!(f, "?{query}")?;
98 }
99
100 Ok(())
101 }
102}
103
104#[derive(Clone)]
106pub struct MakeUri<P, Q> {
107 pub(crate) make_path: P,
108 pub(crate) make_query: Q,
109}
110
111impl<P, Q> Debug for MakeUri<P, Q> {
112 fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
113 f.debug_struct("MakeUri").finish_non_exhaustive()
114 }
115}
116
117impl<'a, P, Q> MakeFmt<&'a http::Uri> for MakeUri<P, Q>
118where
119 Q: Clone,
120 P: Clone,
121{
122 type Target = SensitiveUri<'a, P, Q>;
123
124 fn make(&self, source: &'a http::Uri) -> Self::Target {
125 SensitiveUri::new(source)
126 .make_query(self.make_query.clone())
127 .make_path(self.make_path.clone())
128 }
129}
130
131impl Default for MakeUri<MakeIdentity, MakeIdentity> {
132 fn default() -> Self {
133 Self {
134 make_path: MakeIdentity,
135 make_query: MakeIdentity,
136 }
137 }
138}
139
140#[cfg(test)]
141mod tests {
142 use http::Uri;
143
144 use super::{QueryMarker, SensitiveUri};
145
146 pub const EXAMPLES: [&str; 19] = [
149 "g:h",
150 "http://a/b/c/g",
151 "http://a/b/c/g/",
152 "http://a/g",
153 "http://g",
154 "http://a/b/c/d;p?y",
155 "http://a/b/c/g?y",
156 "http://a/b/c/d;p?q#s",
157 "http://a/b/c/g#s",
158 "http://a/b/c/g?y#s",
159 "http://a/b/c/;x",
160 "http://a/b/c/g;x",
161 "http://a/b/c/g;x?y#s",
162 "http://a/b/c/d;p?q",
163 "http://a/b/c/",
164 "http://a/b/c/",
165 "http://a/b/",
166 "http://a/b/g",
167 "http://a/",
168 ];
169
170 pub const QUERY_STRING_EXAMPLES: [&str; 11] = [
171 "http://a/b/c/g?&",
172 "http://a/b/c/g?x",
173 "http://a/b/c/g?x&y",
174 "http://a/b/c/g?x&y&",
175 "http://a/b/c/g?x&y&z",
176 "http://a/b/c/g?x=y&x=z",
177 "http://a/b/c/g?x=y&z",
178 "http://a/b/c/g?x=y&",
179 "http://a/b/c/g?x=y&y=z",
180 "http://a/b/c/g?&x=z",
181 "http://a/b/c/g?x&x=y",
182 ];
183
184 #[test]
185 fn path_mark_none() {
186 let originals = EXAMPLES.into_iter().map(Uri::from_static);
187 for original in originals {
188 let output = SensitiveUri::new(&original).to_string();
189 assert_eq!(output, original.to_string());
190 }
191 }
192
193 #[cfg(not(feature = "unredacted-logging"))]
194 const FIRST_PATH_EXAMPLES: [&str; 19] = [
195 "g:h",
196 "http://a/{redacted}/c/g",
197 "http://a/{redacted}/c/g/",
198 "http://a/{redacted}",
199 "http://g/{redacted}",
200 "http://a/{redacted}/c/d;p?y",
201 "http://a/{redacted}/c/g?y",
202 "http://a/{redacted}/c/d;p?q#s",
203 "http://a/{redacted}/c/g#s",
204 "http://a/{redacted}/c/g?y#s",
205 "http://a/{redacted}/c/;x",
206 "http://a/{redacted}/c/g;x",
207 "http://a/{redacted}/c/g;x?y#s",
208 "http://a/{redacted}/c/d;p?q",
209 "http://a/{redacted}/c/",
210 "http://a/{redacted}/c/",
211 "http://a/{redacted}/",
212 "http://a/{redacted}/g",
213 "http://a/{redacted}",
214 ];
215 #[cfg(feature = "unredacted-logging")]
216 const FIRST_PATH_EXAMPLES: [&str; 19] = EXAMPLES;
217
218 #[test]
219 fn path_mark_first_segment() {
220 let originals = EXAMPLES.into_iter().map(Uri::from_static);
221 let expecteds = FIRST_PATH_EXAMPLES.into_iter().map(Uri::from_static);
222 for (original, expected) in originals.zip(expecteds) {
223 let output = SensitiveUri::new(&original).label(|x| x == 0, None).to_string();
224 assert_eq!(output, expected.to_string(), "original = {original}");
225 }
226 }
227
228 #[cfg(not(feature = "unredacted-logging"))]
229 const LAST_PATH_EXAMPLES: [&str; 19] = [
230 "g:h",
231 "http://a/b/c/{redacted}",
232 "http://a/b/c/g/{redacted}",
233 "http://a/{redacted}",
234 "http://g/{redacted}",
235 "http://a/b/c/{redacted}?y",
236 "http://a/b/c/{redacted}?y",
237 "http://a/b/c/{redacted}?q#s",
238 "http://a/b/c/{redacted}#s",
239 "http://a/b/c/{redacted}?y#s",
240 "http://a/b/c/{redacted}",
241 "http://a/b/c/{redacted}",
242 "http://a/b/c/{redacted}?y#s",
243 "http://a/b/c/{redacted}?q",
244 "http://a/b/c/{redacted}",
245 "http://a/b/c/{redacted}",
246 "http://a/b/{redacted}",
247 "http://a/b/{redacted}",
248 "http://a/{redacted}",
249 ];
250 #[cfg(feature = "unredacted-logging")]
251 const LAST_PATH_EXAMPLES: [&str; 19] = EXAMPLES;
252
253 #[test]
254 fn path_mark_last_segment() {
255 let originals = EXAMPLES.into_iter().map(Uri::from_static);
256 let expecteds = LAST_PATH_EXAMPLES.into_iter().map(Uri::from_static);
257 for (original, expected) in originals.zip(expecteds) {
258 let path_len = original.path().split('/').skip(1).count();
259 let output = SensitiveUri::new(&original)
260 .label(|x| x + 1 == path_len, None)
261 .to_string();
262 assert_eq!(output, expected.to_string(), "original = {original}");
263 }
264 }
265
266 #[cfg(not(feature = "unredacted-logging"))]
267 pub const ALL_KEYS_QUERY_STRING_EXAMPLES: [&str; 11] = [
268 "http://a/b/c/g?&",
269 "http://a/b/c/g?x",
270 "http://a/b/c/g?x&y",
271 "http://a/b/c/g?x&y&",
272 "http://a/b/c/g?x&y&z",
273 "http://a/b/c/g?{redacted}=y&{redacted}=z",
274 "http://a/b/c/g?{redacted}=y&z",
275 "http://a/b/c/g?{redacted}=y&",
276 "http://a/b/c/g?{redacted}=y&{redacted}=z",
277 "http://a/b/c/g?&{redacted}=z",
278 "http://a/b/c/g?x&{redacted}=y",
279 ];
280 #[cfg(feature = "unredacted-logging")]
281 pub const ALL_KEYS_QUERY_STRING_EXAMPLES: [&str; 11] = QUERY_STRING_EXAMPLES;
282
283 #[test]
284 fn query_mark_all_keys() {
285 let originals = QUERY_STRING_EXAMPLES.into_iter().map(Uri::from_static);
286 let expecteds = ALL_KEYS_QUERY_STRING_EXAMPLES.into_iter().map(Uri::from_static);
287 for (original, expected) in originals.zip(expecteds) {
288 let output = SensitiveUri::new(&original)
289 .query(|_| QueryMarker {
290 key: true,
291 value: false,
292 })
293 .to_string();
294 assert_eq!(output, expected.to_string(), "original = {original}");
295 }
296 }
297
298 #[cfg(not(feature = "unredacted-logging"))]
299 pub const ALL_VALUES_QUERY_STRING_EXAMPLES: [&str; 11] = [
300 "http://a/b/c/g?&",
301 "http://a/b/c/g?x",
302 "http://a/b/c/g?x&y",
303 "http://a/b/c/g?x&y&",
304 "http://a/b/c/g?x&y&z",
305 "http://a/b/c/g?x={redacted}&x={redacted}",
306 "http://a/b/c/g?x={redacted}&z",
307 "http://a/b/c/g?x={redacted}&",
308 "http://a/b/c/g?x={redacted}&y={redacted}",
309 "http://a/b/c/g?&x={redacted}",
310 "http://a/b/c/g?x&x={redacted}",
311 ];
312 #[cfg(feature = "unredacted-logging")]
313 pub const ALL_VALUES_QUERY_STRING_EXAMPLES: [&str; 11] = QUERY_STRING_EXAMPLES;
314
315 #[test]
316 fn query_mark_all_values() {
317 let originals = QUERY_STRING_EXAMPLES.into_iter().map(Uri::from_static);
318 let expecteds = ALL_VALUES_QUERY_STRING_EXAMPLES.into_iter().map(Uri::from_static);
319 for (original, expected) in originals.zip(expecteds) {
320 let output = SensitiveUri::new(&original)
321 .query(|_| QueryMarker {
322 key: false,
323 value: true,
324 })
325 .to_string();
326 assert_eq!(output, expected.to_string(), "original = {original}");
327 }
328 }
329
330 #[cfg(not(feature = "unredacted-logging"))]
331 pub const ALL_PAIRS_QUERY_STRING_EXAMPLES: [&str; 11] = [
332 "http://a/b/c/g?&",
333 "http://a/b/c/g?x",
334 "http://a/b/c/g?x&y",
335 "http://a/b/c/g?x&y&",
336 "http://a/b/c/g?x&y&z",
337 "http://a/b/c/g?{redacted}={redacted}&{redacted}={redacted}",
338 "http://a/b/c/g?{redacted}={redacted}&z",
339 "http://a/b/c/g?{redacted}={redacted}&",
340 "http://a/b/c/g?{redacted}={redacted}&{redacted}={redacted}",
341 "http://a/b/c/g?&{redacted}={redacted}",
342 "http://a/b/c/g?x&{redacted}={redacted}",
343 ];
344 #[cfg(feature = "unredacted-logging")]
345 pub const ALL_PAIRS_QUERY_STRING_EXAMPLES: [&str; 11] = QUERY_STRING_EXAMPLES;
346
347 #[test]
348 fn query_mark_all_pairs() {
349 let originals = QUERY_STRING_EXAMPLES.into_iter().map(Uri::from_static);
350 let expecteds = ALL_PAIRS_QUERY_STRING_EXAMPLES.into_iter().map(Uri::from_static);
351 for (original, expected) in originals.zip(expecteds) {
352 let output = SensitiveUri::new(&original)
353 .query(|_| QueryMarker { key: true, value: true })
354 .to_string();
355 assert_eq!(output, expected.to_string(), "original = {original}");
356 }
357 }
358
359 #[cfg(not(feature = "unredacted-logging"))]
360 pub const X_QUERY_STRING_EXAMPLES: [&str; 11] = [
361 "http://a/b/c/g?&",
362 "http://a/b/c/g?x",
363 "http://a/b/c/g?x&y",
364 "http://a/b/c/g?x&y&",
365 "http://a/b/c/g?x&y&z",
366 "http://a/b/c/g?x={redacted}&x={redacted}",
367 "http://a/b/c/g?x={redacted}&z",
368 "http://a/b/c/g?x={redacted}&",
369 "http://a/b/c/g?x={redacted}&y=z",
370 "http://a/b/c/g?&x={redacted}",
371 "http://a/b/c/g?x&x={redacted}",
372 ];
373 #[cfg(feature = "unredacted-logging")]
374 pub const X_QUERY_STRING_EXAMPLES: [&str; 11] = QUERY_STRING_EXAMPLES;
375
376 #[test]
377 fn query_mark_x() {
378 let originals = QUERY_STRING_EXAMPLES.into_iter().map(Uri::from_static);
379 let expecteds = X_QUERY_STRING_EXAMPLES.into_iter().map(Uri::from_static);
380 for (original, expected) in originals.zip(expecteds) {
381 let output = SensitiveUri::new(&original)
382 .query(|key| QueryMarker {
383 key: false,
384 value: key == "x",
385 })
386 .to_string();
387 assert_eq!(output, expected.to_string(), "original = {original}");
388 }
389 }
390}