use std::fmt; use std::borrow::Cow; use std::str::FromStr; #[allow(unused, deprecated)] use std::ascii::AsciiExt; use mime::Mime; use language_tags::LanguageTag; use parsing; use {Header, Raw}; /// The `Link` header, defined in /// [RFC5988](http://tools.ietf.org/html/rfc5988#section-5) /// /// # ABNF /// /// ```text /// Link = "Link" ":" #link-value /// link-value = "<" URI-Reference ">" *( ";" link-param ) /// link-param = ( ( "rel" "=" relation-types ) /// | ( "anchor" "=" <"> URI-Reference <"> ) /// | ( "rev" "=" relation-types ) /// | ( "hreflang" "=" Language-Tag ) /// | ( "media" "=" ( MediaDesc | ( <"> MediaDesc <"> ) ) ) /// | ( "title" "=" quoted-string ) /// | ( "title*" "=" ext-value ) /// | ( "type" "=" ( media-type | quoted-mt ) ) /// | ( link-extension ) ) /// link-extension = ( parmname [ "=" ( ptoken | quoted-string ) ] ) /// | ( ext-name-star "=" ext-value ) /// ext-name-star = parmname "*" ; reserved for RFC2231-profiled /// ; extensions. Whitespace NOT /// ; allowed in between. /// ptoken = 1*ptokenchar /// ptokenchar = "!" | "#" | "$" | "%" | "&" | "'" | "(" /// | ")" | "*" | "+" | "-" | "." | "/" | DIGIT /// | ":" | "<" | "=" | ">" | "?" | "@" | ALPHA /// | "[" | "]" | "^" | "_" | "`" | "{" | "|" /// | "}" | "~" /// media-type = type-name "/" subtype-name /// quoted-mt = <"> media-type <"> /// relation-types = relation-type /// | <"> relation-type *( 1*SP relation-type ) <"> /// relation-type = reg-rel-type | ext-rel-type /// reg-rel-type = LOALPHA *( LOALPHA | DIGIT | "." | "-" ) /// ext-rel-type = URI /// ``` /// /// # Example values /// /// `Link: ; rel="previous"; /// title="previous chapter"` /// /// `Link: ; rel="previous"; title*=UTF-8'de'letztes%20Kapitel, /// ; rel="next"; title*=UTF-8'de'n%c3%a4chstes%20Kapitel` /// /// # Examples /// /// ``` /// use headers::{Headers, 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( /// 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 } /// A single `link-value` of a `Link` header, based on: /// [RFC5988](http://tools.ietf.org/html/rfc5988#section-5) #[derive(Clone, PartialEq, Debug)] pub struct LinkValue { /// Target IRI: `link-value`. link: Cow<'static, str>, /// Forward Relation Types: `rel`. rel: Option>, /// Context IRI: `anchor`. anchor: Option, /// Reverse Relation Types: `rev`. rev: Option>, /// Hint on the language of the result of dereferencing /// the link: `hreflang`. href_lang: Option>, /// Destination medium or media: `media`. media_desc: Option>, /// Label of the destination of a Link: `title`. title: Option, /// The `title` encoded in a different charset: `title*`. title_star: Option, /// Hint on the media type of the result of dereferencing /// the link: `type`. media_type: Option, } /// A Media Descriptors Enum based on: /// [https://www.w3.org/TR/html401/types.html#h-6.13][url] /// /// [url]: https://www.w3.org/TR/html401/types.html#h-6.13 #[derive(Clone, PartialEq, Debug)] pub enum MediaDesc { /// screen. Screen, /// tty. Tty, /// tv. Tv, /// projection. Projection, /// handheld. Handheld, /// print. Print, /// braille. Braille, /// aural. Aural, /// all. All, /// Unrecognized media descriptor extension. Extension(String) } /// A Link Relation Type Enum based on: /// [RFC5988](https://tools.ietf.org/html/rfc5988#section-6.2.2) #[derive(Clone, PartialEq, Debug)] pub enum RelationType { /// alternate. Alternate, /// appendix. Appendix, /// bookmark. Bookmark, /// chapter. Chapter, /// contents. Contents, /// copyright. Copyright, /// current. Current, /// describedby. DescribedBy, /// edit. Edit, /// edit-media. EditMedia, /// enclosure. Enclosure, /// first. First, /// glossary. Glossary, /// help. Help, /// hub. Hub, /// index. Index, /// last. Last, /// latest-version. LatestVersion, /// license. License, /// next. Next, /// next-archive. NextArchive, /// payment. Payment, /// prev. Prev, /// predecessor-version. PredecessorVersion, /// previous. Previous, /// prev-archive. PrevArchive, /// related. Related, /// replies. Replies, /// section. Section, /// self. RelationTypeSelf, /// service. Service, /// start. Start, /// stylesheet. Stylesheet, /// subsection. Subsection, /// successor-version. SuccessorVersion, /// up. Up, /// versionHistory. VersionHistory, /// via. Via, /// working-copy. WorkingCopy, /// working-copy-of. WorkingCopyOf, /// ext-rel-type. ExtRelType(String) } //////////////////////////////////////////////////////////////////////////////// // Struct methods //////////////////////////////////////////////////////////////////////////////// impl Link { /// Create `Link` from a `Vec`. pub fn new(link_values: Vec) -> Link { Link { values: link_values } } /// Get the `Link` header's `LinkValue`s. pub fn values(&self) -> &[LinkValue] { self.values.as_ref() } /// Add a `LinkValue` instance to the `Link` header's values. pub fn push_value(&mut self, link_value: LinkValue) { self.values.push(link_value); } } impl LinkValue { /// Create `LinkValue` from URI-Reference. pub fn new(uri: T) -> LinkValue where T: Into> { LinkValue { link: uri.into(), rel: None, anchor: None, rev: None, href_lang: None, media_desc: None, title: None, title_star: None, media_type: None, } } /// Get the `LinkValue`'s value. pub fn link(&self) -> &str { self.link.as_ref() } /// Get the `LinkValue`'s `rel` parameter(s). pub fn rel(&self) -> Option<&[RelationType]> { self.rel.as_ref().map(AsRef::as_ref) } /// Get the `LinkValue`'s `anchor` parameter. pub fn anchor(&self) -> Option<&str> { self.anchor.as_ref().map(AsRef::as_ref) } /// Get the `LinkValue`'s `rev` parameter(s). pub fn rev(&self) -> Option<&[RelationType]> { self.rev.as_ref().map(AsRef::as_ref) } /// Get the `LinkValue`'s `hreflang` parameter(s). pub fn href_lang(&self) -> Option<&[LanguageTag]> { self.href_lang.as_ref().map(AsRef::as_ref) } /// Get the `LinkValue`'s `media` parameter(s). pub fn media_desc(&self) -> Option<&[MediaDesc]> { self.media_desc.as_ref().map(AsRef::as_ref) } /// Get the `LinkValue`'s `title` parameter. pub fn title(&self) -> Option<&str> { self.title.as_ref().map(AsRef::as_ref) } /// Get the `LinkValue`'s `title*` parameter. pub fn title_star(&self) -> Option<&str> { self.title_star.as_ref().map(AsRef::as_ref) } /// Get the `LinkValue`'s `type` parameter. pub fn media_type(&self) -> Option<&Mime> { self.media_type.as_ref() } /// Add a `RelationType` to the `LinkValue`'s `rel` parameter. pub fn push_rel(mut self, rel: RelationType) -> LinkValue { let mut v = self.rel.take().unwrap_or(Vec::new()); v.push(rel); self.rel = Some(v); self } /// Set `LinkValue`'s `anchor` parameter. pub fn set_anchor>(mut self, anchor: T) -> LinkValue { self.anchor = Some(anchor.into()); self } /// Add a `RelationType` to the `LinkValue`'s `rev` parameter. pub fn push_rev(mut self, rev: RelationType) -> LinkValue { let mut v = self.rev.take().unwrap_or(Vec::new()); v.push(rev); self.rev = Some(v); self } /// Add a `LanguageTag` to the `LinkValue`'s `hreflang` parameter. pub fn push_href_lang(mut self, language_tag: LanguageTag) -> LinkValue { let mut v = self.href_lang.take().unwrap_or(Vec::new()); v.push(language_tag); self.href_lang = Some(v); self } /// Add a `MediaDesc` to the `LinkValue`'s `media_desc` parameter. pub fn push_media_desc(mut self, media_desc: MediaDesc) -> LinkValue { let mut v = self.media_desc.take().unwrap_or(Vec::new()); v.push(media_desc); self.media_desc = Some(v); self } /// Set `LinkValue`'s `title` parameter. pub fn set_title>(mut self, title: T) -> LinkValue { self.title = Some(title.into()); self } /// Set `LinkValue`'s `title*` parameter. pub fn set_title_star>(mut self, title_star: T) -> LinkValue { self.title_star = Some(title_star.into()); self } /// Set `LinkValue`'s `type` parameter. pub fn set_media_type(mut self, media_type: Mime) -> LinkValue { self.media_type = Some(media_type); self } } //////////////////////////////////////////////////////////////////////////////// // Trait implementations //////////////////////////////////////////////////////////////////////////////// impl Header for Link { fn header_name() -> &'static str { static NAME: &'static str = "Link"; NAME } fn parse_header(raw: &Raw) -> ::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)), } }) .unwrap_or(Err(::Error::Header)) } fn fmt_header(&self, f: &mut ::Formatter) -> fmt::Result { f.fmt_line(self) } } impl fmt::Display for Link { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt_delimited(f, self.values.as_slice(), ", ", ("", "")) } } impl fmt::Display for LinkValue { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { try!(write!(f, "<{}>", self.link)); if let Some(ref rel) = self.rel { try!(fmt_delimited(f, rel.as_slice(), " ", ("; rel=\"", "\""))); } if let Some(ref anchor) = self.anchor { try!(write!(f, "; anchor=\"{}\"", anchor)); } if let Some(ref rev) = self.rev { try!(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)); } } if let Some(ref media_desc) = self.media_desc { try!(fmt_delimited(f, media_desc.as_slice(), ", ", ("; media=\"", "\""))); } if let Some(ref title) = self.title { try!(write!(f, "; title=\"{}\"", title)); } if let Some(ref title_star) = self.title_star { try!(write!(f, "; title*={}", title_star)); } if let Some(ref media_type) = self.media_type { try!(write!(f, "; type=\"{}\"", media_type)); } Ok(()) } } impl FromStr for Link { type Err = ::Error; fn from_str(s: &str) -> ::Result { // Create a split iterator with delimiters: `;`, `,` let link_split = SplitAsciiUnquoted::new(s, ";,"); let mut link_values: Vec = Vec::new(); // Loop over the splits parsing the Link header into // a `Vec` for segment in link_split { // 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, } }, } ); } 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), Some(p) => p.trim(), }; let link_header = match link_values.last_mut() { None => return Err(::Error::Header), Some(l) => l, }; if "rel".eq_ignore_ascii_case(link_param_name) { // Parse relation type: `rel`. // 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() }, }; } } 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), Some(s) => match verify_and_trim(s.trim(), (b'"', b'"')) { Err(_) => return Err(::Error::Header), Ok(a) => Some(String::from(a)), }, }; } else if "rev".eq_ignore_ascii_case(link_param_name) { // Parse relation type: `rev`. // 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() }, } } } else if "hreflang".eq_ignore_ascii_case(link_param_name) { // Parse target attribute: `hreflang`. // 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, }, } ); link_header.href_lang = Some(v); } else if "media".eq_ignore_ascii_case(link_param_name) { // Parse target attribute: `media`. // 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() }, }; } } else if "title".eq_ignore_ascii_case(link_param_name) { // Parse target attribute: `title`. // 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), Some(s) => match verify_and_trim(s.trim(), (b'"', b'"')) { Err(_) => return Err(::Error::Header), Ok(t) => Some(String::from(t)), }, }; } } else if "title*".eq_ignore_ascii_case(link_param_name) { // Parse target attribute: `title*`. // https://tools.ietf.org/html/rfc5988#section-5.4 // // Definition of `ext-value`: // 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), Some(s) => Some(String::from(s.trim())), }; } } else if "type".eq_ignore_ascii_case(link_param_name) { // Parse target attribute: `type`. // 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), Some(s) => match verify_and_trim(s.trim(), (b'"', b'"')) { Err(_) => return Err(::Error::Header), Ok(t) => match t.parse() { Err(_) => return Err(::Error::Header), Ok(m) => Some(m), }, }, }; } } else { return Err(::Error::Header); } } } Ok(Link::new(link_values)) } } impl fmt::Display for MediaDesc { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { MediaDesc::Screen => write!(f, "screen"), MediaDesc::Tty => write!(f, "tty"), MediaDesc::Tv => write!(f, "tv"), MediaDesc::Projection => write!(f, "projection"), MediaDesc::Handheld => write!(f, "handheld"), MediaDesc::Print => write!(f, "print"), MediaDesc::Braille => write!(f, "braille"), 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 { match s { "screen" => Ok(MediaDesc::Screen), "tty" => Ok(MediaDesc::Tty), "tv" => Ok(MediaDesc::Tv), "projection" => Ok(MediaDesc::Projection), "handheld" => Ok(MediaDesc::Handheld), "print" => Ok(MediaDesc::Print), "braille" => Ok(MediaDesc::Braille), "aural" => Ok(MediaDesc::Aural), "all" => Ok(MediaDesc::All), _ => Ok(MediaDesc::Extension(String::from(s))), } } } impl fmt::Display for RelationType { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { RelationType::Alternate => write!(f, "alternate"), RelationType::Appendix => write!(f, "appendix"), RelationType::Bookmark => write!(f, "bookmark"), RelationType::Chapter => write!(f, "chapter"), RelationType::Contents => write!(f, "contents"), RelationType::Copyright => write!(f, "copyright"), RelationType::Current => write!(f, "current"), RelationType::DescribedBy => write!(f, "describedby"), RelationType::Edit => write!(f, "edit"), RelationType::EditMedia => write!(f, "edit-media"), RelationType::Enclosure => write!(f, "enclosure"), RelationType::First => write!(f, "first"), RelationType::Glossary => write!(f, "glossary"), RelationType::Help => write!(f, "help"), RelationType::Hub => write!(f, "hub"), RelationType::Index => write!(f, "index"), RelationType::Last => write!(f, "last"), RelationType::LatestVersion => write!(f, "latest-version"), RelationType::License => write!(f, "license"), RelationType::Next => write!(f, "next"), RelationType::NextArchive => write!(f, "next-archive"), RelationType::Payment => write!(f, "payment"), RelationType::Prev => write!(f, "prev"), RelationType::PredecessorVersion => write!(f, "predecessor-version"), RelationType::Previous => write!(f, "previous"), RelationType::PrevArchive => write!(f, "prev-archive"), RelationType::Related => write!(f, "related"), RelationType::Replies => write!(f, "replies"), RelationType::Section => write!(f, "section"), RelationType::RelationTypeSelf => write!(f, "self"), RelationType::Service => write!(f, "service"), RelationType::Start => write!(f, "start"), RelationType::Stylesheet => write!(f, "stylesheet"), RelationType::Subsection => write!(f, "subsection"), RelationType::SuccessorVersion => write!(f, "successor-version"), RelationType::Up => write!(f, "up"), RelationType::VersionHistory => write!(f, "version-history"), RelationType::Via => write!(f, "via"), 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 { if "alternate".eq_ignore_ascii_case(s) { Ok(RelationType::Alternate) } else if "appendix".eq_ignore_ascii_case(s) { Ok(RelationType::Appendix) } else if "bookmark".eq_ignore_ascii_case(s) { Ok(RelationType::Bookmark) } else if "chapter".eq_ignore_ascii_case(s) { Ok(RelationType::Chapter) } else if "contents".eq_ignore_ascii_case(s) { Ok(RelationType::Contents) } else if "copyright".eq_ignore_ascii_case(s) { Ok(RelationType::Copyright) } else if "current".eq_ignore_ascii_case(s) { Ok(RelationType::Current) } else if "describedby".eq_ignore_ascii_case(s) { Ok(RelationType::DescribedBy) } else if "edit".eq_ignore_ascii_case(s) { Ok(RelationType::Edit) } else if "edit-media".eq_ignore_ascii_case(s) { Ok(RelationType::EditMedia) } else if "enclosure".eq_ignore_ascii_case(s) { Ok(RelationType::Enclosure) } else if "first".eq_ignore_ascii_case(s) { Ok(RelationType::First) } else if "glossary".eq_ignore_ascii_case(s) { Ok(RelationType::Glossary) } else if "help".eq_ignore_ascii_case(s) { Ok(RelationType::Help) } else if "hub".eq_ignore_ascii_case(s) { Ok(RelationType::Hub) } else if "index".eq_ignore_ascii_case(s) { Ok(RelationType::Index) } else if "last".eq_ignore_ascii_case(s) { Ok(RelationType::Last) } else if "latest-version".eq_ignore_ascii_case(s) { Ok(RelationType::LatestVersion) } else if "license".eq_ignore_ascii_case(s) { Ok(RelationType::License) } else if "next".eq_ignore_ascii_case(s) { Ok(RelationType::Next) } else if "next-archive".eq_ignore_ascii_case(s) { Ok(RelationType::NextArchive) } else if "payment".eq_ignore_ascii_case(s) { Ok(RelationType::Payment) } else if "prev".eq_ignore_ascii_case(s) { Ok(RelationType::Prev) } else if "predecessor-version".eq_ignore_ascii_case(s) { Ok(RelationType::PredecessorVersion) } else if "previous".eq_ignore_ascii_case(s) { Ok(RelationType::Previous) } else if "prev-archive".eq_ignore_ascii_case(s) { Ok(RelationType::PrevArchive) } else if "related".eq_ignore_ascii_case(s) { Ok(RelationType::Related) } else if "replies".eq_ignore_ascii_case(s) { Ok(RelationType::Replies) } else if "section".eq_ignore_ascii_case(s) { Ok(RelationType::Section) } else if "self".eq_ignore_ascii_case(s) { Ok(RelationType::RelationTypeSelf) } else if "service".eq_ignore_ascii_case(s) { Ok(RelationType::Service) } else if "start".eq_ignore_ascii_case(s) { Ok(RelationType::Start) } else if "stylesheet".eq_ignore_ascii_case(s) { Ok(RelationType::Stylesheet) } else if "subsection".eq_ignore_ascii_case(s) { Ok(RelationType::Subsection) } else if "successor-version".eq_ignore_ascii_case(s) { Ok(RelationType::SuccessorVersion) } else if "up".eq_ignore_ascii_case(s) { Ok(RelationType::Up) } else if "version-history".eq_ignore_ascii_case(s) { Ok(RelationType::VersionHistory) } else if "via".eq_ignore_ascii_case(s) { Ok(RelationType::Via) } else if "working-copy".eq_ignore_ascii_case(s) { Ok(RelationType::WorkingCopy) } else if "working-copy-of".eq_ignore_ascii_case(s) { Ok(RelationType::WorkingCopyOf) } else { Ok(RelationType::ExtRelType(String::from(s))) } } } //////////////////////////////////////////////////////////////////////////////// // Utilities //////////////////////////////////////////////////////////////////////////////// struct SplitAsciiUnquoted<'a> { src: &'a str, pos: usize, del: &'a str } impl<'a> SplitAsciiUnquoted<'a> { fn new(s: &'a str, d: &'a str) -> SplitAsciiUnquoted<'a> { SplitAsciiUnquoted{ src: s, pos: 0, del: d, } } } impl<'a> Iterator for SplitAsciiUnquoted<'a> { type Item = &'a str; fn next(&mut self) -> Option<&'a str> { if self.pos < self.src.len() { let prev_pos = self.pos; let mut pos = self.pos; let mut in_quotes = false; for c in self.src[prev_pos..].as_bytes().iter() { in_quotes ^= *c == b'"'; // Ignore `c` if we're `in_quotes`. if !in_quotes && self.del.as_bytes().contains(c) { break; } pos += 1; } self.pos = pos + 1; Some(&self.src[prev_pos..pos]) } else { None } } } 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])); for i in &p[1..] { // Write the next element preceded by the delimiter `d` try!(write!(f, "{}{}", d, i)); } // Write a ending string `b.1` before the first element try!(write!(f, "{}", b.1)); } Ok(()) } fn verify_and_trim(s: &str, b: (u8, u8)) -> ::Result<&str> { 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()) ) } else { Err(::Error::Header) } } //////////////////////////////////////////////////////////////////////////////// // Tests //////////////////////////////////////////////////////////////////////////////// #[cfg(test)] mod tests { use std::fmt; use std::fmt::Write; use super::{Link, LinkValue, MediaDesc, RelationType, SplitAsciiUnquoted}; use super::{fmt_delimited, verify_and_trim}; use Header; // use proto::ServerTransaction; use bytes::BytesMut; use mime; #[test] fn test_link() { let link_value = LinkValue::new("http://example.com/TheBook/chapter2") .push_rel(RelationType::Previous) .push_rev(RelationType::Next) .set_title("previous chapter"); let link_header = b"; \ 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)); } #[test] fn test_link_multiple_values() { let first_link = LinkValue::new("/TheBook/chapter2") .push_rel(RelationType::Previous) .set_title_star("UTF-8'de'letztes%20Kapitel"); let second_link = LinkValue::new("/TheBook/chapter4") .push_rel(RelationType::Next) .set_title_star("UTF-8'de'n%c3%a4chstes%20Kapitel"); let link_header = b"; \ 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)); } #[test] fn test_link_all_attributes() { let link_value = LinkValue::new("http://example.com/TheBook/chapter2") .push_rel(RelationType::Previous) .set_anchor("../anchor/example/") .push_rev(RelationType::Next) .push_href_lang("de".parse().unwrap()) .push_media_desc(MediaDesc::Screen) .set_title("previous chapter") .set_title_star("title* unparsed") .set_media_type(mime::TEXT_PLAIN); let link_header = b"; \ rel=\"previous\"; anchor=\"../anchor/example/\"; \ rev=\"next\"; hreflang=de; media=\"screen\"; \ title=\"previous chapter\"; title*=title* unparsed; \ type=\"text/plain\""; 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)); } // TODO // #[test] // fn test_link_multiple_link_headers() { // let first_link = LinkValue::new("/TheBook/chapter2") // .push_rel(RelationType::Previous) // .set_title_star("UTF-8'de'letztes%20Kapitel"); // let second_link = LinkValue::new("/TheBook/chapter4") // .push_rel(RelationType::Next) // .set_title_star("UTF-8'de'n%c3%a4chstes%20Kapitel"); // let third_link = LinkValue::new("http://example.com/TheBook/chapter2") // .push_rel(RelationType::Previous) // .push_rev(RelationType::Next) // .set_title("previous chapter"); // let expected_link = Link::new(vec![first_link, second_link, third_link]); // let mut raw = BytesMut::from(b"GET /super_short_uri/and_whatever HTTP/1.1\r\nHost: \ // hyper.rs\r\nAccept: a lot of things\r\nAccept-Charset: \ // utf8\r\nAccept-Encoding: *\r\nLink: ; \ // rel=\"previous\"; title*=UTF-8'de'letztes%20Kapitel, \ // ; rel=\"next\"; title*=\ // UTF-8'de'n%c3%a4chstes%20Kapitel\r\n\ // Access-Control-Allow-Credentials: None\r\nLink: \ // ; \ // rel=\"previous\"; rev=next; title=\"previous chapter\"\ // \r\n\r\n".to_vec()); // let (mut res, _) = ServerTransaction::parse(&mut raw).unwrap().unwrap(); // let link = res.headers.remove::().unwrap(); // assert_eq!(link, expected_link); // } #[test] fn test_link_display() { let link_value = LinkValue::new("http://example.com/TheBook/chapter2") .push_rel(RelationType::Previous) .set_anchor("/anchor/example/") .push_rev(RelationType::Next) .push_href_lang("de".parse().unwrap()) .push_media_desc(MediaDesc::Screen) .set_title("previous chapter") .set_title_star("title* unparsed") .set_media_type(mime::TEXT_PLAIN); let link = Link::new(vec![link_value]); let mut link_header = String::new(); write!(&mut link_header, "{}", link).unwrap(); let expected_link_header = "; \ rel=\"previous\"; anchor=\"/anchor/example/\"; \ rev=\"next\"; hreflang=de; media=\"screen\"; \ title=\"previous chapter\"; title*=title* unparsed; \ type=\"text/plain\""; assert_eq!(link_header, expected_link_header); } #[test] fn test_link_parsing_errors() { let link_a = b"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 link_b = b"; \ =\"previous\"; rev=next; title=\"previous chapter\""; err = Header::parse_header(&vec![link_b.to_vec()].into()); assert_eq!(err.is_err(), true); let link_c = b"; \ rel=; rev=next; title=\"previous chapter\""; err = Header::parse_header(&vec![link_c.to_vec()].into()); assert_eq!(err.is_err(), true); let link_d = b"; \ rel=\"previous\"; rev=next; title="; err = Header::parse_header(&vec![link_d.to_vec()].into()); assert_eq!(err.is_err(), true); let link_e = b"; \ rel=\"previous\"; rev=next; attr=unknown"; err = Header::parse_header(&vec![link_e.to_vec()].into()); assert_eq!(err.is_err(), true); } #[test] fn test_link_split_ascii_unquoted_iterator() { let string = "some, text; \"and, more; in quotes\", or not"; let mut string_split = SplitAsciiUnquoted::new(string, ";,"); assert_eq!(Some("some"), string_split.next()); assert_eq!(Some(" text"), string_split.next()); assert_eq!(Some(" \"and, more; in quotes\""), string_split.next()); assert_eq!(Some(" or not"), string_split.next()); assert_eq!(None, string_split.next()); } #[test] fn test_link_fmt_delimited() { struct TestFormatterStruct<'a> { v: Vec<&'a str> }; impl<'a> fmt::Display for TestFormatterStruct<'a> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt_delimited(f, self.v.as_slice(), ", ", (">>", "<<")) } } let test_formatter = TestFormatterStruct { v: vec!["first", "second"] }; let mut string = String::new(); write!(&mut string, "{}", test_formatter).unwrap(); let expected_string = ">>first, second<<"; assert_eq!(string, expected_string); } #[test] fn test_link_verify_and_trim() { let string = verify_and_trim("> some string <", (b'>', b'<')); assert_eq!(string.ok(), Some("some string")); let err = verify_and_trim(" > some string <", (b'>', b'<')); assert_eq!(err.is_err(), true); } } bench_header!(bench_link, Link, { vec![b"; rel=\"previous\"; rev=next; title=\"previous chapter\"; type=\"text/html\"; media=\"screen, tty\"".to_vec()] });