watermelon_proto/headers/
value.rs

1use alloc::string::String;
2use core::{
3    fmt::{self, Display},
4    ops::Deref,
5};
6
7use bytestring::ByteString;
8
9/// A string that can be used to represent an header value
10///
11/// `HeaderValue` contains a string that is guaranteed [^1] to
12/// contain a valid header value that meets the following requirements:
13///
14/// * The value is not empty
15/// * The value has a length less than or equal to 1024 [^2]
16/// * The value does not contain any whitespace characters
17///
18/// `HeaderValue` can be constructed from [`HeaderValue::from_static`]
19/// or any of the `TryFrom` implementations.
20///
21/// [^1]: Because [`HeaderValue::from_dangerous_value`] is safe to call,
22///       unsafe code must not assume any of the above invariants.
23/// [^2]: Messages coming from the NATS server are allowed to violate this rule.
24#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
25pub struct HeaderValue(ByteString);
26
27impl HeaderValue {
28    /// Construct `HeaderValue` from a static string
29    ///
30    /// # Panics
31    ///
32    /// Will panic if `value` isn't a valid `HeaderValue`
33    #[must_use]
34    pub fn from_static(value: &'static str) -> Self {
35        Self::try_from(ByteString::from_static(value)).expect("invalid HeaderValue")
36    }
37
38    /// Construct a `HeaderValue` from a string, without checking invariants
39    ///
40    /// This method bypasses invariants checks implemented by [`HeaderValue::from_static`]
41    /// and all `TryFrom` implementations.
42    ///
43    /// # Security
44    ///
45    /// While calling this method can eliminate the runtime performance cost of
46    /// checking the string, constructing `HeaderValue` with an invalid string and
47    /// then calling the NATS server with it can cause serious security issues.
48    /// When in doubt use the [`HeaderValue::from_static`] or any of the `TryFrom`
49    /// implementations.
50    #[must_use]
51    #[expect(
52        clippy::missing_panics_doc,
53        reason = "The header validation is only made in debug"
54    )]
55    pub fn from_dangerous_value(value: ByteString) -> Self {
56        if cfg!(debug_assertions) {
57            if let Err(err) = validate_header_value(&value) {
58                panic!("HeaderValue {value:?} isn't valid {err:?}");
59            }
60        }
61        Self(value)
62    }
63
64    #[must_use]
65    pub fn as_str(&self) -> &str {
66        &self.0
67    }
68}
69
70impl Display for HeaderValue {
71    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
72        Display::fmt(&self.0, f)
73    }
74}
75
76impl TryFrom<ByteString> for HeaderValue {
77    type Error = HeaderValueValidateError;
78
79    fn try_from(value: ByteString) -> Result<Self, Self::Error> {
80        validate_header_value(&value)?;
81        Ok(Self::from_dangerous_value(value))
82    }
83}
84
85impl TryFrom<String> for HeaderValue {
86    type Error = HeaderValueValidateError;
87
88    fn try_from(value: String) -> Result<Self, Self::Error> {
89        validate_header_value(&value)?;
90        Ok(Self::from_dangerous_value(value.into()))
91    }
92}
93
94impl From<HeaderValue> for ByteString {
95    fn from(value: HeaderValue) -> Self {
96        value.0
97    }
98}
99
100impl AsRef<[u8]> for HeaderValue {
101    fn as_ref(&self) -> &[u8] {
102        self.as_str().as_bytes()
103    }
104}
105
106impl AsRef<str> for HeaderValue {
107    fn as_ref(&self) -> &str {
108        self.as_str()
109    }
110}
111
112impl Deref for HeaderValue {
113    type Target = str;
114
115    fn deref(&self) -> &Self::Target {
116        self.as_str()
117    }
118}
119
120/// An error encountered while validating [`HeaderValue`]
121#[derive(Debug, thiserror::Error)]
122pub enum HeaderValueValidateError {
123    /// The value is empty
124    #[error("HeaderValue is empty")]
125    Empty,
126    /// The value has a length greater than 64
127    #[error("HeaderValue is too long")]
128    TooLong,
129    /// The value contains an Unicode whitespace character
130    #[error("HeaderValue contained an illegal whitespace character")]
131    IllegalCharacter,
132}
133
134fn validate_header_value(header_value: &str) -> Result<(), HeaderValueValidateError> {
135    if header_value.is_empty() {
136        return Err(HeaderValueValidateError::Empty);
137    }
138
139    if header_value.len() > 1024 {
140        // This is an arbitrary limit, but I guess the server must also have one
141        return Err(HeaderValueValidateError::TooLong);
142    }
143
144    if header_value.chars().any(char::is_whitespace) {
145        // The theoretical security limit is just ` `, `\t`, `\r` and `\n`.
146        // Let's be more careful.
147        return Err(HeaderValueValidateError::IllegalCharacter);
148    }
149
150    Ok(())
151}