Split common headers into a submodule and into their own files
This is a more extensible place to put them and doesn't clutter up header/mod.rs as much as the old scheme did. Fixes #8
This commit is contained in:
		| @@ -8,7 +8,7 @@ use std::io::net::ip::Ipv4Addr; | |||||||
|  |  | ||||||
| use hyper::{Get, Post}; | use hyper::{Get, Post}; | ||||||
| use hyper::server::{Server, Handler, Incoming}; | use hyper::server::{Server, Handler, Incoming}; | ||||||
| use hyper::header::ContentLength; | use hyper::header::common::ContentLength; | ||||||
|  |  | ||||||
| struct Echo; | struct Echo; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -5,7 +5,8 @@ use std::io::{BufferedWriter, IoResult}; | |||||||
| use url::Url; | use url::Url; | ||||||
|  |  | ||||||
| use method; | use method; | ||||||
| use header::{Headers, Host}; | use header::Headers; | ||||||
|  | use header::common::Host; | ||||||
| use rfc7230::LINE_ENDING; | use rfc7230::LINE_ENDING; | ||||||
| use version; | use version; | ||||||
| use {HttpResult, HttpUriError}; | use {HttpResult, HttpUriError}; | ||||||
|   | |||||||
| @@ -2,7 +2,9 @@ | |||||||
| use std::io::{BufferedReader, IoResult}; | use std::io::{BufferedReader, IoResult}; | ||||||
| use std::io::net::tcp::TcpStream; | use std::io::net::tcp::TcpStream; | ||||||
|  |  | ||||||
| use header::{mod, ContentLength, TransferEncoding, Chunked}; | use header; | ||||||
|  | use header::common::{ContentLength, TransferEncoding}; | ||||||
|  | use header::common::transfer_encoding::Chunked; | ||||||
| use rfc7230::{read_status_line, HttpReader, SizedReader, ChunkedReader, EofReader}; | use rfc7230::{read_status_line, HttpReader, SizedReader, ChunkedReader, EofReader}; | ||||||
| use status; | use status; | ||||||
| use version; | use version; | ||||||
|   | |||||||
							
								
								
									
										631
									
								
								src/header.rs
									
									
									
									
									
								
							
							
						
						
									
										631
									
								
								src/header.rs
									
									
									
									
									
								
							| @@ -1,631 +0,0 @@ | |||||||
