//! Various http headers use std::fmt; use std::str::FromStr; use bytes::{Bytes, BytesMut}; use mime::Mime; use modhttp::header::GetAll; use modhttp::Error as HttpError; use percent_encoding; pub use modhttp::header::*; use error::ParseError; use httpmessage::HttpMessage; #[doc(hidden)] /// A trait for any object that will represent a header field and value. pub trait Header where Self: IntoHeaderValue, { /// Returns the name of the header field fn name() -> HeaderName; /// Parse a header fn parse(msg: &T) -> Result; } #[doc(hidden)] /// A trait for any object that can be Converted to a `HeaderValue` pub trait IntoHeaderValue: Sized { /// The type returned in the event of a conversion error. type Error: Into; /// Try to convert value to a Header value. fn try_into(self) -> Result; } impl IntoHeaderValue for HeaderValue { type Error = InvalidHeaderValue; #[inline] fn try_into(self) -> Result { Ok(self) } } impl<'a> IntoHeaderValue for &'a str { type Error = InvalidHeaderValue; #[inline] fn try_into(self) -> Result { self.parse() } } impl<'a> IntoHeaderValue for &'a [u8] { type Error = InvalidHeaderValue; #[inline] fn try_into(self) -> Result { HeaderValue::from_bytes(self) } } impl IntoHeaderValue for Bytes { type Error = InvalidHeaderValueBytes; #[inline] fn try_into(self) -> Result { HeaderValue::from_shared(self) } } impl IntoHeaderValue for Vec { type Error = InvalidHeaderValueBytes; #[inline] fn try_into(self) -> Result { HeaderValue::from_shared(Bytes::from(self)) } } impl IntoHeaderValue for String { type Error = InvalidHeaderValueBytes; #[inline] fn try_into(self) -> Result { HeaderValue::from_shared(Bytes::from(self)) } } impl IntoHeaderValue for Mime { type Error = InvalidHeaderValueBytes; #[inline] fn try_into(self) -> Result { HeaderValue::from_shared(Bytes::from(format!("{}", self))) } } /// Represents supported types of content encodings #[derive(Copy, Clone, PartialEq, Debug)] pub enum ContentEncoding { /// Automatically select encoding based on encoding negotiation Auto, /// A format using the Brotli algorithm Br, /// A format using the zlib structure with deflate algorithm Deflate, /// Gzip algorithm Gzip, /// Indicates the identity function (i.e. no compression, nor modification) Identity, } impl ContentEncoding { #[inline] /// Is the content compressed? pub fn is_compression(self) -> bool { match self { ContentEncoding::Identity | ContentEncoding::Auto => false, _ => true, } } #[inline] /// Convert content encoding to string pub fn as_str(self) -> &'static str { match self { ContentEncoding::Br => "br", ContentEncoding::Gzip => "gzip", ContentEncoding::Deflate => "deflate", ContentEncoding::Identity | ContentEncoding::Auto => "identity", } } #[inline] /// default quality value pub fn quality(self) -> f64 { match self { ContentEncoding::Br => 1.1, ContentEncoding::Gzip => 1.0, ContentEncoding::Deflate => 0.9, ContentEncoding::Identity | ContentEncoding::Auto => 0.1, } } } // TODO: remove memory allocation impl<'a> From<&'a str> for ContentEncoding { fn from(s: &'a str) -> ContentEncoding { match AsRef::::as_ref(&s.trim().to_lowercase()) { "br" => ContentEncoding::Br, "gzip" => ContentEncoding::Gzip, "deflate" => ContentEncoding::Deflate, _ => ContentEncoding::Identity, } } } #[doc(hidden)] pub(crate) struct Writer { buf: BytesMut, } impl Writer { fn new() -> Writer { Writer { buf: BytesMut::new(), } } fn take(&mut self) -> Bytes { self.buf.take().freeze() } } impl fmt::Write for Writer { #[inline] fn write_str(&mut self, s: &str) -> fmt::Result { self.buf.extend_from_slice(s.as_bytes()); Ok(()) } #[inline] fn write_fmt(&mut self, args: fmt::Arguments) -> fmt::Result { fmt::write(self, args) } } #[inline] #[doc(hidden)] /// Reads a comma-delimited raw header into a Vec. pub fn from_comma_delimited( all: GetAll, ) -> Result, ParseError> { let mut result = Vec::new(); for h in all { let s = h.to_str().map_err(|_| ParseError::Header)?; result.extend( s.split(',') .filter_map(|x| match x.trim() { "" => None, y => Some(y), }).filter_map(|x| x.trim().parse().ok()), ) } Ok(result) } #[inline] #[doc(hidden)] /// Reads a single string when parsing a header. pub fn from_one_raw_str(val: Option<&HeaderValue>) -> Result { if let Some(line) = val { let line = line.to_str().map_err(|_| ParseError::Header)?; if !line.is_empty() { return T::from_str(line).or(Err(ParseError::Header)); } } Err(ParseError::Header) } #[inline] #[doc(hidden)] /// Format an array into a comma-delimited string. pub fn fmt_comma_delimited(f: &mut fmt::Formatter, parts: &[T]) -> fmt::Result where T: fmt::Display, { let mut iter = parts.iter(); if let Some(part) = iter.next() { fmt::Display::fmt(part, f)?; } for part in iter { f.write_str(", ")?; fmt::Display::fmt(part, f)?; } Ok(()) } /// Percent encode a sequence of bytes with a character set defined in /// [https://tools.ietf.org/html/rfc5987#section-3.2][url] /// /// [url]: https://tools.ietf.org/html/rfc5987#section-3.2 pub fn http_percent_encode(f: &mut fmt::Formatter, bytes: &[u8]) -> fmt::Result { let encoded = percent_encoding::percent_encode(bytes, self::percent_encoding_http::HTTP_VALUE); fmt::Display::fmt(&encoded, f) } mod percent_encoding_http { use percent_encoding; // internal module because macro is hard-coded to make a public item // but we don't want to public export this item define_encode_set! { // This encode set is used for HTTP header values and is defined at // https://tools.ietf.org/html/rfc5987#section-3.2 pub HTTP_VALUE = [percent_encoding::SIMPLE_ENCODE_SET] | { ' ', '"', '%', '\'', '(', ')', '*', ',', '/', ':', ';', '<', '-', '>', '?', '[', '\\', ']', '{', '}' } } }