Skip to main content

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}