ferrin_spec/shared/
headers.rs1use std::fmt;
4
5use http::HeaderMap;
6use http::HeaderName;
7use http::HeaderValue;
8use http::header::USER_AGENT;
9use serde::Deserialize;
10use serde::Serialize;
11
12const SENSITIVE_HEADERS: &[&str] = &[
14 "authorization",
15 "proxy-authorization",
16 "x-api-key",
17 "api-key",
18 "x-goog-api-key",
19 "cookie",
20 "set-cookie",
21];
22
23const MASKED: &str = "***";
25
26#[derive(Clone, Default, PartialEq, Eq)]
33pub struct Headers(HeaderMap);
34
35impl Headers {
36 #[must_use]
38 pub fn new() -> Self {
39 Self(HeaderMap::new())
40 }
41
42 #[must_use]
44 pub fn from_map(map: HeaderMap) -> Self {
45 Self(map)
46 }
47
48 #[must_use]
50 pub fn as_map(&self) -> &HeaderMap {
51 &self.0
52 }
53
54 pub fn as_map_mut(&mut self) -> &mut HeaderMap {
56 &mut self.0
57 }
58
59 #[must_use]
61 pub fn into_map(self) -> HeaderMap {
62 self.0
63 }
64
65 #[must_use]
67 pub fn is_empty(&self) -> bool {
68 self.0.is_empty()
69 }
70
71 #[must_use]
73 pub fn len(&self) -> usize {
74 self.0.len()
75 }
76
77 #[must_use]
79 pub fn get_str(&self, name: &str) -> Option<&str> {
80 self.0.get(name).and_then(|value| value.to_str().ok())
81 }
82
83 #[must_use]
85 pub fn contains(&self, name: &str) -> bool {
86 self.0.contains_key(name)
87 }
88
89 pub fn insert(&mut self, name: &str, value: &str) -> Result<(), InvalidHeader> {
98 let name = HeaderName::from_bytes(name.as_bytes()).map_err(|_| InvalidHeader {
99 name: name.to_owned(),
100 })?;
101 let value = HeaderValue::from_str(value).map_err(|_| InvalidHeader {
102 name: name.to_string(),
103 })?;
104 self.0.insert(name, value);
105 Ok(())
106 }
107
108 #[must_use]
113 pub fn with(mut self, name: &str, value: &str) -> Self {
114 let _ = self.insert(name, value);
115 self
116 }
117
118 pub fn remove(&mut self, name: &str) {
120 self.0.remove(name);
121 }
122
123 pub fn merge(&mut self, other: &Headers) {
129 let mut current: Option<HeaderName> = None;
130 for (name, value) in &other.0 {
131 if current.as_ref() != Some(name) {
132 self.0.remove(name);
133 current = Some(name.clone());
134 }
135 self.0.append(name.clone(), value.clone());
136 }
137 }
138
139 #[must_use]
141 pub fn merged(mut self, other: &Headers) -> Self {
142 self.merge(other);
143 self
144 }
145
146 pub fn merge_pairs<'a, I>(&mut self, pairs: I)
151 where
152 I: IntoIterator<Item = (&'a str, Option<&'a str>)>,
153 {
154 for (name, value) in pairs {
155 if let Some(value) = value {
156 let _ = self.insert(name, value);
157 }
158 }
159 }
160
161 pub fn with_user_agent_suffix<'a, I>(mut self, suffixes: I) -> Self
166 where
167 I: IntoIterator<Item = &'a str>,
168 {
169 let mut parts: Vec<String> = Vec::new();
170 if let Some(existing) = self.get_str(USER_AGENT.as_str()) {
171 let existing = existing.trim();
172 if !existing.is_empty() {
173 parts.push(existing.to_owned());
174 }
175 }
176 parts.extend(
177 suffixes
178 .into_iter()
179 .map(str::trim)
180 .filter(|s| !s.is_empty())
181 .map(str::to_owned),
182 );
183 let joined = parts.join(" ");
184 if let Ok(value) = HeaderValue::from_str(&joined) {
185 self.0.insert(USER_AGENT, value);
186 }
187 self
188 }
189
190 pub fn iter_str(&self) -> impl Iterator<Item = (&str, String)> + '_ {
194 self.0.iter().map(|(name, value)| {
195 (
196 name.as_str(),
197 String::from_utf8_lossy(value.as_bytes()).into_owned(),
198 )
199 })
200 }
201
202 #[must_use]
204 pub fn masked(&self) -> Headers {
205 let mut masked = HeaderMap::with_capacity(self.0.len());
206 for (name, value) in &self.0 {
207 if is_sensitive(name) {
208 masked.append(name.clone(), HeaderValue::from_static(MASKED));
209 } else {
210 masked.append(name.clone(), value.clone());
211 }
212 }
213 Headers(masked)
214 }
215}
216
217fn is_sensitive(name: &HeaderName) -> bool {
218 SENSITIVE_HEADERS.contains(&name.as_str())
219}
220
221impl fmt::Debug for Headers {
222 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
223 let mut map = f.debug_map();
224 for (name, value) in &self.0 {
225 if is_sensitive(name) {
226 map.entry(&name.as_str(), &MASKED);
227 } else {
228 map.entry(&name.as_str(), &String::from_utf8_lossy(value.as_bytes()));
229 }
230 }
231 map.finish()
232 }
233}
234
235impl From<HeaderMap> for Headers {
236 fn from(map: HeaderMap) -> Self {
237 Self(map)
238 }
239}
240
241impl From<Headers> for HeaderMap {
242 fn from(headers: Headers) -> Self {
243 headers.0
244 }
245}
246
247impl<'a> IntoIterator for &'a Headers {
248 type Item = (&'a HeaderName, &'a HeaderValue);
249 type IntoIter = http::header::Iter<'a, HeaderValue>;
250
251 fn into_iter(self) -> Self::IntoIter {
252 self.0.iter()
253 }
254}
255
256impl Serialize for Headers {
259 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
260 use serde::ser::SerializeMap;
261 let mut entries: Vec<(&str, String)> = Vec::with_capacity(self.0.len());
262 for name in self.0.keys() {
263 let rendered = if is_sensitive(name) {
264 MASKED.to_owned()
265 } else {
266 self.0
267 .get_all(name)
268 .iter()
269 .map(|v| String::from_utf8_lossy(v.as_bytes()).into_owned())
270 .collect::<Vec<_>>()
271 .join(", ")
272 };
273 entries.push((name.as_str(), rendered));
274 }
275 let mut map = serializer.serialize_map(Some(entries.len()))?;
276 for (name, value) in entries {
277 map.serialize_entry(name, &value)?;
278 }
279 map.end()
280 }
281}
282
283impl<'de> Deserialize<'de> for Headers {
286 fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
287 struct HeadersVisitor;
288
289 impl<'de> serde::de::Visitor<'de> for HeadersVisitor {
290 type Value = Headers;
291
292 fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
293 f.write_str("a map of header names to string values")
294 }
295
296 fn visit_map<A: serde::de::MapAccess<'de>>(
297 self,
298 mut map: A,
299 ) -> Result<Headers, A::Error> {
300 let mut headers = Headers::new();
301 while let Some((name, value)) = map.next_entry::<String, String>()? {
302 headers
303 .insert(&name, &value)
304 .map_err(serde::de::Error::custom)?;
305 }
306 Ok(headers)
307 }
308 }
309
310 deserializer.deserialize_map(HeadersVisitor)
311 }
312}
313
314#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
316#[error("invalid header `{name}`")]
317pub struct InvalidHeader {
318 pub name: String,
320}