diff --git a/Cargo.toml b/Cargo.toml index 5086af87..4cb02c1b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,6 +22,7 @@ headers-core = { version = "0.2", path = "./headers-core" } base64 = "0.13" bitflags = "1.0" bytes = "1" +language-tags = "0.3" mime = "0.3.14" sha-1 = "0.9" httpdate = "1" diff --git a/src/disabled/link.rs b/src/common/link.rs similarity index 81% rename from src/disabled/link.rs rename to src/common/link.rs index a6d84944..163ca3ac 100644 --- a/src/disabled/link.rs +++ b/src/common/link.rs @@ -1,14 +1,13 @@ -use std::fmt; -use std::borrow::Cow; -use std::str::FromStr; #[allow(unused, deprecated)] use std::ascii::AsciiExt; +use std::borrow::Cow; +use std::fmt; +use std::str::FromStr; -use mime::Mime; use language_tags::LanguageTag; +use mime::Mime; -use parsing; -use {Header, Raw}; +use Header; /// The `Link` header, defined in /// [RFC5988](http://tools.ietf.org/html/rfc5988#section-5) @@ -58,21 +57,21 @@ use {Header, Raw}; /// # Examples /// /// ``` -/// use headers::{Headers, Link, LinkValue, RelationType}; +/// use headers::{HeaderMap, HeaderMapExt, Link, LinkValue, RelationType}; /// /// let link_value = LinkValue::new("http://example.com/TheBook/chapter2") /// .push_rel(RelationType::Previous) /// .set_title("previous chapter"); /// -/// let mut headers = Headers::new(); -/// headers.set( +/// let mut headers = HeaderMap::new(); +/// headers.typed_insert( /// Link::new(vec![link_value]) /// ); /// ``` #[derive(Clone, PartialEq, Debug)] pub struct Link { /// A list of the `link-value`s of the Link entity-header. - values: Vec + values: Vec, } /// A single `link-value` of a `Link` header, based on: @@ -134,7 +133,7 @@ pub enum MediaDesc { /// all. All, /// Unrecognized media descriptor extension. - Extension(String) + Extension(String), } /// A Link Relation Type Enum based on: @@ -222,7 +221,7 @@ pub enum RelationType { /// working-copy-of. WorkingCopyOf, /// ext-rel-type. - ExtRelType(String) + ExtRelType(String), } //////////////////////////////////////////////////////////////////////////////// @@ -232,7 +231,9 @@ pub enum RelationType { impl Link { /// Create `Link` from a `Vec`. pub fn new(link_values: Vec) -> Link { - Link { values: link_values } + Link { + values: link_values, + } } /// Get the `Link` header's `LinkValue`s. @@ -249,7 +250,9 @@ impl Link { impl LinkValue { /// Create `LinkValue` from URI-Reference. pub fn new(uri: T) -> LinkValue - where T: Into> { + where + T: Into>, + { LinkValue { link: uri.into(), rel: None, @@ -386,34 +389,31 @@ impl LinkValue { //////////////////////////////////////////////////////////////////////////////// impl Header for Link { - fn header_name() -> &'static str { - static NAME: &'static str = "Link"; - NAME + fn name() -> &'static http::header::HeaderName { + &http::header::LINK } - fn parse_header(raw: &Raw) -> ::Result { + fn decode<'i, I: Iterator>(values: &mut I) -> Result { // If more that one `Link` headers are present in a request's // headers they are combined in a single `Link` header containing // all the `link-value`s present in each of those `Link` headers. - raw.iter() - .map(parsing::from_raw_str::) - .fold(None, |p, c| { - match (p, c) { - (None, c) => Some(c), - (e @ Some(Err(_)), _) => e, - (Some(Ok(mut p)), Ok(c)) => { - p.values.extend(c.values); - - Some(Ok(p)) - }, - _ => Some(Err(::Error::Header)), + values + .map(|v| v.to_str().map_err(|_| ::Error::invalid())?.parse::()) + .fold(None, |p, c| match (p, c) { + (None, c) => Some(c), + (e @ Some(Err(_)), _) => e, + (Some(Ok(mut p)), Ok(c)) => { + p.values.extend(c.values); + + Some(Ok(p)) } + _ => Some(Err(::Error::invalid())), }) - .unwrap_or(Err(::Error::Header)) + .unwrap_or_else(|| Err(::Error::invalid())) } - fn fmt_header(&self, f: &mut ::Formatter) -> fmt::Result { - f.fmt_line(self) + fn encode>(&self, values: &mut E) { + values.extend(::std::iter::once(crate::util::fmt(self))); } } @@ -425,33 +425,33 @@ impl fmt::Display for Link { impl fmt::Display for LinkValue { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - try!(write!(f, "<{}>", self.link)); + write!(f, "<{}>", self.link)?; if let Some(ref rel) = self.rel { - try!(fmt_delimited(f, rel.as_slice(), " ", ("; rel=\"", "\""))); + fmt_delimited(f, rel.as_slice(), " ", ("; rel=\"", "\""))?; } if let Some(ref anchor) = self.anchor { - try!(write!(f, "; anchor=\"{}\"", anchor)); + write!(f, "; anchor=\"{}\"", anchor)?; } if let Some(ref rev) = self.rev { - try!(fmt_delimited(f, rev.as_slice(), " ", ("; rev=\"", "\""))); + fmt_delimited(f, rev.as_slice(), " ", ("; rev=\"", "\""))?; } if let Some(ref href_lang) = self.href_lang { for tag in href_lang { - try!(write!(f, "; hreflang={}", tag)); + write!(f, "; hreflang={}", tag)?; } } if let Some(ref media_desc) = self.media_desc { - try!(fmt_delimited(f, media_desc.as_slice(), ", ", ("; media=\"", "\""))); + fmt_delimited(f, media_desc.as_slice(), ", ", ("; media=\"", "\""))?; } if let Some(ref title) = self.title { - try!(write!(f, "; title=\"{}\"", title)); + write!(f, "; title=\"{}\"", title)?; } if let Some(ref title_star) = self.title_star { - try!(write!(f, "; title*={}", title_star)); + write!(f, "; title*={}", title_star)?; } if let Some(ref media_type) = self.media_type { - try!(write!(f, "; type=\"{}\"", media_type)); + write!(f, "; type=\"{}\"", media_type)?; } Ok(()) @@ -461,7 +461,7 @@ impl fmt::Display for LinkValue { impl FromStr for Link { type Err = ::Error; - fn from_str(s: &str) -> ::Result { + fn from_str(s: &str) -> Result { // Create a split iterator with delimiters: `;`, `,` let link_split = SplitAsciiUnquoted::new(s, ";,"); @@ -473,35 +473,31 @@ impl FromStr for Link { // Parse the `Target IRI` // https://tools.ietf.org/html/rfc5988#section-5.1 if segment.trim().starts_with('<') { - link_values.push( - match verify_and_trim(segment.trim(), (b'<', b'>')) { - Err(_) => return Err(::Error::Header), - Ok(s) => { - LinkValue { - link: s.to_owned().into(), - rel: None, - anchor: None, - rev: None, - href_lang: None, - media_desc: None, - title: None, - title_star: None, - media_type: None, - } - }, - } - ); + link_values.push(match verify_and_trim(segment.trim(), (b'<', b'>')) { + Err(_) => return Err(::Error::invalid()), + Ok(s) => LinkValue { + link: s.to_owned().into(), + rel: None, + anchor: None, + rev: None, + href_lang: None, + media_desc: None, + title: None, + title_star: None, + media_type: None, + }, + }); } else { // Parse the current link-value's parameters let mut link_param_split = segment.splitn(2, '='); let link_param_name = match link_param_split.next() { - None => return Err(::Error::Header), + None => return Err(::Error::invalid()), Some(p) => p.trim(), }; let link_header = match link_values.last_mut() { - None => return Err(::Error::Header), + None => return Err(::Error::invalid()), Some(l) => l, }; @@ -510,24 +506,23 @@ impl FromStr for Link { // https://tools.ietf.org/html/rfc5988#section-5.3 if link_header.rel.is_none() { link_header.rel = match link_param_split.next() { - None | Some("") => return Err(::Error::Header), - Some(s) => { - s.trim_matches(|c: char| c == '"' || c.is_whitespace()) - .split(' ') - .map(|t| t.trim().parse()) - .collect::, _>>() - .or_else(|_| Err(::Error::Header)) - .ok() - }, + None | Some("") => return Err(::Error::invalid()), + Some(s) => s + .trim_matches(|c: char| c == '"' || c.is_whitespace()) + .split(' ') + .map(|t| t.trim().parse()) + .collect::, _>>() + .or_else(|_| Err(::Error::invalid())) + .ok(), }; } } else if "anchor".eq_ignore_ascii_case(link_param_name) { // Parse the `Context IRI`. // https://tools.ietf.org/html/rfc5988#section-5.2 link_header.anchor = match link_param_split.next() { - None | Some("") => return Err(::Error::Header), + None | Some("") => return Err(::Error::invalid()), Some(s) => match verify_and_trim(s.trim(), (b'"', b'"')) { - Err(_) => return Err(::Error::Header), + Err(_) => return Err(::Error::invalid()), Ok(a) => Some(String::from(a)), }, }; @@ -536,15 +531,14 @@ impl FromStr for Link { // https://tools.ietf.org/html/rfc5988#section-5.3 if link_header.rev.is_none() { link_header.rev = match link_param_split.next() { - None | Some("") => return Err(::Error::Header), - Some(s) => { - s.trim_matches(|c: char| c == '"' || c.is_whitespace()) - .split(' ') - .map(|t| t.trim().parse()) - .collect::, _>>() - .or_else(|_| Err(::Error::Header)) - .ok() - }, + None | Some("") => return Err(::Error::invalid()), + Some(s) => s + .trim_matches(|c: char| c == '"' || c.is_whitespace()) + .split(' ') + .map(|t| t.trim().parse()) + .collect::, _>>() + .or_else(|_| Err(::Error::invalid())) + .ok(), } } } else if "hreflang".eq_ignore_ascii_case(link_param_name) { @@ -552,15 +546,13 @@ impl FromStr for Link { // https://tools.ietf.org/html/rfc5988#section-5.4 let mut v = link_header.href_lang.take().unwrap_or(Vec::new()); - v.push( - match link_param_split.next() { - None | Some("") => return Err(::Error::Header), - Some(s) => match s.trim().parse() { - Err(_) => return Err(::Error::Header), - Ok(t) => t, - }, - } - ); + v.push(match link_param_split.next() { + None | Some("") => return Err(::Error::invalid()), + Some(s) => match s.trim().parse() { + Err(_) => return Err(::Error::invalid()), + Ok(t) => t, + }, + }); link_header.href_lang = Some(v); } else if "media".eq_ignore_ascii_case(link_param_name) { @@ -568,15 +560,14 @@ impl FromStr for Link { // https://tools.ietf.org/html/rfc5988#section-5.4 if link_header.media_desc.is_none() { link_header.media_desc = match link_param_split.next() { - None | Some("") => return Err(::Error::Header), - Some(s) => { - s.trim_matches(|c: char| c == '"' || c.is_whitespace()) - .split(',') - .map(|t| t.trim().parse()) - .collect::, _>>() - .or_else(|_| Err(::Error::Header)) - .ok() - }, + None | Some("") => return Err(::Error::invalid()), + Some(s) => s + .trim_matches(|c: char| c == '"' || c.is_whitespace()) + .split(',') + .map(|t| t.trim().parse()) + .collect::, _>>() + .or_else(|_| Err(::Error::invalid())) + .ok(), }; } } else if "title".eq_ignore_ascii_case(link_param_name) { @@ -584,9 +575,9 @@ impl FromStr for Link { // https://tools.ietf.org/html/rfc5988#section-5.4 if link_header.title.is_none() { link_header.title = match link_param_split.next() { - None | Some("") => return Err(::Error::Header), + None | Some("") => return Err(::Error::invalid()), Some(s) => match verify_and_trim(s.trim(), (b'"', b'"')) { - Err(_) => return Err(::Error::Header), + Err(_) => return Err(::Error::invalid()), Ok(t) => Some(String::from(t)), }, }; @@ -599,7 +590,7 @@ impl FromStr for Link { // https://tools.ietf.org/html/rfc5987#section-3.2.1 if link_header.title_star.is_none() { link_header.title_star = match link_param_split.next() { - None | Some("") => return Err(::Error::Header), + None | Some("") => return Err(::Error::invalid()), Some(s) => Some(String::from(s.trim())), }; } @@ -608,19 +599,18 @@ impl FromStr for Link { // https://tools.ietf.org/html/rfc5988#section-5.4 if link_header.media_type.is_none() { link_header.media_type = match link_param_split.next() { - None | Some("") => return Err(::Error::Header), + None | Some("") => return Err(::Error::invalid()), Some(s) => match verify_and_trim(s.trim(), (b'"', b'"')) { - Err(_) => return Err(::Error::Header), + Err(_) => return Err(::Error::invalid()), Ok(t) => match t.parse() { - Err(_) => return Err(::Error::Header), + Err(_) => return Err(::Error::invalid()), Ok(m) => Some(m), }, }, - }; } } else { - return Err(::Error::Header); + return Err(::Error::invalid()); } } } @@ -642,14 +632,14 @@ impl fmt::Display for MediaDesc { MediaDesc::Aural => write!(f, "aural"), MediaDesc::All => write!(f, "all"), MediaDesc::Extension(ref other) => write!(f, "{}", other), - } + } } } impl FromStr for MediaDesc { type Err = ::Error; - fn from_str(s: &str) -> ::Result { + fn from_str(s: &str) -> Result { match s { "screen" => Ok(MediaDesc::Screen), "tty" => Ok(MediaDesc::Tty), @@ -660,7 +650,7 @@ impl FromStr for MediaDesc { "braille" => Ok(MediaDesc::Braille), "aural" => Ok(MediaDesc::Aural), "all" => Ok(MediaDesc::All), - _ => Ok(MediaDesc::Extension(String::from(s))), + _ => Ok(MediaDesc::Extension(String::from(s))), } } } @@ -709,14 +699,14 @@ impl fmt::Display for RelationType { RelationType::WorkingCopy => write!(f, "working-copy"), RelationType::WorkingCopyOf => write!(f, "working-copy-of"), RelationType::ExtRelType(ref uri) => write!(f, "{}", uri), - } + } } } impl FromStr for RelationType { type Err = ::Error; - fn from_str(s: &str) -> ::Result { + fn from_str(s: &str) -> Result { if "alternate".eq_ignore_ascii_case(s) { Ok(RelationType::Alternate) } else if "appendix".eq_ignore_ascii_case(s) { @@ -810,12 +800,12 @@ impl FromStr for RelationType { struct SplitAsciiUnquoted<'a> { src: &'a str, pos: usize, - del: &'a str + del: &'a str, } impl<'a> SplitAsciiUnquoted<'a> { fn new(s: &'a str, d: &'a str) -> SplitAsciiUnquoted<'a> { - SplitAsciiUnquoted{ + SplitAsciiUnquoted { src: s, pos: 0, del: d, @@ -853,35 +843,38 @@ impl<'a> Iterator for SplitAsciiUnquoted<'a> { } } -fn fmt_delimited(f: &mut fmt::Formatter, p: &[T], d: &str, b: (&str, &str)) -> fmt::Result { +fn fmt_delimited( + f: &mut fmt::Formatter, + p: &[T], + d: &str, + b: (&str, &str), +) -> fmt::Result { if p.len() != 0 { // Write a starting string `b.0` before the first element - try!(write!(f, "{}{}", b.0, p[0])); + write!(f, "{}{}", b.0, p[0])?; for i in &p[1..] { // Write the next element preceded by the delimiter `d` - try!(write!(f, "{}{}", d, i)); + write!(f, "{}{}", d, i)?; } // Write a ending string `b.1` before the first element - try!(write!(f, "{}", b.1)); + write!(f, "{}", b.1)?; } Ok(()) } -fn verify_and_trim(s: &str, b: (u8, u8)) -> ::Result<&str> { +fn verify_and_trim(s: &str, b: (u8, u8)) -> Result<&str, ::Error> { let length = s.len(); let byte_array = s.as_bytes(); // Verify that `s` starts with `b.0` and ends with `b.1` and return // the contained substring after trimming whitespace. if length > 1 && b.0 == byte_array[0] && b.1 == byte_array[length - 1] { - Ok(s.trim_matches( - |c: char| c == b.0 as char || c == b.1 as char || c.is_whitespace()) - ) + Ok(s.trim_matches(|c: char| c == b.0 as char || c == b.1 as char || c.is_whitespace())) } else { - Err(::Error::Header) + Err(::Error::invalid()) } } @@ -894,13 +887,11 @@ mod tests { use std::fmt; use std::fmt::Write; - use super::{Link, LinkValue, MediaDesc, RelationType, SplitAsciiUnquoted}; + use super::super::test_decode; use super::{fmt_delimited, verify_and_trim}; - - use Header; + use super::{Link, LinkValue, MediaDesc, RelationType, SplitAsciiUnquoted}; // use proto::ServerTransaction; - use bytes::BytesMut; use mime; @@ -911,13 +902,13 @@ mod tests { .push_rev(RelationType::Next) .set_title("previous chapter"); - let link_header = b"; \ + let link_header = "; \ rel=\"previous\"; rev=next; title=\"previous chapter\""; let expected_link = Link::new(vec![link_value]); - let link = Header::parse_header(&vec![link_header.to_vec()].into()); - assert_eq!(link.ok(), Some(expected_link)); + let link = test_decode(&vec![link_header]); + assert_eq!(link, Some(expected_link)); } #[test] @@ -930,15 +921,15 @@ mod tests { .push_rel(RelationType::Next) .set_title_star("UTF-8'de'n%c3%a4chstes%20Kapitel"); - let link_header = b"; \ + let link_header = "; \ rel=\"previous\"; title*=UTF-8'de'letztes%20Kapitel, \ ; \ rel=\"next\"; title*=UTF-8'de'n%c3%a4chstes%20Kapitel"; let expected_link = Link::new(vec![first_link, second_link]); - let link = Header::parse_header(&vec![link_header.to_vec()].into()); - assert_eq!(link.ok(), Some(expected_link)); + let link = test_decode(&vec![link_header]); + assert_eq!(link, Some(expected_link)); } #[test] @@ -953,7 +944,7 @@ mod tests { .set_title_star("title* unparsed") .set_media_type(mime::TEXT_PLAIN); - let link_header = b"; \ + let link_header = "; \ rel=\"previous\"; anchor=\"../anchor/example/\"; \ rev=\"next\"; hreflang=de; media=\"screen\"; \ title=\"previous chapter\"; title*=title* unparsed; \ @@ -961,8 +952,8 @@ mod tests { let expected_link = Link::new(vec![link_value]); - let link = Header::parse_header(&vec![link_header.to_vec()].into()); - assert_eq!(link.ok(), Some(expected_link)); + let link = test_decode(&vec![link_header]); + assert_eq!(link, Some(expected_link)); } // TODO @@ -1029,36 +1020,36 @@ mod tests { #[test] fn test_link_parsing_errors() { - let link_a = b"http://example.com/TheBook/chapter2; \ + let link_a = "http://example.com/TheBook/chapter2; \ rel=\"previous\"; rev=next; title=\"previous chapter\""; - let mut err: Result = Header::parse_header(&vec![link_a.to_vec()].into()); - assert_eq!(err.is_err(), true); + let mut link = test_decode::(&vec![link_a]); + assert_eq!(link.is_none(), true); - let link_b = b"; \ + let link_b = "; \ =\"previous\"; rev=next; title=\"previous chapter\""; - err = Header::parse_header(&vec![link_b.to_vec()].into()); - assert_eq!(err.is_err(), true); + link = test_decode(&vec![link_b]); + assert_eq!(link.is_none(), true); - let link_c = b"; \ + let link_c = "; \ rel=; rev=next; title=\"previous chapter\""; - err = Header::parse_header(&vec![link_c.to_vec()].into()); - assert_eq!(err.is_err(), true); + link = test_decode(&vec![link_c]); + assert_eq!(link.is_none(), true); - let link_d = b"; \ + let link_d = "; \ rel=\"previous\"; rev=next; title="; - err = Header::parse_header(&vec![link_d.to_vec()].into()); - assert_eq!(err.is_err(), true); + link = test_decode(&vec![link_d]); + assert_eq!(link.is_none(), true); - let link_e = b"; \ + let link_e = "; \ rel=\"previous\"; rev=next; attr=unknown"; - err = Header::parse_header(&vec![link_e.to_vec()].into()); - assert_eq!(err.is_err(), true); - } + link = test_decode(&vec![link_e]); + assert_eq!(link.is_none(), true); + } #[test] fn test_link_split_ascii_unquoted_iterator() { @@ -1074,7 +1065,9 @@ mod tests { #[test] fn test_link_fmt_delimited() { - struct TestFormatterStruct<'a> { v: Vec<&'a str> }; + struct TestFormatterStruct<'a> { + v: Vec<&'a str>, + } impl<'a> fmt::Display for TestFormatterStruct<'a> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { @@ -1082,7 +1075,9 @@ mod tests { } } - let test_formatter = TestFormatterStruct { v: vec!["first", "second"] }; + let test_formatter = TestFormatterStruct { + v: vec!["first", "second"], + }; let mut string = String::new(); write!(&mut string, "{}", test_formatter).unwrap(); @@ -1102,4 +1097,6 @@ mod tests { } } -bench_header!(bench_link, Link, { vec![b"; rel=\"previous\"; rev=next; title=\"previous chapter\"; type=\"text/html\"; media=\"screen, tty\"".to_vec()] }); +// bench_header!(bench_link, Link, { +// vec![b"; rel=\"previous\"; rev=next; title=\"previous chapter\"; type=\"text/html\"; media=\"screen, tty\"".to_vec()] +// }); diff --git a/src/common/mod.rs b/src/common/mod.rs index 3a1e9c0f..690e550e 100644 --- a/src/common/mod.rs +++ b/src/common/mod.rs @@ -44,7 +44,7 @@ pub use self::if_range::IfRange; pub use self::if_unmodified_since::IfUnmodifiedSince; //pub use self::last_event_id::LastEventId; pub use self::last_modified::LastModified; -//pub use self::link::{Link, LinkValue, RelationType, MediaDesc}; +pub use self::link::{Link, LinkValue, MediaDesc, RelationType}; pub use self::location::Location; pub use self::origin::Origin; pub use self::pragma::Pragma; @@ -163,7 +163,7 @@ mod if_range; mod if_unmodified_since; //mod last_event_id; mod last_modified; -//mod link; +mod link; mod location; mod origin; mod pragma; diff --git a/src/lib.rs b/src/lib.rs index 0c3fa7a8..1c4e6441 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -79,6 +79,7 @@ extern crate bytes; extern crate headers_core; extern crate http; extern crate httpdate; +extern crate language_tags; extern crate mime; extern crate sha1; #[cfg(all(test, feature = "nightly"))]