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