diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:22:09 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:22:09 +0000 |
commit | 43a97878ce14b72f0981164f87f2e35e14151312 (patch) | |
tree | 620249daf56c0258faa40cbdcf9cfba06de2a846 /third_party/rust/headers/src/disabled | |
parent | Initial commit. (diff) | |
download | firefox-upstream.tar.xz firefox-upstream.zip |
Adding upstream version 110.0.1.upstream/110.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/rust/headers/src/disabled')
-rw-r--r-- | third_party/rust/headers/src/disabled/accept.rs | 150 | ||||
-rw-r--r-- | third_party/rust/headers/src/disabled/accept_charset.rs | 57 | ||||
-rw-r--r-- | third_party/rust/headers/src/disabled/accept_encoding.rs | 72 | ||||
-rw-r--r-- | third_party/rust/headers/src/disabled/accept_language.rs | 72 | ||||
-rw-r--r-- | third_party/rust/headers/src/disabled/content_language.rs | 35 | ||||
-rw-r--r-- | third_party/rust/headers/src/disabled/from.rs | 29 | ||||
-rw-r--r-- | third_party/rust/headers/src/disabled/last_event_id.rs | 40 | ||||
-rw-r--r-- | third_party/rust/headers/src/disabled/link.rs | 1105 | ||||
-rw-r--r-- | third_party/rust/headers/src/disabled/prefer.rs | 210 | ||||
-rw-r--r-- | third_party/rust/headers/src/disabled/preference_applied.rs | 110 | ||||
-rw-r--r-- | third_party/rust/headers/src/disabled/util/charset.rs | 229 | ||||
-rw-r--r-- | third_party/rust/headers/src/disabled/util/encoding.rs | 57 | ||||
-rw-r--r-- | third_party/rust/headers/src/disabled/util/extended_value.rs | 192 | ||||
-rw-r--r-- | third_party/rust/headers/src/disabled/util/quality_value.rs | 268 | ||||
-rw-r--r-- | third_party/rust/headers/src/disabled/warning.rs | 182 |
15 files changed, 2808 insertions, 0 deletions
diff --git a/third_party/rust/headers/src/disabled/accept.rs b/third_party/rust/headers/src/disabled/accept.rs new file mode 100644 index 0000000000..3e8b77396c --- /dev/null +++ b/third_party/rust/headers/src/disabled/accept.rs @@ -0,0 +1,150 @@ +use mime::{self, Mime}; + +use {QualityItem, qitem}; + +header! { + /// `Accept` header, defined in [RFC7231](http://tools.ietf.org/html/rfc7231#section-5.3.2) + /// + /// The `Accept` header field can be used by user agents to specify + /// response media types that are acceptable. Accept header fields can + /// be used to indicate that the request is specifically limited to a + /// small set of desired types, as in the case of a request for an + /// in-line image + /// + /// # ABNF + /// + /// ```text + /// Accept = #( media-range [ accept-params ] ) + /// + /// media-range = ( "*/*" + /// / ( type "/" "*" ) + /// / ( type "/" subtype ) + /// ) *( OWS ";" OWS parameter ) + /// accept-params = weight *( accept-ext ) + /// accept-ext = OWS ";" OWS token [ "=" ( token / quoted-string ) ] + /// ``` + /// + /// # Example values + /// * `audio/*; q=0.2, audio/basic` + /// * `text/plain; q=0.5, text/html, text/x-dvi; q=0.8, text/x-c` + /// + /// # Examples + /// ``` + /// # extern crate headers; + /// extern crate mime; + /// use headers::{Headers, Accept, qitem}; + /// + /// let mut headers = Headers::new(); + /// + /// headers.set( + /// Accept(vec![ + /// qitem(mime::TEXT_HTML), + /// ]) + /// ); + /// ``` + /// + /// ``` + /// # extern crate headers; + /// extern crate mime; + /// use headers::{Headers, Accept, qitem}; + /// + /// let mut headers = Headers::new(); + /// headers.set( + /// Accept(vec![ + /// qitem(mime::APPLICATION_JSON), + /// ]) + /// ); + /// ``` + /// ``` + /// # extern crate headers; + /// extern crate mime; + /// use headers::{Headers, Accept, QualityItem, q, qitem}; + /// + /// let mut headers = Headers::new(); + /// + /// headers.set( + /// Accept(vec![ + /// qitem(mime::TEXT_HTML), + /// qitem("application/xhtml+xml".parse().unwrap()), + /// QualityItem::new( + /// mime::TEXT_XML, + /// q(900) + /// ), + /// qitem("image/webp".parse().unwrap()), + /// QualityItem::new( + /// mime::STAR_STAR, + /// q(800) + /// ), + /// ]) + /// ); + /// ``` + (Accept, ACCEPT) => (QualityItem<Mime>)+ + + test_accept { + // Tests from the RFC + test_header!( + test1, + vec![b"audio/*; q=0.2, audio/basic"], + Some(HeaderField(vec![ + QualityItem::new("audio/*".parse().unwrap(), q(200)), + qitem("audio/basic".parse().unwrap()), + ]))); + test_header!( + test2, + vec![b"text/plain; q=0.5, text/html, text/x-dvi; q=0.8, text/x-c"], + Some(HeaderField(vec![ + QualityItem::new(TEXT_PLAIN, q(500)), + qitem(TEXT_HTML), + QualityItem::new( + "text/x-dvi".parse().unwrap(), + q(800)), + qitem("text/x-c".parse().unwrap()), + ]))); + // Custom tests + test_header!( + test3, + vec![b"text/plain; charset=utf-8"], + Some(Accept(vec![ + qitem(TEXT_PLAIN_UTF_8), + ]))); + test_header!( + test4, + vec![b"text/plain; charset=utf-8; q=0.5"], + Some(Accept(vec![ + QualityItem::new(TEXT_PLAIN_UTF_8, + q(500)), + ]))); + + #[test] + fn test_fuzzing1() { + let raw: Raw = "chunk#;e".into(); + let header = Accept::parse_header(&raw); + assert!(header.is_ok()); + } + } +} + +impl Accept { + /// A constructor to easily create `Accept: */*`. + pub fn star() -> Accept { + Accept(vec![qitem(mime::STAR_STAR)]) + } + + /// A constructor to easily create `Accept: application/json`. + pub fn json() -> Accept { + Accept(vec![qitem(mime::APPLICATION_JSON)]) + } + + /// A constructor to easily create `Accept: text/*`. + pub fn text() -> Accept { + Accept(vec![qitem(mime::TEXT_STAR)]) + } + + /// A constructor to easily create `Accept: image/*`. + pub fn image() -> Accept { + Accept(vec![qitem(mime::IMAGE_STAR)]) + } +} + + +bench_header!(bench, Accept, { vec![b"text/plain; q=0.5, text/html".to_vec()] }); diff --git a/third_party/rust/headers/src/disabled/accept_charset.rs b/third_party/rust/headers/src/disabled/accept_charset.rs new file mode 100644 index 0000000000..96eec89683 --- /dev/null +++ b/third_party/rust/headers/src/disabled/accept_charset.rs @@ -0,0 +1,57 @@ +use {Charset, QualityItem}; + +header! { + /// `Accept-Charset` header, defined in + /// [RFC7231](http://tools.ietf.org/html/rfc7231#section-5.3.3) + /// + /// The `Accept-Charset` header field can be sent by a user agent to + /// indicate what charsets are acceptable in textual response content. + /// This field allows user agents capable of understanding more + /// comprehensive or special-purpose charsets to signal that capability + /// to an origin server that is capable of representing information in + /// those charsets. + /// + /// # ABNF + /// + /// ```text + /// Accept-Charset = 1#( ( charset / "*" ) [ weight ] ) + /// ``` + /// + /// # Example values + /// * `iso-8859-5, unicode-1-1;q=0.8` + /// + /// # Examples + /// ``` + /// use headers::{Headers, AcceptCharset, Charset, qitem}; + /// + /// let mut headers = Headers::new(); + /// headers.set( + /// AcceptCharset(vec![qitem(Charset::Us_Ascii)]) + /// ); + /// ``` + /// ``` + /// use headers::{Headers, AcceptCharset, Charset, q, QualityItem}; + /// + /// let mut headers = Headers::new(); + /// headers.set( + /// AcceptCharset(vec![ + /// QualityItem::new(Charset::Us_Ascii, q(900)), + /// QualityItem::new(Charset::Iso_8859_10, q(200)), + /// ]) + /// ); + /// ``` + /// ``` + /// use headers::{Headers, AcceptCharset, Charset, qitem}; + /// + /// let mut headers = Headers::new(); + /// headers.set( + /// AcceptCharset(vec![qitem(Charset::Ext("utf-8".to_owned()))]) + /// ); + /// ``` + (AcceptCharset, ACCEPT_CHARSET) => (QualityItem<Charset>)+ + + test_accept_charset { + /// Testcase from RFC + test_header!(test1, vec![b"iso-8859-5, unicode-1-1;q=0.8"]); + } +} diff --git a/third_party/rust/headers/src/disabled/accept_encoding.rs b/third_party/rust/headers/src/disabled/accept_encoding.rs new file mode 100644 index 0000000000..5eb3adf50b --- /dev/null +++ b/third_party/rust/headers/src/disabled/accept_encoding.rs @@ -0,0 +1,72 @@ +use {Encoding, QualityItem}; + +header! { + /// `Accept-Encoding` header, defined in + /// [RFC7231](http://tools.ietf.org/html/rfc7231#section-5.3.4) + /// + /// The `Accept-Encoding` header field can be used by user agents to + /// indicate what response content-codings are + /// acceptable in the response. An `identity` token is used as a synonym + /// for "no encoding" in order to communicate when no encoding is + /// preferred. + /// + /// # ABNF + /// + /// ```text + /// Accept-Encoding = #( codings [ weight ] ) + /// codings = content-coding / "identity" / "*" + /// ``` + /// + /// # Example values + /// * `compress, gzip` + /// * `` + /// * `*` + /// * `compress;q=0.5, gzip;q=1` + /// * `gzip;q=1.0, identity; q=0.5, *;q=0` + /// + /// # Examples + /// ``` + /// use headers::{Headers, AcceptEncoding, Encoding, qitem}; + /// + /// let mut headers = Headers::new(); + /// headers.set( + /// AcceptEncoding(vec![qitem(Encoding::Chunked)]) + /// ); + /// ``` + /// ``` + /// use headers::{Headers, AcceptEncoding, Encoding, qitem}; + /// + /// let mut headers = Headers::new(); + /// headers.set( + /// AcceptEncoding(vec![ + /// qitem(Encoding::Chunked), + /// qitem(Encoding::Gzip), + /// qitem(Encoding::Deflate), + /// ]) + /// ); + /// ``` + /// ``` + /// use headers::{Headers, AcceptEncoding, Encoding, QualityItem, q, qitem}; + /// + /// let mut headers = Headers::new(); + /// headers.set( + /// AcceptEncoding(vec![ + /// qitem(Encoding::Chunked), + /// QualityItem::new(Encoding::Gzip, q(600)), + /// QualityItem::new(Encoding::EncodingExt("*".to_owned()), q(0)), + /// ]) + /// ); + /// ``` + (AcceptEncoding, ACCEPT_ENCODING) => (QualityItem<Encoding>)* + + test_accept_encoding { + // From the RFC + test_header!(test1, vec![b"compress, gzip"]); + test_header!(test2, vec![b""], Some(AcceptEncoding(vec![]))); + test_header!(test3, vec![b"*"]); + // Note: Removed quality 1 from gzip + test_header!(test4, vec![b"compress;q=0.5, gzip"]); + // Note: Removed quality 1 from gzip + test_header!(test5, vec![b"gzip, identity; q=0.5, *;q=0"]); + } +} diff --git a/third_party/rust/headers/src/disabled/accept_language.rs b/third_party/rust/headers/src/disabled/accept_language.rs new file mode 100644 index 0000000000..dccfdb0bc3 --- /dev/null +++ b/third_party/rust/headers/src/disabled/accept_language.rs @@ -0,0 +1,72 @@ +use language_tags::LanguageTag; +use QualityItem; + +header! { + /// `Accept-Language` header, defined in + /// [RFC7231](http://tools.ietf.org/html/rfc7231#section-5.3.5) + /// + /// The `Accept-Language` header field can be used by user agents to + /// indicate the set of natural languages that are preferred in the + /// response. + /// + /// # ABNF + /// + /// ```text + /// Accept-Language = 1#( language-range [ weight ] ) + /// language-range = <language-range, see [RFC4647], Section 2.1> + /// ``` + /// + /// # Example values + /// * `da, en-gb;q=0.8, en;q=0.7` + /// * `en-us;q=1.0, en;q=0.5, fr` + /// + /// # Examples + /// + /// ``` + /// use headers::{Headers, AcceptLanguage, LanguageTag, qitem}; + /// + /// let mut headers = Headers::new(); + /// let mut langtag: LanguageTag = Default::default(); + /// langtag.language = Some("en".to_owned()); + /// langtag.region = Some("US".to_owned()); + /// headers.set( + /// AcceptLanguage(vec![ + /// qitem(langtag), + /// ]) + /// ); + /// ``` + /// + /// ``` + /// # extern crate headers; + /// # #[macro_use] extern crate language_tags; + /// # use headers::{Headers, AcceptLanguage, QualityItem, q, qitem}; + /// # + /// # fn main() { + /// let mut headers = Headers::new(); + /// headers.set( + /// AcceptLanguage(vec![ + /// qitem(langtag!(da)), + /// QualityItem::new(langtag!(en;;;GB), q(800)), + /// QualityItem::new(langtag!(en), q(700)), + /// ]) + /// ); + /// # } + /// ``` + (AcceptLanguage, ACCEPT_LANGUAGE) => (QualityItem<LanguageTag>)+ + + test_accept_language { + // From the RFC + test_header!(test1, vec![b"da, en-gb;q=0.8, en;q=0.7"]); + // Own test + test_header!( + test2, vec![b"en-US, en; q=0.5, fr"], + Some(AcceptLanguage(vec![ + qitem("en-US".parse().unwrap()), + QualityItem::new("en".parse().unwrap(), q(500)), + qitem("fr".parse().unwrap()), + ]))); + } +} + +bench_header!(bench, AcceptLanguage, + { vec![b"en-us;q=1.0, en;q=0.5, fr".to_vec()] }); diff --git a/third_party/rust/headers/src/disabled/content_language.rs b/third_party/rust/headers/src/disabled/content_language.rs new file mode 100644 index 0000000000..cca8dcbbf6 --- /dev/null +++ b/third_party/rust/headers/src/disabled/content_language.rs @@ -0,0 +1,35 @@ +use util::FlatCsv; + +/// `Content-Language` header, defined in +/// [RFC7231](https://tools.ietf.org/html/rfc7231#section-3.1.3.2) +/// +/// The `Content-Language` header field describes the natural language(s) +/// of the intended audience for the representation. Note that this +/// might not be equivalent to all the languages used within the +/// representation. +/// +/// # ABNF +/// +/// ```text +/// Content-Language = 1#language-tag +/// ``` +/// +/// # Example values +/// +/// * `da` +/// * `mi, en` +/// +/// # Examples +/// +/// ``` +/// # extern crate headers; +/// #[macro_use] extern crate language_tags; +/// use headers::ContentLanguage; +/// # +/// # fn main() { +/// let con_lang = ContentLanguage::new([langtag!(en)]) +/// # } +/// ``` +#[derive(Clone, Debug, PartialEq, Header)] +pub struct ContentLanguage(FlatCsv); + diff --git a/third_party/rust/headers/src/disabled/from.rs b/third_party/rust/headers/src/disabled/from.rs new file mode 100644 index 0000000000..ed00d8e986 --- /dev/null +++ b/third_party/rust/headers/src/disabled/from.rs @@ -0,0 +1,29 @@ +header! { + /// `From` header, defined in [RFC7231](http://tools.ietf.org/html/rfc7231#section-5.5.1) + /// + /// The `From` header field contains an Internet email address for a + /// human user who controls the requesting user agent. The address ought + /// to be machine-usable. + /// + /// # ABNF + /// + /// ```text + /// From = mailbox + /// mailbox = <mailbox, see [RFC5322], Section 3.4> + /// ``` + /// + /// # Example + /// + /// ``` + /// use headers::{Headers, From}; + /// + /// let mut headers = Headers::new(); + /// headers.set(From("webmaster@example.org".to_owned())); + /// ``` + // FIXME: Maybe use mailbox? + (From, FROM) => [String] + + test_from { + test_header!(test1, vec![b"webmaster@example.org"]); + } +} diff --git a/third_party/rust/headers/src/disabled/last_event_id.rs b/third_party/rust/headers/src/disabled/last_event_id.rs new file mode 100644 index 0000000000..c2499b0a0d --- /dev/null +++ b/third_party/rust/headers/src/disabled/last_event_id.rs @@ -0,0 +1,40 @@ +use std::fmt; + +use util::HeaderValueString; + +/// `Last-Event-ID` header, defined in +/// [RFC3864](https://html.spec.whatwg.org/multipage/references.html#refsRFC3864) +/// +/// The `Last-Event-ID` header contains information about +/// the last event in an http interaction so that it's easier to +/// track of event state. This is helpful when working +/// with [Server-Sent-Events](http://www.html5rocks.com/en/tutorials/eventsource/basics/). If the connection were to be dropped, for example, it'd +/// be useful to let the server know what the last event you +/// received was. +/// +/// The spec is a String with the id of the last event, it can be +/// an empty string which acts a sort of "reset". +// NOTE: This module is disabled since there is no const LAST_EVENT_ID to be +// used for the `impl Header`. It should be possible to enable this module +// when `HeaderName::from_static` can become a `const fn`. +#[derive(Clone, Debug, PartialEq, Header)] +pub struct LastEventId(HeaderValueString); + + +impl fmt::Display for LastEventId { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fmt::Display::fmt(&self.0, f) + } +} + +#[cfg(test)] +mod tests { + + /* + // Initial state + test_header!(test1, vec![b""]); + // Own testcase + test_header!(test2, vec![b"1"], Some(LastEventId("1".to_owned()))); + */ +} + 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()] }); diff --git a/third_party/rust/headers/src/disabled/prefer.rs b/third_party/rust/headers/src/disabled/prefer.rs new file mode 100644 index 0000000000..a6e4dc37bc --- /dev/null +++ b/third_party/rust/headers/src/disabled/prefer.rs @@ -0,0 +1,210 @@ +use std::fmt; +use std::str::FromStr; +use {Header, Raw}; +use parsing::{from_comma_delimited, fmt_comma_delimited}; + +/// `Prefer` header, defined in [RFC7240](http://tools.ietf.org/html/rfc7240) +/// +/// The `Prefer` header field can be used by a client to request that certain +/// behaviors be employed by a server while processing a request. +/// +/// # ABNF +/// +/// ```text +/// Prefer = "Prefer" ":" 1#preference +/// preference = token [ BWS "=" BWS word ] +/// *( OWS ";" [ OWS parameter ] ) +/// parameter = token [ BWS "=" BWS word ] +/// ``` +/// +/// # Example values +/// * `respond-async` +/// * `return=minimal` +/// * `wait=30` +/// +/// # Examples +/// +/// ``` +/// use headers::{Headers, Prefer, Preference}; +/// +/// let mut headers = Headers::new(); +/// headers.set( +/// Prefer(vec![Preference::RespondAsync]) +/// ); +/// ``` +/// +/// ``` +/// use headers::{Headers, Prefer, Preference}; +/// +/// let mut headers = Headers::new(); +/// headers.set( +/// Prefer(vec![ +/// Preference::RespondAsync, +/// Preference::ReturnRepresentation, +/// Preference::Wait(10u32), +/// Preference::Extension("foo".to_owned(), +/// "bar".to_owned(), +/// vec![]), +/// ]) +/// ); +/// ``` +#[derive(PartialEq, Clone, Debug)] +pub struct Prefer(pub Vec<Preference>); + +__hyper__deref!(Prefer => Vec<Preference>); + +impl Header for Prefer { + fn header_name() -> &'static str { + static NAME: &'static str = "Prefer"; + NAME + } + + fn parse_header(raw: &Raw) -> ::Result<Prefer> { + let preferences = try!(from_comma_delimited(raw)); + if !preferences.is_empty() { + Ok(Prefer(preferences)) + } else { + Err(::Error::Header) + } + } + + fn fmt_header(&self, f: &mut ::Formatter) -> fmt::Result { + f.fmt_line(self) + } +} + +impl fmt::Display for Prefer { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fmt_comma_delimited(f, &self[..]) + } +} + +/// Prefer contains a list of these preferences. +#[derive(PartialEq, Clone, Debug)] +pub enum Preference { + /// "respond-async" + RespondAsync, + /// "return=representation" + ReturnRepresentation, + /// "return=minimal" + ReturnMinimal, + /// "handling=strict" + HandlingStrict, + /// "handling=lenient" + HandlingLenient, + /// "wait=delta" + Wait(u32), + + /// Extension preferences. Always has a value, if none is specified it is + /// just "". A preference can also have a list of parameters. + Extension(String, String, Vec<(String, String)>) +} + +impl fmt::Display for Preference { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + use self::Preference::*; + fmt::Display::fmt(match *self { + RespondAsync => "respond-async", + ReturnRepresentation => "return=representation", + ReturnMinimal => "return=minimal", + HandlingStrict => "handling=strict", + HandlingLenient => "handling=lenient", + + Wait(secs) => return write!(f, "wait={}", secs), + + Extension(ref name, ref value, ref params) => { + try!(write!(f, "{}", name)); + if value != "" { try!(write!(f, "={}", value)); } + if !params.is_empty() { + for &(ref name, ref value) in params { + try!(write!(f, "; {}", name)); + if value != "" { try!(write!(f, "={}", value)); } + } + } + return Ok(()); + } + }, f) + } +} + +impl FromStr for Preference { + type Err = Option<<u32 as FromStr>::Err>; + fn from_str(s: &str) -> Result<Preference, Option<<u32 as FromStr>::Err>> { + use self::Preference::*; + let mut params = s.split(';').map(|p| { + let mut param = p.splitn(2, '='); + match (param.next(), param.next()) { + (Some(name), Some(value)) => (name.trim(), value.trim().trim_matches('"')), + (Some(name), None) => (name.trim(), ""), + // This can safely be unreachable because the [`splitn`][1] + // function (used above) will always have at least one value. + // + // [1]: http://doc.rust-lang.org/std/primitive.str.html#method.splitn + _ => { unreachable!(); } + } + }); + match params.nth(0) { + Some(param) => { + let rest: Vec<(String, String)> = params.map(|(l, r)| (l.to_owned(), r.to_owned())).collect(); + match param { + ("respond-async", "") => if rest.is_empty() { Ok(RespondAsync) } else { Err(None) }, + ("return", "representation") => if rest.is_empty() { Ok(ReturnRepresentation) } else { Err(None) }, + ("return", "minimal") => if rest.is_empty() { Ok(ReturnMinimal) } else { Err(None) }, + ("handling", "strict") => if rest.is_empty() { Ok(HandlingStrict) } else { Err(None) }, + ("handling", "lenient") => if rest.is_empty() { Ok(HandlingLenient) } else { Err(None) }, + ("wait", secs) => if rest.is_empty() { secs.parse().map(Wait).map_err(Some) } else { Err(None) }, + (left, right) => Ok(Extension(left.to_owned(), right.to_owned(), rest)) + } + }, + None => Err(None) + } + } +} + +#[cfg(test)] +mod tests { + use Header; + use super::*; + + #[test] + fn test_parse_multiple_headers() { + let prefer = Header::parse_header(&"respond-async, return=representation".into()); + assert_eq!(prefer.ok(), Some(Prefer(vec![Preference::RespondAsync, + Preference::ReturnRepresentation]))) + } + + #[test] + fn test_parse_argument() { + let prefer = Header::parse_header(&"wait=100, handling=lenient, respond-async".into()); + assert_eq!(prefer.ok(), Some(Prefer(vec![Preference::Wait(100), + Preference::HandlingLenient, + Preference::RespondAsync]))) + } + + #[test] + fn test_parse_quote_form() { + let prefer = Header::parse_header(&"wait=\"200\", handling=\"strict\"".into()); + assert_eq!(prefer.ok(), Some(Prefer(vec![Preference::Wait(200), + Preference::HandlingStrict]))) + } + + #[test] + fn test_parse_extension() { + let prefer = Header::parse_header(&"foo, bar=baz, baz; foo; bar=baz, bux=\"\"; foo=\"\", buz=\"some parameter\"".into()); + assert_eq!(prefer.ok(), Some(Prefer(vec![ + Preference::Extension("foo".to_owned(), "".to_owned(), vec![]), + Preference::Extension("bar".to_owned(), "baz".to_owned(), vec![]), + Preference::Extension("baz".to_owned(), "".to_owned(), vec![("foo".to_owned(), "".to_owned()), ("bar".to_owned(), "baz".to_owned())]), + Preference::Extension("bux".to_owned(), "".to_owned(), vec![("foo".to_owned(), "".to_owned())]), + Preference::Extension("buz".to_owned(), "some parameter".to_owned(), vec![])]))) + } + + #[test] + fn test_fail_with_args() { + let prefer: ::Result<Prefer> = Header::parse_header(&"respond-async; foo=bar".into()); + assert_eq!(prefer.ok(), None); + } +} + +bench_header!(normal, + Prefer, { vec![b"respond-async, return=representation".to_vec(), b"wait=100".to_vec()] }); diff --git a/third_party/rust/headers/src/disabled/preference_applied.rs b/third_party/rust/headers/src/disabled/preference_applied.rs new file mode 100644 index 0000000000..b368bc9bb9 --- /dev/null +++ b/third_party/rust/headers/src/disabled/preference_applied.rs @@ -0,0 +1,110 @@ +use std::fmt; +use {Header, Raw, Preference}; +use parsing::{from_comma_delimited, fmt_comma_delimited}; + +/// `Preference-Applied` header, defined in [RFC7240](http://tools.ietf.org/html/rfc7240) +/// +/// The `Preference-Applied` response header may be included within a +/// response message as an indication as to which `Prefer` header tokens were +/// honored by the server and applied to the processing of a request. +/// +/// # ABNF +/// +/// ```text +/// Preference-Applied = "Preference-Applied" ":" 1#applied-pref +/// applied-pref = token [ BWS "=" BWS word ] +/// ``` +/// +/// # Example values +/// +/// * `respond-async` +/// * `return=minimal` +/// * `wait=30` +/// +/// # Examples +/// +/// ``` +/// use headers::{Headers, PreferenceApplied, Preference}; +/// +/// let mut headers = Headers::new(); +/// headers.set( +/// PreferenceApplied(vec![Preference::RespondAsync]) +/// ); +/// ``` +/// +/// ``` +/// use headers::{Headers, PreferenceApplied, Preference}; +/// +/// let mut headers = Headers::new(); +/// headers.set( +/// PreferenceApplied(vec![ +/// Preference::RespondAsync, +/// Preference::ReturnRepresentation, +/// Preference::Wait(10u32), +/// Preference::Extension("foo".to_owned(), +/// "bar".to_owned(), +/// vec![]), +/// ]) +/// ); +/// ``` +#[derive(PartialEq, Clone, Debug)] +pub struct PreferenceApplied(pub Vec<Preference>); + +__hyper__deref!(PreferenceApplied => Vec<Preference>); + +impl Header for PreferenceApplied { + fn header_name() -> &'static str { + static NAME: &'static str = "Preference-Applied"; + NAME + } + + fn parse_header(raw: &Raw) -> ::Result<PreferenceApplied> { + let preferences = try!(from_comma_delimited(raw)); + if !preferences.is_empty() { + Ok(PreferenceApplied(preferences)) + } else { + Err(::Error::Header) + } + } + + fn fmt_header(&self, f: &mut ::Formatter) -> fmt::Result { + f.fmt_line(self) + } +} + +impl fmt::Display for PreferenceApplied { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + //TODO: format this without allocating a Vec and cloning contents + let preferences: Vec<_> = self.0.iter().map(|pref| match pref { + // The spec ignores parameters in `Preferences-Applied` + &Preference::Extension(ref name, ref value, _) => Preference::Extension( + name.to_owned(), + value.to_owned(), + vec![] + ), + preference => preference.clone() + }).collect(); + fmt_comma_delimited(f, &preferences) + } +} + +#[cfg(test)] +mod tests { + use Preference; + use super::*; + + #[test] + fn test_format_ignore_parameters() { + assert_eq!( + format!("{}", PreferenceApplied(vec![Preference::Extension( + "foo".to_owned(), + "bar".to_owned(), + vec![("bar".to_owned(), "foo".to_owned()), ("buz".to_owned(), "".to_owned())] + )])), + "foo=bar".to_owned() + ); + } +} + +bench_header!(normal, + PreferenceApplied, { vec![b"respond-async, return=representation".to_vec(), b"wait=100".to_vec()] }); diff --git a/third_party/rust/headers/src/disabled/util/charset.rs b/third_party/rust/headers/src/disabled/util/charset.rs new file mode 100644 index 0000000000..5a3f462829 --- /dev/null +++ b/third_party/rust/headers/src/disabled/util/charset.rs @@ -0,0 +1,229 @@ +use std::fmt; +use std::str::FromStr; + +/// A Mime charset. +/// +/// The string representation is normalised to upper case. +/// +/// See [http://www.iana.org/assignments/character-sets/character-sets.xhtml][url]. +/// +/// [url]: http://www.iana.org/assignments/character-sets/character-sets.xhtml +#[derive(Clone, PartialEq)] +pub struct Charset(Charset_); + +impl Charset { + /// US ASCII + pub const US_ASCII: Charset = Charset(Charset_::Us_Ascii); + + /// ISO-8859-1 + pub const ISO_8859_1: Charset = Charset(Charset_::Iso_8859_1); + + /// ISO-8859-2 + pub const ISO_8859_2: Charset = Charset(Charset_::Iso_8859_2); + + /// ISO-8859-3 + pub const ISO_8859_3: Charset = Charset(Charset_::Iso_8859_3); + + /// ISO-8859-4 + pub const ISO_8859_4: Charset = Charset(Charset_::Iso_8859_4); + + /// ISO-8859-5 + pub const ISO_8859_5: Charset = Charset(Charset_::Iso_8859_5); + + /// ISO-8859-6 + pub const ISO_8859_6: Charset = Charset(Charset_::Iso_8859_6); + + /// ISO-8859-7 + pub const ISO_8859_7: Charset = Charset(Charset_::Iso_8859_7); + + /// ISO-8859-8 + pub const ISO_8859_8: Charset = Charset(Charset_::Iso_8859_8); + + /// ISO-8859-9 + pub const ISO_8859_9: Charset = Charset(Charset_::Iso_8859_9); + + /// ISO-8859-10 + pub const ISO_8859_10: Charset = Charset(Charset_::Iso_8859_10); + + /// Shift_JIS + pub const SHIFT_JIS: Charset = Charset(Charset_::Shift_Jis); + + /// EUC-JP + pub const EUC_JP: Charset = Charset(Charset_::Euc_Jp); + + /// ISO-2022-KR + pub const ISO_2022_KR: Charset = Charset(Charset_::Iso_2022_Kr); + + /// EUC-KR + pub const EUC_KR: Charset: Charset(Charset_::Euc_Kr); + + /// ISO-2022-JP + pub const ISO_2022_JP: Charset = Charset(Charset_::Iso_2022_Jp); + + /// ISO-2022-JP-2 + pub const ISO_2022_JP_2: Charset = Charset(Charset_::Iso_2022_Jp_2); + + /// ISO-8859-6-E + pub const ISO_8859_6_E: Charset = Charset(Charset_::Iso_8859_6_E); + + /// ISO-8859-6-I + pub const ISO_8859_6_I: Charset = Charset(Charset_::Iso_8859_6_I); + + /// ISO-8859-8-E + pub const ISO_8859_8_E: Charset = Charset(Charset_::Iso_8859_8_E); + + /// ISO-8859-8-I + pub const ISO_8859_8_I: Charset = Charset(Charset_::Iso_8859_8_I); + + /// GB2312 + pub const GB_2312: Charset = Charset(Charset_::Gb2312); + + /// Big5 + pub const BIG_5: Charset = Charset(Charset_::Big5); + + /// KOI8-R + pub const KOI8_R: Charset = Charset(Charset_::Koi8_R); +} + +#[derive(Clone, Debug, PartialEq)] +#[allow(non_camel_case_types)] +enum Charset_ { + /// US ASCII + Us_Ascii, + /// ISO-8859-1 + Iso_8859_1, + /// ISO-8859-2 + Iso_8859_2, + /// ISO-8859-3 + Iso_8859_3, + /// ISO-8859-4 + Iso_8859_4, + /// ISO-8859-5 + Iso_8859_5, + /// ISO-8859-6 + Iso_8859_6, + /// ISO-8859-7 + Iso_8859_7, + /// ISO-8859-8 + Iso_8859_8, + /// ISO-8859-9 + Iso_8859_9, + /// ISO-8859-10 + Iso_8859_10, + /// Shift_JIS + Shift_Jis, + /// EUC-JP + Euc_Jp, + /// ISO-2022-KR + Iso_2022_Kr, + /// EUC-KR + Euc_Kr, + /// ISO-2022-JP + Iso_2022_Jp, + /// ISO-2022-JP-2 + Iso_2022_Jp_2, + /// ISO-8859-6-E + Iso_8859_6_E, + /// ISO-8859-6-I + Iso_8859_6_I, + /// ISO-8859-8-E + Iso_8859_8_E, + /// ISO-8859-8-I + Iso_8859_8_I, + /// GB2312 + Gb2312, + /// Big5 + Big5, + /// KOI8-R + Koi8_R, + + _Unknown, +} + +impl Charset { + fn name(&self) -> &'static str { + match self.0 { + Charset_::Us_Ascii => "US-ASCII", + Charset_::Iso_8859_1 => "ISO-8859-1", + Charset_::Iso_8859_2 => "ISO-8859-2", + Charset_::Iso_8859_3 => "ISO-8859-3", + Charset_::Iso_8859_4 => "ISO-8859-4", + Charset_::Iso_8859_5 => "ISO-8859-5", + Charset_::Iso_8859_6 => "ISO-8859-6", + Charset_::Iso_8859_7 => "ISO-8859-7", + Charset_::Iso_8859_8 => "ISO-8859-8", + Charset_::Iso_8859_9 => "ISO-8859-9", + Charset_::Iso_8859_10 => "ISO-8859-10", + Charset_::Shift_Jis => "Shift-JIS", + Charset_::Euc_Jp => "EUC-JP", + Charset_::Iso_2022_Kr => "ISO-2022-KR", + Charset_::Euc_Kr => "EUC-KR", + Charset_::Iso_2022_Jp => "ISO-2022-JP", + Charset_::Iso_2022_Jp_2 => "ISO-2022-JP-2", + Charset_::Iso_8859_6_E => "ISO-8859-6-E", + Charset_::Iso_8859_6_I => "ISO-8859-6-I", + Charset_::Iso_8859_8_E => "ISO-8859-8-E", + Charset_::Iso_8859_8_I => "ISO-8859-8-I", + Charset_::Gb2312 => "GB2312", + Charset_::Big5 => "5", + Charset_::Koi8_R => "KOI8-R", + Charset_::_Unknown => unreachable!("Charset::_Unknown"), + } + } +} + +impl fmt::Display for Charset { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.write_str(self.name()) + } +} + +#[derive(Debug)] +pub struct CharsetFromStrError(()); + +impl FromStr for Charset { + type Err = CharsetFromStrError; + fn from_str(s: &str) -> Result<Self, Self::Err> { + Ok(Charset(match s.to_ascii_uppercase().as_ref() { + "US-ASCII" => Charset_::Us_Ascii, + "ISO-8859-1" => Charset_::Iso_8859_1, + "ISO-8859-2" => Charset_::Iso_8859_2, + "ISO-8859-3" => Charset_::Iso_8859_3, + "ISO-8859-4" => Charset_::Iso_8859_4, + "ISO-8859-5" => Charset_::Iso_8859_5, + "ISO-8859-6" => Charset_::Iso_8859_6, + "ISO-8859-7" => Charset_::Iso_8859_7, + "ISO-8859-8" => Charset_::Iso_8859_8, + "ISO-8859-9" => Charset_::Iso_8859_9, + "ISO-8859-10" => Charset_::Iso_8859_10, + "SHIFT-JIS" => Charset_::Shift_Jis, + "EUC-JP" => Charset_::Euc_Jp, + "ISO-2022-KR" => Charset_::Iso_2022_Kr, + "EUC-KR" => Charset_::Euc_Kr, + "ISO-2022-JP" => Charset_::Iso_2022_Jp, + "ISO-2022-JP-2" => Charset_::Iso_2022_Jp_2, + "ISO-8859-6-E" => Charset_::Iso_8859_6_E, + "ISO-8859-6-I" => Charset_::Iso_8859_6_I, + "ISO-8859-8-E" => Charset_::Iso_8859_8_E, + "ISO-8859-8-I" => Charset_::Iso_8859_8_I, + "GB2312" => Charset_::Gb2312, + "5" => Charset_::Big5, + "KOI8-R" => Charset_::Koi8_R, + _unknown => return Err(CharsetFromStrError(())), + })) + } +} + +#[test] +fn test_parse() { + assert_eq!(Charset::US_ASCII,"us-ascii".parse().unwrap()); + assert_eq!(Charset::US_ASCII,"US-Ascii".parse().unwrap()); + assert_eq!(Charset::US_ASCII,"US-ASCII".parse().unwrap()); + assert_eq!(Charset::SHIFT_JIS,"Shift-JIS".parse().unwrap()); + assert!("abcd".parse(::<Charset>().is_err()); +} + +#[test] +fn test_display() { + assert_eq!("US-ASCII", format!("{}", Charset::US_ASCII)); +} diff --git a/third_party/rust/headers/src/disabled/util/encoding.rs b/third_party/rust/headers/src/disabled/util/encoding.rs new file mode 100644 index 0000000000..fc972dd3c7 --- /dev/null +++ b/third_party/rust/headers/src/disabled/util/encoding.rs @@ -0,0 +1,57 @@ +use std::fmt; +use std::str; + +pub use self::Encoding::{Chunked, Brotli, Gzip, Deflate, Compress, Identity, EncodingExt, Trailers}; + +/// A value to represent an encoding used in `Transfer-Encoding` +/// or `Accept-Encoding` header. +#[derive(Clone, PartialEq, Debug)] +pub enum Encoding { + /// The `chunked` encoding. + Chunked, + /// The `br` encoding. + Brotli, + /// The `gzip` encoding. + Gzip, + /// The `deflate` encoding. + Deflate, + /// The `compress` encoding. + Compress, + /// The `identity` encoding. + Identity, + /// The `trailers` encoding. + Trailers, + /// Some other encoding that is less common, can be any String. + EncodingExt(String) +} + +impl fmt::Display for Encoding { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.write_str(match *self { + Chunked => "chunked", + Brotli => "br", + Gzip => "gzip", + Deflate => "deflate", + Compress => "compress", + Identity => "identity", + Trailers => "trailers", + EncodingExt(ref s) => s.as_ref() + }) + } +} + +impl str::FromStr for Encoding { + type Err = ::Error; + fn from_str(s: &str) -> ::Result<Encoding> { + match s { + "chunked" => Ok(Chunked), + "br" => Ok(Brotli), + "deflate" => Ok(Deflate), + "gzip" => Ok(Gzip), + "compress" => Ok(Compress), + "identity" => Ok(Identity), + "trailers" => Ok(Trailers), + _ => Ok(EncodingExt(s.to_owned())) + } + } +} diff --git a/third_party/rust/headers/src/disabled/util/extended_value.rs b/third_party/rust/headers/src/disabled/util/extended_value.rs new file mode 100644 index 0000000000..0098f6270c --- /dev/null +++ b/third_party/rust/headers/src/disabled/util/extended_value.rs @@ -0,0 +1,192 @@ +/// An extended header parameter value (i.e., tagged with a character set and optionally, +/// a language), as defined in [RFC 5987](https://tools.ietf.org/html/rfc5987#section-3.2). +#[derive(Clone, Debug, PartialEq)] +pub struct ExtendedValue { + /// The character set that is used to encode the `value` to a string. + pub charset: Charset, + /// The human language details of the `value`, if available. + pub language_tag: Option<LanguageTag>, + /// The parameter value, as expressed in octets. + pub value: Vec<u8>, +} + +/// Parses extended header parameter values (`ext-value`), as defined in +/// [RFC 5987](https://tools.ietf.org/html/rfc5987#section-3.2). +/// +/// Extended values are denoted by parameter names that end with `*`. +/// +/// ## ABNF +/// +/// ```text +/// ext-value = charset "'" [ language ] "'" value-chars +/// ; like RFC 2231's <extended-initial-value> +/// ; (see [RFC2231], Section 7) +/// +/// charset = "UTF-8" / "ISO-8859-1" / mime-charset +/// +/// mime-charset = 1*mime-charsetc +/// mime-charsetc = ALPHA / DIGIT +/// / "!" / "#" / "$" / "%" / "&" +/// / "+" / "-" / "^" / "_" / "`" +/// / "{" / "}" / "~" +/// ; as <mime-charset> in Section 2.3 of [RFC2978] +/// ; except that the single quote is not included +/// ; SHOULD be registered in the IANA charset registry +/// +/// language = <Language-Tag, defined in [RFC5646], Section 2.1> +/// +/// value-chars = *( pct-encoded / attr-char ) +/// +/// pct-encoded = "%" HEXDIG HEXDIG +/// ; see [RFC3986], Section 2.1 +/// +/// attr-char = ALPHA / DIGIT +/// / "!" / "#" / "$" / "&" / "+" / "-" / "." +/// / "^" / "_" / "`" / "|" / "~" +/// ; token except ( "*" / "'" / "%" ) +/// ``` +pub fn parse_extended_value(val: &str) -> ::Result<ExtendedValue> { + + // Break into three pieces separated by the single-quote character + let mut parts = val.splitn(3,'\''); + + // Interpret the first piece as a Charset + let charset: Charset = match parts.next() { + None => return Err(::Error::Header), + Some(n) => try!(FromStr::from_str(n)), + }; + + // Interpret the second piece as a language tag + let lang: Option<LanguageTag> = match parts.next() { + None => return Err(::Error::Header), + Some("") => None, + Some(s) => match s.parse() { + Ok(lt) => Some(lt), + Err(_) => return Err(::Error::Header), + } + }; + + // Interpret the third piece as a sequence of value characters + let value: Vec<u8> = match parts.next() { + None => return Err(::Error::Header), + Some(v) => percent_encoding::percent_decode(v.as_bytes()).collect(), + }; + + Ok(ExtendedValue { + charset: charset, + language_tag: lang, + value: value, + }) +} + + +impl Display for ExtendedValue { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let encoded_value = + percent_encoding::percent_encode(&self.value[..], self::percent_encoding_http::HTTP_VALUE); + if let Some(ref lang) = self.language_tag { + write!(f, "{}'{}'{}", self.charset, lang, encoded_value) + } else { + write!(f, "{}''{}", self.charset, encoded_value) + } + } +} + +/// Percent encode a sequence of bytes with a character set defined in +/// [https://tools.ietf.org/html/rfc5987#section-3.2][url] +/// +/// [url]: https://tools.ietf.org/html/rfc5987#section-3.2 +pub fn http_percent_encode(f: &mut fmt::Formatter, bytes: &[u8]) -> fmt::Result { + let encoded = percent_encoding::percent_encode(bytes, self::percent_encoding_http::HTTP_VALUE); + fmt::Display::fmt(&encoded, f) +} + +mod percent_encoding_http { + use percent_encoding; + + // internal module because macro is hard-coded to make a public item + // but we don't want to public export this item + define_encode_set! { + // This encode set is used for HTTP header values and is defined at + // https://tools.ietf.org/html/rfc5987#section-3.2 + pub HTTP_VALUE = [percent_encoding::SIMPLE_ENCODE_SET] | { + ' ', '"', '%', '\'', '(', ')', '*', ',', '/', ':', ';', '<', '-', '>', '?', + '[', '\\', ']', '{', '}' + } + } +} + +#[cfg(test)] +mod tests { + use shared::Charset; + use super::{ExtendedValue, parse_extended_value}; + use language_tags::LanguageTag; + + #[test] + fn test_parse_extended_value_with_encoding_and_language_tag() { + let expected_language_tag = "en".parse::<LanguageTag>().unwrap(); + // RFC 5987, Section 3.2.2 + // Extended notation, using the Unicode character U+00A3 (POUND SIGN) + let result = parse_extended_value("iso-8859-1'en'%A3%20rates"); + assert!(result.is_ok()); + let extended_value = result.unwrap(); + assert_eq!(Charset::Iso_8859_1, extended_value.charset); + assert!(extended_value.language_tag.is_some()); + assert_eq!(expected_language_tag, extended_value.language_tag.unwrap()); + assert_eq!(vec![163, b' ', b'r', b'a', b't', b'e', b's'], extended_value.value); + } + + #[test] + fn test_parse_extended_value_with_encoding() { + // RFC 5987, Section 3.2.2 + // Extended notation, using the Unicode characters U+00A3 (POUND SIGN) + // and U+20AC (EURO SIGN) + let result = parse_extended_value("UTF-8''%c2%a3%20and%20%e2%82%ac%20rates"); + assert!(result.is_ok()); + let extended_value = result.unwrap(); + assert_eq!(Charset::Ext("UTF-8".to_string()), extended_value.charset); + assert!(extended_value.language_tag.is_none()); + assert_eq!(vec![194, 163, b' ', b'a', b'n', b'd', b' ', 226, 130, 172, b' ', b'r', b'a', b't', b'e', b's'], extended_value.value); + } + + #[test] + fn test_parse_extended_value_missing_language_tag_and_encoding() { + // From: https://greenbytes.de/tech/tc2231/#attwithfn2231quot2 + let result = parse_extended_value("foo%20bar.html"); + assert!(result.is_err()); + } + + #[test] + fn test_parse_extended_value_partially_formatted() { + let result = parse_extended_value("UTF-8'missing third part"); + assert!(result.is_err()); + } + + #[test] + fn test_parse_extended_value_partially_formatted_blank() { + let result = parse_extended_value("blank second part'"); + assert!(result.is_err()); + } + + #[test] + fn test_fmt_extended_value_with_encoding_and_language_tag() { + let extended_value = ExtendedValue { + charset: Charset::Iso_8859_1, + language_tag: Some("en".parse().expect("Could not parse language tag")), + value: vec![163, b' ', b'r', b'a', b't', b'e', b's'], + }; + assert_eq!("ISO-8859-1'en'%A3%20rates", format!("{}", extended_value)); + } + + #[test] + fn test_fmt_extended_value_with_encoding() { + let extended_value = ExtendedValue { + charset: Charset::Ext("UTF-8".to_string()), + language_tag: None, + value: vec![194, 163, b' ', b'a', b'n', b'd', b' ', 226, 130, 172, b' ', b'r', b'a', + b't', b'e', b's'], + }; + assert_eq!("UTF-8''%C2%A3%20and%20%E2%82%AC%20rates", + format!("{}", extended_value)); + } +} diff --git a/third_party/rust/headers/src/disabled/util/quality_value.rs b/third_party/rust/headers/src/disabled/util/quality_value.rs new file mode 100644 index 0000000000..bcc797280d --- /dev/null +++ b/third_party/rust/headers/src/disabled/util/quality_value.rs @@ -0,0 +1,268 @@ +#[allow(unused, deprecated)] +use std::ascii::AsciiExt; +use std::cmp; +use std::default::Default; +use std::fmt; +use std::str; + +#[cfg(test)] +use self::internal::IntoQuality; + +/// Represents a quality used in quality values. +/// +/// Can be created with the `q` function. +/// +/// # Implementation notes +/// +/// The quality value is defined as a number between 0 and 1 with three decimal places. This means +/// there are 1001 possible values. Since floating point numbers are not exact and the smallest +/// floating point data type (`f32`) consumes four bytes, hyper uses an `u16` value to store the +/// quality internally. For performance reasons you may set quality directly to a value between +/// 0 and 1000 e.g. `Quality(532)` matches the quality `q=0.532`. +/// +/// [RFC7231 Section 5.3.1](https://tools.ietf.org/html/rfc7231#section-5.3.1) +/// gives more information on quality values in HTTP header fields. +#[derive(Copy, Clone, Debug, Eq, Ord, PartialEq, PartialOrd)] +pub struct Quality(u16); + +impl Default for Quality { + fn default() -> Quality { + Quality(1000) + } +} + +/// Represents an item with a quality value as defined in +/// [RFC7231](https://tools.ietf.org/html/rfc7231#section-5.3.1). +#[derive(Clone, PartialEq, Debug)] +pub struct QualityValue<T> { + /// The actual contents of the field. + value: T, + /// The quality (client or server preference) for the value. + quality: Quality, +} + +impl<T> QualityValue<T> { + /// Creates a new `QualityValue` from an item and a quality. + pub fn new(value: T, quality: Quality) -> QualityValue<T> { + QualityValue { + value, + quality, + } + } + + /* + /// Convenience function to set a `Quality` from a float or integer. + /// + /// Implemented for `u16` and `f32`. + /// + /// # Panic + /// + /// Panics if value is out of range. + pub fn with_q<Q: IntoQuality>(mut self, q: Q) -> QualityValue<T> { + self.quality = q.into_quality(); + self + } + */ +} + +impl<T> From<T> for QualityValue<T> { + fn from(value: T) -> QualityValue<T> { + QualityValue { + value, + quality: Quality::default(), + } + } +} + +impl<T: PartialEq> cmp::PartialOrd for QualityValue<T> { + fn partial_cmp(&self, other: &QualityValue<T>) -> Option<cmp::Ordering> { + self.quality.partial_cmp(&other.quality) + } +} + +impl<T: fmt::Display> fmt::Display for QualityValue<T> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fmt::Display::fmt(&self.value, f)?; + match self.quality.0 { + 1000 => Ok(()), + 0 => f.write_str("; q=0"), + x => write!(f, "; q=0.{}", format!("{:03}", x).trim_right_matches('0')) + } + } +} + +impl<T: str::FromStr> str::FromStr for QualityValue<T> { + type Err = ::Error; + fn from_str(s: &str) -> ::Result<QualityValue<T>> { + // Set defaults used if parsing fails. + let mut raw_item = s; + let mut quality = 1f32; + + let parts: Vec<&str> = s.rsplitn(2, ';').map(|x| x.trim()).collect(); + if parts.len() == 2 { + if parts[0].len() < 2 { + return Err(::Error::invalid()); + } + if parts[0].starts_with("q=") || parts[0].starts_with("Q=") { + let q_part = &parts[0][2..parts[0].len()]; + if q_part.len() > 5 { + return Err(::Error::invalid()); + } + match q_part.parse::<f32>() { + Ok(q_value) => { + if 0f32 <= q_value && q_value <= 1f32 { + quality = q_value; + raw_item = parts[1]; + } else { + return Err(::Error::invalid()); + } + }, + Err(_) => { + return Err(::Error::invalid()) + }, + } + } + } + match raw_item.parse::<T>() { + // we already checked above that the quality is within range + Ok(item) => Ok(QualityValue::new(item, from_f32(quality))), + Err(_) => { + Err(::Error::invalid()) + }, + } + } +} + +#[inline] +fn from_f32(f: f32) -> Quality { + // this function is only used internally. A check that `f` is within range + // should be done before calling this method. Just in case, this + // debug_assert should catch if we were forgetful + debug_assert!(f >= 0f32 && f <= 1f32, "q value must be between 0.0 and 1.0"); + Quality((f * 1000f32) as u16) +} + +#[cfg(test)] +fn q<T: IntoQuality>(val: T) -> Quality { + val.into_quality() +} + +mod internal { + use super::Quality; + + // TryFrom is probably better, but it's not stable. For now, we want to + // keep the functionality of the `q` function, while allowing it to be + // generic over `f32` and `u16`. + // + // `q` would panic before, so keep that behavior. `TryFrom` can be + // introduced later for a non-panicking conversion. + + pub trait IntoQuality: Sealed + Sized { + fn into_quality(self) -> Quality; + } + + impl IntoQuality for f32 { + fn into_quality(self) -> Quality { + assert!(self >= 0f32 && self <= 1f32, "float must be between 0.0 and 1.0"); + super::from_f32(self) + } + } + + impl IntoQuality for u16 { + fn into_quality(self) -> Quality { + assert!(self <= 1000, "u16 must be between 0 and 1000"); + Quality(self) + } + } + + + pub trait Sealed {} + impl Sealed for u16 {} + impl Sealed for f32 {} +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_quality_item_fmt_q_1() { + let x = QualityValue::from("foo"); + assert_eq!(format!("{}", x), "foo"); + } + #[test] + fn test_quality_item_fmt_q_0001() { + let x = QualityValue::new("foo", Quality(1)); + assert_eq!(format!("{}", x), "foo; q=0.001"); + } + #[test] + fn test_quality_item_fmt_q_05() { + let x = QualityValue::new("foo", Quality(500)); + assert_eq!(format!("{}", x), "foo; q=0.5"); + } + + #[test] + fn test_quality_item_fmt_q_0() { + let x = QualityValue::new("foo", Quality(0)); + assert_eq!(x.to_string(), "foo; q=0"); + } + + #[test] + fn test_quality_item_from_str1() { + let x: QualityValue<String> = "chunked".parse().unwrap(); + assert_eq!(x, QualityValue { value: "chunked".to_owned(), quality: Quality(1000), }); + } + #[test] + fn test_quality_item_from_str2() { + let x: QualityValue<String> = "chunked; q=1".parse().unwrap(); + assert_eq!(x, QualityValue { value: "chunked".to_owned(), quality: Quality(1000), }); + } + #[test] + fn test_quality_item_from_str3() { + let x: QualityValue<String> = "gzip; q=0.5".parse().unwrap(); + assert_eq!(x, QualityValue { value: "gzip".to_owned(), quality: Quality(500), }); + } + #[test] + fn test_quality_item_from_str4() { + let x: QualityValue<String> = "gzip; q=0.273".parse().unwrap(); + assert_eq!(x, QualityValue { value: "gzip".to_owned(), quality: Quality(273), }); + } + #[test] + fn test_quality_item_from_str5() { + assert!("gzip; q=0.2739999".parse::<QualityValue<String>>().is_err()); + } + + #[test] + fn test_quality_item_from_str6() { + assert!("gzip; q=2".parse::<QualityValue<String>>().is_err()); + } + #[test] + fn test_quality_item_ordering() { + let x: QualityValue<String> = "gzip; q=0.5".parse().unwrap(); + let y: QualityValue<String> = "gzip; q=0.273".parse().unwrap(); + assert!(x > y) + } + + #[test] + fn test_quality() { + assert_eq!(q(0.5), Quality(500)); + } + + #[test] + #[should_panic] + fn test_quality_invalid() { + q(-1.0); + } + + #[test] + #[should_panic] + fn test_quality_invalid2() { + q(2.0); + } + + #[test] + fn test_fuzzing_bugs() { + assert!("99999;".parse::<QualityValue<String>>().is_err()); + assert!("\x0d;;;=\u{d6aa}==".parse::<QualityValue<String>>().is_ok()) + } +} diff --git a/third_party/rust/headers/src/disabled/warning.rs b/third_party/rust/headers/src/disabled/warning.rs new file mode 100644 index 0000000000..9227201540 --- /dev/null +++ b/third_party/rust/headers/src/disabled/warning.rs @@ -0,0 +1,182 @@ +use std::fmt; +use std::str::{FromStr}; +use {Header, HttpDate, Raw}; +use parsing::from_one_raw_str; + +/// `Warning` header, defined in [RFC7234](https://tools.ietf.org/html/rfc7234#section-5.5) +/// +/// The `Warning` header field can be be used to carry additional information +/// about the status or transformation of a message that might not be reflected +/// in the status code. This header is sometimes used as backwards +/// compatible way to notify of a deprecated API. +/// +/// # ABNF +/// +/// ```text +/// Warning = 1#warning-value +/// warning-value = warn-code SP warn-agent SP warn-text +/// [ SP warn-date ] +/// warn-code = 3DIGIT +/// warn-agent = ( uri-host [ ":" port ] ) / pseudonym +/// ; the name or pseudonym of the server adding +/// ; the Warning header field, for use in debugging +/// ; a single "-" is recommended when agent unknown +/// warn-text = quoted-string +/// warn-date = DQUOTE HTTP-date DQUOTE +/// ``` +/// +/// # Example values +/// +/// * `Warning: 112 - "network down" "Sat, 25 Aug 2012 23:34:45 GMT"` +/// * `Warning: 299 - "Deprecated API " "Tue, 15 Nov 1994 08:12:31 GMT"` +/// * `Warning: 299 api.hyper.rs:8080 "Deprecated API : use newapi.hyper.rs instead."` +/// * `Warning: 299 api.hyper.rs:8080 "Deprecated API : use newapi.hyper.rs instead." "Tue, 15 Nov 1994 08:12:31 GMT"` +/// +/// # Examples +/// +/// ``` +/// use headers::{Headers, Warning}; +/// +/// let mut headers = Headers::new(); +/// headers.set( +/// Warning{ +/// code: 299, +/// agent: "api.hyper.rs".to_owned(), +/// text: "Deprecated".to_owned(), +/// date: None +/// } +/// ); +/// ``` +/// +/// ``` +/// use headers::{Headers, HttpDate, Warning}; +/// +/// let mut headers = Headers::new(); +/// headers.set( +/// Warning{ +/// code: 299, +/// agent: "api.hyper.rs".to_owned(), +/// text: "Deprecated".to_owned(), +/// date: "Tue, 15 Nov 1994 08:12:31 GMT".parse::<HttpDate>().ok() +/// } +/// ); +/// ``` +/// +/// ``` +/// use std::time::SystemTime; +/// use headers::{Headers, Warning}; +/// +/// let mut headers = Headers::new(); +/// headers.set( +/// Warning{ +/// code: 199, +/// agent: "api.hyper.rs".to_owned(), +/// text: "Deprecated".to_owned(), +/// date: Some(SystemTime::now().into()) +/// } +/// ); +/// ``` +#[derive(PartialEq, Clone, Debug)] +pub struct Warning { + /// The 3 digit warn code. + pub code: u16, + /// The name or pseudonym of the server adding this header. + pub agent: String, + /// The warning message describing the error. + pub text: String, + /// An optional warning date. + pub date: Option<HttpDate> +} + +impl Header for Warning { + fn header_name() -> &'static str { + static NAME: &'static str = "Warning"; + NAME + } + + fn parse_header(raw: &Raw) -> ::Result<Warning> { + from_one_raw_str(raw) + } + + fn fmt_header(&self, f: &mut ::Formatter) -> fmt::Result { + f.fmt_line(self) + } +} + +impl fmt::Display for Warning { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self.date { + Some(date) => write!(f, "{:03} {} \"{}\" \"{}\"", self.code, self.agent, self.text, date), + None => write!(f, "{:03} {} \"{}\"", self.code, self.agent, self.text) + } + } +} + +impl FromStr for Warning { + type Err = ::Error; + + fn from_str(s: &str) -> ::Result<Warning> { + let mut warning_split = s.split_whitespace(); + let code = match warning_split.next() { + Some(c) => match c.parse::<u16>() { + Ok(c) => c, + Err(..) => return Err(::Error::Header) + }, + None => return Err(::Error::Header) + }; + let agent = match warning_split.next() { + Some(a) => a.to_string(), + None => return Err(::Error::Header) + }; + + let mut warning_split = s.split('"').skip(1); + let text = match warning_split.next() { + Some(t) => t.to_string(), + None => return Err(::Error::Header) + }; + let date = match warning_split.skip(1).next() { + Some(d) => d.parse::<HttpDate>().ok(), + None => None // Optional + }; + + Ok(Warning { + code: code, + agent: agent, + text: text, + date: date + }) + } +} + +#[cfg(test)] +mod tests { + use super::Warning; + use {Header, HttpDate}; + + #[test] + fn test_parsing() { + let warning = Header::parse_header(&vec![b"112 - \"network down\" \"Sat, 25 Aug 2012 23:34:45 GMT\"".to_vec()].into()); + assert_eq!(warning.ok(), Some(Warning { + code: 112, + agent: "-".to_owned(), + text: "network down".to_owned(), + date: "Sat, 25 Aug 2012 23:34:45 GMT".parse::<HttpDate>().ok() + })); + + let warning = Header::parse_header(&vec![b"299 api.hyper.rs:8080 \"Deprecated API : use newapi.hyper.rs instead.\"".to_vec()].into()); + assert_eq!(warning.ok(), Some(Warning { + code: 299, + agent: "api.hyper.rs:8080".to_owned(), + text: "Deprecated API : use newapi.hyper.rs instead.".to_owned(), + date: None + })); + + let warning = Header::parse_header(&vec![b"299 api.hyper.rs:8080 \"Deprecated API : use newapi.hyper.rs instead.\" \"Tue, 15 Nov 1994 08:12:31 GMT\"".to_vec()].into()); + assert_eq!(warning.ok(), Some(Warning { + code: 299, + agent: "api.hyper.rs:8080".to_owned(), + text: "Deprecated API : use newapi.hyper.rs instead.".to_owned(), + date: "Tue, 15 Nov 1994 08:12:31 GMT".parse::<HttpDate>().ok() + })); + } +} |