1101 lines
		
	
	
		
			38 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
			
		
		
	
	
			1101 lines
		
	
	
		
			38 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
| use std::fmt;
 | |
| use std::borrow::Cow;
 | |
| use std::str::FromStr;
 | |
| #[allow(unused)]
 | |
| use std::ascii::AsciiExt;
 | |
| 
 | |
| use mime::Mime;
 | |
| use language_tags::LanguageTag;
 | |
| 
 | |
| use header::parsing;
 | |
| use header::{Header, Raw};
 | |
| 
 | |
| /// The `Link` header, defined in
 | |
| /// [RFC5988](http://tools.ietf.org/html/rfc5988#section-5)
 | |
| ///
 | |
| /// # ABNF
 | |
| /// ```plain
 | |
| /// 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: <http://example.com/TheBook/chapter2>; rel="previous";
 | |
| ///        title="previous chapter"`
 | |
| ///
 | |
| /// `Link: </TheBook/chapter2>; rel="previous"; title*=UTF-8'de'letztes%20Kapitel,
 | |
| ///        </TheBook/chapter4>; rel="next"; title*=UTF-8'de'n%c3%a4chstes%20Kapitel`
 | |
| ///
 | |
| /// # Examples
 | |
| /// ```
 | |
| /// use hyper::header::{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<LinkValue>
 | |
| }
 | |
| 
 | |
| /// 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<Vec<RelationType>>,
 | |
| 
 | |
|     /// Context IRI: `anchor`.
 | |
|     anchor: Option<String>,
 | |
| 
 | |
|     /// Reverse Relation Types: `rev`.
 | |
|     rev: Option<Vec<RelationType>>,
 | |
| 
 | |
|     /// Hint on the language of the result of dereferencing
 | |
|     /// the link: `hreflang`.
 | |
|     href_lang: Option<Vec<LanguageTag>>,
 | |
| 
 | |
|     /// Destination medium or media: `media`.
 | |
|     media_desc: Option<Vec<MediaDesc>>,
 | |
| 
 | |
|     /// Label of the destination of a Link: `title`.
 | |
|     title: Option<String>,
 | |
| 
 | |
|     /// The `title` encoded in a different charset: `title*`.
 | |
|     title_star: Option<String>,
 | |
| 
 | |
|     /// Hint on the media type of the result of dereferencing
 | |
|     /// the link: `type`.
 | |
|     media_type: Option<Mime>,
 | |
| }
 | |
| 
 | |
| /// A Media Descriptors Enum based on:
 | |
| /// 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<LinkValue>`.
 | |
|     pub fn new(link_values: Vec<LinkValue>) -> 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<T>(uri: T) -> LinkValue
 | |
|         where T: Into<Cow<'static, str>> {
 | |
|         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<T: Into<String>>(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<T: Into<String>>(mut self, title: T) -> LinkValue {
 | |
|         self.title = Some(title.into());
 | |
| 
 | |
|         self
 | |
|     }
 | |
| 
 | |
|     /// Set `LinkValue`'s `title*` parameter.
 | |
|     pub fn set_title_star<T: Into<String>>(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<Link> {
 | |
|         // 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::<Link>)
 | |
|             .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 ::header::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<Link> {
 | |
|         // Create a split iterator with delimiters: `;`, `,`
 | |
|         let link_split = SplitAsciiUnquoted::new(s, ";,");
 | |
| 
 | |
|         let mut link_values: Vec<LinkValue> = Vec::new();
 | |
| 
 | |
|         // Loop over the splits parsing the Link header into
 | |
|         // a `Vec<LinkValue>`
 | |
|         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::<Result<Vec<RelationType>, _>>()
 | |
|                                     .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::<Result<Vec<RelationType>, _>>()
 | |
|                                     .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::<Result<Vec<MediaDesc>, _>>()
 | |
|                                     .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<MediaDesc> {
 | |
|         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<RelationType> {
 | |
|         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<T: fmt::Display>(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::Header;
 | |
| 
 | |
|     use proto::{ServerTransaction, Http1Transaction};
 | |
|     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"<http://example.com/TheBook/chapter2>; \
 | |
|             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"</TheBook/chapter2>; \
 | |
|             rel=\"previous\"; title*=UTF-8'de'letztes%20Kapitel, \
 | |
|             </TheBook/chapter4>; \
 | |
|             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"<http://example.com/TheBook/chapter2>; \
 | |
|             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));
 | |
|     }
 | |
| 
 | |
|     #[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: </TheBook/chapter2>; \
 | |
|                                   rel=\"previous\"; title*=UTF-8'de'letztes%20Kapitel, \
 | |
|                                   </TheBook/chapter4>; rel=\"next\"; title*=\
 | |
|                                   UTF-8'de'n%c3%a4chstes%20Kapitel\r\n\
 | |
|                                   Access-Control-Allow-Credentials: None\r\nLink: \
 | |
|                                   <http://example.com/TheBook/chapter2>; \
 | |
|                                   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::<Link>().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 = "<http://example.com/TheBook/chapter2>; \
 | |
|             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<Link, _> = Header::parse_header(&vec![link_a.to_vec()].into());
 | |
|         assert_eq!(err.is_err(), true);
 | |
| 
 | |
|         let link_b = b"<http://example.com/TheBook/chapter2>; \
 | |
|             =\"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"<http://example.com/TheBook/chapter2>; \
 | |
|             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"<http://example.com/TheBook/chapter2>; \
 | |
|             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"<http://example.com/TheBook/chapter2>; \
 | |
|             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"<http://example.com/TheBook/chapter2>; rel=\"previous\"; rev=next; title=\"previous chapter\"; type=\"text/html\"; media=\"screen, tty\"".to_vec()] });
 |