| //! 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::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, SendStr, Slice, Owned}; |  | ||||||
| use std::string::raw; |  | ||||||
| use std::collections::hashmap::{HashMap, Entries}; |  | ||||||
|  |  | ||||||
| use mime::Mime; |  | ||||||
| use time::{Tm, strptime}; |  | ||||||
| use uany::UncheckedAnyDowncast; |  | ||||||
|  |  | ||||||
| use rfc7230::read_header; |  | ||||||
| use {HttpResult}; |  | ||||||
|  |  | ||||||
| /// A trait for any object that will represent a header field and value. |  | ||||||
| pub trait Header: 'static { |  | ||||||
|     /// Returns the name of the header field this belongs to. |  | ||||||
|     /// |  | ||||||
|     /// The market `Option` is to hint to the type system which implementation |  | ||||||
|     /// to call. This can be done away with once UFCS arrives. |  | ||||||
|     fn header_name(marker: Option<Self>) -> &'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<u8>]) -> Option<Self>; |  | ||||||
|     /// Format a header to be output into a TcpStream. |  | ||||||
|     fn fmt_header(&self, fmt: &mut fmt::Formatter) -> fmt::Result; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| impl<'a> UncheckedAnyDowncast<'a> for &'a Header + 'a { |  | ||||||
|     #[inline] |  | ||||||
|     unsafe fn downcast_ref_unchecked<T: 'static>(self) -> &'a T { |  | ||||||
|         let to: TraitObject = transmute_copy(&self); |  | ||||||
|         transmute(to.data) |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| fn header_name<T: Header>() -> &'static str { |  | ||||||
|     let name = Header::header_name(None::<T>); |  | ||||||
|     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<SendStr, Item> |  | ||||||
| } |  | ||||||
|  |  | ||||||
| impl Headers { |  | ||||||
|  |  | ||||||
|     /// Creates a new, empty headers map. |  | ||||||
|     pub fn new() -> Headers { |  | ||||||
|         Headers { |  | ||||||
|             data: HashMap::new() |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     #[doc(hidden)] |  | ||||||
|     pub fn from_raw<R: Reader>(rdr: &mut R) -> HttpResult<Headers> { |  | ||||||
|         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(); |  | ||||||
|                     match headers.data.find_or_insert(Owned(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<H: Header>(&mut self, value: H) { |  | ||||||
|         self.data.insert(Slice(header_name::<H>()), 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::<ContentType>(); |  | ||||||
|     /// ``` |  | ||||||
|     pub fn get<H: Header + Clone>(&mut self) -> Option<H> { |  | ||||||
|         self.get_ref().map(|v: &H| v.clone()) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     /// Access the raw value of a header, if it exists and has not |  | ||||||
|     /// been already parsed. |  | ||||||
|     /// |  | ||||||
|     /// If the header field has already been parsed into a typed header, |  | ||||||
|     /// then you *must* access it through that representation. |  | ||||||
|     /// |  | ||||||
|     /// Example: |  | ||||||
|     /// ``` |  | ||||||
|     /// # use hyper::header::{Headers, ContentType}; |  | ||||||
|     /// # let mut headers = Headers::new(); |  | ||||||
|     /// let raw_content_type = unsafe { headers.get_raw("content-type") }; |  | ||||||
|     /// ``` |  | ||||||
|     pub unsafe fn get_raw(&self, name: &'static str) -> Option<&[Vec<u8>]> { |  | ||||||
|         self.data.find(&Slice(name)).and_then(|item| { |  | ||||||
|             match *item { |  | ||||||
|                 Raw(ref raw) => Some(raw.as_slice()), |  | ||||||
|                 _ => None |  | ||||||
|             } |  | ||||||
|         }) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     /// Get a reference to the header field's value, if it exists. |  | ||||||
|     pub fn get_ref<H: Header>(&mut self) -> Option<&H> { |  | ||||||
|         self.data.find_mut(&Slice(header_name::<H>())).and_then(|item| { |  | ||||||
|             debug!("get_ref, name={}, val={}", header_name::<H>(), item); |  | ||||||
|             let header = match *item { |  | ||||||
|                 Raw(ref raw) => match Header::parse_header(raw.as_slice()) { |  | ||||||
|                     Some::<H>(h) => { |  | ||||||
|                         h |  | ||||||
|                     }, |  | ||||||
|                     None => return None |  | ||||||
|                 }, |  | ||||||
|                 Typed(..) => return Some(item) |  | ||||||
|             }; |  | ||||||
|             *item = Typed(box header as Box<Header>); |  | ||||||
|             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::<ContentType>(); |  | ||||||
|     /// ``` |  | ||||||
|     pub fn has<H: Header>(&self) -> bool { |  | ||||||
|         self.data.contains_key(&Slice(header_name::<H>())) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     /// Removes a header from the map, if one existed. |  | ||||||
|     /// Returns true if a header has been removed. |  | ||||||
|     pub fn remove<H: Header>(&mut self) -> bool { |  | ||||||
|         self.data.pop_equiv(&Header::header_name(None::<H>)).is_some() |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     /// 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, SendStr, Item> |  | ||||||
| } |  | ||||||
|  |  | ||||||
| impl<'a> Iterator<(&'a str, HeaderView<'a>)> for HeadersItems<'a> { |  | ||||||
|     fn next(&mut self) -> Option<(&'a str, HeaderView<'a>)> { |  | ||||||
|         match self.inner.next() { |  | ||||||
|             Some((k, v)) => Some((k.as_slice(), 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<Vec<u8>>), |  | ||||||
|     Typed(Box<Header>) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| 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<Host>) -> &'static str { |  | ||||||
|         "host" |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fn parse_header(raw: &[Vec<u8>]) -> Option<Host> { |  | ||||||
|         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<ContentLength>) -> &'static str { |  | ||||||
|         "content-length" |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fn parse_header(raw: &[Vec<u8>]) -> Option<ContentLength> { |  | ||||||
|         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<ContentType>) -> &'static str { |  | ||||||
|         "content-type" |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fn parse_header(raw: &[Vec<u8>]) -> Option<ContentType> { |  | ||||||
|         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<Mime>); |  | ||||||
|  |  | ||||||
| impl Header for Accept { |  | ||||||
|     fn header_name(_: Option<Accept>) -> &'static str { |  | ||||||
|         "accept" |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fn parse_header(_raw: &[Vec<u8>]) -> Option<Accept> { |  | ||||||
|         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<Connection> { |  | ||||||
|         debug!("Connection::from_str =? {}", s); |  | ||||||
|         match s { |  | ||||||
|             "keep-alive" => Some(KeepAlive), |  | ||||||
|             "close" => Some(Close), |  | ||||||
|             _ => None |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| impl Header for Connection { |  | ||||||
|     fn header_name(_: Option<Connection>) -> &'static str { |  | ||||||
|         "connection" |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fn parse_header(raw: &[Vec<u8>]) -> Option<Connection> { |  | ||||||
|         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<Encoding>); |  | ||||||
|  |  | ||||||
| /// 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<Encoding> { |  | ||||||
|         match s { |  | ||||||
|             "chunked" => Some(Chunked), |  | ||||||
|             _ => None |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| impl Header for TransferEncoding { |  | ||||||
|     fn header_name(_: Option<TransferEncoding>) -> &'static str { |  | ||||||
|         "transfer-encoding" |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fn parse_header(raw: &[Vec<u8>]) -> Option<TransferEncoding> { |  | ||||||
|         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<UserAgent>) -> &'static str { |  | ||||||
|         "user-agent" |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fn parse_header(raw: &[Vec<u8>]) -> Option<UserAgent> { |  | ||||||
|         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) |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| /// The `Server` header field. |  | ||||||
| /// |  | ||||||
| /// They can contain any value, so it just wraps a `String`. |  | ||||||
| #[deriving(Clone, PartialEq, Show)] |  | ||||||
| pub struct Server(pub String); |  | ||||||
|  |  | ||||||
| impl Header for Server { |  | ||||||
|     fn header_name(_: Option<Server>) -> &'static str { |  | ||||||
|         "server" |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fn parse_header(raw: &[Vec<u8>]) -> Option<Server> { |  | ||||||
|         from_one_raw_str(raw).map(|s| Server(s)) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fn fmt_header(&self, fmt: &mut fmt::Formatter) -> fmt::Result { |  | ||||||
|         let Server(ref value) = *self; |  | ||||||
|         value.fmt(fmt) |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // Egh, replace as soon as something better than time::Tm exists. |  | ||||||
| /// The `Date` header field. |  | ||||||
| #[deriving(PartialEq, Clone)] |  | ||||||
| pub struct Date(pub Tm); |  | ||||||
|  |  | ||||||
| impl Header for Date { |  | ||||||
|     fn header_name(_: Option<Date>) -> &'static str { |  | ||||||
|         "date" |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fn parse_header(raw: &[Vec<u8>]) -> Option<Date> { |  | ||||||
|         from_one_raw_str(raw) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fn fmt_header(&self, fmt: &mut fmt::Formatter) -> fmt::Result { |  | ||||||
|         self.fmt(fmt) |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| impl fmt::Show for Date { |  | ||||||
|     fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { |  | ||||||
|         let Date(ref tm) = *self; |  | ||||||
|         // bummer that tm.strftime allocates a string. It would nice if it |  | ||||||
|         // returned a Show instead, since I don't need the String here |  | ||||||
|         write!(fmt, "{}", tm.to_utc().rfc822()) |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| impl FromStr for Date { |  | ||||||
|     //    Prior to 1995, there were three different formats commonly used by |  | ||||||
|     //   servers to communicate timestamps.  For compatibility with old |  | ||||||
|     //   implementations, all three are defined here.  The preferred format is |  | ||||||
|     //   a fixed-length and single-zone subset of the date and time |  | ||||||
|     //   specification used by the Internet Message Format [RFC5322]. |  | ||||||
|     // |  | ||||||
|     //     HTTP-date    = IMF-fixdate / obs-date |  | ||||||
|     // |  | ||||||
|     //   An example of the preferred format is |  | ||||||
|     // |  | ||||||
|     //     Sun, 06 Nov 1994 08:49:37 GMT    ; IMF-fixdate |  | ||||||
|     // |  | ||||||
|     //   Examples of the two obsolete formats are |  | ||||||
|     // |  | ||||||
|     //     Sunday, 06-Nov-94 08:49:37 GMT   ; obsolete RFC 850 format |  | ||||||
|     //     Sun Nov  6 08:49:37 1994         ; ANSI C's asctime() format |  | ||||||
|     // |  | ||||||
|     //   A recipient that parses a timestamp value in an HTTP header field |  | ||||||
|     //   MUST accept all three HTTP-date formats.  When a sender generates a |  | ||||||
|     //   header field that contains one or more timestamps defined as |  | ||||||
|     //   HTTP-date, the sender MUST generate those timestamps in the |  | ||||||
|     //   IMF-fixdate format. |  | ||||||
|     fn from_str(s: &str) -> Option<Date> { |  | ||||||
|         strptime(s, "%a, %d %b %Y %T %Z").or_else(|_| { |  | ||||||
|             strptime(s, "%A, %d-%b-%y %T %Z") |  | ||||||
|         }).or_else(|_| { |  | ||||||
|             strptime(s, "%c") |  | ||||||
|         }).ok().map(|tm| Date(tm)) |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| fn from_one_raw_str<T: FromStr>(raw: &[Vec<u8>]) -> Option<T> { |  | ||||||
|     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![])))); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
							
								
								
									
										44
									
								
								src/header/common/accept.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								src/header/common/accept.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,44 @@ | |||||||
|  | use header::Header; | ||||||
|  | use std::fmt::{mod, Show}; | ||||||
|  | use mime::Mime; | ||||||
|  |  | ||||||
|  | /// 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; | ||||||
|  | /// # use hyper::header::common::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<Mime>); | ||||||
|  |  | ||||||
|  | impl Header for Accept { | ||||||
|  |     fn header_name(_: Option<Accept>) -> &'static str { | ||||||
|  |         "accept" | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fn parse_header(_raw: &[Vec<u8>]) -> Option<Accept> { | ||||||
|  |         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(()) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
							
								
								
									
										45
									
								
								src/header/common/connection.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								src/header/common/connection.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,45 @@ | |||||||
|  | use header::Header; | ||||||
|  | use std::fmt::{mod, Show}; | ||||||
|  | use super::from_one_raw_str; | ||||||
|  | use std::from_str::FromStr; | ||||||
|  |  | ||||||
|  | /// 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<Connection> { | ||||||
|  |         debug!("Connection::from_str =? {}", s); | ||||||
|  |         match s { | ||||||
|  |             "keep-alive" => Some(KeepAlive), | ||||||
|  |             "close" => Some(Close), | ||||||
|  |             _ => None | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | impl Header for Connection { | ||||||
|  |     fn header_name(_: Option<Connection>) -> &'static str { | ||||||
|  |         "connection" | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fn parse_header(raw: &[Vec<u8>]) -> Option<Connection> { | ||||||
|  |         from_one_raw_str(raw) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fn fmt_header(&self, fmt: &mut fmt::Formatter) -> fmt::Result { | ||||||
|  |         match *self { | ||||||
|  |             KeepAlive => "keep-alive", | ||||||
|  |             Close => "close", | ||||||
|  |         }.fmt(fmt) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
							
								
								
									
										25
									
								
								src/header/common/content_length.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								src/header/common/content_length.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,25 @@ | |||||||
|  | use header::Header; | ||||||
|  | use std::fmt::{mod, Show}; | ||||||
|  | use super::from_one_raw_str; | ||||||
|  |  | ||||||
|  | /// 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<ContentLength>) -> &'static str { | ||||||
|  |         "content-length" | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fn parse_header(raw: &[Vec<u8>]) -> Option<ContentLength> { | ||||||
|  |         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) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
							
								
								
									
										27
									
								
								src/header/common/content_type.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								src/header/common/content_type.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,27 @@ | |||||||
|  | use header::Header; | ||||||
|  | use std::fmt::{mod, Show}; | ||||||
|  | use super::from_one_raw_str; | ||||||
|  | use mime::Mime; | ||||||
|  |  | ||||||
|  | /// 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<ContentType>) -> &'static str { | ||||||
|  |         "content-type" | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fn parse_header(raw: &[Vec<u8>]) -> Option<ContentType> { | ||||||
|  |         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) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
							
								
								
									
										66
									
								
								src/header/common/date.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										66
									
								
								src/header/common/date.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,66 @@ | |||||||
|  | use header::Header; | ||||||
|  | use std::fmt::{mod, Show}; | ||||||
|  | use super::from_one_raw_str; | ||||||
|  | use std::from_str::FromStr; | ||||||
|  | use time::{Tm, strptime}; | ||||||
|  |  | ||||||
|  | // Egh, replace as soon as something better than time::Tm exists. | ||||||
|  | /// The `Date` header field. | ||||||
|  | #[deriving(PartialEq, Clone)] | ||||||
|  | pub struct Date(pub Tm); | ||||||
|  |  | ||||||
|  | impl Header for Date { | ||||||
|  |     fn header_name(_: Option<Date>) -> &'static str { | ||||||
|  |         "date" | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fn parse_header(raw: &[Vec<u8>]) -> Option<Date> { | ||||||
|  |         from_one_raw_str(raw) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fn fmt_header(&self, fmt: &mut fmt::Formatter) -> fmt::Result { | ||||||
|  |         self.fmt(fmt) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | impl fmt::Show for Date { | ||||||
|  |     fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { | ||||||
|  |         let Date(ref tm) = *self; | ||||||
|  |         // bummer that tm.strftime allocates a string. It would nice if it | ||||||
|  |         // returned a Show instead, since I don't need the String here | ||||||
|  |         write!(fmt, "{}", tm.to_utc().rfc822()) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | impl FromStr for Date { | ||||||
|  |     //    Prior to 1995, there were three different formats commonly used by | ||||||
|  |     //   servers to communicate timestamps.  For compatibility with old | ||||||
|  |     //   implementations, all three are defined here.  The preferred format is | ||||||
|  |     //   a fixed-length and single-zone subset of the date and time | ||||||
|  |     //   specification used by the Internet Message Format [RFC5322]. | ||||||
|  |     // | ||||||
|  |     //     HTTP-date    = IMF-fixdate / obs-date | ||||||
|  |     // | ||||||
|  |     //   An example of the preferred format is | ||||||
|  |     // | ||||||
|  |     //     Sun, 06 Nov 1994 08:49:37 GMT    ; IMF-fixdate | ||||||
|  |     // | ||||||
|  |     //   Examples of the two obsolete formats are | ||||||
|  |     // | ||||||
|  |     //     Sunday, 06-Nov-94 08:49:37 GMT   ; obsolete RFC 850 format | ||||||
|  |     //     Sun Nov  6 08:49:37 1994         ; ANSI C's asctime() format | ||||||
|  |     // | ||||||
|  |     //   A recipient that parses a timestamp value in an HTTP header field | ||||||
|  |     //   MUST accept all three HTTP-date formats.  When a sender generates a | ||||||
|  |     //   header field that contains one or more timestamps defined as | ||||||
|  |     //   HTTP-date, the sender MUST generate those timestamps in the | ||||||
|  |     //   IMF-fixdate format. | ||||||
|  |     fn from_str(s: &str) -> Option<Date> { | ||||||
|  |         strptime(s, "%a, %d %b %Y %T %Z").or_else(|_| { | ||||||
|  |             strptime(s, "%A, %d-%b-%y %T %Z") | ||||||
|  |         }).or_else(|_| { | ||||||
|  |             strptime(s, "%c") | ||||||
|  |         }).ok().map(|tm| Date(tm)) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
							
								
								
									
										29
									
								
								src/header/common/host.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								src/header/common/host.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,29 @@ | |||||||
|  | use header::Header; | ||||||
|  | use std::fmt::{mod, Show}; | ||||||
|  | use super::from_one_raw_str; | ||||||
|  |  | ||||||
|  | /// 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<Host>) -> &'static str { | ||||||
|  |         "host" | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fn parse_header(raw: &[Vec<u8>]) -> Option<Host> { | ||||||
|  |         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) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
							
								
								
									
										59
									
								
								src/header/common/mod.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								src/header/common/mod.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,59 @@ | |||||||
|  | //! A Collection of Header implementations for common HTTP Headers. | ||||||
|  | //! | ||||||
|  | //! ## 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)`. | ||||||
|  |  | ||||||
|  | pub use self::host::Host; | ||||||
|  | pub use self::content_length::ContentLength; | ||||||
|  | pub use self::content_type::ContentType; | ||||||
|  | pub use self::accept::Accept; | ||||||
|  | pub use self::connection::Connection; | ||||||
|  | pub use self::transfer_encoding::TransferEncoding; | ||||||
|  | pub use self::user_agent::UserAgent; | ||||||
|  | pub use self::server::Server; | ||||||
|  | pub use self::date::Date; | ||||||
|  |  | ||||||
|  | use std::from_str::FromStr; | ||||||
|  | use std::str::from_utf8; | ||||||
|  |  | ||||||
|  | /// Exposes the Host header. | ||||||
|  | pub mod host; | ||||||
|  |  | ||||||
|  | /// Exposes the ContentLength header. | ||||||
|  | pub mod content_length; | ||||||
|  |  | ||||||
|  | /// Exposes the ContentType header. | ||||||
|  | pub mod content_type; | ||||||
|  |  | ||||||
|  | /// Exposes the Accept header. | ||||||
|  | pub mod accept; | ||||||
|  |  | ||||||
|  | /// Exposes the Connection header. | ||||||
|  | pub mod connection; | ||||||
|  |  | ||||||
|  | /// Exposes the TransferEncoding header. | ||||||
|  | pub mod transfer_encoding; | ||||||
|  |  | ||||||
|  | /// Exposes the UserAgent header. | ||||||
|  | pub mod user_agent; | ||||||
|  |  | ||||||
|  | /// Exposes the Server header. | ||||||
|  | pub mod server; | ||||||
|  |  | ||||||
|  | /// Exposes the Date header. | ||||||
|  | pub mod date; | ||||||
|  |  | ||||||
|  | fn from_one_raw_str<T: FromStr>(raw: &[Vec<u8>]) -> Option<T> { | ||||||
|  |     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 | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
							
								
								
									
										25
									
								
								src/header/common/server.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								src/header/common/server.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,25 @@ | |||||||
|  | use header::Header; | ||||||
|  | use std::fmt::{mod, Show}; | ||||||
|  | use super::from_one_raw_str; | ||||||
|  |  | ||||||
|  | /// The `Server` header field. | ||||||
|  | /// | ||||||
|  | /// They can contain any value, so it just wraps a `String`. | ||||||
|  | #[deriving(Clone, PartialEq, Show)] | ||||||
|  | pub struct Server(pub String); | ||||||
|  |  | ||||||
|  | impl Header for Server { | ||||||
|  |     fn header_name(_: Option<Server>) -> &'static str { | ||||||
|  |         "server" | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fn parse_header(raw: &[Vec<u8>]) -> Option<Server> { | ||||||
|  |         from_one_raw_str(raw).map(|s| Server(s)) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fn fmt_header(&self, fmt: &mut fmt::Formatter) -> fmt::Result { | ||||||
|  |         let Server(ref value) = *self; | ||||||
|  |         value.fmt(fmt) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
							
								
								
									
										89
									
								
								src/header/common/transfer_encoding.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										89
									
								
								src/header/common/transfer_encoding.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,89 @@ | |||||||
|  | use header::Header; | ||||||
|  | use std::fmt::{mod, Show}; | ||||||
|  | use std::from_str::FromStr; | ||||||
|  | use std::str::from_utf8; | ||||||
|  |  | ||||||
|  | /// 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<Encoding>); | ||||||
|  |  | ||||||
|  | /// A value to be used with the `Transfer-Encoding` header. | ||||||
|  | /// | ||||||
|  | /// Example: | ||||||
|  | /// | ||||||
|  | /// ``` | ||||||
|  | /// # use hyper::header::common::transfer_encoding::{TransferEncoding, Gzip, Chunked}; | ||||||
|  | /// # use hyper::header::Headers; | ||||||
|  | /// # 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<Encoding> { | ||||||
|  |         match s { | ||||||
|  |             "chunked" => Some(Chunked), | ||||||
|  |             _ => None | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | impl Header for TransferEncoding { | ||||||
|  |     fn header_name(_: Option<TransferEncoding>) -> &'static str { | ||||||
|  |         "transfer-encoding" | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fn parse_header(raw: &[Vec<u8>]) -> Option<TransferEncoding> { | ||||||
|  |         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(()) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
							
								
								
									
										25
									
								
								src/header/common/user_agent.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								src/header/common/user_agent.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,25 @@ | |||||||
|  | use header::Header; | ||||||
|  | use std::fmt::{mod, Show}; | ||||||
|  | use super::from_one_raw_str; | ||||||
|  |  | ||||||
|  | /// 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<UserAgent>) -> &'static str { | ||||||
|  |         "user-agent" | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fn parse_header(raw: &[Vec<u8>]) -> Option<UserAgent> { | ||||||
|  |         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) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
							
								
								
									
										282
									
								
								src/header/mod.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										282
									
								
								src/header/mod.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,282 @@ | |||||||
|  | //! 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. | ||||||
|  | use std::ascii::OwnedAsciiExt; | ||||||
|  | use std::char::is_lowercase; | ||||||
|  | use std::fmt::{mod, Show}; | ||||||
|  | use std::mem::{transmute, transmute_copy}; | ||||||
|  | use std::raw::TraitObject; | ||||||
|  | use std::str::{from_utf8, SendStr, Slice, Owned}; | ||||||
|  | use std::string::raw; | ||||||
|  | use std::collections::hashmap::{HashMap, Entries}; | ||||||
|  |  | ||||||
|  | use uany::UncheckedAnyDowncast; | ||||||
|  |  | ||||||
|  | use rfc7230::read_header; | ||||||
|  | use {HttpResult}; | ||||||
|  |  | ||||||
|  | /// Common Headers | ||||||
|  | pub mod common; | ||||||
|  |  | ||||||
|  | /// A trait for any object that will represent a header field and value. | ||||||
|  | pub trait Header: 'static { | ||||||
|  |     /// Returns the name of the header field this belongs to. | ||||||
|  |     /// | ||||||
|  |     /// The market `Option` is to hint to the type system which implementation | ||||||
|  |     /// to call. This can be done away with once UFCS arrives. | ||||||
|  |     fn header_name(marker: Option<Self>) -> &'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<u8>]) -> Option<Self>; | ||||||
|  |     /// Format a header to be output into a TcpStream. | ||||||
|  |     fn fmt_header(&self, fmt: &mut fmt::Formatter) -> fmt::Result; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | impl<'a> UncheckedAnyDowncast<'a> for &'a Header + 'a { | ||||||
|  |     #[inline] | ||||||
|  |     unsafe fn downcast_ref_unchecked<T: 'static>(self) -> &'a T { | ||||||
|  |         let to: TraitObject = transmute_copy(&self); | ||||||
|  |         transmute(to.data) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | fn header_name<T: Header>() -> &'static str { | ||||||
|  |     let name = Header::header_name(None::<T>); | ||||||
|  |     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<SendStr, Item> | ||||||
|  | } | ||||||
|  |  | ||||||
|  | impl Headers { | ||||||
|  |  | ||||||
|  |     /// Creates a new, empty headers map. | ||||||
|  |     pub fn new() -> Headers { | ||||||
|  |         Headers { | ||||||
|  |             data: HashMap::new() | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     #[doc(hidden)] | ||||||
|  |     pub fn from_raw<R: Reader>(rdr: &mut R) -> HttpResult<Headers> { | ||||||
|  |         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(); | ||||||
|  |                     match headers.data.find_or_insert(Owned(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<H: Header>(&mut self, value: H) { | ||||||
|  |         self.data.insert(Slice(header_name::<H>()), Typed(box value)); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// Get a clone of the header field's value, if it exists. | ||||||
|  |     /// | ||||||
|  |     /// Example: | ||||||
|  |     /// | ||||||
|  |     /// ``` | ||||||
|  |     /// # use hyper::header::Headers; | ||||||
|  |     /// # use hyper::header::common::ContentType; | ||||||
|  |     /// # let mut headers = Headers::new(); | ||||||
|  |     /// let content_type = headers.get::<ContentType>(); | ||||||
|  |     /// ``` | ||||||
|  |     pub fn get<H: Header + Clone>(&mut self) -> Option<H> { | ||||||
|  |         self.get_ref().map(|v: &H| v.clone()) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// Access the raw value of a header, if it exists and has not | ||||||
|  |     /// been already parsed. | ||||||
|  |     /// | ||||||
|  |     /// If the header field has already been parsed into a typed header, | ||||||
|  |     /// then you *must* access it through that representation. | ||||||
|  |     /// | ||||||
|  |     /// Example: | ||||||
|  |     /// ``` | ||||||
|  |     /// # use hyper::header::Headers; | ||||||
|  |     /// # let mut headers = Headers::new(); | ||||||
|  |     /// let raw_content_type = unsafe { headers.get_raw("content-type") }; | ||||||
|  |     /// ``` | ||||||
|  |     pub unsafe fn get_raw(&self, name: &'static str) -> Option<&[Vec<u8>]> { | ||||||
|  |         self.data.find(&Slice(name)).and_then(|item| { | ||||||
|  |             match *item { | ||||||
|  |                 Raw(ref raw) => Some(raw.as_slice()), | ||||||
|  |                 _ => None | ||||||
|  |             } | ||||||
|  |         }) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// Get a reference to the header field's value, if it exists. | ||||||
|  |     pub fn get_ref<H: Header>(&mut self) -> Option<&H> { | ||||||
|  |         self.data.find_mut(&Slice(header_name::<H>())).and_then(|item| { | ||||||
|  |             debug!("get_ref, name={}, val={}", header_name::<H>(), item); | ||||||
|  |             let header = match *item { | ||||||
|  |                 Raw(ref raw) => match Header::parse_header(raw.as_slice()) { | ||||||
|  |                     Some::<H>(h) => { | ||||||
|  |                         h | ||||||
|  |                     }, | ||||||
|  |                     None => return None | ||||||
|  |                 }, | ||||||
|  |                 Typed(..) => return Some(item) | ||||||
|  |             }; | ||||||
|  |             *item = Typed(box header as Box<Header>); | ||||||
|  |             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; | ||||||
|  |     /// # use hyper::header::common::ContentType; | ||||||
|  |     /// # let mut headers = Headers::new(); | ||||||
|  |     /// let has_type = headers.has::<ContentType>(); | ||||||
|  |     /// ``` | ||||||
|  |     pub fn has<H: Header>(&self) -> bool { | ||||||
|  |         self.data.contains_key(&Slice(header_name::<H>())) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// Removes a header from the map, if one existed. | ||||||
|  |     /// Returns true if a header has been removed. | ||||||
|  |     pub fn remove<H: Header>(&mut self) -> bool { | ||||||
|  |         self.data.pop_equiv(&Header::header_name(None::<H>)).is_some() | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// 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, SendStr, Item> | ||||||
|  | } | ||||||
|  |  | ||||||
|  | impl<'a> Iterator<(&'a str, HeaderView<'a>)> for HeadersItems<'a> { | ||||||
|  |     fn next(&mut self) -> Option<(&'a str, HeaderView<'a>)> { | ||||||
|  |         match self.inner.next() { | ||||||
|  |             Some((k, v)) => Some((k.as_slice(), 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<Vec<u8>>), | ||||||
|  |     Typed(Box<Header>) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | 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) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[cfg(test)] | ||||||
|  | mod tests { | ||||||
|  |     use std::io::MemReader; | ||||||
|  |     use mime::{Mime, Text, Plain}; | ||||||
|  |     use super::{Headers, Header}; | ||||||
|  |     use super::common::{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![])))); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -9,7 +9,8 @@ use std::io::net::tcp::TcpStream; | |||||||
| use {HttpResult}; | use {HttpResult}; | ||||||
| use version::{HttpVersion}; | use version::{HttpVersion}; | ||||||
| use method; | use method; | ||||||
| use header::{Headers, ContentLength}; | use header::Headers; | ||||||
|  | use header::common::ContentLength; | ||||||
| use rfc7230::{read_request_line}; | use rfc7230::{read_request_line}; | ||||||
| use rfc7230::{HttpReader, SizedReader, ChunkedReader}; | use rfc7230::{HttpReader, SizedReader, ChunkedReader}; | ||||||
| use uri::RequestUri; | use uri::RequestUri; | ||||||
|   | |||||||
| @@ -8,6 +8,7 @@ use std::io::net::tcp::TcpStream; | |||||||
| use time::now_utc; | use time::now_utc; | ||||||
|  |  | ||||||
| use header; | use header; | ||||||
|  | use header::common; | ||||||
| use status; | use status; | ||||||
| use version; | use version; | ||||||
| use rfc7230::{CR, LF, LINE_ENDING}; | use rfc7230::{CR, LF, LINE_ENDING}; | ||||||
| @@ -48,8 +49,8 @@ impl Response { | |||||||
|         debug!("writing head: {} {}", self.version, self.status); |         debug!("writing head: {} {}", self.version, self.status); | ||||||
|         try!(write!(self.body, "{} {}{}{}", self.version, self.status, CR as char, LF as char)); |         try!(write!(self.body, "{} {}{}{}", self.version, self.status, CR as char, LF as char)); | ||||||
|  |  | ||||||
|         if !self.headers.has::<header::Date>() { |         if !self.headers.has::<common::Date>() { | ||||||
|             self.headers.set(header::Date(now_utc())); |             self.headers.set(common::Date(now_utc())); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         for (name, header) in self.headers.iter() { |         for (name, header) in self.headers.iter() { | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user