property treat header names as case insensitive
This commit is contained in:
		| @@ -22,7 +22,7 @@ pub struct Accept(pub Vec<Mime>); | ||||
|  | ||||
| impl Header for Accept { | ||||
|     fn header_name(_: Option<Accept>) -> &'static str { | ||||
|         "accept" | ||||
|         "Accept" | ||||
|     } | ||||
|  | ||||
|     fn parse_header(_raw: &[Vec<u8>]) -> Option<Accept> { | ||||
|   | ||||
| @@ -28,7 +28,7 @@ impl FromStr for Connection { | ||||
|  | ||||
| impl Header for Connection { | ||||
|     fn header_name(_: Option<Connection>) -> &'static str { | ||||
|         "connection" | ||||
|         "Connection" | ||||
|     } | ||||
|  | ||||
|     fn parse_header(raw: &[Vec<u8>]) -> Option<Connection> { | ||||
|   | ||||
| @@ -11,7 +11,7 @@ pub struct ContentLength(pub uint); | ||||
|  | ||||
| impl Header for ContentLength { | ||||
|     fn header_name(_: Option<ContentLength>) -> &'static str { | ||||
|         "content-length" | ||||
|         "Content-Length" | ||||
|     } | ||||
|  | ||||
|     fn parse_header(raw: &[Vec<u8>]) -> Option<ContentLength> { | ||||
|   | ||||
| @@ -12,7 +12,7 @@ pub struct ContentType(pub Mime); | ||||
|  | ||||
| impl Header for ContentType { | ||||
|     fn header_name(_: Option<ContentType>) -> &'static str { | ||||
|         "content-type" | ||||
|         "Content-Type" | ||||
|     } | ||||
|  | ||||
|     fn parse_header(raw: &[Vec<u8>]) -> Option<ContentType> { | ||||
|   | ||||
| @@ -11,7 +11,7 @@ pub struct Date(pub Tm); | ||||
|  | ||||
| impl Header for Date { | ||||
|     fn header_name(_: Option<Date>) -> &'static str { | ||||
|         "date" | ||||
|         "Date" | ||||
|     } | ||||
|  | ||||
|     fn parse_header(raw: &[Vec<u8>]) -> Option<Date> { | ||||
|   | ||||
| @@ -14,7 +14,7 @@ pub struct Host(pub String); | ||||
|  | ||||
| impl Header for Host { | ||||
|     fn header_name(_: Option<Host>) -> &'static str { | ||||
|         "host" | ||||
|         "Host" | ||||
|     } | ||||
|  | ||||
|     fn parse_header(raw: &[Vec<u8>]) -> Option<Host> { | ||||
|   | ||||
| @@ -10,7 +10,7 @@ pub struct Server(pub String); | ||||
|  | ||||
| impl Header for Server { | ||||
|     fn header_name(_: Option<Server>) -> &'static str { | ||||
|         "server" | ||||
|         "Server" | ||||
|     } | ||||
|  | ||||
|     fn parse_header(raw: &[Vec<u8>]) -> Option<Server> { | ||||
|   | ||||
| @@ -57,7 +57,7 @@ impl FromStr for Encoding { | ||||
|  | ||||
| impl Header for TransferEncoding { | ||||
|     fn header_name(_: Option<TransferEncoding>) -> &'static str { | ||||
|         "transfer-encoding" | ||||
|         "Transfer-Encoding" | ||||
|     } | ||||
|  | ||||
|     fn parse_header(raw: &[Vec<u8>]) -> Option<TransferEncoding> { | ||||
|   | ||||
| @@ -10,7 +10,7 @@ pub struct UserAgent(pub String); | ||||
|  | ||||
| impl Header for UserAgent { | ||||
|     fn header_name(_: Option<UserAgent>) -> &'static str { | ||||
|         "user-agent" | ||||
|         "User-Agent" | ||||
|     } | ||||
|  | ||||
|     fn parse_header(raw: &[Vec<u8>]) -> Option<UserAgent> { | ||||
|   | ||||
| @@ -4,9 +4,9 @@ | ||||
| //! 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::ascii::{AsciiExt, ASCII_LOWER_MAP}; | ||||
| use std::fmt::{mod, Show}; | ||||
| use std::hash; | ||||
| use std::intrinsics::TypeId; | ||||
| use std::mem::{transmute, transmute_copy}; | ||||
| use std::raw::TraitObject; | ||||
| @@ -63,14 +63,12 @@ impl<'a> UncheckedAnyDowncast<'a> for &'a Header { | ||||
|  | ||||
| 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> | ||||
|     data: HashMap<CaseInsensitive, Item> | ||||
| } | ||||
|  | ||||
| impl Headers { | ||||
| @@ -92,8 +90,9 @@ impl Headers { | ||||
|                     // means its safe utf8 | ||||
|                     let name = unsafe { | ||||
|                         raw::from_utf8(name) | ||||
|                     }.into_ascii_lower(); | ||||
|                     let item = headers.data.find_or_insert(Owned(name), Raw(vec![])); | ||||
|                     }; | ||||
|                     let name = CaseInsensitive(Owned(name)); | ||||
|                     let item = headers.data.find_or_insert(name, Raw(vec![])); | ||||
|                     match *item { | ||||
|                         Raw(ref mut raw) => raw.push(value), | ||||
|                         // Unreachable | ||||
| @@ -110,7 +109,7 @@ impl Headers { | ||||
|     /// | ||||
|     /// 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 as Box<Header>)); | ||||
|         self.data.insert(CaseInsensitive(Slice(header_name::<H>())), Typed(box value as Box<Header>)); | ||||
|     } | ||||
|  | ||||
|     /// Get a clone of the header field's value, if it exists. | ||||
| @@ -140,7 +139,7 @@ impl Headers { | ||||
|     /// 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| { | ||||
|         self.data.find(&CaseInsensitive(Slice(name))).and_then(|item| { | ||||
|             match *item { | ||||
|                 Raw(ref raw) => Some(raw.as_slice()), | ||||
|                 _ => None | ||||
| @@ -150,7 +149,7 @@ impl Headers { | ||||
|  | ||||
|     /// 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| { | ||||
|         self.data.find_mut(&CaseInsensitive(Slice(header_name::<H>()))).and_then(|item| { | ||||
|             debug!("get_ref, name={}, val={}", header_name::<H>(), item); | ||||
|             let header = match *item { | ||||
|                 // Huge borrowck hack here, should be refactored to just return here. | ||||
| @@ -197,13 +196,13 @@ impl Headers { | ||||
|     /// let has_type = headers.has::<ContentType>(); | ||||
|     /// ``` | ||||
|     pub fn has<H: Header>(&self) -> bool { | ||||
|         self.data.contains_key(&Slice(header_name::<H>())) | ||||
|         self.data.contains_key(&CaseInsensitive(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.remove(&Slice(Header::header_name(None::<H>))) | ||||
|         self.data.remove(&CaseInsensitive(Slice(Header::header_name(None::<H>)))) | ||||
|     } | ||||
|  | ||||
|     /// Returns an iterator over the header fields. | ||||
| @@ -226,7 +225,7 @@ impl fmt::Show for Headers { | ||||
|  | ||||
| /// An `Iterator` over the fields in a `Headers` map. | ||||
| pub struct HeadersItems<'a> { | ||||
|     inner: Entries<'a, SendStr, Item> | ||||
|     inner: Entries<'a, CaseInsensitive, Item> | ||||
| } | ||||
|  | ||||
| impl<'a> Iterator<(&'a str, HeaderView<'a>)> for HeadersItems<'a> { | ||||
| @@ -279,11 +278,47 @@ impl fmt::Show for Item { | ||||
|     } | ||||
| } | ||||
|  | ||||
| struct CaseInsensitive(SendStr); | ||||
|  | ||||
| impl Str for CaseInsensitive { | ||||
|     fn as_slice(&self) -> &str { | ||||
|         let CaseInsensitive(ref s) = *self; | ||||
|         s.as_slice() | ||||
|     } | ||||
|  | ||||
| } | ||||
|  | ||||
| impl fmt::Show for CaseInsensitive { | ||||
|     fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { | ||||
|         self.as_slice().fmt(fmt) | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl PartialEq for CaseInsensitive { | ||||
|     fn eq(&self, other: &CaseInsensitive) -> bool { | ||||
|         self.as_slice().eq_ignore_ascii_case(other.as_slice()) | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl Eq for CaseInsensitive {} | ||||
|  | ||||
| impl<H: hash::Writer> hash::Hash<H> for CaseInsensitive { | ||||
|     #[inline] | ||||
|     fn hash(&self, hasher: &mut H) { | ||||
|         for byte in self.as_slice().bytes() { | ||||
|             hasher.write([ASCII_LOWER_MAP[byte as uint]].as_slice()); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[cfg(test)] | ||||
| mod tests { | ||||
|     use std::io::MemReader; | ||||
|     use std::fmt; | ||||
|     use std::str::Slice; | ||||
|     use std::hash::sip::hash; | ||||
|     use mime::{Mime, Text, Plain}; | ||||
|     use super::CaseInsensitive; | ||||
|     use super::{Headers, Header}; | ||||
|     use super::common::{ContentLength, ContentType}; | ||||
|  | ||||
| @@ -291,6 +326,15 @@ mod tests { | ||||
|         MemReader::new(s.as_bytes().to_vec()) | ||||
|     } | ||||
|  | ||||
|     #[test] | ||||
|     fn test_case_insensitive() { | ||||
|         let a = CaseInsensitive(Slice("foobar")); | ||||
|         let b = CaseInsensitive(Slice("FOOBAR")); | ||||
|  | ||||
|         assert_eq!(a, b); | ||||
|         assert_eq!(hash(&a), hash(&b)); | ||||
|     } | ||||
|  | ||||
|     #[test] | ||||
|     fn test_from_raw() { | ||||
|         let mut headers = Headers::from_raw(&mut mem("Content-Length: 10\r\n\r\n")).unwrap(); | ||||
|   | ||||
		Reference in New Issue
	
	Block a user