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%20 Kapitel, \
</TheBook/chapter4>; \
rel=\"next\" ; title*=UTF-8 'de' n%c3%a4chstes%20 Kapitel";
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()] });
Messung V0.5 in Prozent C=82 H=98 G=90
¤ Dauer der Verarbeitung: 0.22 Sekunden
(vorverarbeitet am 2026-06-19)
¤
*© Formatika GbR, Deutschland