//! Headers container, and common header fields. //! //! hyper has the opinion that Headers should be strongly-typed, because that's //! why we're using Rust in the first place. To set or get any header, an object //! must implement the `Header` trait from this module. Several common headers //! are already provided, such as `Host`, `ContentType`, `UserAgent`, and others. //! //! ## Mime //! //! Several header fields use MIME values for their contents. Keeping with the //! strongly-typed theme, the [mime](http://seanmonstar.github.io/mime.rs) crate //! is used, such as `ContentType(pub Mime)`. use std::any::Any; use std::ascii::OwnedAsciiExt; use std::char::is_lowercase; use std::fmt::{mod, Show}; use std::from_str::{FromStr, from_str}; use std::mem::{transmute, transmute_copy}; use std::raw::TraitObject; use std::str::from_utf8; use std::string::raw; use std::collections::hashmap::{HashMap, Entries}; use mime::Mime; use rfc7230::read_header; use {HttpResult}; /// A trait for any object that will represent a header field and value. pub trait Header: Any { /// Returns the name of the header field this belongs to. fn header_name(marker: Option) -> &'static str; /// Parse a header from a raw stream of bytes. /// /// It's possible that a request can include a header field more than once, /// and in that case, the slice will have a length greater than 1. However, /// it's not necessarily the case that a Header is *allowed* to have more /// than one field value. If that's the case, you **should** return `None` /// if `raw.len() > 1`. fn parse_header(raw: &[Vec]) -> Option; /// Format a header to be output into a TcpStream. fn fmt_header(&self, fmt: &mut fmt::Formatter) -> fmt::Result; } /// A trait for downcasting owned TraitObjects to another type without checking /// `AnyRefExt::is::(t)` first. trait UncheckedAnyRefExt<'a> { /// This will downcast an object to another type without checking that it is /// legal. Do not call this unless you are ABSOLUTE SURE of the types. unsafe fn downcast_ref_unchecked(self) -> &'a T; } impl<'a> UncheckedAnyRefExt<'a> for &'a Header + 'a { #[inline] unsafe fn downcast_ref_unchecked(self) -> &'a T { let to: TraitObject = transmute_copy(&self); transmute(to.data) } } fn header_name() -> &'static str { let name = Header::header_name(None::); debug_assert!(name.as_slice().chars().all(|c| c == '-' || is_lowercase(c)), "Header names should be lowercase: {}", name); name } /// A map of header fields on requests and responses. pub struct Headers { data: HashMap<&'static str, Item> } impl Headers { /// Creates a new, empty headers map. pub fn new() -> Headers { Headers { data: HashMap::new() } } #[doc(hidden)] pub fn from_raw(rdr: &mut R) -> HttpResult { let mut headers = Headers::new(); loop { match try!(read_header(rdr)) { Some((name, value)) => { // read_header already checks that name is a token, which // means its safe utf8 let name = unsafe { raw::from_utf8(name) }.into_ascii_lower(); let name: &'static str = unsafe { transmute(name.as_slice()) }; match headers.data.find_or_insert(name, Raw(vec![])) { &Raw(ref mut pieces) => pieces.push(value), // at this point, Raw is the only thing that has been inserted _ => unreachable!() } }, None => break, } } Ok(headers) } /// Set a header field to the corresponding value. /// /// The field is determined by the type of the value being set. pub fn set(&mut self, value: H) { self.data.insert(header_name::(), Typed(box value)); } /// Get a clone of the header field's value, if it exists. /// /// Example: /// /// ``` /// # use hyper::header::{Headers, ContentType}; /// # let mut headers = Headers::new(); /// let content_type = headers.get::(); /// ``` pub fn get(&mut self) -> Option { self.get_ref().map(|v: &H| v.clone()) } /// Get a reference to the header field's value, if it exists. pub fn get_ref(&mut self) -> Option<&H> { self.data.find_mut(&header_name::()).and_then(|item| { debug!("get_ref, name={}, val={}", header_name::(), item); let header = match *item { Raw(ref raw) => match Header::parse_header(raw.as_slice()) { Some::(h) => { h }, None => return None }, Typed(..) => return Some(item) }; *item = Typed(box header as Box
); Some(item) }).and_then(|item| { debug!("downcasting {}", item); let ret = match *item { Typed(ref val) => { unsafe { Some(val.downcast_ref_unchecked()) } }, Raw(..) => unreachable!() }; debug!("returning {}", ret.is_some()); ret }) } /// Returns a boolean of whether a certain header is in the map. /// /// Example: /// /// ``` /// # use hyper::header::{Headers, ContentType}; /// # let mut headers = Headers::new(); /// let has_type = headers.has::(); /// ``` pub fn has(&self) -> bool { self.data.contains_key(&header_name::()) } /// Removes a header from the map, if one existed. Returns true if a header has been removed. pub fn remove(&mut self) -> bool { self.data.remove(&Header::header_name(None::)) } /// Returns an iterator over the header fields. pub fn iter<'a>(&'a self) -> HeadersItems<'a> { HeadersItems { inner: self.data.iter() } } } impl fmt::Show for Headers { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { try!("Headers {\n".fmt(fmt)); for (k, v) in self.iter() { try!(write!(fmt, "\t{}: {}\n", k, v)); } "}".fmt(fmt) } } /// An `Iterator` over the fields in a `Headers` map. pub struct HeadersItems<'a> { inner: Entries<'a, &'static str, Item> } impl<'a> Iterator<(&'a &'static str, HeaderView<'a>)> for HeadersItems<'a> { fn next(&mut self) -> Option<(&'a &'static str, HeaderView<'a>)> { match self.inner.next() { Some((k, v)) => Some((k, HeaderView(v))), None => None } } } /// Returned with the `HeadersItems` iterator. pub struct HeaderView<'a>(&'a Item); impl<'a> fmt::Show for HeaderView<'a> { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { let HeaderView(item) = *self; item.fmt(fmt) } } impl Collection for Headers { fn len(&self) -> uint { self.data.len() } } impl Mutable for Headers { fn clear(&mut self) { self.data.clear() } } enum Item { Raw(Vec>), Typed(Box
) } impl fmt::Show for Item { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { match *self { Raw(ref v) => { for part in v.iter() { try!(fmt.write(part.as_slice())); } Ok(()) }, Typed(ref h) => h.fmt_header(fmt) } } } // common headers /// The `Host` header. /// /// HTTP/1.1 requires that all requests include a `Host` header, and so hyper /// client requests add one automatically. /// /// Currently is just a String, but it should probably become a better type, /// like url::Host or something. #[deriving(Clone, PartialEq, Show)] pub struct Host(pub String); impl Header for Host { fn header_name(_: Option) -> &'static str { "host" } fn parse_header(raw: &[Vec]) -> Option { from_one_raw_str(raw).map(|s| Host(s)) } fn fmt_header(&self, fmt: &mut fmt::Formatter) -> fmt::Result { let Host(ref value) = *self; value.fmt(fmt) } } /// The `Content-Length` header. /// /// Simply a wrapper around a `uint`. #[deriving(Clone, PartialEq, Show)] pub struct ContentLength(pub uint); impl Header for ContentLength { fn header_name(_: Option) -> &'static str { "content-length" } fn parse_header(raw: &[Vec]) -> Option { from_one_raw_str(raw).map(|u| ContentLength(u)) } fn fmt_header(&self, fmt: &mut fmt::Formatter) -> fmt::Result { let ContentLength(ref value) = *self; value.fmt(fmt) } } /// The `Content-Type` header. /// /// Used to describe the MIME type of message body. Can be used with both /// requests and responses. #[deriving(Clone, PartialEq, Show)] pub struct ContentType(pub Mime); impl Header for ContentType { fn header_name(_: Option) -> &'static str { "content-type" } fn parse_header(raw: &[Vec]) -> Option { from_one_raw_str(raw).map(|mime| ContentType(mime)) } fn fmt_header(&self, fmt: &mut fmt::Formatter) -> fmt::Result { let ContentType(ref value) = *self; value.fmt(fmt) } } /// The `Accept` header. /// /// The `Accept` header is used to tell a server which content-types the client /// is capable of using. It can be a comma-separated list of `Mime`s, and the /// priority can be indicated with a `q` parameter. /// /// Example: /// /// ``` /// # use hyper::header::{Headers, Accept}; /// use hyper::mime::{Mime, Text, Html, Xml}; /// # let mut headers = Headers::new(); /// headers.set(Accept(vec![ Mime(Text, Html, vec![]), Mime(Text, Xml, vec![]) ])); /// ``` #[deriving(Clone, PartialEq, Show)] pub struct Accept(pub Vec); impl Header for Accept { fn header_name(_: Option) -> &'static str { "accept" } fn parse_header(_raw: &[Vec]) -> Option { unimplemented!() } fn fmt_header(&self, fmt: &mut fmt::Formatter) -> fmt::Result { let Accept(ref value) = *self; let last = value.len() - 1; for (i, mime) in value.iter().enumerate() { try!(mime.fmt(fmt)); if i < last { try!(", ".fmt(fmt)); } } Ok(()) } } /// The `Connection` header. /// /// Describes whether the socket connection should be closed or reused after /// this request/response is completed. #[deriving(Clone, PartialEq, Show)] pub enum Connection { /// The `keep-alive` connection value. KeepAlive, /// The `close` connection value. Close } impl FromStr for Connection { fn from_str(s: &str) -> Option { debug!("Connection::from_str =? {}", s); match s { "keep-alive" => Some(KeepAlive), "close" => Some(Close), _ => None } } } impl Header for Connection { fn header_name(_: Option) -> &'static str { "connection" } fn parse_header(raw: &[Vec]) -> Option { from_one_raw_str(raw) } fn fmt_header(&self, fmt: &mut fmt::Formatter) -> fmt::Result { match *self { KeepAlive => "keep-alive", Close => "close", }.fmt(fmt) } } /// The `Transfer-Encoding` header. /// /// This header describes the encoding of the message body. It can be /// comma-separated, including multiple encodings. /// /// ```notrust /// Transfer-Encoding: gzip, chunked /// ``` /// /// According to the spec, if a `Content-Length` header is not included, /// this header should include `chunked` as the last encoding. /// /// The implementation uses a vector of `Encoding` values. #[deriving(Clone, PartialEq, Show)] pub struct TransferEncoding(pub Vec); /// A value to be used with the `Transfer-Encoding` header. /// /// Example: /// /// ``` /// # use hyper::header::{Headers, TransferEncoding, Gzip, Chunked}; /// # let mut headers = Headers::new(); /// headers.set(TransferEncoding(vec![Gzip, Chunked])); #[deriving(Clone, PartialEq, Show)] pub enum Encoding { /// The `chunked` encoding. Chunked, // TODO: #2 implement this in `HttpReader`. /// The `gzip` encoding. Gzip, /// The `deflate` encoding. Deflate, /// The `compress` encoding. Compress, /// Some other encoding that is less common, can be any String. EncodingExt(String) } impl FromStr for Encoding { fn from_str(s: &str) -> Option { match s { "chunked" => Some(Chunked), _ => None } } } impl Header for TransferEncoding { fn header_name(_: Option) -> &'static str { "transfer-encoding" } fn parse_header(raw: &[Vec]) -> Option { if raw.len() != 1 { return None; } // we JUST checked that raw.len() == 1, so raw[0] WILL exist. match from_utf8(unsafe { raw.as_slice().unsafe_get(0).as_slice() }) { Some(s) => { Some(TransferEncoding(s.as_slice() .split([',', ' '].as_slice()) .filter_map(from_str) .collect())) } None => None } } fn fmt_header(&self, fmt: &mut fmt::Formatter) -> fmt::Result { let TransferEncoding(ref parts) = *self; let last = parts.len() - 1; for (i, part) in parts.iter().enumerate() { try!(part.fmt(fmt)); if i < last { try!(", ".fmt(fmt)); } } Ok(()) } } /// The `User-Agent` header field. /// /// They can contain any value, so it just wraps a `String`. #[deriving(Clone, PartialEq, Show)] pub struct UserAgent(pub String); impl Header for UserAgent { fn header_name(_: Option) -> &'static str { "user-agent" } fn parse_header(raw: &[Vec]) -> Option { from_one_raw_str(raw).map(|s| UserAgent(s)) } fn fmt_header(&self, fmt: &mut fmt::Formatter) -> fmt::Result { let UserAgent(ref value) = *self; value.fmt(fmt) } } fn from_one_raw_str(raw: &[Vec]) -> Option { if raw.len() != 1 { return None; } // we JUST checked that raw.len() == 1, so raw[0] WILL exist. match from_utf8(unsafe { raw.as_slice().unsafe_get(0).as_slice() }) { Some(s) => FromStr::from_str(s), None => None } } #[cfg(test)] mod tests { use std::io::MemReader; use mime::{Mime, Text, Plain}; use super::{Headers, Header, ContentLength, ContentType}; fn mem(s: &str) -> MemReader { MemReader::new(s.as_bytes().to_vec()) } #[test] fn test_from_raw() { let mut headers = Headers::from_raw(&mut mem("Content-Length: 10\r\n\r\n")).unwrap(); assert_eq!(headers.get_ref(), Some(&ContentLength(10))); } #[test] fn test_content_type() { let content_type = Header::parse_header(["text/plain".as_bytes().to_vec()].as_slice()); assert_eq!(content_type, Some(ContentType(Mime(Text, Plain, vec![])))); } }