Skip to main content

mocra_core/common/model/
headers.rs

1use crate::cacheable::CacheAble;
2use reqwest::header::{HeaderMap, HeaderValue};
3use serde::{Deserialize, Serialize};
4use std::fmt;
5
6#[derive(Clone, Serialize, Deserialize)]
7pub struct HeaderItem {
8    pub key: String,
9    pub value: String,
10}
11
12impl fmt::Debug for HeaderItem {
13    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
14        let key_lower = self.key.to_lowercase();
15        let value = if key_lower.contains("auth")
16            || key_lower.contains("cookie")
17            || key_lower.contains("secret")
18            || key_lower.contains("token")
19        {
20            "***REDACTED***"
21        } else {
22            &self.value
23        };
24
25        f.debug_struct("HeaderItem")
26            .field("key", &self.key)
27            .field("value", &value)
28            .finish()
29    }
30}
31
32#[derive(Clone, Serialize, Deserialize)]
33pub struct Headers {
34    pub headers: Vec<HeaderItem>,
35}
36
37impl fmt::Debug for Headers {
38    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
39        f.debug_struct("Headers")
40            .field("headers", &self.headers)
41            .finish()
42    }
43}
44
45impl From<Headers> for Vec<(String, String)> {
46    fn from(value: Headers) -> Self {
47        value
48            .headers
49            .into_iter()
50            .map(|h| (h.key, h.value))
51            .collect()
52    }
53}
54
55impl Default for Headers {
56    fn default() -> Self {
57        Self::new()
58    }
59}
60
61impl Headers {
62    pub fn new() -> Self {
63        Headers {
64            headers: Vec::new(),
65        }
66    }
67
68    pub fn add(mut self, key: impl AsRef<str>, value: impl AsRef<str>) -> Self {
69        if let Some(v) = self
70            .headers
71            .iter_mut()
72            .find(|h| h.key.eq_ignore_ascii_case(key.as_ref()))
73        {
74            v.value = value.as_ref().into();
75        } else {
76            self.headers.push(HeaderItem {
77                key: key.as_ref().into(),
78                value: value.as_ref().into(),
79            })
80        }
81        self
82    }
83    pub fn merge(&mut self, other: &Headers) {
84        for header_item in &other.headers {
85            self.headers.push(header_item.clone());
86        }
87    }
88    pub fn merge_if_absent(&mut self, other: &Headers) {
89        for header_item in &other.headers {
90            if !self
91                .headers
92                .iter()
93                .any(|h| h.key.eq_ignore_ascii_case(&header_item.key))
94            {
95                self.headers.push(header_item.clone());
96            }
97        }
98    }
99    pub fn merge_map(&mut self, other: &HeaderMap) {
100        for (key, value) in other.iter() {
101            if let Ok(value_str) = value.to_str() {
102                self.headers.push(HeaderItem {
103                    key: key.as_str().to_string(),
104                    value: value_str.to_string(),
105                });
106            }
107        }
108    }
109    pub fn is_empty(&self) -> bool {
110        self.headers.is_empty()
111    }
112    pub fn contains(&self, key: impl AsRef<str>) -> bool {
113        self.headers
114            .iter()
115            .any(|header_item| header_item.key.eq_ignore_ascii_case(key.as_ref()))
116    }
117    pub fn get(&self, key: impl AsRef<str>) -> Option<String> {
118        for header_item in &self.headers {
119            if header_item.key.eq_ignore_ascii_case(key.as_ref()) {
120                return Some(header_item.value.clone());
121            }
122        }
123        None
124    }
125}
126
127impl From<&Headers> for HeaderMap {
128    fn from(value: &Headers) -> Self {
129        let mut header_map = HeaderMap::new();
130
131        for header_item in &value.headers {
132            match (
133                header_item.key.parse::<reqwest::header::HeaderName>(),
134                HeaderValue::from_str(&header_item.value),
135            ) {
136                (Ok(name), Ok(value)) => {
137                    // Use `append` for headers that can contain multiple values.
138                    let name_str = name.as_str().to_lowercase();
139                    if matches!(
140                        name_str.as_str(),
141                        // Cookie-related
142                        "set-cookie" | "cookie" |
143                        // Content negotiation
144                        "accept" | "accept-encoding" | "accept-language" | "accept-charset" |
145                        // Cache control
146                        "cache-control" | "pragma" |
147                        // Links and forwarding
148                        "link" | "forwarded" | "x-forwarded-for" | "x-forwarded-proto" |
149                        // Security policies
150                        "content-security-policy" | "x-content-security-policy" |
151                        "x-webkit-csp" | "feature-policy" | "permissions-policy" |
152                        // CORS
153                        "access-control-allow-origin" | "access-control-allow-methods" |
154                        "access-control-allow-headers" | "access-control-expose-headers" |
155                        // Authentication
156                        "www-authenticate" | "proxy-authenticate" |
157                        // Variants and content
158                        "vary" | "via" | "warning" |
159                        // Custom and extension headers
160                        "x-forwarded-host" | "x-real-ip" | "x-original-forwarded-for"
161                    ) {
162                        header_map.append(name, value);
163                    } else {
164                        // Overwrite behavior for other headers.
165                        header_map.insert(name, value);
166                    }
167                }
168                _ => continue,
169            }
170        }
171
172        header_map
173    }
174}
175impl From<HeaderMap> for Headers {
176    fn from(value: HeaderMap) -> Self {
177        let headers = value
178            .iter()
179            .map(|(key, value)| HeaderItem {
180                key: key.as_str().to_string(),
181                value: value.to_str().unwrap_or("").to_string(),
182            })
183            .collect();
184
185        Headers { headers }
186    }
187}
188
189impl CacheAble for Headers {
190    fn field() -> impl AsRef<str> {
191        "headers"
192    }
193}
194
195#[test]
196fn test() {
197    let headers = Headers::new()
198        .add("Content-Type", "application/json")
199        .add("User-Agent", "MyApp/1.0");
200
201    let header_map: HeaderMap = HeaderMap::from(&headers);
202
203    assert_eq!(header_map.get("Content-Type").unwrap(), "application/json");
204    assert_eq!(header_map.get("User-Agent").unwrap(), "MyApp/1.0");
205}