diff --git a/headers-derive/Cargo.toml b/headers-derive/Cargo.toml deleted file mode 100644 index d353059d..00000000 --- a/headers-derive/Cargo.toml +++ /dev/null @@ -1,18 +0,0 @@ -[package] -name = "headers-derive" -version = "0.1.1" # don't forget to update html_root_url -description = "derive(Header)" -license = "MIT" -readme = "README.md" -homepage = "https://hyper.rs" -repository = "https://github.com/hyperium/headers" -authors = ["Sean McArthur "] - -[lib] -name = "headers_derive" -proc-macro = true - -[dependencies] -proc-macro2 = "1" -quote = "1" -syn = "1" diff --git a/headers-derive/LICENSE b/headers-derive/LICENSE deleted file mode 120000 index ea5b6064..00000000 --- a/headers-derive/LICENSE +++ /dev/null @@ -1 +0,0 @@ -../LICENSE \ No newline at end of file diff --git a/headers-derive/README.md b/headers-derive/README.md deleted file mode 100644 index ec8dcc54..00000000 --- a/headers-derive/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# Internal derive(Header) macro for `headers` crate - -Doesn't work outside the `headers` crate, nothing to see here. diff --git a/headers-derive/src/lib.rs b/headers-derive/src/lib.rs deleted file mode 100644 index 9805cbfa..00000000 --- a/headers-derive/src/lib.rs +++ /dev/null @@ -1,207 +0,0 @@ -#![recursion_limit = "128"] - -extern crate proc_macro; -extern crate proc_macro2; -#[macro_use] -extern crate quote; -extern crate syn; - -use proc_macro::TokenStream; -use proc_macro2::Span; -use syn::{Data, Fields, Ident, Lit, Meta, NestedMeta}; - -#[proc_macro_derive(Header, attributes(header))] -pub fn derive_header(input: TokenStream) -> TokenStream { - let ast = syn::parse(input).unwrap(); - impl_header(&ast).into() -} - -fn impl_header(ast: &syn::DeriveInput) -> proc_macro2::TokenStream { - let fns = match impl_fns(ast) { - Ok(fns) => fns, - Err(msg) => { - return quote! { - compile_error!(#msg); - } - .into(); - } - }; - - let decode = fns.decode; - let encode = fns.encode; - - let ty = &ast.ident; - let hname = fns.name.unwrap_or_else(|| to_header_name(&ty.to_string())); - let hname_ident = Ident::new(&hname, Span::call_site()); - let dummy_const = Ident::new(&format!("_IMPL_HEADER_FOR_{}", hname), Span::call_site()); - let impl_block = quote! { - impl __hc::Header for #ty { - fn name() -> &'static __hc::HeaderName { - &__hc::header::#hname_ident - } - fn decode<'i, I>(values: &mut I) -> Result - where - I: Iterator, - { - #decode - } - fn encode>(&self, values: &mut E) { - #encode - } - } - }; - - quote! { - const #dummy_const: () = { - extern crate headers_core as __hc; - #impl_block - }; - } -} - -struct Fns { - encode: proc_macro2::TokenStream, - decode: proc_macro2::TokenStream, - name: Option, -} - -fn impl_fns(ast: &syn::DeriveInput) -> Result { - let ty = &ast.ident; - - // Only structs are allowed... - let st = match ast.data { - Data::Struct(ref st) => st, - _ => return Err("derive(Header) only works on structs".into()), - }; - - // Check attributes for `#[header(...)]` that may influence the code - // that is generated... - let mut name = None; - for attr in &ast.attrs { - if attr.path.segments.len() != 1 { - continue; - } - if attr.path.segments[0].ident != "header" { - continue; - } - - match attr.parse_meta() { - Ok(Meta::List(list)) => { - for meta in &list.nested { - match meta { - NestedMeta::Meta(Meta::NameValue(ref kv)) - if kv.path.is_ident("name_const") => - { - if name.is_some() { - return Err( - "repeated 'name_const' option in #[header] attribute".into() - ); - } - name = match kv.lit { - Lit::Str(ref s) => Some(s.value()), - _ => { - return Err( - "illegal literal in #[header(name_const = ..)] attribute" - .into(), - ); - } - }; - } - _ => return Err("illegal option in #[header(..)] attribute".into()), - } - } - } - Ok(Meta::NameValue(_)) => return Err("illegal #[header = ..] attribute".into()), - Ok(Meta::Path(_)) => return Err("empty #[header] attributes do nothing".into()), - Err(e) => { - // TODO stringify attribute to return better error - return Err(format!("illegal #[header ??] attribute: {:?}", e)); - } - } - } - - let decode_res = quote! { - ::util::TryFromValues::try_from_values(values) - }; - - let (decode, encode_name) = match st.fields { - Fields::Named(ref fields) => { - if fields.named.len() != 1 { - return Err("derive(Header) doesn't support multiple fields".into()); - } - - let field = fields - .named - .iter() - .next() - .expect("just checked for len() == 1"); - let field_name = field.ident.as_ref().unwrap(); - - let decode = quote! { - #decode_res - .map(|inner| #ty { - #field_name: inner, - }) - }; - - let encode_name = Ident::new(&field_name.to_string(), Span::call_site()); - (decode, Value::Named(encode_name)) - } - Fields::Unnamed(ref fields) => { - if fields.unnamed.len() != 1 { - return Err("derive(Header) doesn't support multiple fields".into()); - } - - let decode = quote! { - #decode_res - .map(#ty) - }; - - (decode, Value::Unnamed) - } - Fields::Unit => return Err("derive(Header) doesn't support unit structs".into()), - }; - - let encode = { - let field = if let Value::Named(field) = encode_name { - quote! { - (&self.#field) - } - } else { - quote! { - (&self.0) - } - }; - quote! { - values.extend(::std::iter::once((#field).into())); - } - }; - - Ok(Fns { - decode, - encode, - name, - }) -} - -fn to_header_name(ty_name: &str) -> String { - let mut out = String::new(); - let mut first = true; - for c in ty_name.chars() { - if first { - out.push(c.to_ascii_uppercase()); - first = false; - } else { - if c.is_uppercase() { - out.push('_'); - } - out.push(c.to_ascii_uppercase()); - } - } - out -} - -enum Value { - Named(Ident), - Unnamed, -} diff --git a/src/disabled/accept.rs b/src/disabled/accept.rs deleted file mode 100644 index 3e8b7739..00000000 --- a/src/disabled/accept.rs +++ /dev/null @@ -1,150 +0,0 @@ -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)+ - - 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/src/disabled/accept_charset.rs b/src/disabled/accept_charset.rs deleted file mode 100644 index 96eec896..00000000 --- a/src/disabled/accept_charset.rs +++ /dev/null @@ -1,57 +0,0 @@ -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)+ - - test_accept_charset { - /// Testcase from RFC - test_header!(test1, vec![b"iso-8859-5, unicode-1-1;q=0.8"]); - } -} diff --git a/src/disabled/accept_encoding.rs b/src/disabled/accept_encoding.rs deleted file mode 100644 index 5eb3adf5..00000000 --- a/src/disabled/accept_encoding.rs +++ /dev/null @@ -1,72 +0,0 @@ -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)* - - 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/src/disabled/accept_language.rs b/src/disabled/accept_language.rs deleted file mode 100644 index dccfdb0b..00000000 --- a/src/disabled/accept_language.rs +++ /dev/null @@ -1,72 +0,0 @@ -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 = - /// ``` - /// - /// # 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)+ - - 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/src/disabled/content_language.rs b/src/disabled/content_language.rs deleted file mode 100644 index cca8dcbb..00000000 --- a/src/disabled/content_language.rs +++ /dev/null @@ -1,35 +0,0 @@ -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/src/disabled/from.rs b/src/disabled/from.rs deleted file mode 100644 index ed00d8e9..00000000 --- a/src/disabled/from.rs +++ /dev/null @@ -1,29 +0,0 @@ -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 = - /// ``` - /// - /// # 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/src/disabled/last_event_id.rs b/src/disabled/last_event_id.rs deleted file mode 100644 index c2499b0a..00000000 --- a/src/disabled/last_event_id.rs +++ /dev/null @@ -1,40 +0,0 @@ -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/src/disabled/link.rs b/src/disabled/link.rs deleted file mode 100644 index a6d84944..00000000 --- a/src/disabled/link.rs +++ /dev/null @@ -1,1105 +0,0 @@ -use std::fmt; -use std::borrow::Cow; -use std::str::FromStr; -#[allow(unused, deprecated)] -use std::ascii::AsciiExt; - -use mime::Mime; -use language_tags::LanguageTag; - -use parsing; -use {Header, Raw}; - -/// The `Link` header, defined in -/// [RFC5988](http://tools.ietf.org/html/rfc5988#section-5) -/// -/// # ABNF -/// -/// ```text -/// Link = "Link" ":" #link-value -/// link-value = "<" URI-Reference ">" *( ";" link-param ) -/// link-param = ( ( "rel" "=" relation-types ) -/// | ( "anchor" "=" <"> URI-Reference <"> ) -/// | ( "rev" "=" relation-types ) -/// | ( "hreflang" "=" Language-Tag ) -/// | ( "media" "=" ( MediaDesc | ( <"> MediaDesc <"> ) ) ) -/// | ( "title" "=" quoted-string ) -/// | ( "title*" "=" ext-value ) -/// | ( "type" "=" ( media-type | quoted-mt ) ) -/// | ( link-extension ) ) -/// link-extension = ( parmname [ "=" ( ptoken | quoted-string ) ] ) -/// | ( ext-name-star "=" ext-value ) -/// ext-name-star = parmname "*" ; reserved for RFC2231-profiled -/// ; extensions. Whitespace NOT -/// ; allowed in between. -/// ptoken = 1*ptokenchar -/// ptokenchar = "!" | "#" | "$" | "%" | "&" | "'" | "(" -/// | ")" | "*" | "+" | "-" | "." | "/" | DIGIT -/// | ":" | "<" | "=" | ">" | "?" | "@" | ALPHA -/// | "[" | "]" | "^" | "_" | "`" | "{" | "|" -/// | "}" | "~" -/// media-type = type-name "/" subtype-name -/// quoted-mt = <"> media-type <"> -/// relation-types = relation-type -/// | <"> relation-type *( 1*SP relation-type ) <"> -/// relation-type = reg-rel-type | ext-rel-type -/// reg-rel-type = LOALPHA *( LOALPHA | DIGIT | "." | "-" ) -/// ext-rel-type = URI -/// ``` -/// -/// # Example values -/// -/// `Link: ; rel="previous"; -/// title="previous chapter"` -/// -/// `Link: ; rel="previous"; title*=UTF-8'de'letztes%20Kapitel, -/// ; rel="next"; title*=UTF-8'de'n%c3%a4chstes%20Kapitel` -/// -/// # Examples -/// -/// ``` -/// use headers::{Headers, Link, LinkValue, RelationType}; -/// -/// let link_value = LinkValue::new("http://example.com/TheBook/chapter2") -/// .push_rel(RelationType::Previous) -/// .set_title("previous chapter"); -/// -/// let mut headers = Headers::new(); -/// headers.set( -/// Link::new(vec![link_value]) -/// ); -/// ``` -#[derive(Clone, PartialEq, Debug)] -pub struct Link { - /// A list of the `link-value`s of the Link entity-header. - values: Vec -} - -/// A single `link-value` of a `Link` header, based on: -/// [RFC5988](http://tools.ietf.org/html/rfc5988#section-5) -#[derive(Clone, PartialEq, Debug)] -pub struct LinkValue { - /// Target IRI: `link-value`. - link: Cow<'static, str>, - - /// Forward Relation Types: `rel`. - rel: Option>, - - /// Context IRI: `anchor`. - anchor: Option, - - /// Reverse Relation Types: `rev`. - rev: Option>, - - /// Hint on the language of the result of dereferencing - /// the link: `hreflang`. - href_lang: Option>, - - /// Destination medium or media: `media`. - media_desc: Option>, - - /// Label of the destination of a Link: `title`. - title: Option, - - /// The `title` encoded in a different charset: `title*`. - title_star: Option, - - /// Hint on the media type of the result of dereferencing - /// the link: `type`. - media_type: Option, -} - -/// A Media Descriptors Enum based on: -/// [https://www.w3.org/TR/html401/types.html#h-6.13][url] -/// -/// [url]: https://www.w3.org/TR/html401/types.html#h-6.13 -#[derive(Clone, PartialEq, Debug)] -pub enum MediaDesc { - /// screen. - Screen, - /// tty. - Tty, - /// tv. - Tv, - /// projection. - Projection, - /// handheld. - Handheld, - /// print. - Print, - /// braille. - Braille, - /// aural. - Aural, - /// all. - All, - /// Unrecognized media descriptor extension. - Extension(String) -} - -/// A Link Relation Type Enum based on: -/// [RFC5988](https://tools.ietf.org/html/rfc5988#section-6.2.2) -#[derive(Clone, PartialEq, Debug)] -pub enum RelationType { - /// alternate. - Alternate, - /// appendix. - Appendix, - /// bookmark. - Bookmark, - /// chapter. - Chapter, - /// contents. - Contents, - /// copyright. - Copyright, - /// current. - Current, - /// describedby. - DescribedBy, - /// edit. - Edit, - /// edit-media. - EditMedia, - /// enclosure. - Enclosure, - /// first. - First, - /// glossary. - Glossary, - /// help. - Help, - /// hub. - Hub, - /// index. - Index, - /// last. - Last, - /// latest-version. - LatestVersion, - /// license. - License, - /// next. - Next, - /// next-archive. - NextArchive, - /// payment. - Payment, - /// prev. - Prev, - /// predecessor-version. - PredecessorVersion, - /// previous. - Previous, - /// prev-archive. - PrevArchive, - /// related. - Related, - /// replies. - Replies, - /// section. - Section, - /// self. - RelationTypeSelf, - /// service. - Service, - /// start. - Start, - /// stylesheet. - Stylesheet, - /// subsection. - Subsection, - /// successor-version. - SuccessorVersion, - /// up. - Up, - /// versionHistory. - VersionHistory, - /// via. - Via, - /// working-copy. - WorkingCopy, - /// working-copy-of. - WorkingCopyOf, - /// ext-rel-type. - ExtRelType(String) -} - -//////////////////////////////////////////////////////////////////////////////// -// Struct methods -//////////////////////////////////////////////////////////////////////////////// - -impl Link { - /// Create `Link` from a `Vec`. - pub fn new(link_values: Vec) -> Link { - Link { values: link_values } - } - - /// Get the `Link` header's `LinkValue`s. - pub fn values(&self) -> &[LinkValue] { - self.values.as_ref() - } - - /// Add a `LinkValue` instance to the `Link` header's values. - pub fn push_value(&mut self, link_value: LinkValue) { - self.values.push(link_value); - } -} - -impl LinkValue { - /// Create `LinkValue` from URI-Reference. - pub fn new(uri: T) -> LinkValue - where T: Into> { - LinkValue { - link: uri.into(), - rel: None, - anchor: None, - rev: None, - href_lang: None, - media_desc: None, - title: None, - title_star: None, - media_type: None, - } - } - - /// Get the `LinkValue`'s value. - pub fn link(&self) -> &str { - self.link.as_ref() - } - - /// Get the `LinkValue`'s `rel` parameter(s). - pub fn rel(&self) -> Option<&[RelationType]> { - self.rel.as_ref().map(AsRef::as_ref) - } - - /// Get the `LinkValue`'s `anchor` parameter. - pub fn anchor(&self) -> Option<&str> { - self.anchor.as_ref().map(AsRef::as_ref) - } - - /// Get the `LinkValue`'s `rev` parameter(s). - pub fn rev(&self) -> Option<&[RelationType]> { - self.rev.as_ref().map(AsRef::as_ref) - } - - /// Get the `LinkValue`'s `hreflang` parameter(s). - pub fn href_lang(&self) -> Option<&[LanguageTag]> { - self.href_lang.as_ref().map(AsRef::as_ref) - } - - /// Get the `LinkValue`'s `media` parameter(s). - pub fn media_desc(&self) -> Option<&[MediaDesc]> { - self.media_desc.as_ref().map(AsRef::as_ref) - } - - /// Get the `LinkValue`'s `title` parameter. - pub fn title(&self) -> Option<&str> { - self.title.as_ref().map(AsRef::as_ref) - } - - /// Get the `LinkValue`'s `title*` parameter. - pub fn title_star(&self) -> Option<&str> { - self.title_star.as_ref().map(AsRef::as_ref) - } - - /// Get the `LinkValue`'s `type` parameter. - pub fn media_type(&self) -> Option<&Mime> { - self.media_type.as_ref() - } - - /// Add a `RelationType` to the `LinkValue`'s `rel` parameter. - pub fn push_rel(mut self, rel: RelationType) -> LinkValue { - let mut v = self.rel.take().unwrap_or(Vec::new()); - - v.push(rel); - - self.rel = Some(v); - - self - } - - /// Set `LinkValue`'s `anchor` parameter. - pub fn set_anchor>(mut self, anchor: T) -> LinkValue { - self.anchor = Some(anchor.into()); - - self - } - - /// Add a `RelationType` to the `LinkValue`'s `rev` parameter. - pub fn push_rev(mut self, rev: RelationType) -> LinkValue { - let mut v = self.rev.take().unwrap_or(Vec::new()); - - v.push(rev); - - self.rev = Some(v); - - self - } - - /// Add a `LanguageTag` to the `LinkValue`'s `hreflang` parameter. - pub fn push_href_lang(mut self, language_tag: LanguageTag) -> LinkValue { - let mut v = self.href_lang.take().unwrap_or(Vec::new()); - - v.push(language_tag); - - self.href_lang = Some(v); - - self - } - - /// Add a `MediaDesc` to the `LinkValue`'s `media_desc` parameter. - pub fn push_media_desc(mut self, media_desc: MediaDesc) -> LinkValue { - let mut v = self.media_desc.take().unwrap_or(Vec::new()); - - v.push(media_desc); - - self.media_desc = Some(v); - - self - } - - /// Set `LinkValue`'s `title` parameter. - pub fn set_title>(mut self, title: T) -> LinkValue { - self.title = Some(title.into()); - - self - } - - /// Set `LinkValue`'s `title*` parameter. - pub fn set_title_star>(mut self, title_star: T) -> LinkValue { - self.title_star = Some(title_star.into()); - - self - } - - /// Set `LinkValue`'s `type` parameter. - pub fn set_media_type(mut self, media_type: Mime) -> LinkValue { - self.media_type = Some(media_type); - - self - } -} - -//////////////////////////////////////////////////////////////////////////////// -// Trait implementations -//////////////////////////////////////////////////////////////////////////////// - -impl Header for Link { - fn header_name() -> &'static str { - static NAME: &'static str = "Link"; - NAME - } - - fn parse_header(raw: &Raw) -> ::Result { - // If more that one `Link` headers are present in a request's - // headers they are combined in a single `Link` header containing - // all the `link-value`s present in each of those `Link` headers. - raw.iter() - .map(parsing::from_raw_str::) - .fold(None, |p, c| { - match (p, c) { - (None, c) => Some(c), - (e @ Some(Err(_)), _) => e, - (Some(Ok(mut p)), Ok(c)) => { - p.values.extend(c.values); - - Some(Ok(p)) - }, - _ => Some(Err(::Error::Header)), - } - }) - .unwrap_or(Err(::Error::Header)) - } - - fn fmt_header(&self, f: &mut ::Formatter) -> fmt::Result { - f.fmt_line(self) - } -} - -impl fmt::Display for Link { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - fmt_delimited(f, self.values.as_slice(), ", ", ("", "")) - } -} - -impl fmt::Display for LinkValue { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - try!(write!(f, "<{}>", self.link)); - - if let Some(ref rel) = self.rel { - try!(fmt_delimited(f, rel.as_slice(), " ", ("; rel=\"", "\""))); - } - if let Some(ref anchor) = self.anchor { - try!(write!(f, "; anchor=\"{}\"", anchor)); - } - if let Some(ref rev) = self.rev { - try!(fmt_delimited(f, rev.as_slice(), " ", ("; rev=\"", "\""))); - } - if let Some(ref href_lang) = self.href_lang { - for tag in href_lang { - try!(write!(f, "; hreflang={}", tag)); - } - } - if let Some(ref media_desc) = self.media_desc { - try!(fmt_delimited(f, media_desc.as_slice(), ", ", ("; media=\"", "\""))); - } - if let Some(ref title) = self.title { - try!(write!(f, "; title=\"{}\"", title)); - } - if let Some(ref title_star) = self.title_star { - try!(write!(f, "; title*={}", title_star)); - } - if let Some(ref media_type) = self.media_type { - try!(write!(f, "; type=\"{}\"", media_type)); - } - - Ok(()) - } -} - -impl FromStr for Link { - type Err = ::Error; - - fn from_str(s: &str) -> ::Result { - // Create a split iterator with delimiters: `;`, `,` - let link_split = SplitAsciiUnquoted::new(s, ";,"); - - let mut link_values: Vec = Vec::new(); - - // Loop over the splits parsing the Link header into - // a `Vec` - for segment in link_split { - // Parse the `Target IRI` - // https://tools.ietf.org/html/rfc5988#section-5.1 - if segment.trim().starts_with('<') { - link_values.push( - match verify_and_trim(segment.trim(), (b'<', b'>')) { - Err(_) => return Err(::Error::Header), - Ok(s) => { - LinkValue { - link: s.to_owned().into(), - rel: None, - anchor: None, - rev: None, - href_lang: None, - media_desc: None, - title: None, - title_star: None, - media_type: None, - } - }, - } - ); - } else { - // Parse the current link-value's parameters - let mut link_param_split = segment.splitn(2, '='); - - let link_param_name = match link_param_split.next() { - None => return Err(::Error::Header), - Some(p) => p.trim(), - }; - - let link_header = match link_values.last_mut() { - None => return Err(::Error::Header), - Some(l) => l, - }; - - if "rel".eq_ignore_ascii_case(link_param_name) { - // Parse relation type: `rel`. - // https://tools.ietf.org/html/rfc5988#section-5.3 - if link_header.rel.is_none() { - link_header.rel = match link_param_split.next() { - None | Some("") => return Err(::Error::Header), - Some(s) => { - s.trim_matches(|c: char| c == '"' || c.is_whitespace()) - .split(' ') - .map(|t| t.trim().parse()) - .collect::, _>>() - .or_else(|_| Err(::Error::Header)) - .ok() - }, - }; - } - } else if "anchor".eq_ignore_ascii_case(link_param_name) { - // Parse the `Context IRI`. - // https://tools.ietf.org/html/rfc5988#section-5.2 - link_header.anchor = match link_param_split.next() { - None | Some("") => return Err(::Error::Header), - Some(s) => match verify_and_trim(s.trim(), (b'"', b'"')) { - Err(_) => return Err(::Error::Header), - Ok(a) => Some(String::from(a)), - }, - }; - } else if "rev".eq_ignore_ascii_case(link_param_name) { - // Parse relation type: `rev`. - // https://tools.ietf.org/html/rfc5988#section-5.3 - if link_header.rev.is_none() { - link_header.rev = match link_param_split.next() { - None | Some("") => return Err(::Error::Header), - Some(s) => { - s.trim_matches(|c: char| c == '"' || c.is_whitespace()) - .split(' ') - .map(|t| t.trim().parse()) - .collect::, _>>() - .or_else(|_| Err(::Error::Header)) - .ok() - }, - } - } - } else if "hreflang".eq_ignore_ascii_case(link_param_name) { - // Parse target attribute: `hreflang`. - // https://tools.ietf.org/html/rfc5988#section-5.4 - let mut v = link_header.href_lang.take().unwrap_or(Vec::new()); - - v.push( - match link_param_split.next() { - None | Some("") => return Err(::Error::Header), - Some(s) => match s.trim().parse() { - Err(_) => return Err(::Error::Header), - Ok(t) => t, - }, - } - ); - - link_header.href_lang = Some(v); - } else if "media".eq_ignore_ascii_case(link_param_name) { - // Parse target attribute: `media`. - // https://tools.ietf.org/html/rfc5988#section-5.4 - if link_header.media_desc.is_none() { - link_header.media_desc = match link_param_split.next() { - None | Some("") => return Err(::Error::Header), - Some(s) => { - s.trim_matches(|c: char| c == '"' || c.is_whitespace()) - .split(',') - .map(|t| t.trim().parse()) - .collect::, _>>() - .or_else(|_| Err(::Error::Header)) - .ok() - }, - }; - } - } else if "title".eq_ignore_ascii_case(link_param_name) { - // Parse target attribute: `title`. - // https://tools.ietf.org/html/rfc5988#section-5.4 - if link_header.title.is_none() { - link_header.title = match link_param_split.next() { - None | Some("") => return Err(::Error::Header), - Some(s) => match verify_and_trim(s.trim(), (b'"', b'"')) { - Err(_) => return Err(::Error::Header), - Ok(t) => Some(String::from(t)), - }, - }; - } - } else if "title*".eq_ignore_ascii_case(link_param_name) { - // Parse target attribute: `title*`. - // https://tools.ietf.org/html/rfc5988#section-5.4 - // - // Definition of `ext-value`: - // https://tools.ietf.org/html/rfc5987#section-3.2.1 - if link_header.title_star.is_none() { - link_header.title_star = match link_param_split.next() { - None | Some("") => return Err(::Error::Header), - Some(s) => Some(String::from(s.trim())), - }; - } - } else if "type".eq_ignore_ascii_case(link_param_name) { - // Parse target attribute: `type`. - // https://tools.ietf.org/html/rfc5988#section-5.4 - if link_header.media_type.is_none() { - link_header.media_type = match link_param_split.next() { - None | Some("") => return Err(::Error::Header), - Some(s) => match verify_and_trim(s.trim(), (b'"', b'"')) { - Err(_) => return Err(::Error::Header), - Ok(t) => match t.parse() { - Err(_) => return Err(::Error::Header), - Ok(m) => Some(m), - }, - }, - - }; - } - } else { - return Err(::Error::Header); - } - } - } - - Ok(Link::new(link_values)) - } -} - -impl fmt::Display for MediaDesc { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match *self { - MediaDesc::Screen => write!(f, "screen"), - MediaDesc::Tty => write!(f, "tty"), - MediaDesc::Tv => write!(f, "tv"), - MediaDesc::Projection => write!(f, "projection"), - MediaDesc::Handheld => write!(f, "handheld"), - MediaDesc::Print => write!(f, "print"), - MediaDesc::Braille => write!(f, "braille"), - MediaDesc::Aural => write!(f, "aural"), - MediaDesc::All => write!(f, "all"), - MediaDesc::Extension(ref other) => write!(f, "{}", other), - } - } -} - -impl FromStr for MediaDesc { - type Err = ::Error; - - fn from_str(s: &str) -> ::Result { - match s { - "screen" => Ok(MediaDesc::Screen), - "tty" => Ok(MediaDesc::Tty), - "tv" => Ok(MediaDesc::Tv), - "projection" => Ok(MediaDesc::Projection), - "handheld" => Ok(MediaDesc::Handheld), - "print" => Ok(MediaDesc::Print), - "braille" => Ok(MediaDesc::Braille), - "aural" => Ok(MediaDesc::Aural), - "all" => Ok(MediaDesc::All), - _ => Ok(MediaDesc::Extension(String::from(s))), - } - } -} - -impl fmt::Display for RelationType { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match *self { - RelationType::Alternate => write!(f, "alternate"), - RelationType::Appendix => write!(f, "appendix"), - RelationType::Bookmark => write!(f, "bookmark"), - RelationType::Chapter => write!(f, "chapter"), - RelationType::Contents => write!(f, "contents"), - RelationType::Copyright => write!(f, "copyright"), - RelationType::Current => write!(f, "current"), - RelationType::DescribedBy => write!(f, "describedby"), - RelationType::Edit => write!(f, "edit"), - RelationType::EditMedia => write!(f, "edit-media"), - RelationType::Enclosure => write!(f, "enclosure"), - RelationType::First => write!(f, "first"), - RelationType::Glossary => write!(f, "glossary"), - RelationType::Help => write!(f, "help"), - RelationType::Hub => write!(f, "hub"), - RelationType::Index => write!(f, "index"), - RelationType::Last => write!(f, "last"), - RelationType::LatestVersion => write!(f, "latest-version"), - RelationType::License => write!(f, "license"), - RelationType::Next => write!(f, "next"), - RelationType::NextArchive => write!(f, "next-archive"), - RelationType::Payment => write!(f, "payment"), - RelationType::Prev => write!(f, "prev"), - RelationType::PredecessorVersion => write!(f, "predecessor-version"), - RelationType::Previous => write!(f, "previous"), - RelationType::PrevArchive => write!(f, "prev-archive"), - RelationType::Related => write!(f, "related"), - RelationType::Replies => write!(f, "replies"), - RelationType::Section => write!(f, "section"), - RelationType::RelationTypeSelf => write!(f, "self"), - RelationType::Service => write!(f, "service"), - RelationType::Start => write!(f, "start"), - RelationType::Stylesheet => write!(f, "stylesheet"), - RelationType::Subsection => write!(f, "subsection"), - RelationType::SuccessorVersion => write!(f, "successor-version"), - RelationType::Up => write!(f, "up"), - RelationType::VersionHistory => write!(f, "version-history"), - RelationType::Via => write!(f, "via"), - RelationType::WorkingCopy => write!(f, "working-copy"), - RelationType::WorkingCopyOf => write!(f, "working-copy-of"), - RelationType::ExtRelType(ref uri) => write!(f, "{}", uri), - } - } -} - -impl FromStr for RelationType { - type Err = ::Error; - - fn from_str(s: &str) -> ::Result { - if "alternate".eq_ignore_ascii_case(s) { - Ok(RelationType::Alternate) - } else if "appendix".eq_ignore_ascii_case(s) { - Ok(RelationType::Appendix) - } else if "bookmark".eq_ignore_ascii_case(s) { - Ok(RelationType::Bookmark) - } else if "chapter".eq_ignore_ascii_case(s) { - Ok(RelationType::Chapter) - } else if "contents".eq_ignore_ascii_case(s) { - Ok(RelationType::Contents) - } else if "copyright".eq_ignore_ascii_case(s) { - Ok(RelationType::Copyright) - } else if "current".eq_ignore_ascii_case(s) { - Ok(RelationType::Current) - } else if "describedby".eq_ignore_ascii_case(s) { - Ok(RelationType::DescribedBy) - } else if "edit".eq_ignore_ascii_case(s) { - Ok(RelationType::Edit) - } else if "edit-media".eq_ignore_ascii_case(s) { - Ok(RelationType::EditMedia) - } else if "enclosure".eq_ignore_ascii_case(s) { - Ok(RelationType::Enclosure) - } else if "first".eq_ignore_ascii_case(s) { - Ok(RelationType::First) - } else if "glossary".eq_ignore_ascii_case(s) { - Ok(RelationType::Glossary) - } else if "help".eq_ignore_ascii_case(s) { - Ok(RelationType::Help) - } else if "hub".eq_ignore_ascii_case(s) { - Ok(RelationType::Hub) - } else if "index".eq_ignore_ascii_case(s) { - Ok(RelationType::Index) - } else if "last".eq_ignore_ascii_case(s) { - Ok(RelationType::Last) - } else if "latest-version".eq_ignore_ascii_case(s) { - Ok(RelationType::LatestVersion) - } else if "license".eq_ignore_ascii_case(s) { - Ok(RelationType::License) - } else if "next".eq_ignore_ascii_case(s) { - Ok(RelationType::Next) - } else if "next-archive".eq_ignore_ascii_case(s) { - Ok(RelationType::NextArchive) - } else if "payment".eq_ignore_ascii_case(s) { - Ok(RelationType::Payment) - } else if "prev".eq_ignore_ascii_case(s) { - Ok(RelationType::Prev) - } else if "predecessor-version".eq_ignore_ascii_case(s) { - Ok(RelationType::PredecessorVersion) - } else if "previous".eq_ignore_ascii_case(s) { - Ok(RelationType::Previous) - } else if "prev-archive".eq_ignore_ascii_case(s) { - Ok(RelationType::PrevArchive) - } else if "related".eq_ignore_ascii_case(s) { - Ok(RelationType::Related) - } else if "replies".eq_ignore_ascii_case(s) { - Ok(RelationType::Replies) - } else if "section".eq_ignore_ascii_case(s) { - Ok(RelationType::Section) - } else if "self".eq_ignore_ascii_case(s) { - Ok(RelationType::RelationTypeSelf) - } else if "service".eq_ignore_ascii_case(s) { - Ok(RelationType::Service) - } else if "start".eq_ignore_ascii_case(s) { - Ok(RelationType::Start) - } else if "stylesheet".eq_ignore_ascii_case(s) { - Ok(RelationType::Stylesheet) - } else if "subsection".eq_ignore_ascii_case(s) { - Ok(RelationType::Subsection) - } else if "successor-version".eq_ignore_ascii_case(s) { - Ok(RelationType::SuccessorVersion) - } else if "up".eq_ignore_ascii_case(s) { - Ok(RelationType::Up) - } else if "version-history".eq_ignore_ascii_case(s) { - Ok(RelationType::VersionHistory) - } else if "via".eq_ignore_ascii_case(s) { - Ok(RelationType::Via) - } else if "working-copy".eq_ignore_ascii_case(s) { - Ok(RelationType::WorkingCopy) - } else if "working-copy-of".eq_ignore_ascii_case(s) { - Ok(RelationType::WorkingCopyOf) - } else { - Ok(RelationType::ExtRelType(String::from(s))) - } - } -} - -//////////////////////////////////////////////////////////////////////////////// -// Utilities -//////////////////////////////////////////////////////////////////////////////// - -struct SplitAsciiUnquoted<'a> { - src: &'a str, - pos: usize, - del: &'a str -} - -impl<'a> SplitAsciiUnquoted<'a> { - fn new(s: &'a str, d: &'a str) -> SplitAsciiUnquoted<'a> { - SplitAsciiUnquoted{ - src: s, - pos: 0, - del: d, - } - } -} - -impl<'a> Iterator for SplitAsciiUnquoted<'a> { - type Item = &'a str; - - fn next(&mut self) -> Option<&'a str> { - if self.pos < self.src.len() { - let prev_pos = self.pos; - let mut pos = self.pos; - - let mut in_quotes = false; - - for c in self.src[prev_pos..].as_bytes().iter() { - in_quotes ^= *c == b'"'; - - // Ignore `c` if we're `in_quotes`. - if !in_quotes && self.del.as_bytes().contains(c) { - break; - } - - pos += 1; - } - - self.pos = pos + 1; - - Some(&self.src[prev_pos..pos]) - } else { - None - } - } -} - -fn fmt_delimited(f: &mut fmt::Formatter, p: &[T], d: &str, b: (&str, &str)) -> fmt::Result { - if p.len() != 0 { - // Write a starting string `b.0` before the first element - try!(write!(f, "{}{}", b.0, p[0])); - - for i in &p[1..] { - // Write the next element preceded by the delimiter `d` - try!(write!(f, "{}{}", d, i)); - } - - // Write a ending string `b.1` before the first element - try!(write!(f, "{}", b.1)); - } - - Ok(()) -} - -fn verify_and_trim(s: &str, b: (u8, u8)) -> ::Result<&str> { - let length = s.len(); - let byte_array = s.as_bytes(); - - // Verify that `s` starts with `b.0` and ends with `b.1` and return - // the contained substring after trimming whitespace. - if length > 1 && b.0 == byte_array[0] && b.1 == byte_array[length - 1] { - Ok(s.trim_matches( - |c: char| c == b.0 as char || c == b.1 as char || c.is_whitespace()) - ) - } else { - Err(::Error::Header) - } -} - -//////////////////////////////////////////////////////////////////////////////// -// Tests -//////////////////////////////////////////////////////////////////////////////// - -#[cfg(test)] -mod tests { - use std::fmt; - use std::fmt::Write; - - use super::{Link, LinkValue, MediaDesc, RelationType, SplitAsciiUnquoted}; - use super::{fmt_delimited, verify_and_trim}; - - use Header; - - // use proto::ServerTransaction; - use bytes::BytesMut; - - use mime; - - #[test] - fn test_link() { - let link_value = LinkValue::new("http://example.com/TheBook/chapter2") - .push_rel(RelationType::Previous) - .push_rev(RelationType::Next) - .set_title("previous chapter"); - - let link_header = b"; \ - rel=\"previous\"; rev=next; title=\"previous chapter\""; - - let expected_link = Link::new(vec![link_value]); - - let link = Header::parse_header(&vec![link_header.to_vec()].into()); - assert_eq!(link.ok(), Some(expected_link)); - } - - #[test] - fn test_link_multiple_values() { - let first_link = LinkValue::new("/TheBook/chapter2") - .push_rel(RelationType::Previous) - .set_title_star("UTF-8'de'letztes%20Kapitel"); - - let second_link = LinkValue::new("/TheBook/chapter4") - .push_rel(RelationType::Next) - .set_title_star("UTF-8'de'n%c3%a4chstes%20Kapitel"); - - let link_header = b"; \ - rel=\"previous\"; title*=UTF-8'de'letztes%20Kapitel, \ - ; \ - rel=\"next\"; title*=UTF-8'de'n%c3%a4chstes%20Kapitel"; - - let expected_link = Link::new(vec![first_link, second_link]); - - let link = Header::parse_header(&vec![link_header.to_vec()].into()); - assert_eq!(link.ok(), Some(expected_link)); - } - - #[test] - fn test_link_all_attributes() { - let link_value = LinkValue::new("http://example.com/TheBook/chapter2") - .push_rel(RelationType::Previous) - .set_anchor("../anchor/example/") - .push_rev(RelationType::Next) - .push_href_lang("de".parse().unwrap()) - .push_media_desc(MediaDesc::Screen) - .set_title("previous chapter") - .set_title_star("title* unparsed") - .set_media_type(mime::TEXT_PLAIN); - - let link_header = b"; \ - rel=\"previous\"; anchor=\"../anchor/example/\"; \ - rev=\"next\"; hreflang=de; media=\"screen\"; \ - title=\"previous chapter\"; title*=title* unparsed; \ - type=\"text/plain\""; - - let expected_link = Link::new(vec![link_value]); - - let link = Header::parse_header(&vec![link_header.to_vec()].into()); - assert_eq!(link.ok(), Some(expected_link)); - } - - // TODO - // #[test] - // fn test_link_multiple_link_headers() { - // let first_link = LinkValue::new("/TheBook/chapter2") - // .push_rel(RelationType::Previous) - // .set_title_star("UTF-8'de'letztes%20Kapitel"); - - // let second_link = LinkValue::new("/TheBook/chapter4") - // .push_rel(RelationType::Next) - // .set_title_star("UTF-8'de'n%c3%a4chstes%20Kapitel"); - - // let third_link = LinkValue::new("http://example.com/TheBook/chapter2") - // .push_rel(RelationType::Previous) - // .push_rev(RelationType::Next) - // .set_title("previous chapter"); - - // let expected_link = Link::new(vec![first_link, second_link, third_link]); - - // let mut raw = BytesMut::from(b"GET /super_short_uri/and_whatever HTTP/1.1\r\nHost: \ - // hyper.rs\r\nAccept: a lot of things\r\nAccept-Charset: \ - // utf8\r\nAccept-Encoding: *\r\nLink: ; \ - // rel=\"previous\"; title*=UTF-8'de'letztes%20Kapitel, \ - // ; rel=\"next\"; title*=\ - // UTF-8'de'n%c3%a4chstes%20Kapitel\r\n\ - // Access-Control-Allow-Credentials: None\r\nLink: \ - // ; \ - // rel=\"previous\"; rev=next; title=\"previous chapter\"\ - // \r\n\r\n".to_vec()); - - // let (mut res, _) = ServerTransaction::parse(&mut raw).unwrap().unwrap(); - - // let link = res.headers.remove::().unwrap(); - - // assert_eq!(link, expected_link); - // } - - #[test] - fn test_link_display() { - let link_value = LinkValue::new("http://example.com/TheBook/chapter2") - .push_rel(RelationType::Previous) - .set_anchor("/anchor/example/") - .push_rev(RelationType::Next) - .push_href_lang("de".parse().unwrap()) - .push_media_desc(MediaDesc::Screen) - .set_title("previous chapter") - .set_title_star("title* unparsed") - .set_media_type(mime::TEXT_PLAIN); - - let link = Link::new(vec![link_value]); - - let mut link_header = String::new(); - write!(&mut link_header, "{}", link).unwrap(); - - let expected_link_header = "; \ - rel=\"previous\"; anchor=\"/anchor/example/\"; \ - rev=\"next\"; hreflang=de; media=\"screen\"; \ - title=\"previous chapter\"; title*=title* unparsed; \ - type=\"text/plain\""; - - assert_eq!(link_header, expected_link_header); - } - - #[test] - fn test_link_parsing_errors() { - let link_a = b"http://example.com/TheBook/chapter2; \ - rel=\"previous\"; rev=next; title=\"previous chapter\""; - - let mut err: Result = Header::parse_header(&vec![link_a.to_vec()].into()); - assert_eq!(err.is_err(), true); - - let link_b = b"; \ - =\"previous\"; rev=next; title=\"previous chapter\""; - - err = Header::parse_header(&vec![link_b.to_vec()].into()); - assert_eq!(err.is_err(), true); - - let link_c = b"; \ - rel=; rev=next; title=\"previous chapter\""; - - err = Header::parse_header(&vec![link_c.to_vec()].into()); - assert_eq!(err.is_err(), true); - - let link_d = b"; \ - rel=\"previous\"; rev=next; title="; - - err = Header::parse_header(&vec![link_d.to_vec()].into()); - assert_eq!(err.is_err(), true); - - let link_e = b"; \ - rel=\"previous\"; rev=next; attr=unknown"; - - err = Header::parse_header(&vec![link_e.to_vec()].into()); - assert_eq!(err.is_err(), true); - } - - #[test] - fn test_link_split_ascii_unquoted_iterator() { - let string = "some, text; \"and, more; in quotes\", or not"; - let mut string_split = SplitAsciiUnquoted::new(string, ";,"); - - assert_eq!(Some("some"), string_split.next()); - assert_eq!(Some(" text"), string_split.next()); - assert_eq!(Some(" \"and, more; in quotes\""), string_split.next()); - assert_eq!(Some(" or not"), string_split.next()); - assert_eq!(None, string_split.next()); - } - - #[test] - fn test_link_fmt_delimited() { - struct TestFormatterStruct<'a> { v: Vec<&'a str> }; - - impl<'a> fmt::Display for TestFormatterStruct<'a> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - fmt_delimited(f, self.v.as_slice(), ", ", (">>", "<<")) - } - } - - let test_formatter = TestFormatterStruct { v: vec!["first", "second"] }; - - let mut string = String::new(); - write!(&mut string, "{}", test_formatter).unwrap(); - - let expected_string = ">>first, second<<"; - - assert_eq!(string, expected_string); - } - - #[test] - fn test_link_verify_and_trim() { - let string = verify_and_trim("> some string <", (b'>', b'<')); - assert_eq!(string.ok(), Some("some string")); - - let err = verify_and_trim(" > some string <", (b'>', b'<')); - assert_eq!(err.is_err(), true); - } -} - -bench_header!(bench_link, Link, { vec![b"; rel=\"previous\"; rev=next; title=\"previous chapter\"; type=\"text/html\"; media=\"screen, tty\"".to_vec()] }); diff --git a/src/disabled/prefer.rs b/src/disabled/prefer.rs deleted file mode 100644 index a6e4dc37..00000000 --- a/src/disabled/prefer.rs +++ /dev/null @@ -1,210 +0,0 @@ -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); - -__hyper__deref!(Prefer => Vec); - -impl Header for Prefer { - fn header_name() -> &'static str { - static NAME: &'static str = "Prefer"; - NAME - } - - fn parse_header(raw: &Raw) -> ::Result { - 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<::Err>; - fn from_str(s: &str) -> Result::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 = 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/src/disabled/preference_applied.rs b/src/disabled/preference_applied.rs deleted file mode 100644 index b368bc9b..00000000 --- a/src/disabled/preference_applied.rs +++ /dev/null @@ -1,110 +0,0 @@ -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); - -__hyper__deref!(PreferenceApplied => Vec); - -impl Header for PreferenceApplied { - fn header_name() -> &'static str { - static NAME: &'static str = "Preference-Applied"; - NAME - } - - fn parse_header(raw: &Raw) -> ::Result { - 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/src/disabled/util/charset.rs b/src/disabled/util/charset.rs deleted file mode 100644 index 5a3f4628..00000000 --- a/src/disabled/util/charset.rs +++ /dev/null @@ -1,229 +0,0 @@ -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 { - 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(::().is_err()); -} - -#[test] -fn test_display() { - assert_eq!("US-ASCII", format!("{}", Charset::US_ASCII)); -} diff --git a/src/disabled/util/encoding.rs b/src/disabled/util/encoding.rs deleted file mode 100644 index fc972dd3..00000000 --- a/src/disabled/util/encoding.rs +++ /dev/null @@ -1,57 +0,0 @@ -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 { - 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/src/disabled/util/extended_value.rs b/src/disabled/util/extended_value.rs deleted file mode 100644 index 0098f627..00000000 --- a/src/disabled/util/extended_value.rs +++ /dev/null @@ -1,192 +0,0 @@ -/// 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, - /// The parameter value, as expressed in octets. - pub value: Vec, -} - -/// 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 -/// ; (see [RFC2231], Section 7) -/// -/// charset = "UTF-8" / "ISO-8859-1" / mime-charset -/// -/// mime-charset = 1*mime-charsetc -/// mime-charsetc = ALPHA / DIGIT -/// / "!" / "#" / "$" / "%" / "&" -/// / "+" / "-" / "^" / "_" / "`" -/// / "{" / "}" / "~" -/// ; as in Section 2.3 of [RFC2978] -/// ; except that the single quote is not included -/// ; SHOULD be registered in the IANA charset registry -/// -/// language = -/// -/// 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 { - - // 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 = 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 = 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::().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/src/disabled/util/quality_value.rs b/src/disabled/util/quality_value.rs deleted file mode 100644 index bcc79728..00000000 --- a/src/disabled/util/quality_value.rs +++ /dev/null @@ -1,268 +0,0 @@ -#[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 { - /// The actual contents of the field. - value: T, - /// The quality (client or server preference) for the value. - quality: Quality, -} - -impl QualityValue { - /// Creates a new `QualityValue` from an item and a quality. - pub fn new(value: T, quality: Quality) -> QualityValue { - 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(mut self, q: Q) -> QualityValue { - self.quality = q.into_quality(); - self - } - */ -} - -impl From for QualityValue { - fn from(value: T) -> QualityValue { - QualityValue { - value, - quality: Quality::default(), - } - } -} - -impl cmp::PartialOrd for QualityValue { - fn partial_cmp(&self, other: &QualityValue) -> Option { - self.quality.partial_cmp(&other.quality) - } -} - -impl fmt::Display for QualityValue { - 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 str::FromStr for QualityValue { - type Err = ::Error; - fn from_str(s: &str) -> ::Result> { - // 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::() { - 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::() { - // 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(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 = "chunked".parse().unwrap(); - assert_eq!(x, QualityValue { value: "chunked".to_owned(), quality: Quality(1000), }); - } - #[test] - fn test_quality_item_from_str2() { - let x: QualityValue = "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 = "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 = "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::>().is_err()); - } - - #[test] - fn test_quality_item_from_str6() { - assert!("gzip; q=2".parse::>().is_err()); - } - #[test] - fn test_quality_item_ordering() { - let x: QualityValue = "gzip; q=0.5".parse().unwrap(); - let y: QualityValue = "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::>().is_err()); - assert!("\x0d;;;=\u{d6aa}==".parse::>().is_ok()) - } -} diff --git a/src/disabled/warning.rs b/src/disabled/warning.rs deleted file mode 100644 index 92272015..00000000 --- a/src/disabled/warning.rs +++ /dev/null @@ -1,182 +0,0 @@ -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::().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 -} - -impl Header for Warning { - fn header_name() -> &'static str { - static NAME: &'static str = "Warning"; - NAME - } - - fn parse_header(raw: &Raw) -> ::Result { - 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 { - let mut warning_split = s.split_whitespace(); - let code = match warning_split.next() { - Some(c) => match c.parse::() { - 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::().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::().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::().ok() - })); - } -}