From 0191bff43a586487f5ff5a84a82ef7546fcc70a0 Mon Sep 17 00:00:00 2001 From: Sean McArthur Date: Tue, 23 Sep 2014 09:02:58 -0700 Subject: [PATCH] property treat header names as case insensitive --- src/header/common/accept.rs | 2 +- src/header/common/connection.rs | 2 +- src/header/common/content_length.rs | 2 +- src/header/common/content_type.rs | 2 +- src/header/common/date.rs | 2 +- src/header/common/host.rs | 2 +- src/header/common/server.rs | 2 +- src/header/common/transfer_encoding.rs | 2 +- src/header/common/user_agent.rs | 2 +- src/header/mod.rs | 70 +++++++++++++++++++++----- 10 files changed, 66 insertions(+), 22 deletions(-) diff --git a/src/header/common/accept.rs b/src/header/common/accept.rs index a4449bc3..68f1d1aa 100644 --- a/src/header/common/accept.rs +++ b/src/header/common/accept.rs @@ -22,7 +22,7 @@ pub struct Accept(pub Vec); impl Header for Accept { fn header_name(_: Option) -> &'static str { - "accept" + "Accept" } fn parse_header(_raw: &[Vec]) -> Option { diff --git a/src/header/common/connection.rs b/src/header/common/connection.rs index 82330ac6..f04b5bef 100644 --- a/src/header/common/connection.rs +++ b/src/header/common/connection.rs @@ -28,7 +28,7 @@ impl FromStr for Connection { impl Header for Connection { fn header_name(_: Option) -> &'static str { - "connection" + "Connection" } fn parse_header(raw: &[Vec]) -> Option { diff --git a/src/header/common/content_length.rs b/src/header/common/content_length.rs index d76636cc..36b8a030 100644 --- a/src/header/common/content_length.rs +++ b/src/header/common/content_length.rs @@ -11,7 +11,7 @@ pub struct ContentLength(pub uint); impl Header for ContentLength { fn header_name(_: Option) -> &'static str { - "content-length" + "Content-Length" } fn parse_header(raw: &[Vec]) -> Option { diff --git a/src/header/common/content_type.rs b/src/header/common/content_type.rs index 64527a01..24c364a7 100644 --- a/src/header/common/content_type.rs +++ b/src/header/common/content_type.rs @@ -12,7 +12,7 @@ pub struct ContentType(pub Mime); impl Header for ContentType { fn header_name(_: Option) -> &'static str { - "content-type" + "Content-Type" } fn parse_header(raw: &[Vec]) -> Option { diff --git a/src/header/common/date.rs b/src/header/common/date.rs index 6d3bb430..1aa6e4ef 100644 --- a/src/header/common/date.rs +++ b/src/header/common/date.rs @@ -11,7 +11,7 @@ pub struct Date(pub Tm); impl Header for Date { fn header_name(_: Option) -> &'static str { - "date" + "Date" } fn parse_header(raw: &[Vec]) -> Option { diff --git a/src/header/common/host.rs b/src/header/common/host.rs index b1f14186..03776d72 100644 --- a/src/header/common/host.rs +++ b/src/header/common/host.rs @@ -14,7 +14,7 @@ pub struct Host(pub String); impl Header for Host { fn header_name(_: Option) -> &'static str { - "host" + "Host" } fn parse_header(raw: &[Vec]) -> Option { diff --git a/src/header/common/server.rs b/src/header/common/server.rs index 719e35b9..b9404e24 100644 --- a/src/header/common/server.rs +++ b/src/header/common/server.rs @@ -10,7 +10,7 @@ pub struct Server(pub String); impl Header for Server { fn header_name(_: Option) -> &'static str { - "server" + "Server" } fn parse_header(raw: &[Vec]) -> Option { diff --git a/src/header/common/transfer_encoding.rs b/src/header/common/transfer_encoding.rs index 2fbc5660..7731557d 100644 --- a/src/header/common/transfer_encoding.rs +++ b/src/header/common/transfer_encoding.rs @@ -57,7 +57,7 @@ impl FromStr for Encoding { impl Header for TransferEncoding { fn header_name(_: Option) -> &'static str { - "transfer-encoding" + "Transfer-Encoding" } fn parse_header(raw: &[Vec]) -> Option { diff --git a/src/header/common/user_agent.rs b/src/header/common/user_agent.rs index c7b0f7ff..40123bec 100644 --- a/src/header/common/user_agent.rs +++ b/src/header/common/user_agent.rs @@ -10,7 +10,7 @@ pub struct UserAgent(pub String); impl Header for UserAgent { fn header_name(_: Option) -> &'static str { - "user-agent" + "User-Agent" } fn parse_header(raw: &[Vec]) -> Option { diff --git a/src/header/mod.rs b/src/header/mod.rs index f1c45b6b..08ba023b 100644 --- a/src/header/mod.rs +++ b/src/header/mod.rs @@ -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() -> &'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 + data: HashMap } 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(&mut self, value: H) { - self.data.insert(Slice(header_name::()), Typed(box value as Box
)); + self.data.insert(CaseInsensitive(Slice(header_name::())), Typed(box value as Box
)); } /// 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]> { - 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(&mut self) -> Option<&H> { - self.data.find_mut(&Slice(header_name::())).and_then(|item| { + self.data.find_mut(&CaseInsensitive(Slice(header_name::()))).and_then(|item| { debug!("get_ref, name={}, val={}", header_name::(), 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::(); /// ``` pub fn has(&self) -> bool { - self.data.contains_key(&Slice(header_name::())) + self.data.contains_key(&CaseInsensitive(Slice(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(&Slice(Header::header_name(None::))) + self.data.remove(&CaseInsensitive(Slice(Header::header_name(None::)))) } /// 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 hash::Hash 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();