clankerdiff_client/
header.rs1use http::{HeaderName, HeaderValue};
2use std::{fmt, str::FromStr};
3use thiserror::Error;
4
5#[derive(Clone)]
6pub struct ConnectionHeader {
7 name: HeaderName,
8 value: HeaderValue,
9}
10
11impl ConnectionHeader {
12 pub fn name(&self) -> &HeaderName {
13 &self.name
14 }
15
16 pub fn value(&self) -> &HeaderValue {
17 &self.value
18 }
19}
20
21impl fmt::Debug for ConnectionHeader {
22 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
23 formatter
24 .debug_struct("ConnectionHeader")
25 .field("name", &self.name)
26 .field("value", &"<redacted>")
27 .finish()
28 }
29}
30
31impl FromStr for ConnectionHeader {
32 type Err = ConnectionHeaderError;
33
34 fn from_str(header: &str) -> Result<Self, Self::Err> {
35 let (name, value) = header
36 .split_once(':')
37 .ok_or(ConnectionHeaderError::MissingSeparator)?;
38 let name = name.trim();
39 if name.is_empty() {
40 return Err(ConnectionHeaderError::EmptyName);
41 }
42 let name = HeaderName::from_bytes(name.as_bytes())
43 .map_err(|_| ConnectionHeaderError::InvalidName)?;
44 if is_reserved(&name) {
45 return Err(ConnectionHeaderError::ReservedName(name.to_string()));
46 }
47 let mut value =
48 HeaderValue::from_str(value.trim()).map_err(|_| ConnectionHeaderError::InvalidValue)?;
49 value.set_sensitive(true);
50 Ok(Self { name, value })
51 }
52}
53
54#[derive(Debug, Error)]
55pub enum ConnectionHeaderError {
56 #[error("header must use NAME: VALUE syntax")]
57 MissingSeparator,
58 #[error("header name must not be empty")]
59 EmptyName,
60 #[error("invalid header name")]
61 InvalidName,
62 #[error("invalid value for header")]
63 InvalidValue,
64 #[error("header `{0}` is managed by the WebSocket client")]
65 ReservedName(String),
66}
67
68fn is_reserved(name: &HeaderName) -> bool {
69 matches!(
70 name.as_str(),
71 "host"
72 | "connection"
73 | "upgrade"
74 | "sec-websocket-key"
75 | "sec-websocket-version"
76 | "sec-websocket-protocol"
77 | "sec-websocket-extensions"
78 )
79}