diff --git a/src/header/common/accept.rs b/src/header/common/accept.rs index bfa847ff..3617f6ee 100644 --- a/src/header/common/accept.rs +++ b/src/header/common/accept.rs @@ -1,7 +1,9 @@ -use header::{Header, HeaderFormat}; -use std::fmt::{mod, Show}; -use std::str::from_utf8; -use mime::Mime; +use std::fmt; + +use header; +use header::shared; + +use mime; /// The `Accept` header. /// @@ -14,60 +16,53 @@ use mime::Mime; /// ``` /// # use hyper::header::Headers; /// # use hyper::header::common::Accept; +/// # use hyper::header::shared::qitem; /// use hyper::mime::Mime; /// use hyper::mime::TopLevel::Text; /// use hyper::mime::SubLevel::{Html, Xml}; /// # let mut headers = Headers::new(); -/// headers.set(Accept(vec![ Mime(Text, Html, vec![]), Mime(Text, Xml, vec![]) ])); +/// headers.set(Accept(vec![ +/// qitem(Mime(Text, Html, vec![])), +/// qitem(Mime(Text, Xml, vec![])) ])); /// ``` #[deriving(Clone, PartialEq, Show)] -pub struct Accept(pub Vec); +pub struct Accept(pub Vec>); -deref!(Accept -> Vec); +deref!(Accept -> Vec>); -impl Header for Accept { +impl header::Header for Accept { fn header_name(_: Option) -> &'static str { "Accept" } fn parse_header(raw: &[Vec]) -> Option { - let mut mimes: Vec = vec![]; - for mimes_raw in raw.iter() { - match from_utf8(mimes_raw.as_slice()) { - Ok(mimes_str) => { - for mime_str in mimes_str.split(',') { - match mime_str.trim().parse() { - Some(mime) => mimes.push(mime), - None => return None - } - } - }, - Err(_) => return None - }; - } - - if !mimes.is_empty() { - Some(Accept(mimes)) - } else { - // Currently is just a None, but later it can be Accept for */* - None - } + // TODO: Return */* if no value is given. + shared::from_comma_delimited(raw).map(Accept) } } -impl HeaderFormat for Accept { +impl header::HeaderFormat for Accept { 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(()) + shared::fmt_comma_delimited(fmt, self[]) } } bench_header!(bench, Accept, { vec![b"text/plain; q=0.5, text/html".to_vec()] }); +#[test] +fn test_parse_header_no_quality() { + let a: Accept = header::Header::parse_header([b"text/plain; charset=utf-8".to_vec()].as_slice()).unwrap(); + let b = Accept(vec![ + shared::QualityItem{item: mime::Mime(mime::TopLevel::Text, mime::SubLevel::Plain, vec![(mime::Attr::Charset, mime::Value::Utf8)]), quality: 1f32}, + ]); + assert_eq!(a, b); +} + +#[test] +fn test_parse_header_with_quality() { + let a: Accept = header::Header::parse_header([b"text/plain; charset=utf-8; q=0.5".to_vec()].as_slice()).unwrap(); + let b = Accept(vec![ + shared::QualityItem{item: mime::Mime(mime::TopLevel::Text, mime::SubLevel::Plain, vec![(mime::Attr::Charset, mime::Value::Utf8)]), quality: 0.5f32}, + ]); + assert_eq!(a, b); +} diff --git a/src/header/common/accept_encoding.rs b/src/header/common/accept_encoding.rs new file mode 100644 index 00000000..622de977 --- /dev/null +++ b/src/header/common/accept_encoding.rs @@ -0,0 +1,39 @@ +use std::fmt; + +use header; +use header::shared; + +/// The `Accept-Encoding` header +/// +/// The `Accept-Encoding` header can be used by clients to indicate what +/// response encodings they accept. +#[deriving(Clone, PartialEq, Show)] +pub struct AcceptEncoding(pub Vec>); + +deref!(AcceptEncoding -> Vec>); + +impl header::Header for AcceptEncoding { + fn header_name(_: Option) -> &'static str { + "AcceptEncoding" + } + + fn parse_header(raw: &[Vec]) -> Option { + shared::from_comma_delimited(raw).map(AcceptEncoding) + } +} + +impl header::HeaderFormat for AcceptEncoding { + fn fmt_header(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + shared::fmt_comma_delimited(fmt, self[]) + } +} + +#[test] +fn test_parse_header() { + let a: AcceptEncoding = header::Header::parse_header([b"gzip;q=1.0, identity; q=0.5".to_vec()].as_slice()).unwrap(); + let b = AcceptEncoding(vec![ + shared::QualityItem{item: shared::Gzip, quality: 1f32}, + shared::QualityItem{item: shared::Identity, quality: 0.5f32}, + ]); + assert_eq!(a, b); +} diff --git a/src/header/common/allow.rs b/src/header/common/allow.rs index 07e17cd2..0efad670 100644 --- a/src/header/common/allow.rs +++ b/src/header/common/allow.rs @@ -1,7 +1,7 @@ use header::{Header, HeaderFormat}; use method::Method; use std::fmt::{mod}; -use super::util::{from_comma_delimited, fmt_comma_delimited}; +use header::shared::util::{from_comma_delimited, fmt_comma_delimited}; /// The `Allow` header. /// See also https://tools.ietf.org/html/rfc7231#section-7.4.1 @@ -46,4 +46,3 @@ mod tests { } bench_header!(bench, Allow, { vec![b"OPTIONS,GET,PUT,POST,DELETE,HEAD,TRACE,CONNECT,PATCH,fOObAr".to_vec()] }); - diff --git a/src/header/common/cache_control.rs b/src/header/common/cache_control.rs index 304c01db..8c309dff 100644 --- a/src/header/common/cache_control.rs +++ b/src/header/common/cache_control.rs @@ -1,7 +1,7 @@ use std::fmt; use std::str::FromStr; use header::{Header, HeaderFormat}; -use super::util::{from_one_comma_delimited, fmt_comma_delimited}; +use header::shared::util::{from_one_comma_delimited, fmt_comma_delimited}; /// The Cache-Control header. #[deriving(PartialEq, Clone, Show)] diff --git a/src/header/common/connection.rs b/src/header/common/connection.rs index 2ae9b972..ad2ce2b6 100644 --- a/src/header/common/connection.rs +++ b/src/header/common/connection.rs @@ -1,7 +1,7 @@ use header::{Header, HeaderFormat}; use std::fmt::{mod, Show}; use std::str::FromStr; -use super::util::{from_comma_delimited, fmt_comma_delimited}; +use header::shared::util::{from_comma_delimited, fmt_comma_delimited}; pub use self::ConnectionOption::{KeepAlive, Close, ConnectionHeader}; diff --git a/src/header/common/content_length.rs b/src/header/common/content_length.rs index 32957b97..3637d02b 100644 --- a/src/header/common/content_length.rs +++ b/src/header/common/content_length.rs @@ -1,7 +1,7 @@ use std::fmt::{mod, Show}; use header::{Header, HeaderFormat}; -use super::util::from_one_raw_str; +use header::shared::util::from_one_raw_str; /// The `Content-Length` header. /// diff --git a/src/header/common/content_type.rs b/src/header/common/content_type.rs index 4b26dac5..d07c3542 100644 --- a/src/header/common/content_type.rs +++ b/src/header/common/content_type.rs @@ -1,6 +1,6 @@ use header::{Header, HeaderFormat}; use std::fmt::{mod, Show}; -use super::util::from_one_raw_str; +use header::shared::util::from_one_raw_str; use mime::Mime; /// The `Content-Type` header. diff --git a/src/header/common/date.rs b/src/header/common/date.rs index a33d16a4..9398cefe 100644 --- a/src/header/common/date.rs +++ b/src/header/common/date.rs @@ -2,7 +2,8 @@ use std::fmt::{mod, Show}; use std::str::FromStr; use time::Tm; use header::{Header, HeaderFormat}; -use super::util::{from_one_raw_str, tm_from_str}; +use header::shared::util::from_one_raw_str; +use header::shared::time::tm_from_str; // Egh, replace as soon as something better than time::Tm exists. /// The `Date` header field. @@ -41,4 +42,3 @@ impl FromStr for Date { bench_header!(imf_fixdate, Date, { vec![b"Sun, 07 Nov 1994 08:48:37 GMT".to_vec()] }); bench_header!(rfc_850, Date, { vec![b"Sunday, 06-Nov-94 08:49:37 GMT".to_vec()] }); bench_header!(asctime, Date, { vec![b"Sun Nov 6 08:49:37 1994".to_vec()] }); - diff --git a/src/header/common/etag.rs b/src/header/common/etag.rs index bddbf02e..3bdd3a6b 100644 --- a/src/header/common/etag.rs +++ b/src/header/common/etag.rs @@ -1,6 +1,6 @@ use header::{Header, HeaderFormat}; use std::fmt::{mod}; -use super::util::from_one_raw_str; +use header::shared::util::from_one_raw_str; /// The `Etag` header. /// diff --git a/src/header/common/expires.rs b/src/header/common/expires.rs index f9dfe35c..4f160ded 100644 --- a/src/header/common/expires.rs +++ b/src/header/common/expires.rs @@ -2,7 +2,8 @@ use std::fmt::{mod, Show}; use std::str::FromStr; use time::Tm; use header::{Header, HeaderFormat}; -use super::util::{from_one_raw_str, tm_from_str}; +use header::shared::util::from_one_raw_str; +use header::shared::time::tm_from_str; /// The `Expires` header field. #[deriving(Copy, PartialEq, Clone)] @@ -40,4 +41,3 @@ impl FromStr for Expires { bench_header!(imf_fixdate, Expires, { vec![b"Sun, 07 Nov 1994 08:48:37 GMT".to_vec()] }); bench_header!(rfc_850, Expires, { vec![b"Sunday, 06-Nov-94 08:49:37 GMT".to_vec()] }); bench_header!(asctime, Expires, { vec![b"Sun Nov 6 08:49:37 1994".to_vec()] }); - diff --git a/src/header/common/host.rs b/src/header/common/host.rs index 3f14d658..7298fa51 100644 --- a/src/header/common/host.rs +++ b/src/header/common/host.rs @@ -1,7 +1,7 @@ use header::{Header, HeaderFormat}; use Port; use std::fmt::{mod, Show}; -use super::util::from_one_raw_str; +use header::shared::util::from_one_raw_str; /// The `Host` header. /// diff --git a/src/header/common/if_modified_since.rs b/src/header/common/if_modified_since.rs index 850e3dff..a462ed54 100644 --- a/src/header/common/if_modified_since.rs +++ b/src/header/common/if_modified_since.rs @@ -2,7 +2,8 @@ use std::fmt::{mod, Show}; use std::str::FromStr; use time::Tm; use header::{Header, HeaderFormat}; -use super::util::{from_one_raw_str, tm_from_str}; +use header::shared::util::from_one_raw_str; +use header::shared::time::tm_from_str; /// The `If-Modified-Since` header field. #[deriving(Copy, PartialEq, Clone)] @@ -40,4 +41,3 @@ impl FromStr for IfModifiedSince { bench_header!(imf_fixdate, IfModifiedSince, { vec![b"Sun, 07 Nov 1994 08:48:37 GMT".to_vec()] }); bench_header!(rfc_850, IfModifiedSince, { vec![b"Sunday, 06-Nov-94 08:49:37 GMT".to_vec()] }); bench_header!(asctime, IfModifiedSince, { vec![b"Sun Nov 6 08:49:37 1994".to_vec()] }); - diff --git a/src/header/common/last_modified.rs b/src/header/common/last_modified.rs index 5606f093..5369ae4d 100644 --- a/src/header/common/last_modified.rs +++ b/src/header/common/last_modified.rs @@ -2,7 +2,8 @@ use std::fmt::{mod, Show}; use std::str::FromStr; use time::Tm; use header::{Header, HeaderFormat}; -use super::util::{from_one_raw_str, tm_from_str}; +use header::shared::util::from_one_raw_str; +use header::shared::time::tm_from_str; /// The `LastModified` header field. #[deriving(Copy, PartialEq, Clone)] @@ -40,4 +41,3 @@ impl FromStr for LastModified { bench_header!(imf_fixdate, LastModified, { vec![b"Sun, 07 Nov 1994 08:48:37 GMT".to_vec()] }); bench_header!(rfc_850, LastModified, { vec![b"Sunday, 06-Nov-94 08:49:37 GMT".to_vec()] }); bench_header!(asctime, LastModified, { vec![b"Sun Nov 6 08:49:37 1994".to_vec()] }); - diff --git a/src/header/common/location.rs b/src/header/common/location.rs index ea3b815d..31fdc962 100644 --- a/src/header/common/location.rs +++ b/src/header/common/location.rs @@ -1,6 +1,6 @@ use header::{Header, HeaderFormat}; use std::fmt::{mod, Show}; -use super::util::from_one_raw_str; +use header::shared::util::from_one_raw_str; /// The `Location` header. /// diff --git a/src/header/common/mod.rs b/src/header/common/mod.rs index bbb4d37e..f6659afa 100644 --- a/src/header/common/mod.rs +++ b/src/header/common/mod.rs @@ -7,6 +7,7 @@ //! is used, such as `ContentType(pub Mime)`. pub use self::accept::Accept; +pub use self::accept_encoding::AcceptEncoding; pub use self::allow::Allow; pub use self::authorization::Authorization; pub use self::cache_control::CacheControl; @@ -76,6 +77,9 @@ macro_rules! deref( /// Exposes the Accept header. pub mod accept; +/// Exposes the AcceptEncoding header. +pub mod accept_encoding; + /// Exposes the Allow header. pub mod allow; @@ -135,5 +139,3 @@ pub mod user_agent; /// Exposes the Vary header. pub mod vary; - -pub mod util; diff --git a/src/header/common/server.rs b/src/header/common/server.rs index 64c3ca1d..a7d5d4ed 100644 --- a/src/header/common/server.rs +++ b/src/header/common/server.rs @@ -1,6 +1,6 @@ use header::{Header, HeaderFormat}; use std::fmt::{mod, Show}; -use super::util::from_one_raw_str; +use header::shared::util::from_one_raw_str; /// The `Server` header field. /// @@ -28,4 +28,3 @@ impl HeaderFormat for Server { } bench_header!(bench, Server, { vec![b"Some String".to_vec()] }); - diff --git a/src/header/common/set_cookie.rs b/src/header/common/set_cookie.rs index d861624e..4bec8724 100644 --- a/src/header/common/set_cookie.rs +++ b/src/header/common/set_cookie.rs @@ -103,16 +103,15 @@ fn test_fmt() { #[test] fn cookie_jar() { - let jar = CookieJar::new("secret".as_bytes()); + let jar = CookieJar::new(b"secret"); let cookie = Cookie::new("foo".to_string(), "bar".to_string()); jar.encrypted().add(cookie); let cookies = SetCookie::from_cookie_jar(&jar); - let mut new_jar = CookieJar::new("secret".as_bytes()); + let mut new_jar = CookieJar::new(b"secret"); cookies.apply_to_cookie_jar(&mut new_jar); assert_eq!(jar.encrypted().find("foo"), new_jar.encrypted().find("foo")); assert_eq!(jar.iter().collect::>(), new_jar.iter().collect::>()); } - diff --git a/src/header/common/transfer_encoding.rs b/src/header/common/transfer_encoding.rs index e0572cf0..47c4ad50 100644 --- a/src/header/common/transfer_encoding.rs +++ b/src/header/common/transfer_encoding.rs @@ -1,7 +1,7 @@ use header::{Header, HeaderFormat}; use std::fmt; use std::str::FromStr; -use super::util::{from_comma_delimited, fmt_comma_delimited}; +use header::shared::util::{from_comma_delimited, fmt_comma_delimited}; use self::Encoding::{Chunked, Gzip, Deflate, Compress, EncodingExt}; diff --git a/src/header/common/upgrade.rs b/src/header/common/upgrade.rs index 296478b8..10c6009b 100644 --- a/src/header/common/upgrade.rs +++ b/src/header/common/upgrade.rs @@ -1,7 +1,7 @@ use header::{Header, HeaderFormat}; use std::fmt::{mod, Show}; use std::str::FromStr; -use super::util::{from_comma_delimited, fmt_comma_delimited}; +use header::shared::util::{from_comma_delimited, fmt_comma_delimited}; use self::Protocol::{WebSocket, ProtocolExt}; diff --git a/src/header/common/user_agent.rs b/src/header/common/user_agent.rs index f35e7cbd..a51bee3c 100644 --- a/src/header/common/user_agent.rs +++ b/src/header/common/user_agent.rs @@ -1,6 +1,6 @@ use header::{Header, HeaderFormat}; use std::fmt::{mod, Show}; -use super::util::from_one_raw_str; +use header::shared::util::from_one_raw_str; /// The `User-Agent` header field. /// diff --git a/src/header/common/util.rs b/src/header/common/util.rs deleted file mode 100644 index f5185592..00000000 --- a/src/header/common/util.rs +++ /dev/null @@ -1,84 +0,0 @@ -//! Utility functions for Header implementations. - -use std::str::{FromStr, from_utf8}; -use std::fmt::{mod, Show}; -use time::{Tm, strptime}; - -/// Reads a single raw string when parsing a header -pub 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[].get_unchecked(0)[] }) { - Ok(s) => FromStr::from_str(s), - Err(_) => None - } -} - -/// Reads a comma-delimited raw header into a Vec. -#[inline] -pub fn from_comma_delimited(raw: &[Vec]) -> Option> { - if raw.len() != 1 { - return None; - } - // we JUST checked that raw.len() == 1, so raw[0] WILL exist. - from_one_comma_delimited(unsafe { raw.as_slice().get_unchecked(0).as_slice() }) -} - -/// Reads a comma-delimited raw string into a Vec. -pub fn from_one_comma_delimited(raw: &[u8]) -> Option> { - match from_utf8(raw) { - Ok(s) => { - Some(s.as_slice() - .split(',') - .map(|x| x.trim()) - .filter_map(FromStr::from_str) - .collect()) - } - Err(_) => None - } -} - -/// Format an array into a comma-delimited string. -pub fn fmt_comma_delimited(fmt: &mut fmt::Formatter, parts: &[T]) -> fmt::Result { - let last = parts.len() - 1; - for (i, part) in parts.iter().enumerate() { - try!(part.fmt(fmt)); - if i < last { - try!(", ".fmt(fmt)); - } - } - Ok(()) -} - -/// Get a Tm from HTTP date formats. -// 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. -pub fn tm_from_str(s: &str) -> Option { - strptime(s, "%a, %d %b %Y %T %Z").or_else(|_| { - strptime(s, "%A, %d-%b-%y %T %Z") - }).or_else(|_| { - strptime(s, "%c") - }).ok() -} diff --git a/src/header/common/vary.rs b/src/header/common/vary.rs index 40d4fa60..1424c278 100644 --- a/src/header/common/vary.rs +++ b/src/header/common/vary.rs @@ -1,6 +1,6 @@ use header::{Header, HeaderFormat, CaseInsensitive}; use std::fmt::{mod}; -use super::util::{from_comma_delimited, fmt_comma_delimited, from_one_raw_str}; +use header::shared::util::{from_comma_delimited, fmt_comma_delimited, from_one_raw_str}; /// The `Allow` header. /// See also https://tools.ietf.org/html/rfc7231#section-7.1.4 diff --git a/src/header/mod.rs b/src/header/mod.rs index e79d168a..ba308727 100644 --- a/src/header/mod.rs +++ b/src/header/mod.rs @@ -22,10 +22,13 @@ use http::{mod, LineEnding}; use {HttpResult}; pub use self::common::*; +pub use self::shared::*; /// Common Headers pub mod common; +pub mod shared; + /// A trait for any object that will represent a header field and value. /// /// This trait represents the construction and identification of headers, @@ -193,7 +196,7 @@ impl Headers { /// ``` /// # use hyper::header::Headers; /// # let mut headers = Headers::new(); - /// headers.set_raw("content-length", vec!["5".as_bytes().to_vec()]); + /// headers.set_raw("content-length", vec![b"5".to_vec()]); /// ``` pub fn set_raw>(&mut self, name: K, value: Vec>) { self.data.insert(CaseInsensitive(name.into_cow()), MuCell::new(Item::raw(value))); @@ -518,6 +521,7 @@ mod tests { use super::CaseInsensitive; use super::{Headers, Header, HeaderFormat}; use super::common::{ContentLength, ContentType, Accept, Host}; + use super::shared::{QualityItem}; use test::Bencher; @@ -542,13 +546,13 @@ mod tests { #[test] fn test_content_type() { - let content_type = Header::parse_header(["text/plain".as_bytes().to_vec()].as_slice()); + let content_type = Header::parse_header([b"text/plain".to_vec()].as_slice()); assert_eq!(content_type, Some(ContentType(Mime(Text, Plain, vec![])))); } #[test] fn test_accept() { - let text_plain = Mime(Text, Plain, vec![]); + let text_plain = QualityItem{item: Mime(Text, Plain, vec![]), quality: 1f32}; let application_vendor = "application/vnd.github.v3.full+json; q=0.5".parse().unwrap(); let accept = Header::parse_header([b"text/plain".to_vec()].as_slice()); diff --git a/src/header/shared/encoding.rs b/src/header/shared/encoding.rs new file mode 100644 index 00000000..f05d79f1 --- /dev/null +++ b/src/header/shared/encoding.rs @@ -0,0 +1,50 @@ +//! Provides an Encoding enum. + +use std::fmt; +use std::str; + +pub use self::Encoding::{Chunked, Gzip, Deflate, Compress, Identity, EncodingExt}; + +/// A value to represent an encoding used in `Transfer-Encoding` +/// or `Accept-Encoding` header. +#[deriving(Clone, PartialEq)] +pub enum Encoding { + /// The `chunked` encoding. + Chunked, + /// The `gzip` encoding. + Gzip, + /// The `deflate` encoding. + Deflate, + /// The `compress` encoding. + Compress, + /// The `identity` encoding. + Identity, + /// Some other encoding that is less common, can be any String. + EncodingExt(String) +} + +impl fmt::Show for Encoding { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + match *self { + Chunked => "chunked", + Gzip => "gzip", + Deflate => "deflate", + Compress => "compress", + Identity => "identity", + EncodingExt(ref s) => s.as_slice() + }.fmt(fmt) + } + } + +impl str::FromStr for Encoding { + fn from_str(s: &str) -> Option { + match s { + "chunked" => Some(Chunked), + "deflate" => Some(Deflate), + "gzip" => Some(Gzip), + "compress" => Some(Compress), + "identity" => Some(Identity), + _ => Some(EncodingExt(s.to_string())) + } + } +} diff --git a/src/header/shared/mod.rs b/src/header/shared/mod.rs new file mode 100644 index 00000000..7f4e488a --- /dev/null +++ b/src/header/shared/mod.rs @@ -0,0 +1,26 @@ +//! Various functions, structs and enums useful for many headers. + +pub use self::encoding::Encoding; +pub use self::encoding::Encoding::{ + Chunked, + Gzip, + Deflate, + Compress, + Identity, + EncodingExt}; + +pub use self::quality_item::QualityItem; +pub use self::quality_item::qitem; + +pub use self::time::tm_from_str; + +pub use self::util::{ + from_one_raw_str, + from_comma_delimited, + from_one_comma_delimited, + fmt_comma_delimited}; + +pub mod encoding; +pub mod quality_item; +pub mod time; +pub mod util; diff --git a/src/header/shared/quality_item.rs b/src/header/shared/quality_item.rs new file mode 100644 index 00000000..a58f4737 --- /dev/null +++ b/src/header/shared/quality_item.rs @@ -0,0 +1,123 @@ +//! Provides a struct for quality values. +//! +//! [RFC7231 Section 5.3.1](https://tools.ietf.org/html/rfc7231#section-5.3.1) +//! gives more information on quality values in HTTP header fields. + +use std::fmt; +use std::str; +#[cfg(test)] use super::encoding::*; + +/// Represents an item with a quality value as defined in +/// [RFC7231](https://tools.ietf.org/html/rfc7231#section-5.3.1). +#[deriving(Clone, PartialEq)] +pub struct QualityItem { + /// The actual contents of the field. + pub item: T, + /// The quality (client or server preference) for the value. + pub quality: f32, +} + +impl QualityItem { + /// Creates a new `QualityItem` from an item and a quality. + /// The item can be of any type. + /// The quality should be a value in the range [0, 1]. + pub fn new(item: T, quality: f32) -> QualityItem { + QualityItem{item: item, quality: quality} + } +} + +impl fmt::Show for QualityItem { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}; q={}", self.item, format!("{:.3}", self.quality).trim_right_matches(['0', '.'].as_slice())) + } +} + +impl str::FromStr for QualityItem { + fn from_str(s: &str) -> Option { + // Set defaults used if parsing fails. + let mut raw_item = s; + let mut quality = 1f32; + + let parts: Vec<&str> = s.rsplitn(1, ';').map(|x| x.trim()).collect(); + if parts.len() == 2 { + let start = parts[0].slice(0, 2); + if start == "q=" || start == "Q=" { + let q_part = parts[0].slice(2, parts[0].len()); + if q_part.len() > 5 { + return None; + } + let x: Option = q_part.parse(); + match x { + Some(q_value) => { + if 0f32 <= q_value && q_value <= 1f32 { + quality = q_value; + raw_item = parts[1]; + } else { + return None; + } + }, + None => return None, + } + } + } + let x: Option = raw_item.parse(); + match x { + Some(item) => { + Some(QualityItem{ item: item, quality: quality, }) + }, + None => return None, + } + } +} + +/// Convinience function to wrap a value in a `QualityItem` +/// Sets `q` to the default 1.0 +pub fn qitem(item: T) -> QualityItem { + QualityItem::new(item, 1.0) +} + +#[test] +fn test_quality_item_show1() { + let x = qitem(Chunked); + assert_eq!(format!("{}", x), "chunked; q=1.000"); +} +#[test] +fn test_quality_item_show2() { + let x = QualityItem::new(Chunked, 0.001); + assert_eq!(format!("{}", x), "chunked; q=0.001"); +} +#[test] +fn test_quality_item_show3() { + // Custom value + let x = QualityItem{ + item: EncodingExt("identity".to_string()), + quality: 0.5f32, + }; + assert_eq!(format!("{}", x), "identity; q=0.500"); +} + +#[test] +fn test_quality_item_from_str1() { + let x: Option> = "chunked".parse(); + assert_eq!(x.unwrap(), QualityItem{ item: Chunked, quality: 1f32, }); +} +#[test] +fn test_quality_item_from_str2() { + let x: Option> = "chunked; q=1".parse(); + assert_eq!(x.unwrap(), QualityItem{ item: Chunked, quality: 1f32, }); +} +#[test] +fn test_quality_item_from_str3() { + let x: Option> = "gzip; q=0.5".parse(); + assert_eq!(x.unwrap(), QualityItem{ item: Gzip, quality: 0.5f32, }); +} +#[test] +fn test_quality_item_from_str4() { + let x: Option> = "gzip; q=0.273".parse(); + assert_eq!(x.unwrap(), QualityItem{ item: Gzip, quality: 0.273f32, }); +} +#[test] +fn test_quality_item_from_str5() { + let x: Option> = "gzip; q=0.2739999".parse(); + assert_eq!(x, None); +} diff --git a/src/header/shared/time.rs b/src/header/shared/time.rs new file mode 100644 index 00000000..90e72f1d --- /dev/null +++ b/src/header/shared/time.rs @@ -0,0 +1,34 @@ +//! Provides utility function to parse HTTP header value time. + +extern crate time; + +/// Get a Tm from HTTP date formats. +// 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. +pub fn tm_from_str(s: &str) -> Option { + time::strptime(s, "%a, %d %b %Y %T %Z").or_else(|_| { + time::strptime(s, "%A, %d-%b-%y %T %Z") + }).or_else(|_| { + time::strptime(s, "%c") + }).ok() +} diff --git a/src/header/shared/util.rs b/src/header/shared/util.rs new file mode 100644 index 00000000..86359d66 --- /dev/null +++ b/src/header/shared/util.rs @@ -0,0 +1,52 @@ +//! Utility functions for Header implementations. + +use std::str; +use std::fmt; + +/// Reads a single raw string when parsing a header +pub 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 str::from_utf8(unsafe { raw[].unsafe_get(0)[] }) { + Ok(s) => str::FromStr::from_str(s), + Err(_) => None + } +} + +/// Reads a comma-delimited raw header into a Vec. +#[inline] +pub fn from_comma_delimited(raw: &[Vec]) -> Option> { + if raw.len() != 1 { + return None; + } + // we JUST checked that raw.len() == 1, so raw[0] WILL exist. + from_one_comma_delimited(unsafe { raw.as_slice().unsafe_get(0).as_slice() }) +} + +/// Reads a comma-delimited raw string into a Vec. +pub fn from_one_comma_delimited(raw: &[u8]) -> Option> { + match str::from_utf8(raw) { + Ok(s) => { + Some(s.as_slice() + .split(',') + .map(|x| x.trim()) + .filter_map(str::FromStr::from_str) + .collect()) + } + Err(_) => None + } +} + +/// Format an array into a comma-delimited string. +pub fn fmt_comma_delimited(fmt: &mut fmt::Formatter, parts: &[T]) -> fmt::Result { + let last = parts.len() - 1; + for (i, part) in parts.iter().enumerate() { + try!(write!(fmt, "{}", part)); + if i < last { + try!(write!(fmt, ", ")); + } + } + Ok(()) +} diff --git a/src/http.rs b/src/http.rs index 6f9db324..33f2cda7 100644 --- a/src/http.rs +++ b/src/http.rs @@ -789,7 +789,7 @@ mod tests { } read("Host: rust-lang.org\r\n", Ok(Some(("Host".to_string(), - "rust-lang.org".as_bytes().to_vec())))); + b"rust-lang.org".to_vec())))); } #[test]