qubit_sanitize/adapter/http/http_header_sanitizer.rs
1/*******************************************************************************
2 *
3 * Copyright (c) 2026 Haixing Hu.
4 *
5 * SPDX-License-Identifier: Apache-2.0
6 *
7 * Licensed under the Apache License, Version 2.0.
8 *
9 ******************************************************************************/
10use std::collections::BTreeMap;
11
12use http::{
13 HeaderMap,
14 HeaderName,
15 HeaderValue,
16};
17
18use crate::{
19 FieldSanitizer,
20 NameMatchMode,
21};
22
23/// Sanitizes HTTP header values for logs and diagnostics.
24#[derive(Debug, Clone, PartialEq, Eq)]
25pub struct HttpHeaderSanitizer {
26 /// Core sanitizer used for HTTP header values.
27 field_sanitizer: FieldSanitizer,
28}
29
30impl HttpHeaderSanitizer {
31 /// Creates an HTTP header sanitizer from a core field sanitizer.
32 ///
33 /// # Parameters
34 ///
35 /// * `field_sanitizer` - Core sanitizer used for HTTP header values.
36 ///
37 /// # Returns
38 ///
39 /// New HTTP header sanitizer.
40 pub const fn new(field_sanitizer: FieldSanitizer) -> Self {
41 Self { field_sanitizer }
42 }
43
44 /// Returns the underlying core field sanitizer.
45 ///
46 /// # Returns
47 ///
48 /// Borrowed core field sanitizer.
49 pub const fn field_sanitizer(&self) -> &FieldSanitizer {
50 &self.field_sanitizer
51 }
52
53 /// Returns the underlying core field sanitizer mutably.
54 ///
55 /// # Returns
56 ///
57 /// Mutable core field sanitizer.
58 pub fn field_sanitizer_mut(&mut self) -> &mut FieldSanitizer {
59 &mut self.field_sanitizer
60 }
61
62 /// Sanitizes one HTTP header value by header name.
63 ///
64 /// Non-UTF-8 header values are rendered as `<non-utf8>` before applying
65 /// sensitive-name masking.
66 ///
67 /// # Parameters
68 ///
69 /// * `name` - HTTP header name.
70 /// * `value` - HTTP header value.
71 /// * `match_mode` - Field-name matching mode for the header name.
72 ///
73 /// # Returns
74 ///
75 /// Log-safe header value.
76 pub fn sanitize_value(
77 &self,
78 name: &HeaderName,
79 value: &HeaderValue,
80 match_mode: NameMatchMode,
81 ) -> String {
82 let value = value.to_str().unwrap_or("<non-utf8>");
83 self.field_sanitizer
84 .sanitize_value(name.as_str(), value, match_mode)
85 .into_owned()
86 }
87
88 /// Sanitizes one HTTP header pair.
89 ///
90 /// # Parameters
91 ///
92 /// * `name` - HTTP header name.
93 /// * `value` - HTTP header value.
94 /// * `match_mode` - Field-name matching mode for the header name.
95 ///
96 /// # Returns
97 ///
98 /// Owned string pair preserving the header name and sanitizing the value
99 /// when needed.
100 pub fn sanitize_pair(
101 &self,
102 name: &HeaderName,
103 value: &HeaderValue,
104 match_mode: NameMatchMode,
105 ) -> (String, String) {
106 (
107 name.to_string(),
108 self.sanitize_value(name, value, match_mode),
109 )
110 }
111
112 /// Sanitizes an HTTP header map.
113 ///
114 /// Duplicate header values are grouped under the lowercase header name
115 /// yielded by [`HeaderName::as_str`]. The returned map is sorted
116 /// deterministically for debug output.
117 ///
118 /// # Parameters
119 ///
120 /// * `headers` - HTTP header map to render safely.
121 /// * `match_mode` - Field-name matching mode for header names.
122 ///
123 /// # Returns
124 ///
125 /// Log-safe header names and values.
126 pub fn sanitize_headers(
127 &self,
128 headers: &HeaderMap,
129 match_mode: NameMatchMode,
130 ) -> BTreeMap<String, Vec<String>> {
131 let mut result = BTreeMap::<String, Vec<String>>::new();
132 for (name, value) in headers {
133 result
134 .entry(name.as_str().to_string())
135 .or_default()
136 .push(self.sanitize_value(name, value, match_mode));
137 }
138 result
139 }
140}
141
142impl Default for HttpHeaderSanitizer {
143 /// Creates an HTTP header sanitizer using [`FieldSanitizer::default`].
144 fn default() -> Self {
145 Self::new(FieldSanitizer::default())
146 }
147}