summaryrefslogtreecommitdiffstats
path: root/third_party/rust/headers/src/disabled/link.rs
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/rust/headers/src/disabled/link.rs')
-rw-r--r--third_party/rust/headers/src/disabled/link.rs1105
1 files changed, 1105 insertions, 0 deletions
diff --git a/third_party/rust/headers/src/disabled/link.rs b/third_party/rust/headers/src/disabled/link.rs
new file mode 100644
index 0000000000..a6d8494438
--- /dev/null
+++ b/third_party/rust/headers/src/disabled/link.rs
@@ -0,0 +1,1105 @@
+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: <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 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<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][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<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 ::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;
+
+ // 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"<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));
+ }
+
+ // 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: </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()] });