use bytes::{Bytes, BytesMut}; use std::convert::TryFrom; use std::error::Error; use std::str::FromStr; use std::{cmp, fmt, mem, str}; use crate::header::name::HeaderName; /// Represents an HTTP header field value. /// /// In practice, HTTP header field values are usually valid ASCII. However, the /// HTTP spec allows for a header value to contain opaque bytes as well. In this /// case, the header field value is not able to be represented as a string. /// /// To handle this, the `HeaderValue` is useable as a type and can be compared /// with strings and implements `Debug`. A `to_str` fn is provided that returns /// an `Err` if the header value contains non visible ascii characters. #[derive(Clone, Hash)] pub struct HeaderValue { inner: Bytes, is_sensitive: bool, } /// A possible error when converting a `HeaderValue` from a string or byte /// slice. pub struct InvalidHeaderValue { _priv: (), } /// A possible error when converting a `HeaderValue` to a string representation. /// /// Header field values may contain opaque bytes, in which case it is not /// possible to represent the value as a string. #[derive(Debug)] pub struct ToStrError { _priv: (), } impl HeaderValue { /// Convert a static string to a `HeaderValue`. /// /// This function will not perform any copying, however the string is /// checked to ensure that no invalid characters are present. Only visible /// ASCII characters (32-127) are permitted. /// /// # Panics /// /// This function panics if the argument contains invalid header value /// characters. /// /// Until [Allow panicking in constants](https://github.com/rust-lang/rfcs/pull/2345) /// makes its way into stable, the panic message at compile-time is /// going to look cryptic, but should at least point at your header value: /// /// ```text /// error: any use of this value will cause an error /// --> http/src/header/value.rs:67:17 /// | /// 67 | ([] as [u8; 0])[0]; // Invalid header value /// | ^^^^^^^^^^^^^^^^^^ /// | | /// | index out of bounds: the length is 0 but the index is 0 /// | inside `HeaderValue::from_static` at http/src/header/value.rs:67:17 /// | inside `INVALID_HEADER` at src/main.rs:73:33 /// | /// ::: src/main.rs:73:1 /// | /// 73 | const INVALID_HEADER: HeaderValue = HeaderValue::from_static("жsome value"); /// | ---------------------------------------------------------------------------- /// ``` /// /// # Examples /// /// ``` /// # use http::header::HeaderValue; /// let val = HeaderValue::from_static("hello"); /// assert_eq!(val, "hello"); /// ``` #[inline] #[allow(unconditional_panic)] // required for the panic circumventon pub const fn from_static(src: &'static str) -> HeaderValue { let bytes = src.as_bytes(); let mut i = 0; while i < bytes.len() { if !is_visible_ascii(bytes[i]) { ([] as [u8; 0])[0]; // Invalid header value } i += 1; } HeaderValue { inner: Bytes::from_static(bytes), is_sensitive: false, } } /// Attempt to convert a string to a `HeaderValue`. /// /// If the argument contains invalid header value characters, an error is /// returned. Only visible ASCII characters (32-127) are permitted. Use /// `from_bytes` to create a `HeaderValue` that includes opaque octets /// (128-255). /// /// This function is intended to be replaced in the future by a `TryFrom` /// implementation once the trait is stabilized in std. /// /// # Examples /// /// ``` /// # use http::header::HeaderValue; /// let val = HeaderValue::from_str("hello").unwrap(); /// assert_eq!(val, "hello"); /// ``` /// /// An invalid value /// /// ``` /// # use http::header::HeaderValue; /// let val = HeaderValue::from_str("\n"); /// assert!(val.is_err()); /// ``` #[inline] pub fn from_str(src: &str) -> Result { HeaderValue::try_from_generic(src, |s| Bytes::copy_from_slice(s.as_bytes())) } /// Converts a HeaderName into a HeaderValue /// /// Since every valid HeaderName is a valid HeaderValue this is done infallibly. /// /// # Examples /// /// ``` /// # use http::header::{HeaderValue, HeaderName}; /// # use http::header::ACCEPT; /// let val = HeaderValue::from_name(ACCEPT); /// assert_eq!(val, HeaderValue::from_bytes(b"accept").unwrap()); /// ``` #[inline] pub fn from_name(name: HeaderName) -> HeaderValue { name.into() } /// Attempt to convert a byte slice to a `HeaderValue`. /// /// If the argument contains invalid header value bytes, an error is /// returned. Only byte values between 32 and 255 (inclusive) are permitted, /// excluding byte 127 (DEL). /// /// This function is intended to be replaced in the future by a `TryFrom` /// implementation once the trait is stabilized in std. /// /// # Examples /// /// ``` /// # use http::header::HeaderValue; /// let val = HeaderValue::from_bytes(b"hello\xfa").unwrap(); /// assert_eq!(val, &b"hello\xfa"[..]); /// ``` /// /// An invalid value /// /// ``` /// # use http::header::HeaderValue; /// let val = HeaderValue::from_bytes(b"\n"); /// assert!(val.is_err()); /// ``` #[inline] pub fn from_bytes(src: &[u8]) -> Result { HeaderValue::try_from_generic(src, Bytes::copy_from_slice) } /// Attempt to convert a `Bytes` buffer to a `HeaderValue`. /// /// This will try to prevent a copy if the type passed is the type used /// internally, and will copy the data if it is not. pub fn from_maybe_shared(src: T) -> Result where T: AsRef<[u8]> + 'static, { if_downcast_into!(T, Bytes, src, { return HeaderValue::from_shared(src); }); HeaderValue::from_bytes(src.as_ref()) } /// Convert a `Bytes` directly into a `HeaderValue` without validating. /// /// This function does NOT validate that illegal bytes are not contained /// within the buffer. pub unsafe fn from_maybe_shared_unchecked(src: T) -> HeaderValue where T: AsRef<[u8]> + 'static, { if cfg!(debug_assertions) { match HeaderValue::from_maybe_shared(src) { Ok(val) => val, Err(_err) => { panic!("HeaderValue::from_maybe_shared_unchecked() with invalid bytes"); } } } else { if_downcast_into!(T, Bytes, src, { return HeaderValue { inner: src, is_sensitive: false, }; }); let src = Bytes::copy_from_slice(src.as_ref()); HeaderValue { inner: src, is_sensitive: false, } } } fn from_shared(src: Bytes) -> Result { HeaderValue::try_from_generic(src, std::convert::identity) } fn try_from_generic, F: FnOnce(T) -> Bytes>(src: T, into: F) -> Result { for &b in src.as_ref() { if !is_valid(b) { return Err(InvalidHeaderValue { _priv: () }); } } Ok(HeaderValue { inner: into(src), is_sensitive: false, }) } /// Yields a `&str` slice if the `HeaderValue` only contains visible ASCII /// chars. /// /// This function will perform a scan of the header value, checking all the /// characters. /// /// # Examples /// /// ``` /// # use http::header::HeaderValue; /// let val = HeaderValue::from_static("hello"); /// assert_eq!(val.to_str().unwrap(), "hello"); /// ``` pub fn to_str(&self) -> Result<&str, ToStrError> { let bytes = self.as_ref(); for &b in bytes { if !is_visible_ascii(b) { return Err(ToStrError { _priv: () }); } } unsafe { Ok(str::from_utf8_unchecked(bytes)) } } /// Returns the length of `self`. /// /// This length is in bytes. /// /// # Examples /// /// ``` /// # use http::header::HeaderValue; /// let val = HeaderValue::from_static("hello"); /// assert_eq!(val.len(), 5); /// ``` #[inline] pub fn len(&self) -> usize { self.as_ref().len() } /// Returns true if the `HeaderValue` has a length of zero bytes. /// /// # Examples /// /// ``` /// # use http::header::HeaderValue; /// let val = HeaderValue::from_static(""); /// assert!(val.is_empty()); /// /// let val = HeaderValue::from_static("hello"); /// assert!(!val.is_empty()); /// ``` #[inline] pub fn is_empty(&self) -> bool { self.len() == 0 } /// Converts a `HeaderValue` to a byte slice. /// /// # Examples /// /// ``` /// # use http::header::HeaderValue; /// let val = HeaderValue::from_static("hello"); /// assert_eq!(val.as_bytes(), b"hello"); /// ``` #[inline] pub fn as_bytes(&self) -> &[u8] { self.as_ref() } /// Mark that the header value represents sensitive information. /// /// # Examples /// /// ``` /// # use http::header::HeaderValue; /// let mut val = HeaderValue::from_static("my secret"); /// /// val.set_sensitive(true); /// assert!(val.is_sensitive()); /// /// val.set_sensitive(false); /// assert!(!val.is_sensitive()); /// ``` #[inline] pub fn set_sensitive(&mut self, val: bool) { self.is_sensitive = val; } /// Returns `true` if the value represents sensitive data. /// /// Sensitive data could represent passwords or other data that should not /// be stored on disk or in memory. By marking header values as sensitive, /// components using this crate can be instructed to treat them with special /// care for security reasons. For example, caches can avoid storing /// sensitive values, and HPACK encoders used by HTTP/2.0 implementations /// can choose not to compress them. /// /// Additionally, sensitive values will be masked by the `Debug` /// implementation of `HeaderValue`. /// /// Note that sensitivity is not factored into equality or ordering. /// /// # Examples /// /// ``` /// # use http::header::HeaderValue; /// let mut val = HeaderValue::from_static("my secret"); /// /// val.set_sensitive(true); /// assert!(val.is_sensitive()); /// /// val.set_sensitive(false); /// assert!(!val.is_sensitive()); /// ``` #[inline] pub fn is_sensitive(&self) -> bool { self.is_sensitive } } impl AsRef<[u8]> for HeaderValue { #[inline] fn as_ref(&self) -> &[u8] { self.inner.as_ref() } } impl fmt::Debug for HeaderValue { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { if self.is_sensitive { f.write_str("Sensitive") } else { f.write_str("\"")?; let mut from = 0; let bytes = self.as_bytes(); for (i, &b) in bytes.iter().enumerate() { if !is_visible_ascii(b) || b == b'"' { if from != i { f.write_str(unsafe { str::from_utf8_unchecked(&bytes[from..i]) })?; } if b == b'"' { f.write_str("\\\"")?; } else { write!(f, "\\x{:x}", b)?; } from = i + 1; } } f.write_str(unsafe { str::from_utf8_unchecked(&bytes[from..]) })?; f.write_str("\"") } } } impl From for HeaderValue { #[inline] fn from(h: HeaderName) -> HeaderValue { HeaderValue { inner: h.into_bytes(), is_sensitive: false, } } } macro_rules! from_integers { ($($name:ident: $t:ident => $max_len:expr),*) => {$( impl From<$t> for HeaderValue { fn from(num: $t) -> HeaderValue { let mut buf = if mem::size_of::() - 1 < $max_len { // On 32bit platforms, BytesMut max inline size // is 15 bytes, but the $max_len could be bigger. // // The likelihood of the number *actually* being // that big is very small, so only allocate // if the number needs that space. // // The largest decimal number in 15 digits: // It wold be 10.pow(15) - 1, but this is a constant // version. if num as u64 > 999_999_999_999_999_999 { BytesMut::with_capacity($max_len) } else { // fits inline... BytesMut::new() } } else { // full value fits inline, so don't allocate! BytesMut::new() }; let _ = ::itoa::fmt(&mut buf, num); HeaderValue { inner: buf.freeze(), is_sensitive: false, } } } #[test] fn $name() { let n: $t = 55; let val = HeaderValue::from(n); assert_eq!(val, &n.to_string()); let n = ::std::$t::MAX; let val = HeaderValue::from(n); assert_eq!(val, &n.to_string()); } )*}; } from_integers! { // integer type => maximum decimal length // u8 purposely left off... HeaderValue::from(b'3') could be confusing from_u16: u16 => 5, from_i16: i16 => 6, from_u32: u32 => 10, from_i32: i32 => 11, from_u64: u64 => 20, from_i64: i64 => 20 } #[cfg(target_pointer_width = "16")] from_integers! { from_usize: usize => 5, from_isize: isize => 6 } #[cfg(target_pointer_width = "32")] from_integers! { from_usize: usize => 10, from_isize: isize => 11 } #[cfg(target_pointer_width = "64")] from_integers! { from_usize: usize => 20, from_isize: isize => 20 } #[cfg(test)] mod from_header_name_tests { use super::*; use crate::header::map::HeaderMap; use crate::header::name; #[test] fn it_can_insert_header_name_as_header_value() { let mut map = HeaderMap::new(); map.insert(name::UPGRADE, name::SEC_WEBSOCKET_PROTOCOL.into()); map.insert( name::ACCEPT, name::HeaderName::from_bytes(b"hello-world").unwrap().into(), ); assert_eq!( map.get(name::UPGRADE).unwrap(), HeaderValue::from_bytes(b"sec-websocket-protocol").unwrap() ); assert_eq!( map.get(name::ACCEPT).unwrap(), HeaderValue::from_bytes(b"hello-world").unwrap() ); } } impl FromStr for HeaderValue { type Err = InvalidHeaderValue; #[inline] fn from_str(s: &str) -> Result { HeaderValue::from_str(s) } } impl<'a> From<&'a HeaderValue> for HeaderValue { #[inline] fn from(t: &'a HeaderValue) -> Self { t.clone() } } impl<'a> TryFrom<&'a str> for HeaderValue { type Error = InvalidHeaderValue; #[inline] fn try_from(t: &'a str) -> Result { t.parse() } } impl<'a> TryFrom<&'a String> for HeaderValue { type Error = InvalidHeaderValue; #[inline] fn try_from(s: &'a String) -> Result { Self::from_bytes(s.as_bytes()) } } impl<'a> TryFrom<&'a [u8]> for HeaderValue { type Error = InvalidHeaderValue; #[inline] fn try_from(t: &'a [u8]) -> Result { HeaderValue::from_bytes(t) } } impl TryFrom for HeaderValue { type Error = InvalidHeaderValue; #[inline] fn try_from(t: String) -> Result { HeaderValue::from_shared(t.into()) } } impl TryFrom> for HeaderValue { type Error = InvalidHeaderValue; #[inline] fn try_from(vec: Vec) -> Result { HeaderValue::from_shared(vec.into()) } } #[cfg(test)] mod try_from_header_name_tests { use super::*; use crate::header::name; #[test] fn it_converts_using_try_from() { assert_eq!( HeaderValue::try_from(name::UPGRADE).unwrap(), HeaderValue::from_bytes(b"upgrade").unwrap() ); } } const fn is_visible_ascii(b: u8) -> bool { b >= 32 && b < 127 || b == b'\t' } #[inline] fn is_valid(b: u8) -> bool { b >= 32 && b != 127 || b == b'\t' } impl fmt::Debug for InvalidHeaderValue { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_struct("InvalidHeaderValue") // skip _priv noise .finish() } } impl fmt::Display for InvalidHeaderValue { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str("failed to parse header value") } } impl Error for InvalidHeaderValue {} impl fmt::Display for ToStrError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str("failed to convert header to a str") } } impl Error for ToStrError {} // ===== PartialEq / PartialOrd ===== impl PartialEq for HeaderValue { #[inline] fn eq(&self, other: &HeaderValue) -> bool { self.inner == other.inner } } impl Eq for HeaderValue {} impl PartialOrd for HeaderValue { #[inline] fn partial_cmp(&self, other: &HeaderValue) -> Option { self.inner.partial_cmp(&other.inner) } } impl Ord for HeaderValue { #[inline] fn cmp(&self, other: &Self) -> cmp::Ordering { self.inner.cmp(&other.inner) } } impl PartialEq for HeaderValue { #[inline] fn eq(&self, other: &str) -> bool { self.inner == other.as_bytes() } } impl PartialEq<[u8]> for HeaderValue { #[inline] fn eq(&self, other: &[u8]) -> bool { self.inner == other } } impl PartialOrd for HeaderValue { #[inline] fn partial_cmp(&self, other: &str) -> Option { (*self.inner).partial_cmp(other.as_bytes()) } } impl PartialOrd<[u8]> for HeaderValue { #[inline] fn partial_cmp(&self, other: &[u8]) -> Option { (*self.inner).partial_cmp(other) } } impl PartialEq for str { #[inline] fn eq(&self, other: &HeaderValue) -> bool { *other == *self } } impl PartialEq for [u8] { #[inline] fn eq(&self, other: &HeaderValue) -> bool { *other == *self } } impl PartialOrd for str { #[inline] fn partial_cmp(&self, other: &HeaderValue) -> Option { self.as_bytes().partial_cmp(other.as_bytes()) } } impl PartialOrd for [u8] { #[inline] fn partial_cmp(&self, other: &HeaderValue) -> Option { self.partial_cmp(other.as_bytes()) } } impl PartialEq for HeaderValue { #[inline] fn eq(&self, other: &String) -> bool { *self == &other[..] } } impl PartialOrd for HeaderValue { #[inline] fn partial_cmp(&self, other: &String) -> Option { self.inner.partial_cmp(other.as_bytes()) } } impl PartialEq for String { #[inline] fn eq(&self, other: &HeaderValue) -> bool { *other == *self } } impl PartialOrd for String { #[inline] fn partial_cmp(&self, other: &HeaderValue) -> Option { self.as_bytes().partial_cmp(other.as_bytes()) } } impl<'a> PartialEq for &'a HeaderValue { #[inline] fn eq(&self, other: &HeaderValue) -> bool { **self == *other } } impl<'a> PartialOrd for &'a HeaderValue { #[inline] fn partial_cmp(&self, other: &HeaderValue) -> Option { (**self).partial_cmp(other) } } impl<'a, T: ?Sized> PartialEq<&'a T> for HeaderValue where HeaderValue: PartialEq, { #[inline] fn eq(&self, other: &&'a T) -> bool { *self == **other } } impl<'a, T: ?Sized> PartialOrd<&'a T> for HeaderValue where HeaderValue: PartialOrd, { #[inline] fn partial_cmp(&self, other: &&'a T) -> Option { self.partial_cmp(*other) } } impl<'a> PartialEq for &'a str { #[inline] fn eq(&self, other: &HeaderValue) -> bool { *other == *self } } impl<'a> PartialOrd for &'a str { #[inline] fn partial_cmp(&self, other: &HeaderValue) -> Option { self.as_bytes().partial_cmp(other.as_bytes()) } } #[test] fn test_try_from() { HeaderValue::try_from(vec![127]).unwrap_err(); } #[test] fn test_debug() { let cases = &[ ("hello", "\"hello\""), ("hello \"world\"", "\"hello \\\"world\\\"\""), ("\u{7FFF}hello", "\"\\xe7\\xbf\\xbfhello\""), ]; for &(value, expected) in cases { let val = HeaderValue::from_bytes(value.as_bytes()).unwrap(); let actual = format!("{:?}", val); assert_eq!(expected, actual); } let mut sensitive = HeaderValue::from_static("password"); sensitive.set_sensitive(true); assert_eq!("Sensitive", format!("{:?}", sensitive)); }