diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
commit | 26a029d407be480d791972afb5975cf62c9360a6 (patch) | |
tree | f435a8308119effd964b339f76abb83a57c29483 /third_party/rust/headers/src | |
parent | Initial commit. (diff) | |
download | firefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz firefox-26a029d407be480d791972afb5975cf62c9360a6.zip |
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/rust/headers/src')
78 files changed, 10181 insertions, 0 deletions
diff --git a/third_party/rust/headers/src/common/accept_ranges.rs b/third_party/rust/headers/src/common/accept_ranges.rs new file mode 100644 index 0000000000..cfea449982 --- /dev/null +++ b/third_party/rust/headers/src/common/accept_ranges.rs @@ -0,0 +1,42 @@ +use util::FlatCsv; + +/// `Accept-Ranges` header, defined in [RFC7233](http://tools.ietf.org/html/rfc7233#section-2.3) +/// +/// The `Accept-Ranges` header field allows a server to indicate that it +/// supports range requests for the target resource. +/// +/// # ABNF +/// +/// ```text +/// Accept-Ranges = acceptable-ranges +/// acceptable-ranges = 1#range-unit / \"none\" +/// +/// # Example values +/// * `bytes` +/// * `none` +/// * `unknown-unit` +/// ``` +/// +/// # Examples +/// +/// ``` +/// use headers::{AcceptRanges, HeaderMap, HeaderMapExt}; +/// +/// let mut headers = HeaderMap::new(); +/// +/// headers.typed_insert(AcceptRanges::bytes()); +/// ``` +#[derive(Clone, Debug, PartialEq)] +pub struct AcceptRanges(FlatCsv); + +derive_header! { + AcceptRanges(_), + name: ACCEPT_RANGES +} + +impl AcceptRanges { + /// A constructor to easily create the common `Accept-Ranges: bytes` header. + pub fn bytes() -> Self { + AcceptRanges(::HeaderValue::from_static("bytes").into()) + } +} diff --git a/third_party/rust/headers/src/common/access_control_allow_credentials.rs b/third_party/rust/headers/src/common/access_control_allow_credentials.rs new file mode 100644 index 0000000000..583f310aff --- /dev/null +++ b/third_party/rust/headers/src/common/access_control_allow_credentials.rs @@ -0,0 +1,72 @@ +use {Header, HeaderName, HeaderValue}; + +/// `Access-Control-Allow-Credentials` header, part of +/// [CORS](http://www.w3.org/TR/cors/#access-control-allow-headers-response-header) +/// +/// > The Access-Control-Allow-Credentials HTTP response header indicates whether the +/// > response to request can be exposed when the credentials flag is true. When part +/// > of the response to an preflight request it indicates that the actual request can +/// > be made with credentials. The Access-Control-Allow-Credentials HTTP header must +/// > match the following ABNF: +/// +/// # ABNF +/// +/// ```text +/// Access-Control-Allow-Credentials: "Access-Control-Allow-Credentials" ":" "true" +/// ``` +/// +/// Since there is only one acceptable field value, the header struct does not accept +/// any values at all. Setting an empty `AccessControlAllowCredentials` header is +/// sufficient. See the examples below. +/// +/// # Example values +/// * "true" +/// +/// # Examples +/// +/// ``` +/// # extern crate headers; +/// use headers::AccessControlAllowCredentials; +/// +/// let allow_creds = AccessControlAllowCredentials; +/// ``` +#[derive(Clone, PartialEq, Eq, Debug)] +pub struct AccessControlAllowCredentials; + +impl Header for AccessControlAllowCredentials { + fn name() -> &'static HeaderName { + &::http::header::ACCESS_CONTROL_ALLOW_CREDENTIALS + } + + fn decode<'i, I: Iterator<Item = &'i HeaderValue>>(values: &mut I) -> Result<Self, ::Error> { + values + .next() + .and_then(|value| { + if value == "true" { + Some(AccessControlAllowCredentials) + } else { + None + } + }) + .ok_or_else(::Error::invalid) + } + + fn encode<E: Extend<::HeaderValue>>(&self, values: &mut E) { + values.extend(::std::iter::once(HeaderValue::from_static("true"))); + } +} + +#[cfg(test)] +mod tests { + use super::super::test_decode; + use super::*; + + #[test] + fn allow_credentials_is_case_sensitive() { + let allow_header = test_decode::<AccessControlAllowCredentials>(&["true"]); + assert!(allow_header.is_some()); + + let allow_header = test_decode::<AccessControlAllowCredentials>(&["True"]); + assert!(allow_header.is_none()); + } +} diff --git a/third_party/rust/headers/src/common/access_control_allow_headers.rs b/third_party/rust/headers/src/common/access_control_allow_headers.rs new file mode 100644 index 0000000000..ca3d100d3b --- /dev/null +++ b/third_party/rust/headers/src/common/access_control_allow_headers.rs @@ -0,0 +1,98 @@ +use std::iter::FromIterator; + +use util::FlatCsv; +use {HeaderName, HeaderValue}; + +/// `Access-Control-Allow-Headers` header, part of +/// [CORS](http://www.w3.org/TR/cors/#access-control-allow-headers-response-header) +/// +/// The `Access-Control-Allow-Headers` header indicates, as part of the +/// response to a preflight request, which header field names can be used +/// during the actual request. +/// +/// # ABNF +/// +/// ```text +/// Access-Control-Allow-Headers: "Access-Control-Allow-Headers" ":" #field-name +/// ``` +/// +/// # Example values +/// * `accept-language, date` +/// +/// # Examples +/// +/// ``` +/// # extern crate headers; +/// extern crate http; +/// use http::header::{CACHE_CONTROL, CONTENT_TYPE}; +/// use headers::AccessControlAllowHeaders; +/// +/// let allow_headers = vec![CACHE_CONTROL, CONTENT_TYPE] +/// .into_iter() +/// .collect::<AccessControlAllowHeaders>(); +/// ``` +#[derive(Clone, Debug, PartialEq)] +pub struct AccessControlAllowHeaders(FlatCsv); + +derive_header! { + AccessControlAllowHeaders(_), + name: ACCESS_CONTROL_ALLOW_HEADERS +} + +impl AccessControlAllowHeaders { + /// Returns an iterator over `HeaderName`s contained within. + pub fn iter<'a>(&'a self) -> impl Iterator<Item = HeaderName> + 'a { + self.0 + .iter() + .map(|s| s.parse().ok()) + .take_while(|val| val.is_some()) + .filter_map(|val| val) + } +} + +impl FromIterator<HeaderName> for AccessControlAllowHeaders { + fn from_iter<I>(iter: I) -> Self + where + I: IntoIterator<Item = HeaderName>, + { + let flat = iter.into_iter().map(HeaderValue::from).collect(); + AccessControlAllowHeaders(flat) + } +} + +#[cfg(test)] +mod tests { + use super::super::{test_decode, test_encode}; + use super::*; + + #[test] + fn iter() { + let allow_headers = test_decode::<AccessControlAllowHeaders>(&["foo, bar"]).unwrap(); + + let as_vec = allow_headers.iter().collect::<Vec<_>>(); + assert_eq!(as_vec.len(), 2); + assert_eq!(as_vec[0], "foo"); + assert_eq!(as_vec[1], "bar"); + } + + #[test] + fn from_iter() { + let allow: AccessControlAllowHeaders = + vec![::http::header::CACHE_CONTROL, ::http::header::IF_RANGE] + .into_iter() + .collect(); + + let headers = test_encode(allow); + assert_eq!( + headers["access-control-allow-headers"], + "cache-control, if-range" + ); + } + + #[test] + fn test_with_invalid() { + let allow_headers = test_decode::<AccessControlAllowHeaders>(&["foo foo, bar"]).unwrap(); + + assert!(allow_headers.iter().collect::<Vec<_>>().is_empty()); + } +} diff --git a/third_party/rust/headers/src/common/access_control_allow_methods.rs b/third_party/rust/headers/src/common/access_control_allow_methods.rs new file mode 100644 index 0000000000..0fd1438c38 --- /dev/null +++ b/third_party/rust/headers/src/common/access_control_allow_methods.rs @@ -0,0 +1,91 @@ +use std::iter::FromIterator; + +use http::Method; + +use util::FlatCsv; + +/// `Access-Control-Allow-Methods` header, part of +/// [CORS](http://www.w3.org/TR/cors/#access-control-allow-methods-response-header) +/// +/// The `Access-Control-Allow-Methods` header indicates, as part of the +/// response to a preflight request, which methods can be used during the +/// actual request. +/// +/// # ABNF +/// +/// ```text +/// Access-Control-Allow-Methods: "Access-Control-Allow-Methods" ":" #Method +/// ``` +/// +/// # Example values +/// * `PUT, DELETE, XMODIFY` +/// +/// # Examples +/// +/// ``` +/// # extern crate headers; +/// extern crate http; +/// use http::Method; +/// use headers::AccessControlAllowMethods; +/// +/// let allow_methods = vec![Method::GET, Method::PUT] +/// .into_iter() +/// .collect::<AccessControlAllowMethods>(); +/// ``` +#[derive(Clone, Debug, PartialEq)] +pub struct AccessControlAllowMethods(FlatCsv); + +derive_header! { + AccessControlAllowMethods(_), + name: ACCESS_CONTROL_ALLOW_METHODS +} + +impl AccessControlAllowMethods { + /// Returns an iterator over `Method`s contained within. + pub fn iter<'a>(&'a self) -> impl Iterator<Item = Method> + 'a { + self.0.iter().filter_map(|s| s.parse().ok()) + } +} + +impl FromIterator<Method> for AccessControlAllowMethods { + fn from_iter<I>(iter: I) -> Self + where + I: IntoIterator<Item = Method>, + { + let methods = iter + .into_iter() + .map(|method| { + method + .as_str() + .parse::<::HeaderValue>() + .expect("Method is a valid HeaderValue") + }) + .collect(); + + AccessControlAllowMethods(methods) + } +} + +#[cfg(test)] +mod tests { + use super::super::{test_decode, test_encode}; + use super::*; + + #[test] + fn iter() { + let allowed = test_decode::<AccessControlAllowMethods>(&["GET, PUT"]).unwrap(); + + let as_vec = allowed.iter().collect::<Vec<_>>(); + assert_eq!(as_vec.len(), 2); + assert_eq!(as_vec[0], Method::GET); + assert_eq!(as_vec[1], Method::PUT); + } + + #[test] + fn from_iter() { + let allow: AccessControlAllowMethods = vec![Method::GET, Method::PUT].into_iter().collect(); + + let headers = test_encode(allow); + assert_eq!(headers["access-control-allow-methods"], "GET, PUT"); + } +} diff --git a/third_party/rust/headers/src/common/access_control_allow_origin.rs b/third_party/rust/headers/src/common/access_control_allow_origin.rs new file mode 100644 index 0000000000..8d38bf17b8 --- /dev/null +++ b/third_party/rust/headers/src/common/access_control_allow_origin.rs @@ -0,0 +1,169 @@ +use std::convert::TryFrom; + +use super::origin::Origin; +use util::{IterExt, TryFromValues}; +use HeaderValue; + +/// The `Access-Control-Allow-Origin` response header, +/// part of [CORS](http://www.w3.org/TR/cors/#access-control-allow-origin-response-header) +/// +/// The `Access-Control-Allow-Origin` header indicates whether a resource +/// can be shared based by returning the value of the Origin request header, +/// `*`, or `null` in the response. +/// +/// ## ABNF +/// +/// ```text +/// Access-Control-Allow-Origin = "Access-Control-Allow-Origin" ":" origin-list-or-null | "*" +/// ``` +/// +/// ## Example values +/// * `null` +/// * `*` +/// * `http://google.com/` +/// +/// # Examples +/// +/// ``` +/// # extern crate headers; +/// use headers::AccessControlAllowOrigin; +/// use std::convert::TryFrom; +/// +/// let any_origin = AccessControlAllowOrigin::ANY; +/// let null_origin = AccessControlAllowOrigin::NULL; +/// let origin = AccessControlAllowOrigin::try_from("http://web-platform.test:8000"); +/// ``` +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub struct AccessControlAllowOrigin(OriginOrAny); + +derive_header! { + AccessControlAllowOrigin(_), + name: ACCESS_CONTROL_ALLOW_ORIGIN +} + +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +enum OriginOrAny { + Origin(Origin), + /// Allow all origins + Any, +} + +impl AccessControlAllowOrigin { + /// `Access-Control-Allow-Origin: *` + pub const ANY: AccessControlAllowOrigin = AccessControlAllowOrigin(OriginOrAny::Any); + /// `Access-Control-Allow-Origin: null` + pub const NULL: AccessControlAllowOrigin = + AccessControlAllowOrigin(OriginOrAny::Origin(Origin::NULL)); + + /// Returns the origin if there's one specified. + pub fn origin(&self) -> Option<&Origin> { + match self.0 { + OriginOrAny::Origin(ref origin) => Some(origin), + _ => None, + } + } +} + +impl TryFrom<&str> for AccessControlAllowOrigin { + type Error = ::Error; + + fn try_from(s: &str) -> Result<Self, ::Error> { + let header_value = HeaderValue::from_str(s).map_err(|_| ::Error::invalid())?; + let origin = OriginOrAny::try_from(&header_value)?; + Ok(Self(origin)) + } +} + +impl TryFrom<&HeaderValue> for OriginOrAny { + type Error = ::Error; + + fn try_from(header_value: &HeaderValue) -> Result<Self, ::Error> { + Origin::try_from_value(header_value) + .map(OriginOrAny::Origin) + .ok_or_else(::Error::invalid) + } +} + +impl TryFromValues for OriginOrAny { + fn try_from_values<'i, I>(values: &mut I) -> Result<Self, ::Error> + where + I: Iterator<Item = &'i HeaderValue>, + { + values + .just_one() + .and_then(|value| { + if value == "*" { + return Some(OriginOrAny::Any); + } + + Origin::try_from_value(value).map(OriginOrAny::Origin) + }) + .ok_or_else(::Error::invalid) + } +} + +impl<'a> From<&'a OriginOrAny> for HeaderValue { + fn from(origin: &'a OriginOrAny) -> HeaderValue { + match origin { + OriginOrAny::Origin(ref origin) => origin.into_value(), + OriginOrAny::Any => HeaderValue::from_static("*"), + } + } +} + +#[cfg(test)] +mod tests { + + use super::super::{test_decode, test_encode}; + use super::*; + + #[test] + fn origin() { + let s = "http://web-platform.test:8000"; + + let allow_origin = test_decode::<AccessControlAllowOrigin>(&[s]).unwrap(); + { + let origin = allow_origin.origin().unwrap(); + assert_eq!(origin.scheme(), "http"); + assert_eq!(origin.hostname(), "web-platform.test"); + assert_eq!(origin.port(), Some(8000)); + } + + let headers = test_encode(allow_origin); + assert_eq!(headers["access-control-allow-origin"], s); + } + + #[test] + fn try_from_origin() { + let s = "http://web-platform.test:8000"; + + let allow_origin = AccessControlAllowOrigin::try_from(s).unwrap(); + { + let origin = allow_origin.origin().unwrap(); + assert_eq!(origin.scheme(), "http"); + assert_eq!(origin.hostname(), "web-platform.test"); + assert_eq!(origin.port(), Some(8000)); + } + + let headers = test_encode(allow_origin); + assert_eq!(headers["access-control-allow-origin"], s); + } + + #[test] + fn any() { + let allow_origin = test_decode::<AccessControlAllowOrigin>(&["*"]).unwrap(); + assert_eq!(allow_origin, AccessControlAllowOrigin::ANY); + + let headers = test_encode(allow_origin); + assert_eq!(headers["access-control-allow-origin"], "*"); + } + + #[test] + fn null() { + let allow_origin = test_decode::<AccessControlAllowOrigin>(&["null"]).unwrap(); + assert_eq!(allow_origin, AccessControlAllowOrigin::NULL); + + let headers = test_encode(allow_origin); + assert_eq!(headers["access-control-allow-origin"], "null"); + } +} diff --git a/third_party/rust/headers/src/common/access_control_expose_headers.rs b/third_party/rust/headers/src/common/access_control_expose_headers.rs new file mode 100644 index 0000000000..a9b7a50f98 --- /dev/null +++ b/third_party/rust/headers/src/common/access_control_expose_headers.rs @@ -0,0 +1,88 @@ +use std::iter::FromIterator; + +use util::FlatCsv; +use {HeaderName, HeaderValue}; + +/// `Access-Control-Expose-Headers` header, part of +/// [CORS](http://www.w3.org/TR/cors/#access-control-expose-headers-response-header) +/// +/// The Access-Control-Expose-Headers header indicates which headers are safe to expose to the +/// API of a CORS API specification. +/// +/// # ABNF +/// +/// ```text +/// Access-Control-Expose-Headers = "Access-Control-Expose-Headers" ":" #field-name +/// ``` +/// +/// # Example values +/// * `ETag, Content-Length` +/// +/// # Examples +/// +/// ``` +/// # extern crate headers; +/// extern crate http; +/// # fn main() { +/// use http::header::{CONTENT_LENGTH, ETAG}; +/// use headers::AccessControlExposeHeaders; +/// +/// let expose = vec![CONTENT_LENGTH, ETAG] +/// .into_iter() +/// .collect::<AccessControlExposeHeaders>(); +/// # } +/// ``` +#[derive(Clone, Debug)] +pub struct AccessControlExposeHeaders(FlatCsv); + +derive_header! { + AccessControlExposeHeaders(_), + name: ACCESS_CONTROL_EXPOSE_HEADERS +} + +impl AccessControlExposeHeaders { + /// Returns an iterator over `HeaderName`s contained within. + pub fn iter<'a>(&'a self) -> impl Iterator<Item = HeaderName> + 'a { + self.0.iter().filter_map(|s| s.parse().ok()) + } +} + +impl FromIterator<HeaderName> for AccessControlExposeHeaders { + fn from_iter<I>(iter: I) -> Self + where + I: IntoIterator<Item = HeaderName>, + { + let flat = iter.into_iter().map(HeaderValue::from).collect(); + AccessControlExposeHeaders(flat) + } +} + +#[cfg(test)] +mod tests { + use super::super::{test_decode, test_encode}; + use super::*; + + #[test] + fn iter() { + let expose_headers = test_decode::<AccessControlExposeHeaders>(&["foo, bar"]).unwrap(); + + let as_vec = expose_headers.iter().collect::<Vec<_>>(); + assert_eq!(as_vec.len(), 2); + assert_eq!(as_vec[0], "foo"); + assert_eq!(as_vec[1], "bar"); + } + + #[test] + fn from_iter() { + let expose: AccessControlExposeHeaders = + vec![::http::header::CACHE_CONTROL, ::http::header::IF_RANGE] + .into_iter() + .collect(); + + let headers = test_encode(expose); + assert_eq!( + headers["access-control-expose-headers"], + "cache-control, if-range" + ); + } +} diff --git a/third_party/rust/headers/src/common/access_control_max_age.rs b/third_party/rust/headers/src/common/access_control_max_age.rs new file mode 100644 index 0000000000..b1bd0c35d6 --- /dev/null +++ b/third_party/rust/headers/src/common/access_control_max_age.rs @@ -0,0 +1,48 @@ +use std::time::Duration; + +use util::Seconds; + +/// `Access-Control-Max-Age` header, part of +/// [CORS](http://www.w3.org/TR/cors/#access-control-max-age-response-header) +/// +/// The `Access-Control-Max-Age` header indicates how long the results of a +/// preflight request can be cached in a preflight result cache. +/// +/// # ABNF +/// +/// ```text +/// Access-Control-Max-Age = \"Access-Control-Max-Age\" \":\" delta-seconds +/// ``` +/// +/// # Example values +/// +/// * `531` +/// +/// # Examples +/// +/// ``` +/// # extern crate headers; +/// use std::time::Duration; +/// use headers::AccessControlMaxAge; +/// +/// let max_age = AccessControlMaxAge::from(Duration::from_secs(531)); +/// ``` +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct AccessControlMaxAge(Seconds); + +derive_header! { + AccessControlMaxAge(_), + name: ACCESS_CONTROL_MAX_AGE +} + +impl From<Duration> for AccessControlMaxAge { + fn from(dur: Duration) -> AccessControlMaxAge { + AccessControlMaxAge(dur.into()) + } +} + +impl From<AccessControlMaxAge> for Duration { + fn from(acma: AccessControlMaxAge) -> Duration { + acma.0.into() + } +} diff --git a/third_party/rust/headers/src/common/access_control_request_headers.rs b/third_party/rust/headers/src/common/access_control_request_headers.rs new file mode 100644 index 0000000000..5628d9d596 --- /dev/null +++ b/third_party/rust/headers/src/common/access_control_request_headers.rs @@ -0,0 +1,89 @@ +use std::iter::FromIterator; + +use util::FlatCsv; +use {HeaderName, HeaderValue}; + +/// `Access-Control-Request-Headers` header, part of +/// [CORS](http://www.w3.org/TR/cors/#access-control-request-headers-request-header) +/// +/// The `Access-Control-Request-Headers` header indicates which headers will +/// be used in the actual request as part of the preflight request. +/// during the actual request. +/// +/// # ABNF +/// +/// ```text +/// Access-Control-Allow-Headers: "Access-Control-Allow-Headers" ":" #field-name +/// ``` +/// +/// # Example values +/// * `accept-language, date` +/// +/// # Examples +/// +/// ``` +/// # extern crate headers; +/// extern crate http; +/// # fn main() { +/// use http::header::{ACCEPT_LANGUAGE, DATE}; +/// use headers::AccessControlRequestHeaders; +/// +/// let req_headers = vec![ACCEPT_LANGUAGE, DATE] +/// .into_iter() +/// .collect::<AccessControlRequestHeaders>(); +/// # } +/// ``` +#[derive(Clone, Debug)] +pub struct AccessControlRequestHeaders(FlatCsv); + +derive_header! { + AccessControlRequestHeaders(_), + name: ACCESS_CONTROL_REQUEST_HEADERS +} + +impl AccessControlRequestHeaders { + /// Returns an iterator over `HeaderName`s contained within. + pub fn iter<'a>(&'a self) -> impl Iterator<Item = HeaderName> + 'a { + self.0.iter().filter_map(|s| s.parse().ok()) + } +} + +impl FromIterator<HeaderName> for AccessControlRequestHeaders { + fn from_iter<I>(iter: I) -> Self + where + I: IntoIterator<Item = HeaderName>, + { + let flat = iter.into_iter().map(HeaderValue::from).collect(); + AccessControlRequestHeaders(flat) + } +} + +#[cfg(test)] +mod tests { + use super::super::{test_decode, test_encode}; + use super::*; + + #[test] + fn iter() { + let req_headers = test_decode::<AccessControlRequestHeaders>(&["foo, bar"]).unwrap(); + + let as_vec = req_headers.iter().collect::<Vec<_>>(); + assert_eq!(as_vec.len(), 2); + assert_eq!(as_vec[0], "foo"); + assert_eq!(as_vec[1], "bar"); + } + + #[test] + fn from_iter() { + let req_headers: AccessControlRequestHeaders = + vec![::http::header::CACHE_CONTROL, ::http::header::IF_RANGE] + .into_iter() + .collect(); + + let headers = test_encode(req_headers); + assert_eq!( + headers["access-control-request-headers"], + "cache-control, if-range" + ); + } +} diff --git a/third_party/rust/headers/src/common/access_control_request_method.rs b/third_party/rust/headers/src/common/access_control_request_method.rs new file mode 100644 index 0000000000..f922f0b5e0 --- /dev/null +++ b/third_party/rust/headers/src/common/access_control_request_method.rs @@ -0,0 +1,73 @@ +use http::Method; +use {Header, HeaderName, HeaderValue}; + +/// `Access-Control-Request-Method` header, part of +/// [CORS](http://www.w3.org/TR/cors/#access-control-request-method-request-header) +/// +/// The `Access-Control-Request-Method` header indicates which method will be +/// used in the actual request as part of the preflight request. +/// # ABNF +/// +/// ```text +/// Access-Control-Request-Method: \"Access-Control-Request-Method\" \":\" Method +/// ``` +/// +/// # Example values +/// * `GET` +/// +/// # Examples +/// +/// ``` +/// # extern crate headers; +/// extern crate http; +/// use headers::AccessControlRequestMethod; +/// use http::Method; +/// +/// let req_method = AccessControlRequestMethod::from(Method::GET); +/// ``` +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub struct AccessControlRequestMethod(Method); + +impl Header for AccessControlRequestMethod { + fn name() -> &'static HeaderName { + &::http::header::ACCESS_CONTROL_REQUEST_METHOD + } + + fn decode<'i, I: Iterator<Item = &'i HeaderValue>>(values: &mut I) -> Result<Self, ::Error> { + values + .next() + .and_then(|value| Method::from_bytes(value.as_bytes()).ok()) + .map(AccessControlRequestMethod) + .ok_or_else(::Error::invalid) + } + + fn encode<E: Extend<::HeaderValue>>(&self, values: &mut E) { + // For the more common methods, try to use a static string. + let s = match self.0 { + Method::GET => "GET", + Method::POST => "POST", + Method::PUT => "PUT", + Method::DELETE => "DELETE", + _ => { + let val = HeaderValue::from_str(self.0.as_ref()) + .expect("Methods are also valid HeaderValues"); + values.extend(::std::iter::once(val)); + return; + } + }; + + values.extend(::std::iter::once(HeaderValue::from_static(s))); + } +} + +impl From<Method> for AccessControlRequestMethod { + fn from(method: Method) -> AccessControlRequestMethod { + AccessControlRequestMethod(method) + } +} + +impl From<AccessControlRequestMethod> for Method { + fn from(method: AccessControlRequestMethod) -> Method { + method.0 + } +} diff --git a/third_party/rust/headers/src/common/age.rs b/third_party/rust/headers/src/common/age.rs new file mode 100644 index 0000000000..11f1f6468c --- /dev/null +++ b/third_party/rust/headers/src/common/age.rs @@ -0,0 +1,69 @@ +use std::time::Duration; + +use util::Seconds; + +/// `Age` header, defined in [RFC7234](https://tools.ietf.org/html/rfc7234#section-5.1) +/// +/// The "Age" header field conveys the sender's estimate of the amount of +/// time since the response was generated or successfully validated at +/// the origin server. Age values are calculated as specified in +/// [Section 4.2.3](https://tools.ietf.org/html/rfc7234#section-4.2.3). +/// +/// ## ABNF +/// +/// ```text +/// Age = delta-seconds +/// ``` +/// +/// The Age field-value is a non-negative integer, representing time in +/// seconds (see [Section 1.2.1](https://tools.ietf.org/html/rfc7234#section-1.2.1)). +/// +/// The presence of an Age header field implies that the response was not +/// generated or validated by the origin server for this request. +/// However, lack of an Age header field does not imply the origin was +/// contacted, since the response might have been received from an +/// HTTP/1.0 cache that does not implement Age. +/// +/// ## Example values +/// +/// * `3600` +/// +/// # Example +/// +/// ``` +/// # extern crate headers; +/// use headers::Age; +/// +/// let len = Age::from_secs(60); +/// ``` +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct Age(Seconds); + +derive_header! { + Age(_), + name: AGE +} + +impl Age { + /// Creates a new `Age` header from the specified number of whole seconds. + pub fn from_secs(secs: u64) -> Self { + Self(Seconds::from_secs(secs)) + } + + /// Returns the number of seconds for this `Age` header. + pub fn as_secs(&self) -> u64 { + self.0.as_u64() + } +} + +impl From<Duration> for Age { + fn from(dur: Duration) -> Self { + Age(Seconds::from(dur)) + } +} + +impl From<Age> for Duration { + fn from(age: Age) -> Self { + age.0.into() + } +} diff --git a/third_party/rust/headers/src/common/allow.rs b/third_party/rust/headers/src/common/allow.rs new file mode 100644 index 0000000000..0285d3c780 --- /dev/null +++ b/third_party/rust/headers/src/common/allow.rs @@ -0,0 +1,68 @@ +use std::iter::FromIterator; + +use http::Method; + +use util::FlatCsv; + +/// `Allow` header, defined in [RFC7231](http://tools.ietf.org/html/rfc7231#section-7.4.1) +/// +/// The `Allow` header field lists the set of methods advertised as +/// supported by the target resource. The purpose of this field is +/// strictly to inform the recipient of valid request methods associated +/// with the resource. +/// +/// # ABNF +/// +/// ```text +/// Allow = #method +/// ``` +/// +/// # Example values +/// * `GET, HEAD, PUT` +/// * `OPTIONS, GET, PUT, POST, DELETE, HEAD, TRACE, CONNECT, PATCH, fOObAr` +/// * `` +/// +/// # Examples +/// +/// ``` +/// # extern crate headers; +/// extern crate http; +/// use headers::Allow; +/// use http::Method; +/// +/// let allow = vec![Method::GET, Method::POST] +/// .into_iter() +/// .collect::<Allow>(); +/// ``` +#[derive(Clone, Debug, PartialEq)] +pub struct Allow(FlatCsv); + +derive_header! { + Allow(_), + name: ALLOW +} + +impl Allow { + /// Returns an iterator over `Method`s contained within. + pub fn iter<'a>(&'a self) -> impl Iterator<Item = Method> + 'a { + self.0.iter().filter_map(|s| s.parse().ok()) + } +} + +impl FromIterator<Method> for Allow { + fn from_iter<I>(iter: I) -> Self + where + I: IntoIterator<Item = Method>, + { + let flat = iter + .into_iter() + .map(|method| { + method + .as_str() + .parse::<::HeaderValue>() + .expect("Method is a valid HeaderValue") + }) + .collect(); + Allow(flat) + } +} diff --git a/third_party/rust/headers/src/common/authorization.rs b/third_party/rust/headers/src/common/authorization.rs new file mode 100644 index 0000000000..e62f2fd938 --- /dev/null +++ b/third_party/rust/headers/src/common/authorization.rs @@ -0,0 +1,280 @@ +//! Authorization header and types. + +use base64::engine::general_purpose::STANDARD as ENGINE; +use base64::Engine; +use bytes::Bytes; + +use util::HeaderValueString; +use HeaderValue; + +/// `Authorization` header, defined in [RFC7235](https://tools.ietf.org/html/rfc7235#section-4.2) +/// +/// The `Authorization` header field allows a user agent to authenticate +/// itself with an origin server -- usually, but not necessarily, after +/// receiving a 401 (Unauthorized) response. Its value consists of +/// credentials containing the authentication information of the user +/// agent for the realm of the resource being requested. +/// +/// # ABNF +/// +/// ```text +/// Authorization = credentials +/// ``` +/// +/// # Example values +/// * `Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==` +/// * `Bearer fpKL54jvWmEGVoRdCNjG` +/// +/// # Examples +/// +/// ``` +/// # extern crate headers; +/// use headers::Authorization; +/// +/// let basic = Authorization::basic("Aladdin", "open sesame"); +/// let bearer = Authorization::bearer("some-opaque-token").unwrap(); +/// ``` +/// +#[derive(Clone, PartialEq, Debug)] +pub struct Authorization<C: Credentials>(pub C); + +impl Authorization<Basic> { + /// Create a `Basic` authorization header. + pub fn basic(username: &str, password: &str) -> Self { + let colon_pos = username.len(); + let decoded = format!("{}:{}", username, password); + + Authorization(Basic { decoded, colon_pos }) + } + + /// View the decoded username. + pub fn username(&self) -> &str { + self.0.username() + } + + /// View the decoded password. + pub fn password(&self) -> &str { + self.0.password() + } +} + +impl Authorization<Bearer> { + /// Try to create a `Bearer` authorization header. + pub fn bearer(token: &str) -> Result<Self, InvalidBearerToken> { + HeaderValueString::from_string(format!("Bearer {}", token)) + .map(|val| Authorization(Bearer(val))) + .ok_or_else(|| InvalidBearerToken { _inner: () }) + } + + /// View the token part as a `&str`. + pub fn token(&self) -> &str { + self.0.token() + } +} + +impl<C: Credentials> ::Header for Authorization<C> { + fn name() -> &'static ::HeaderName { + &::http::header::AUTHORIZATION + } + + fn decode<'i, I: Iterator<Item = &'i HeaderValue>>(values: &mut I) -> Result<Self, ::Error> { + values + .next() + .and_then(|val| { + let slice = val.as_bytes(); + if slice.starts_with(C::SCHEME.as_bytes()) + && slice.len() > C::SCHEME.len() + && slice[C::SCHEME.len()] == b' ' + { + C::decode(val).map(Authorization) + } else { + None + } + }) + .ok_or_else(::Error::invalid) + } + + fn encode<E: Extend<::HeaderValue>>(&self, values: &mut E) { + let mut value = self.0.encode(); + value.set_sensitive(true); + debug_assert!( + value.as_bytes().starts_with(C::SCHEME.as_bytes()), + "Credentials::encode should include its scheme: scheme = {:?}, encoded = {:?}", + C::SCHEME, + value, + ); + + values.extend(::std::iter::once(value)); + } +} + +/// Credentials to be used in the `Authorization` header. +pub trait Credentials: Sized { + /// The scheme identify the format of these credentials. + /// + /// This is the static string that always prefixes the actual credentials, + /// like `"Basic"` in basic authorization. + const SCHEME: &'static str; + + /// Try to decode the credentials from the `HeaderValue`. + /// + /// The `SCHEME` will be the first part of the `value`. + fn decode(value: &HeaderValue) -> Option<Self>; + + /// Encode the credentials to a `HeaderValue`. + /// + /// The `SCHEME` must be the first part of the `value`. + fn encode(&self) -> HeaderValue; +} + +/// Credential holder for Basic Authentication +#[derive(Clone, PartialEq, Debug)] +pub struct Basic { + decoded: String, + colon_pos: usize, +} + +impl Basic { + /// View the decoded username. + pub fn username(&self) -> &str { + &self.decoded[..self.colon_pos] + } + + /// View the decoded password. + pub fn password(&self) -> &str { + &self.decoded[self.colon_pos + 1..] + } +} + +impl Credentials for Basic { + const SCHEME: &'static str = "Basic"; + + fn decode(value: &HeaderValue) -> Option<Self> { + debug_assert!( + value.as_bytes().starts_with(b"Basic "), + "HeaderValue to decode should start with \"Basic ..\", received = {:?}", + value, + ); + + let bytes = &value.as_bytes()["Basic ".len()..]; + let non_space_pos = bytes.iter().position(|b| *b != b' ')?; + let bytes = &bytes[non_space_pos..]; + + let bytes = ENGINE.decode(bytes).ok()?; + + let decoded = String::from_utf8(bytes).ok()?; + + let colon_pos = decoded.find(':')?; + + Some(Basic { decoded, colon_pos }) + } + + fn encode(&self) -> HeaderValue { + let mut encoded = String::from("Basic "); + ENGINE.encode_string(&self.decoded, &mut encoded); + + let bytes = Bytes::from(encoded); + HeaderValue::from_maybe_shared(bytes) + .expect("base64 encoding is always a valid HeaderValue") + } +} + +#[derive(Clone, PartialEq, Debug)] +/// Token holder for Bearer Authentication, most often seen with oauth +pub struct Bearer(HeaderValueString); + +impl Bearer { + /// View the token part as a `&str`. + pub fn token(&self) -> &str { + &self.0.as_str()["Bearer ".len()..] + } +} + +impl Credentials for Bearer { + const SCHEME: &'static str = "Bearer"; + + fn decode(value: &HeaderValue) -> Option<Self> { + debug_assert!( + value.as_bytes().starts_with(b"Bearer "), + "HeaderValue to decode should start with \"Bearer ..\", received = {:?}", + value, + ); + + HeaderValueString::from_val(value).ok().map(Bearer) + } + + fn encode(&self) -> HeaderValue { + (&self.0).into() + } +} + +error_type!(InvalidBearerToken); + +#[cfg(test)] +mod tests { + use super::super::{test_decode, test_encode}; + use super::{Authorization, Basic, Bearer}; + use http::header::HeaderMap; + use HeaderMapExt; + + #[test] + fn basic_encode() { + let auth = Authorization::basic("Aladdin", "open sesame"); + let headers = test_encode(auth); + + assert_eq!( + headers["authorization"], + "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==", + ); + } + + #[test] + fn basic_roundtrip() { + let auth = Authorization::basic("Aladdin", "open sesame"); + let mut h = HeaderMap::new(); + h.typed_insert(auth.clone()); + assert_eq!(h.typed_get(), Some(auth)); + } + + #[test] + fn basic_encode_no_password() { + let auth = Authorization::basic("Aladdin", ""); + let headers = test_encode(auth); + + assert_eq!(headers["authorization"], "Basic QWxhZGRpbjo=",); + } + + #[test] + fn basic_decode() { + let auth: Authorization<Basic> = + test_decode(&["Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ=="]).unwrap(); + assert_eq!(auth.0.username(), "Aladdin"); + assert_eq!(auth.0.password(), "open sesame"); + } + + #[test] + fn basic_decode_no_password() { + let auth: Authorization<Basic> = test_decode(&["Basic QWxhZGRpbjo="]).unwrap(); + assert_eq!(auth.0.username(), "Aladdin"); + assert_eq!(auth.0.password(), ""); + } + + #[test] + fn bearer_encode() { + let auth = Authorization::bearer("fpKL54jvWmEGVoRdCNjG").unwrap(); + + let headers = test_encode(auth); + + assert_eq!(headers["authorization"], "Bearer fpKL54jvWmEGVoRdCNjG",); + } + + #[test] + fn bearer_decode() { + let auth: Authorization<Bearer> = test_decode(&["Bearer fpKL54jvWmEGVoRdCNjG"]).unwrap(); + assert_eq!(auth.0.token().as_bytes(), b"fpKL54jvWmEGVoRdCNjG"); + } +} + +//bench_header!(raw, Authorization<String>, { vec![b"foo bar baz".to_vec()] }); +//bench_header!(basic, Authorization<Basic>, { vec![b"Basic QWxhZGRpbjpuIHNlc2FtZQ==".to_vec()] }); +//bench_header!(bearer, Authorization<Bearer>, { vec![b"Bearer fpKL54jvWmEGVoRdCNjG".to_vec()] }); diff --git a/third_party/rust/headers/src/common/cache_control.rs b/third_party/rust/headers/src/common/cache_control.rs new file mode 100644 index 0000000000..afb692494d --- /dev/null +++ b/third_party/rust/headers/src/common/cache_control.rs @@ -0,0 +1,508 @@ +use std::fmt; +use std::iter::FromIterator; +use std::str::FromStr; +use std::time::Duration; + +use util::{self, csv, Seconds}; +use HeaderValue; + +/// `Cache-Control` header, defined in [RFC7234](https://tools.ietf.org/html/rfc7234#section-5.2) +/// with extensions in [RFC8246](https://www.rfc-editor.org/rfc/rfc8246) +/// +/// The `Cache-Control` header field is used to specify directives for +/// caches along the request/response chain. Such cache directives are +/// unidirectional in that the presence of a directive in a request does +/// not imply that the same directive is to be given in the response. +/// +/// ## ABNF +/// +/// ```text +/// Cache-Control = 1#cache-directive +/// cache-directive = token [ "=" ( token / quoted-string ) ] +/// ``` +/// +/// ## Example values +/// +/// * `no-cache` +/// * `private, community="UCI"` +/// * `max-age=30` +/// +/// # Example +/// +/// ``` +/// # extern crate headers; +/// use headers::CacheControl; +/// +/// let cc = CacheControl::new(); +/// ``` +#[derive(PartialEq, Clone, Debug)] +pub struct CacheControl { + flags: Flags, + max_age: Option<Seconds>, + max_stale: Option<Seconds>, + min_fresh: Option<Seconds>, + s_max_age: Option<Seconds>, +} + +#[derive(Debug, Clone, PartialEq)] +struct Flags { + bits: u64, +} + +impl Flags { + const NO_CACHE: Self = Self { bits: 0b000000001 }; + const NO_STORE: Self = Self { bits: 0b000000010 }; + const NO_TRANSFORM: Self = Self { bits: 0b000000100 }; + const ONLY_IF_CACHED: Self = Self { bits: 0b000001000 }; + const MUST_REVALIDATE: Self = Self { bits: 0b000010000 }; + const PUBLIC: Self = Self { bits: 0b000100000 }; + const PRIVATE: Self = Self { bits: 0b001000000 }; + const PROXY_REVALIDATE: Self = Self { bits: 0b010000000 }; + const IMMUTABLE: Self = Self { bits: 0b100000000 }; + + fn empty() -> Self { + Self { bits: 0 } + } + + fn contains(&self, flag: Self) -> bool { + (self.bits & flag.bits) != 0 + } + + fn insert(&mut self, flag: Self) { + self.bits |= flag.bits; + } +} + +impl CacheControl { + /// Construct a new empty `CacheControl` header. + pub fn new() -> Self { + CacheControl { + flags: Flags::empty(), + max_age: None, + max_stale: None, + min_fresh: None, + s_max_age: None, + } + } + + // getters + + /// Check if the `no-cache` directive is set. + pub fn no_cache(&self) -> bool { + self.flags.contains(Flags::NO_CACHE) + } + + /// Check if the `no-store` directive is set. + pub fn no_store(&self) -> bool { + self.flags.contains(Flags::NO_STORE) + } + + /// Check if the `no-transform` directive is set. + pub fn no_transform(&self) -> bool { + self.flags.contains(Flags::NO_TRANSFORM) + } + + /// Check if the `only-if-cached` directive is set. + pub fn only_if_cached(&self) -> bool { + self.flags.contains(Flags::ONLY_IF_CACHED) + } + + /// Check if the `public` directive is set. + pub fn public(&self) -> bool { + self.flags.contains(Flags::PUBLIC) + } + + /// Check if the `private` directive is set. + pub fn private(&self) -> bool { + self.flags.contains(Flags::PRIVATE) + } + + /// Check if the `immutable` directive is set. + pub fn immutable(&self) -> bool { + self.flags.contains(Flags::IMMUTABLE) + } + + /// Get the value of the `max-age` directive if set. + pub fn max_age(&self) -> Option<Duration> { + self.max_age.map(Into::into) + } + + /// Get the value of the `max-stale` directive if set. + pub fn max_stale(&self) -> Option<Duration> { + self.max_stale.map(Into::into) + } + + /// Get the value of the `min-fresh` directive if set. + pub fn min_fresh(&self) -> Option<Duration> { + self.min_fresh.map(Into::into) + } + + /// Get the value of the `s-maxage` directive if set. + pub fn s_max_age(&self) -> Option<Duration> { + self.s_max_age.map(Into::into) + } + + // setters + + /// Set the `no-cache` directive. + pub fn with_no_cache(mut self) -> Self { + self.flags.insert(Flags::NO_CACHE); + self + } + + /// Set the `no-store` directive. + pub fn with_no_store(mut self) -> Self { + self.flags.insert(Flags::NO_STORE); + self + } + + /// Set the `no-transform` directive. + pub fn with_no_transform(mut self) -> Self { + self.flags.insert(Flags::NO_TRANSFORM); + self + } + + /// Set the `only-if-cached` directive. + pub fn with_only_if_cached(mut self) -> Self { + self.flags.insert(Flags::ONLY_IF_CACHED); + self + } + + /// Set the `private` directive. + pub fn with_private(mut self) -> Self { + self.flags.insert(Flags::PRIVATE); + self + } + + /// Set the `public` directive. + pub fn with_public(mut self) -> Self { + self.flags.insert(Flags::PUBLIC); + self + } + + /// Set the `immutable` directive. + pub fn with_immutable(mut self) -> Self { + self.flags.insert(Flags::IMMUTABLE); + self + } + + /// Set the `max-age` directive. + pub fn with_max_age(mut self, duration: Duration) -> Self { + self.max_age = Some(duration.into()); + self + } + + /// Set the `max-stale` directive. + pub fn with_max_stale(mut self, duration: Duration) -> Self { + self.max_stale = Some(duration.into()); + self + } + + /// Set the `min-fresh` directive. + pub fn with_min_fresh(mut self, duration: Duration) -> Self { + self.min_fresh = Some(duration.into()); + self + } + + /// Set the `s-maxage` directive. + pub fn with_s_max_age(mut self, duration: Duration) -> Self { + self.s_max_age = Some(duration.into()); + self + } +} + +impl ::Header for CacheControl { + fn name() -> &'static ::HeaderName { + &::http::header::CACHE_CONTROL + } + + fn decode<'i, I: Iterator<Item = &'i HeaderValue>>(values: &mut I) -> Result<Self, ::Error> { + csv::from_comma_delimited(values).map(|FromIter(cc)| cc) + } + + fn encode<E: Extend<::HeaderValue>>(&self, values: &mut E) { + values.extend(::std::iter::once(util::fmt(Fmt(self)))); + } +} + +// Adapter to be used in Header::decode +struct FromIter(CacheControl); + +impl FromIterator<KnownDirective> for FromIter { + fn from_iter<I>(iter: I) -> Self + where + I: IntoIterator<Item = KnownDirective>, + { + let mut cc = CacheControl::new(); + + // ignore all unknown directives + let iter = iter.into_iter().filter_map(|dir| match dir { + KnownDirective::Known(dir) => Some(dir), + KnownDirective::Unknown => None, + }); + + for directive in iter { + match directive { + Directive::NoCache => { + cc.flags.insert(Flags::NO_CACHE); + } + Directive::NoStore => { + cc.flags.insert(Flags::NO_STORE); + } + Directive::NoTransform => { + cc.flags.insert(Flags::NO_TRANSFORM); + } + Directive::OnlyIfCached => { + cc.flags.insert(Flags::ONLY_IF_CACHED); + } + Directive::MustRevalidate => { + cc.flags.insert(Flags::MUST_REVALIDATE); + } + Directive::Public => { + cc.flags.insert(Flags::PUBLIC); + } + Directive::Private => { + cc.flags.insert(Flags::PRIVATE); + } + Directive::Immutable => { + cc.flags.insert(Flags::IMMUTABLE); + } + Directive::ProxyRevalidate => { + cc.flags.insert(Flags::PROXY_REVALIDATE); + } + Directive::MaxAge(secs) => { + cc.max_age = Some(Duration::from_secs(secs.into()).into()); + } + Directive::MaxStale(secs) => { + cc.max_stale = Some(Duration::from_secs(secs.into()).into()); + } + Directive::MinFresh(secs) => { + cc.min_fresh = Some(Duration::from_secs(secs.into()).into()); + } + Directive::SMaxAge(secs) => { + cc.s_max_age = Some(Duration::from_secs(secs.into()).into()); + } + } + } + + FromIter(cc) + } +} + +struct Fmt<'a>(&'a CacheControl); + +impl<'a> fmt::Display for Fmt<'a> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let if_flag = |f: Flags, dir: Directive| { + if self.0.flags.contains(f) { + Some(dir) + } else { + None + } + }; + + let slice = &[ + if_flag(Flags::NO_CACHE, Directive::NoCache), + if_flag(Flags::NO_STORE, Directive::NoStore), + if_flag(Flags::NO_TRANSFORM, Directive::NoTransform), + if_flag(Flags::ONLY_IF_CACHED, Directive::OnlyIfCached), + if_flag(Flags::MUST_REVALIDATE, Directive::MustRevalidate), + if_flag(Flags::PUBLIC, Directive::Public), + if_flag(Flags::PRIVATE, Directive::Private), + if_flag(Flags::IMMUTABLE, Directive::Immutable), + if_flag(Flags::PROXY_REVALIDATE, Directive::ProxyRevalidate), + self.0 + .max_age + .as_ref() + .map(|s| Directive::MaxAge(s.as_u64())), + self.0 + .max_stale + .as_ref() + .map(|s| Directive::MaxStale(s.as_u64())), + self.0 + .min_fresh + .as_ref() + .map(|s| Directive::MinFresh(s.as_u64())), + self.0 + .s_max_age + .as_ref() + .map(|s| Directive::SMaxAge(s.as_u64())), + ]; + + let iter = slice.iter().filter_map(|o| *o); + + csv::fmt_comma_delimited(f, iter) + } +} + +#[derive(Clone, Copy)] +enum KnownDirective { + Known(Directive), + Unknown, +} + +#[derive(Clone, Copy)] +enum Directive { + NoCache, + NoStore, + NoTransform, + OnlyIfCached, + + // request directives + MaxAge(u64), + MaxStale(u64), + MinFresh(u64), + + // response directives + MustRevalidate, + Public, + Private, + Immutable, + ProxyRevalidate, + SMaxAge(u64), +} + +impl fmt::Display for Directive { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fmt::Display::fmt( + match *self { + Directive::NoCache => "no-cache", + Directive::NoStore => "no-store", + Directive::NoTransform => "no-transform", + Directive::OnlyIfCached => "only-if-cached", + + Directive::MaxAge(secs) => return write!(f, "max-age={}", secs), + Directive::MaxStale(secs) => return write!(f, "max-stale={}", secs), + Directive::MinFresh(secs) => return write!(f, "min-fresh={}", secs), + + Directive::MustRevalidate => "must-revalidate", + Directive::Public => "public", + Directive::Private => "private", + Directive::Immutable => "immutable", + Directive::ProxyRevalidate => "proxy-revalidate", + Directive::SMaxAge(secs) => return write!(f, "s-maxage={}", secs), + }, + f, + ) + } +} + +impl FromStr for KnownDirective { + type Err = (); + fn from_str(s: &str) -> Result<Self, Self::Err> { + Ok(KnownDirective::Known(match s { + "no-cache" => Directive::NoCache, + "no-store" => Directive::NoStore, + "no-transform" => Directive::NoTransform, + "only-if-cached" => Directive::OnlyIfCached, + "must-revalidate" => Directive::MustRevalidate, + "public" => Directive::Public, + "private" => Directive::Private, + "immutable" => Directive::Immutable, + "proxy-revalidate" => Directive::ProxyRevalidate, + "" => return Err(()), + _ => match s.find('=') { + Some(idx) if idx + 1 < s.len() => { + match (&s[..idx], (&s[idx + 1..]).trim_matches('"')) { + ("max-age", secs) => secs.parse().map(Directive::MaxAge).map_err(|_| ())?, + ("max-stale", secs) => { + secs.parse().map(Directive::MaxStale).map_err(|_| ())? + } + ("min-fresh", secs) => { + secs.parse().map(Directive::MinFresh).map_err(|_| ())? + } + ("s-maxage", secs) => { + secs.parse().map(Directive::SMaxAge).map_err(|_| ())? + } + _unknown => return Ok(KnownDirective::Unknown), + } + } + Some(_) | None => return Ok(KnownDirective::Unknown), + }, + })) + } +} + +#[cfg(test)] +mod tests { + use super::super::{test_decode, test_encode}; + use super::*; + + #[test] + fn test_parse_multiple_headers() { + assert_eq!( + test_decode::<CacheControl>(&["no-cache", "private"]).unwrap(), + CacheControl::new().with_no_cache().with_private(), + ); + } + + #[test] + fn test_parse_argument() { + assert_eq!( + test_decode::<CacheControl>(&["max-age=100, private"]).unwrap(), + CacheControl::new() + .with_max_age(Duration::from_secs(100)) + .with_private(), + ); + } + + #[test] + fn test_parse_quote_form() { + assert_eq!( + test_decode::<CacheControl>(&["max-age=\"200\""]).unwrap(), + CacheControl::new().with_max_age(Duration::from_secs(200)), + ); + } + + #[test] + fn test_parse_extension() { + assert_eq!( + test_decode::<CacheControl>(&["foo, no-cache, bar=baz"]).unwrap(), + CacheControl::new().with_no_cache(), + "unknown extensions are ignored but shouldn't fail parsing", + ); + } + + #[test] + fn test_immutable() { + let cc = CacheControl::new().with_immutable(); + let headers = test_encode(cc.clone()); + assert_eq!(headers["cache-control"], "immutable"); + assert_eq!(test_decode::<CacheControl>(&["immutable"]).unwrap(), cc); + assert!(cc.immutable()); + } + + #[test] + fn test_parse_bad_syntax() { + assert_eq!(test_decode::<CacheControl>(&["max-age=lolz"]), None); + } + + #[test] + fn encode_one_flag_directive() { + let cc = CacheControl::new().with_no_cache(); + + let headers = test_encode(cc); + assert_eq!(headers["cache-control"], "no-cache"); + } + + #[test] + fn encode_one_param_directive() { + let cc = CacheControl::new().with_max_age(Duration::from_secs(300)); + + let headers = test_encode(cc); + assert_eq!(headers["cache-control"], "max-age=300"); + } + + #[test] + fn encode_two_directive() { + let headers = test_encode(CacheControl::new().with_no_cache().with_private()); + assert_eq!(headers["cache-control"], "no-cache, private"); + + let headers = test_encode( + CacheControl::new() + .with_no_cache() + .with_max_age(Duration::from_secs(100)), + ); + assert_eq!(headers["cache-control"], "no-cache, max-age=100"); + } +} diff --git a/third_party/rust/headers/src/common/connection.rs b/third_party/rust/headers/src/common/connection.rs new file mode 100644 index 0000000000..00299e4702 --- /dev/null +++ b/third_party/rust/headers/src/common/connection.rs @@ -0,0 +1,136 @@ +use std::iter::FromIterator; + +use self::sealed::AsConnectionOption; +use util::FlatCsv; +use {HeaderName, HeaderValue}; + +/// `Connection` header, defined in +/// [RFC7230](http://tools.ietf.org/html/rfc7230#section-6.1) +/// +/// The `Connection` header field allows the sender to indicate desired +/// control options for the current connection. In order to avoid +/// confusing downstream recipients, a proxy or gateway MUST remove or +/// replace any received connection options before forwarding the +/// message. +/// +/// # ABNF +/// +/// ```text +/// Connection = 1#connection-option +/// connection-option = token +/// +/// # Example values +/// * `close` +/// * `keep-alive` +/// * `upgrade` +/// ``` +/// +/// # Examples +/// +/// ``` +/// # extern crate headers; +/// use headers::Connection; +/// +/// let keep_alive = Connection::keep_alive(); +/// ``` +// This is frequently just 1 or 2 values, so optimize for that case. +#[derive(Clone, Debug)] +pub struct Connection(FlatCsv); + +derive_header! { + Connection(_), + name: CONNECTION +} + +impl Connection { + /// A constructor to easily create a `Connection: close` header. + #[inline] + pub fn close() -> Connection { + Connection(HeaderValue::from_static("close").into()) + } + + /// A constructor to easily create a `Connection: keep-alive` header. + #[inline] + pub fn keep_alive() -> Connection { + Connection(HeaderValue::from_static("keep-alive").into()) + } + + /// A constructor to easily create a `Connection: Upgrade` header. + #[inline] + pub fn upgrade() -> Connection { + Connection(HeaderValue::from_static("upgrade").into()) + } + + /// Check if this header contains a given "connection option". + /// + /// This can be used with various argument types: + /// + /// - `&str` + /// - `&HeaderName` + /// - `HeaderName` + /// + /// # Example + /// + /// ``` + /// # extern crate headers; + /// extern crate http; + /// + /// use http::header::UPGRADE; + /// use headers::Connection; + /// + /// let conn = Connection::keep_alive(); + /// + /// assert!(!conn.contains("close")); + /// assert!(!conn.contains(UPGRADE)); + /// assert!(conn.contains("keep-alive")); + /// assert!(conn.contains("Keep-Alive")); + /// ``` + pub fn contains(&self, name: impl AsConnectionOption) -> bool { + let s = name.as_connection_option(); + self.0 + .iter() + .find(|&opt| opt.eq_ignore_ascii_case(s)) + .is_some() + } +} + +impl FromIterator<HeaderName> for Connection { + fn from_iter<I>(iter: I) -> Self + where + I: IntoIterator<Item = HeaderName>, + { + let flat = iter.into_iter().map(HeaderValue::from).collect(); + Connection(flat) + } +} + +mod sealed { + pub trait AsConnectionOption: Sealed { + fn as_connection_option(&self) -> &str; + } + pub trait Sealed {} + + impl<'a> AsConnectionOption for &'a str { + fn as_connection_option(&self) -> &str { + *self + } + } + + impl<'a> Sealed for &'a str {} + + impl<'a> AsConnectionOption for &'a ::HeaderName { + fn as_connection_option(&self) -> &str { + self.as_ref() + } + } + + impl<'a> Sealed for &'a ::HeaderName {} + + impl AsConnectionOption for ::HeaderName { + fn as_connection_option(&self) -> &str { + self.as_ref() + } + } + + impl Sealed for ::HeaderName {} +} diff --git a/third_party/rust/headers/src/common/content_disposition.rs b/third_party/rust/headers/src/common/content_disposition.rs new file mode 100644 index 0000000000..5c1ea0f745 --- /dev/null +++ b/third_party/rust/headers/src/common/content_disposition.rs @@ -0,0 +1,323 @@ +// # References +// +// "The Content-Disposition Header Field" https://www.ietf.org/rfc/rfc2183.txt +// "The Content-Disposition Header Field in the Hypertext Transfer Protocol (HTTP)" https://www.ietf.org/rfc/rfc6266.txt +// "Returning Values from Forms: multipart/form-data" https://www.ietf.org/rfc/rfc2388.txt +// Browser conformance tests at: http://greenbytes.de/tech/tc2231/ +// IANA assignment: http://www.iana.org/assignments/cont-disp/cont-disp.xhtml + +/// A `Content-Disposition` header, (re)defined in [RFC6266](https://tools.ietf.org/html/rfc6266). +/// +/// The Content-Disposition response header field is used to convey +/// additional information about how to process the response payload, and +/// also can be used to attach additional metadata, such as the filename +/// to use when saving the response payload locally. +/// +/// # ABNF + +/// ```text +/// content-disposition = "Content-Disposition" ":" +/// disposition-type *( ";" disposition-parm ) +/// +/// disposition-type = "inline" | "attachment" | disp-ext-type +/// ; case-insensitive +/// +/// disp-ext-type = token +/// +/// disposition-parm = filename-parm | disp-ext-parm +/// +/// filename-parm = "filename" "=" value +/// | "filename*" "=" ext-value +/// +/// disp-ext-parm = token "=" value +/// | ext-token "=" ext-value +/// +/// ext-token = <the characters in token, followed by "*"> +/// ``` +/// +/// # Example +/// +/// ``` +/// # extern crate headers; +/// use headers::ContentDisposition; +/// +/// let cd = ContentDisposition::inline(); +/// ``` +#[derive(Clone, Debug)] +pub struct ContentDisposition(::HeaderValue); + +impl ContentDisposition { + /// Construct a `Content-Disposition: inline` header. + pub fn inline() -> ContentDisposition { + ContentDisposition(::HeaderValue::from_static("inline")) + } + + /* + pub fn attachment(filename: &str) -> ContentDisposition { + let full = Bytes::from(format!("attachment; filename={}", filename)); + match ::HeaderValue::from_maybe_shared(full) { + Ok(val) => ContentDisposition(val), + Err(_) => { + unimplemented!("filename that isn't ASCII"); + } + } + } + */ + + /// Check if the disposition-type is `inline`. + pub fn is_inline(&self) -> bool { + self.get_type() == "inline" + } + + /// Check if the disposition-type is `attachment`. + pub fn is_attachment(&self) -> bool { + self.get_type() == "attachment" + } + + /// Check if the disposition-type is `form-data`. + pub fn is_form_data(&self) -> bool { + self.get_type() == "form-data" + } + + fn get_type(&self) -> &str { + self.0 + .to_str() + .unwrap_or("") + .split(';') + .next() + .expect("split always has at least 1 item") + } +} + +impl ::Header for ContentDisposition { + fn name() -> &'static ::HeaderName { + &::http::header::CONTENT_DISPOSITION + } + + fn decode<'i, I: Iterator<Item = &'i ::HeaderValue>>(values: &mut I) -> Result<Self, ::Error> { + //TODO: parse harder + values + .next() + .cloned() + .map(ContentDisposition) + .ok_or_else(::Error::invalid) + } + + fn encode<E: Extend<::HeaderValue>>(&self, values: &mut E) { + values.extend(::std::iter::once(self.0.clone())); + } +} +/* +use language_tags::LanguageTag; +use std::fmt; +use unicase; + +use {Header, Raw, parsing}; +use parsing::{parse_extended_value, http_percent_encode}; +use shared::Charset; + +/// The implied disposition of the content of the HTTP body. +#[derive(Clone, Debug, PartialEq)] +pub enum DispositionType { + /// Inline implies default processing + Inline, + /// Attachment implies that the recipient should prompt the user to save the response locally, + /// rather than process it normally (as per its media type). + Attachment, + /// Extension type. Should be handled by recipients the same way as Attachment + Ext(String) +} + +/// A parameter to the disposition type. +#[derive(Clone, Debug, PartialEq)] +pub enum DispositionParam { + /// A Filename consisting of a Charset, an optional LanguageTag, and finally a sequence of + /// bytes representing the filename + Filename(Charset, Option<LanguageTag>, Vec<u8>), + /// Extension type consisting of token and value. Recipients should ignore unrecognized + /// parameters. + Ext(String, String) +} + +#[derive(Clone, Debug, PartialEq)] +pub struct ContentDisposition { + /// The disposition + pub disposition: DispositionType, + /// Disposition parameters + pub parameters: Vec<DispositionParam>, +} + +impl Header for ContentDisposition { + fn header_name() -> &'static str { + static NAME: &'static str = "Content-Disposition"; + NAME + } + + fn parse_header(raw: &Raw) -> ::Result<ContentDisposition> { + parsing::from_one_raw_str(raw).and_then(|s: String| { + let mut sections = s.split(';'); + let disposition = match sections.next() { + Some(s) => s.trim(), + None => return Err(::Error::Header), + }; + + let mut cd = ContentDisposition { + disposition: if unicase::eq_ascii(&*disposition, "inline") { + DispositionType::Inline + } else if unicase::eq_ascii(&*disposition, "attachment") { + DispositionType::Attachment + } else { + DispositionType::Ext(disposition.to_owned()) + }, + parameters: Vec::new(), + }; + + for section in sections { + let mut parts = section.splitn(2, '='); + + let key = if let Some(key) = parts.next() { + key.trim() + } else { + return Err(::Error::Header); + }; + + let val = if let Some(val) = parts.next() { + val.trim() + } else { + return Err(::Error::Header); + }; + + cd.parameters.push( + if unicase::eq_ascii(&*key, "filename") { + DispositionParam::Filename( + Charset::Ext("UTF-8".to_owned()), None, + val.trim_matches('"').as_bytes().to_owned()) + } else if unicase::eq_ascii(&*key, "filename*") { + let extended_value = try!(parse_extended_value(val)); + DispositionParam::Filename(extended_value.charset, extended_value.language_tag, extended_value.value) + } else { + DispositionParam::Ext(key.to_owned(), val.trim_matches('"').to_owned()) + } + ); + } + + Ok(cd) + }) + } + + #[inline] + fn fmt_header(&self, f: &mut ::Formatter) -> fmt::Result { + f.fmt_line(self) + } +} + +impl fmt::Display for ContentDisposition { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self.disposition { + DispositionType::Inline => try!(write!(f, "inline")), + DispositionType::Attachment => try!(write!(f, "attachment")), + DispositionType::Ext(ref s) => try!(write!(f, "{}", s)), + } + for param in &self.parameters { + match *param { + DispositionParam::Filename(ref charset, ref opt_lang, ref bytes) => { + let mut use_simple_format: bool = false; + if opt_lang.is_none() { + if let Charset::Ext(ref ext) = *charset { + if unicase::eq_ascii(&**ext, "utf-8") { + use_simple_format = true; + } + } + } + if use_simple_format { + try!(write!(f, "; filename=\"{}\"", + match String::from_utf8(bytes.clone()) { + Ok(s) => s, + Err(_) => return Err(fmt::Error), + })); + } else { + try!(write!(f, "; filename*={}'", charset)); + if let Some(ref lang) = *opt_lang { + try!(write!(f, "{}", lang)); + }; + try!(write!(f, "'")); + try!(http_percent_encode(f, bytes)) + } + }, + DispositionParam::Ext(ref k, ref v) => try!(write!(f, "; {}=\"{}\"", k, v)), + } + } + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::{ContentDisposition,DispositionType,DispositionParam}; + use ::Header; + use ::shared::Charset; + + #[test] + fn test_parse_header() { + assert!(ContentDisposition::parse_header(&"".into()).is_err()); + + let a = "form-data; dummy=3; name=upload;\r\n filename=\"sample.png\"".into(); + let a: ContentDisposition = ContentDisposition::parse_header(&a).unwrap(); + let b = ContentDisposition { + disposition: DispositionType::Ext("form-data".to_owned()), + parameters: vec![ + DispositionParam::Ext("dummy".to_owned(), "3".to_owned()), + DispositionParam::Ext("name".to_owned(), "upload".to_owned()), + DispositionParam::Filename( + Charset::Ext("UTF-8".to_owned()), + None, + "sample.png".bytes().collect()) ] + }; + assert_eq!(a, b); + + let a = "attachment; filename=\"image.jpg\"".into(); + let a: ContentDisposition = ContentDisposition::parse_header(&a).unwrap(); + let b = ContentDisposition { + disposition: DispositionType::Attachment, + parameters: vec![ + DispositionParam::Filename( + Charset::Ext("UTF-8".to_owned()), + None, + "image.jpg".bytes().collect()) ] + }; + assert_eq!(a, b); + + let a = "attachment; filename*=UTF-8''%c2%a3%20and%20%e2%82%ac%20rates".into(); + let a: ContentDisposition = ContentDisposition::parse_header(&a).unwrap(); + let b = ContentDisposition { + disposition: DispositionType::Attachment, + parameters: vec![ + DispositionParam::Filename( + Charset::Ext("UTF-8".to_owned()), + None, + vec![0xc2, 0xa3, 0x20, b'a', b'n', b'd', 0x20, + 0xe2, 0x82, 0xac, 0x20, b'r', b'a', b't', b'e', b's']) ] + }; + assert_eq!(a, b); + } + + #[test] + fn test_display() { + let as_string = "attachment; filename*=UTF-8'en'%C2%A3%20and%20%E2%82%AC%20rates"; + let a = as_string.into(); + let a: ContentDisposition = ContentDisposition::parse_header(&a).unwrap(); + let display_rendered = format!("{}",a); + assert_eq!(as_string, display_rendered); + + let a = "attachment; filename*=UTF-8''black%20and%20white.csv".into(); + let a: ContentDisposition = ContentDisposition::parse_header(&a).unwrap(); + let display_rendered = format!("{}",a); + assert_eq!("attachment; filename=\"black and white.csv\"".to_owned(), display_rendered); + + let a = "attachment; filename=colourful.csv".into(); + let a: ContentDisposition = ContentDisposition::parse_header(&a).unwrap(); + let display_rendered = format!("{}",a); + assert_eq!("attachment; filename=\"colourful.csv\"".to_owned(), display_rendered); + } +} +*/ diff --git a/third_party/rust/headers/src/common/content_encoding.rs b/third_party/rust/headers/src/common/content_encoding.rs new file mode 100644 index 0000000000..444eb41c0b --- /dev/null +++ b/third_party/rust/headers/src/common/content_encoding.rs @@ -0,0 +1,86 @@ +use self::sealed::AsCoding; +use util::FlatCsv; +use HeaderValue; + +/// `Content-Encoding` header, defined in +/// [RFC7231](http://tools.ietf.org/html/rfc7231#section-3.1.2.2) +/// +/// The `Content-Encoding` header field indicates what content codings +/// have been applied to the representation, beyond those inherent in the +/// media type, and thus what decoding mechanisms have to be applied in +/// order to obtain data in the media type referenced by the Content-Type +/// header field. Content-Encoding is primarily used to allow a +/// representation's data to be compressed without losing the identity of +/// its underlying media type. +/// +/// # ABNF +/// +/// ```text +/// Content-Encoding = 1#content-coding +/// ``` +/// +/// # Example values +/// +/// * `gzip` +/// +/// # Examples +/// +/// ``` +/// # extern crate headers; +/// use headers::ContentEncoding; +/// +/// let content_enc = ContentEncoding::gzip(); +/// ``` +#[derive(Clone, Debug)] +pub struct ContentEncoding(FlatCsv); + +derive_header! { + ContentEncoding(_), + name: CONTENT_ENCODING +} + +impl ContentEncoding { + /// A constructor to easily create a `Content-Encoding: gzip` header. + #[inline] + pub fn gzip() -> ContentEncoding { + ContentEncoding(HeaderValue::from_static("gzip").into()) + } + + /// Check if this header contains a given "coding". + /// + /// This can be used with these argument types: + /// + /// - `&str` + /// + /// # Example + /// + /// ``` + /// # extern crate headers; + /// use headers::ContentEncoding; + /// + /// let content_enc = ContentEncoding::gzip(); + /// + /// assert!(content_enc.contains("gzip")); + /// assert!(!content_enc.contains("br")); + /// ``` + pub fn contains(&self, coding: impl AsCoding) -> bool { + let s = coding.as_coding(); + self.0.iter().find(|&opt| opt == s).is_some() + } +} + +mod sealed { + pub trait AsCoding: Sealed {} + + pub trait Sealed { + fn as_coding(&self) -> &str; + } + + impl<'a> AsCoding for &'a str {} + + impl<'a> Sealed for &'a str { + fn as_coding(&self) -> &str { + *self + } + } +} diff --git a/third_party/rust/headers/src/common/content_length.rs b/third_party/rust/headers/src/common/content_length.rs new file mode 100644 index 0000000000..ff8c54234d --- /dev/null +++ b/third_party/rust/headers/src/common/content_length.rs @@ -0,0 +1,95 @@ +use {Header, HeaderValue}; + +/// `Content-Length` header, defined in +/// [RFC7230](http://tools.ietf.org/html/rfc7230#section-3.3.2) +/// +/// When a message does not have a `Transfer-Encoding` header field, a +/// Content-Length header field can provide the anticipated size, as a +/// decimal number of octets, for a potential payload body. For messages +/// that do include a payload body, the Content-Length field-value +/// provides the framing information necessary for determining where the +/// body (and message) ends. For messages that do not include a payload +/// body, the Content-Length indicates the size of the selected +/// representation. +/// +/// Note that setting this header will *remove* any previously set +/// `Transfer-Encoding` header, in accordance with +/// [RFC7230](http://tools.ietf.org/html/rfc7230#section-3.3.2): +/// +/// > A sender MUST NOT send a Content-Length header field in any message +/// > that contains a Transfer-Encoding header field. +/// +/// ## ABNF +/// +/// ```text +/// Content-Length = 1*DIGIT +/// ``` +/// +/// ## Example values +/// +/// * `3495` +/// +/// # Example +/// +/// ``` +/// # extern crate headers; +/// use headers::ContentLength; +/// +/// let len = ContentLength(1_000); +/// ``` +#[derive(Clone, Copy, Debug, PartialEq)] +pub struct ContentLength(pub u64); + +impl Header for ContentLength { + fn name() -> &'static ::http::header::HeaderName { + &::http::header::CONTENT_LENGTH + } + + fn decode<'i, I: Iterator<Item = &'i HeaderValue>>(values: &mut I) -> Result<Self, ::Error> { + // If multiple Content-Length headers were sent, everything can still + // be alright if they all contain the same value, and all parse + // correctly. If not, then it's an error. + let mut len = None; + for value in values { + let parsed = value + .to_str() + .map_err(|_| ::Error::invalid())? + .parse::<u64>() + .map_err(|_| ::Error::invalid())?; + + if let Some(prev) = len { + if prev != parsed { + return Err(::Error::invalid()); + } + } else { + len = Some(parsed); + } + } + + len.map(ContentLength).ok_or_else(::Error::invalid) + } + + fn encode<E: Extend<::HeaderValue>>(&self, values: &mut E) { + values.extend(::std::iter::once(self.0.into())); + } +} + +/* +__hyper__tm!(ContentLength, tests { + // Testcase from RFC + test_header!(test1, vec![b"3495"], Some(HeaderField(3495))); + + test_header!(test_invalid, vec![b"34v95"], None); + + // Can't use the test_header macro because "5, 5" gets cleaned to "5". + #[test] + fn test_duplicates() { + let parsed = HeaderField::parse_header(&vec![b"5".to_vec(), + b"5".to_vec()].into()).unwrap(); + assert_eq!(parsed, HeaderField(5)); + assert_eq!(format!("{}", parsed), "5"); + } + + test_header!(test_duplicates_vary, vec![b"5", b"6", b"5"], None); +}); +*/ diff --git a/third_party/rust/headers/src/common/content_location.rs b/third_party/rust/headers/src/common/content_location.rs new file mode 100644 index 0000000000..29817d1b32 --- /dev/null +++ b/third_party/rust/headers/src/common/content_location.rs @@ -0,0 +1,55 @@ +use HeaderValue; + +/// `Content-Location` header, defined in +/// [RFC7231](https://tools.ietf.org/html/rfc7231#section-3.1.4.2) +/// +/// The header can be used by both the client in requests and the server +/// in responses with different semantics. Client sets `Content-Location` +/// to refer to the URI where original representation of the body was +/// obtained. +/// +/// In responses `Content-Location` represents URI for the representation +/// that was content negotiated, created or for the response payload. +/// +/// # ABNF +/// +/// ```text +/// Content-Location = absolute-URI / partial-URI +/// ``` +/// +/// # Example values +/// +/// * `/hypertext/Overview.html` +/// * `http://www.example.org/hypertext/Overview.html` +/// +/// # Examples +/// +#[derive(Clone, Debug, PartialEq)] +pub struct ContentLocation(HeaderValue); + +derive_header! { + ContentLocation(_), + name: CONTENT_LOCATION +} + +#[cfg(test)] +mod tests { + use super::super::test_decode; + use super::*; + + #[test] + fn absolute_uri() { + let s = "http://www.example.net/index.html"; + let loc = test_decode::<ContentLocation>(&[s]).unwrap(); + + assert_eq!(loc, ContentLocation(HeaderValue::from_static(s))); + } + + #[test] + fn relative_uri_with_fragment() { + let s = "/People.html#tim"; + let loc = test_decode::<ContentLocation>(&[s]).unwrap(); + + assert_eq!(loc, ContentLocation(HeaderValue::from_static(s))); + } +} diff --git a/third_party/rust/headers/src/common/content_range.rs b/third_party/rust/headers/src/common/content_range.rs new file mode 100644 index 0000000000..65cd7965bd --- /dev/null +++ b/third_party/rust/headers/src/common/content_range.rs @@ -0,0 +1,237 @@ +use std::fmt; +use std::ops::{Bound, RangeBounds}; + +use {util, HeaderValue}; + +/// Content-Range, described in [RFC7233](https://tools.ietf.org/html/rfc7233#section-4.2) +/// +/// # ABNF +/// +/// ```text +/// Content-Range = byte-content-range +/// / other-content-range +/// +/// byte-content-range = bytes-unit SP +/// ( byte-range-resp / unsatisfied-range ) +/// +/// byte-range-resp = byte-range "/" ( complete-length / "*" ) +/// byte-range = first-byte-pos "-" last-byte-pos +/// unsatisfied-range = "*/" complete-length +/// +/// complete-length = 1*DIGIT +/// +/// other-content-range = other-range-unit SP other-range-resp +/// other-range-resp = *CHAR +/// ``` +/// +/// # Example +/// +/// ``` +/// # extern crate headers; +/// use headers::ContentRange; +/// +/// // 100 bytes (included byte 199), with a full length of 3,400 +/// let cr = ContentRange::bytes(100..200, 3400).unwrap(); +/// ``` +//NOTE: only supporting bytes-content-range, YAGNI the extension +#[derive(Clone, Debug, PartialEq)] +pub struct ContentRange { + /// First and last bytes of the range, omitted if request could not be + /// satisfied + range: Option<(u64, u64)>, + + /// Total length of the instance, can be omitted if unknown + complete_length: Option<u64>, +} + +error_type!(InvalidContentRange); + +impl ContentRange { + /// Construct a new `Content-Range: bytes ..` header. + pub fn bytes( + range: impl RangeBounds<u64>, + complete_length: impl Into<Option<u64>>, + ) -> Result<ContentRange, InvalidContentRange> { + let complete_length = complete_length.into(); + + let start = match range.start_bound() { + Bound::Included(&s) => s, + Bound::Excluded(&s) => s + 1, + Bound::Unbounded => 0, + }; + + let end = match range.end_bound() { + Bound::Included(&e) => e, + Bound::Excluded(&e) => e - 1, + Bound::Unbounded => match complete_length { + Some(max) => max - 1, + None => return Err(InvalidContentRange { _inner: () }), + }, + }; + + Ok(ContentRange { + range: Some((start, end)), + complete_length, + }) + } + + /// Create a new `ContentRange` stating the range could not be satisfied. + /// + /// The passed argument is the complete length of the entity. + pub fn unsatisfied_bytes(complete_length: u64) -> Self { + ContentRange { + range: None, + complete_length: Some(complete_length), + } + } + + /// Get the byte range if satisified. + /// + /// Note that these byte ranges are inclusive on both ends. + pub fn bytes_range(&self) -> Option<(u64, u64)> { + self.range + } + + /// Get the bytes complete length if available. + pub fn bytes_len(&self) -> Option<u64> { + self.complete_length + } +} + +impl ::Header for ContentRange { + fn name() -> &'static ::HeaderName { + &::http::header::CONTENT_RANGE + } + + fn decode<'i, I: Iterator<Item = &'i HeaderValue>>(values: &mut I) -> Result<Self, ::Error> { + values + .next() + .and_then(|v| v.to_str().ok()) + .and_then(|s| split_in_two(s, ' ')) + .and_then(|(unit, spec)| { + if unit != "bytes" { + // For now, this only supports bytes-content-range. nani? + return None; + } + + let (range, complete_length) = split_in_two(spec, '/')?; + + let complete_length = if complete_length == "*" { + None + } else { + Some(complete_length.parse().ok()?) + }; + + let range = if range == "*" { + None + } else { + let (first_byte, last_byte) = split_in_two(range, '-')?; + let first_byte = first_byte.parse().ok()?; + let last_byte = last_byte.parse().ok()?; + if last_byte < first_byte { + return None; + } + Some((first_byte, last_byte)) + }; + + Some(ContentRange { + range, + complete_length, + }) + }) + .ok_or_else(::Error::invalid) + } + + fn encode<E: Extend<::HeaderValue>>(&self, values: &mut E) { + struct Adapter<'a>(&'a ContentRange); + + impl<'a> fmt::Display for Adapter<'a> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.write_str("bytes ")?; + + if let Some((first_byte, last_byte)) = self.0.range { + write!(f, "{}-{}", first_byte, last_byte)?; + } else { + f.write_str("*")?; + } + + f.write_str("/")?; + + if let Some(v) = self.0.complete_length { + write!(f, "{}", v) + } else { + f.write_str("*") + } + } + } + + values.extend(::std::iter::once(util::fmt(Adapter(self)))); + } +} + +fn split_in_two(s: &str, separator: char) -> Option<(&str, &str)> { + let mut iter = s.splitn(2, separator); + match (iter.next(), iter.next()) { + (Some(a), Some(b)) => Some((a, b)), + _ => None, + } +} + +/* +test_header!(test_bytes, + vec![b"bytes 0-499/500"], + Some(ContentRange(ContentRangeSpec::Bytes { + range: Some((0, 499)), + complete_length: Some(500) + }))); + +test_header!(test_bytes_unknown_len, + vec![b"bytes 0-499/*"], + Some(ContentRange(ContentRangeSpec::Bytes { + range: Some((0, 499)), + complete_length: None + }))); + +test_header!(test_bytes_unknown_range, + vec![b"bytes */ +500"], + Some(ContentRange(ContentRangeSpec::Bytes { + range: None, + complete_length: Some(500) + }))); + + test_header!(test_unregistered, + vec![b"seconds 1-2"], + Some(ContentRange(ContentRangeSpec::Unregistered { + unit: "seconds".to_owned(), + resp: "1-2".to_owned() + }))); + + test_header!(test_no_len, + vec![b"bytes 0-499"], + None::<ContentRange>); + + test_header!(test_only_unit, + vec![b"bytes"], + None::<ContentRange>); + + test_header!(test_end_less_than_start, + vec![b"bytes 499-0/500"], + None::<ContentRange>); + + test_header!(test_blank, + vec![b""], + None::<ContentRange>); + + test_header!(test_bytes_many_spaces, + vec![b"bytes 1-2/500 3"], + None::<ContentRange>); + + test_header!(test_bytes_many_slashes, + vec![b"bytes 1-2/500/600"], + None::<ContentRange>); + + test_header!(test_bytes_many_dashes, + vec![b"bytes 1-2-3/500"], + None::<ContentRange>); +*/ diff --git a/third_party/rust/headers/src/common/content_type.rs b/third_party/rust/headers/src/common/content_type.rs new file mode 100644 index 0000000000..1ae15c2e83 --- /dev/null +++ b/third_party/rust/headers/src/common/content_type.rs @@ -0,0 +1,177 @@ +use std::fmt; + +use mime::{self, Mime}; + +/// `Content-Type` header, defined in +/// [RFC7231](http://tools.ietf.org/html/rfc7231#section-3.1.1.5) +/// +/// The `Content-Type` header field indicates the media type of the +/// associated representation: either the representation enclosed in the +/// message payload or the selected representation, as determined by the +/// message semantics. The indicated media type defines both the data +/// format and how that data is intended to be processed by a recipient, +/// within the scope of the received message semantics, after any content +/// codings indicated by Content-Encoding are decoded. +/// +/// Although the `mime` crate allows the mime options to be any slice, this crate +/// forces the use of Vec. This is to make sure the same header can't have more than 1 type. If +/// this is an issue, it's possible to implement `Header` on a custom struct. +/// +/// # ABNF +/// +/// ```text +/// Content-Type = media-type +/// ``` +/// +/// # Example values +/// +/// * `text/html; charset=utf-8` +/// * `application/json` +/// +/// # Examples +/// +/// ``` +/// # extern crate headers; +/// use headers::ContentType; +/// +/// let ct = ContentType::json(); +/// ``` +#[derive(Clone, Debug, PartialEq)] +pub struct ContentType(Mime); + +impl ContentType { + /// A constructor to easily create a `Content-Type: application/json` header. + #[inline] + pub fn json() -> ContentType { + ContentType(mime::APPLICATION_JSON) + } + + /// A constructor to easily create a `Content-Type: text/plain` header. + #[inline] + pub fn text() -> ContentType { + ContentType(mime::TEXT_PLAIN) + } + + /// A constructor to easily create a `Content-Type: text/plain; charset=utf-8` header. + #[inline] + pub fn text_utf8() -> ContentType { + ContentType(mime::TEXT_PLAIN_UTF_8) + } + + /// A constructor to easily create a `Content-Type: text/html` header. + #[inline] + pub fn html() -> ContentType { + ContentType(mime::TEXT_HTML) + } + + /// A constructor to easily create a `Content-Type: text/xml` header. + #[inline] + pub fn xml() -> ContentType { + ContentType(mime::TEXT_XML) + } + + /// A constructor to easily create a `Content-Type: application/www-form-url-encoded` header. + #[inline] + pub fn form_url_encoded() -> ContentType { + ContentType(mime::APPLICATION_WWW_FORM_URLENCODED) + } + /// A constructor to easily create a `Content-Type: image/jpeg` header. + #[inline] + pub fn jpeg() -> ContentType { + ContentType(mime::IMAGE_JPEG) + } + + /// A constructor to easily create a `Content-Type: image/png` header. + #[inline] + pub fn png() -> ContentType { + ContentType(mime::IMAGE_PNG) + } + + /// A constructor to easily create a `Content-Type: application/octet-stream` header. + #[inline] + pub fn octet_stream() -> ContentType { + ContentType(mime::APPLICATION_OCTET_STREAM) + } +} + +impl ::Header for ContentType { + fn name() -> &'static ::HeaderName { + &::http::header::CONTENT_TYPE + } + + fn decode<'i, I: Iterator<Item = &'i ::HeaderValue>>(values: &mut I) -> Result<Self, ::Error> { + values + .next() + .and_then(|v| v.to_str().ok()?.parse().ok()) + .map(ContentType) + .ok_or_else(::Error::invalid) + } + + fn encode<E: Extend<::HeaderValue>>(&self, values: &mut E) { + let value = self + .0 + .as_ref() + .parse() + .expect("Mime is always a valid HeaderValue"); + values.extend(::std::iter::once(value)); + } +} + +impl From<mime::Mime> for ContentType { + fn from(m: mime::Mime) -> ContentType { + ContentType(m) + } +} + +impl From<ContentType> for mime::Mime { + fn from(ct: ContentType) -> mime::Mime { + ct.0 + } +} + +impl fmt::Display for ContentType { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fmt::Display::fmt(&self.0, f) + } +} + +impl std::str::FromStr for ContentType { + type Err = ::Error; + + fn from_str(s: &str) -> Result<ContentType, Self::Err> { + s.parse::<Mime>() + .map(|m| m.into()) + .map_err(|_| ::Error::invalid()) + } +} + +#[cfg(test)] +mod tests { + use super::super::test_decode; + use super::ContentType; + + #[test] + fn json() { + assert_eq!( + test_decode::<ContentType>(&["application/json"]), + Some(ContentType::json()), + ); + } + + #[test] + fn from_str() { + assert_eq!( + "application/json".parse::<ContentType>().unwrap(), + ContentType::json(), + ); + assert!("invalid-mimetype".parse::<ContentType>().is_err()); + } + + bench_header!(bench_plain, ContentType, "text/plain"); + bench_header!(bench_json, ContentType, "application/json"); + bench_header!( + bench_formdata, + ContentType, + "multipart/form-data; boundary=---------------abcd" + ); +} diff --git a/third_party/rust/headers/src/common/cookie.rs b/third_party/rust/headers/src/common/cookie.rs new file mode 100644 index 0000000000..211ad58043 --- /dev/null +++ b/third_party/rust/headers/src/common/cookie.rs @@ -0,0 +1,205 @@ +use util::{FlatCsv, SemiColon}; + +/// `Cookie` header, defined in [RFC6265](http://tools.ietf.org/html/rfc6265#section-5.4) +/// +/// If the user agent does attach a Cookie header field to an HTTP +/// request, the user agent must send the cookie-string +/// as the value of the header field. +/// +/// When the user agent generates an HTTP request, the user agent MUST NOT +/// attach more than one Cookie header field. +/// +/// # Example values +/// * `SID=31d4d96e407aad42` +/// * `SID=31d4d96e407aad42; lang=en-US` +/// +#[derive(Clone, Debug)] +pub struct Cookie(FlatCsv<SemiColon>); + +derive_header! { + Cookie(_), + name: COOKIE +} + +impl Cookie { + /// Lookup a value for a cookie name. + /// + /// # Example + /// + /// ``` + /// # extern crate headers; + /// use headers::{Cookie, HeaderMap, HeaderMapExt, HeaderValue}; + /// + /// // Setup the header map with strings... + /// let mut headers = HeaderMap::new(); + /// headers.insert("cookie", HeaderValue::from_static("lang=en-US")); + /// + /// // Parse a `Cookie` so we can play with it... + /// let cookie = headers + /// .typed_get::<Cookie>() + /// .expect("we just inserted a valid Cookie"); + /// + /// assert_eq!(cookie.get("lang"), Some("en-US")); + /// assert_eq!(cookie.get("SID"), None); + /// ``` + pub fn get(&self, name: &str) -> Option<&str> { + self.iter() + .find(|&(key, _)| key == name) + .map(|(_, val)| val) + } + + /// Get the number of key-value pairs this `Cookie` contains. + pub fn len(&self) -> usize { + self.iter().count() + } + + /// Iterator the key-value pairs of this `Cookie` header. + pub fn iter(&self) -> impl Iterator<Item = (&str, &str)> { + self.0.iter().filter_map(|kv| { + let mut iter = kv.splitn(2, '='); + let key = iter.next()?.trim(); + let val = iter.next()?.trim(); + Some((key, val)) + }) + } +} + +/* +impl PartialEq for Cookie { + fn eq(&self, other: &Cookie) -> bool { + if self.0.len() == other.0.len() { + for &(ref k, ref v) in self.0.iter() { + if other.get(k) != Some(v) { + return false; + } + } + true + } else { + false + } + } +} +*/ + +#[cfg(test)] +mod tests { + use super::super::test_decode; + use super::Cookie; + + #[test] + fn test_parse() { + let cookie = test_decode::<Cookie>(&["foo=bar"]).unwrap(); + + assert_eq!(cookie.get("foo"), Some("bar")); + assert_eq!(cookie.get("bar"), None); + } + + #[test] + fn test_multipe_same_name() { + let cookie = test_decode::<Cookie>(&["foo=bar; foo=baz"]).unwrap(); + + assert_eq!(cookie.get("foo"), Some("bar")); + } + + #[test] + fn test_multipe_lines() { + let cookie = test_decode::<Cookie>(&["foo=bar", "lol = cat"]).unwrap(); + + assert_eq!(cookie.get("foo"), Some("bar")); + assert_eq!(cookie.get("lol"), Some("cat")); + } + + /* + #[test] + fn test_set_and_get() { + let mut cookie = Cookie::new(); + cookie.append("foo", "bar"); + cookie.append(String::from("dyn"), String::from("amic")); + + assert_eq!(cookie.get("foo"), Some("bar")); + assert_eq!(cookie.get("dyn"), Some("amic")); + assert!(cookie.get("nope").is_none()); + + cookie.append("foo", "notbar"); + assert_eq!(cookie.get("foo"), Some("bar")); + + cookie.set("foo", "hi"); + assert_eq!(cookie.get("foo"), Some("hi")); + assert_eq!(cookie.get("dyn"), Some("amic")); + } + + #[test] + fn test_eq() { + let mut cookie = Cookie::new(); + let mut cookie2 = Cookie::new(); + + // empty is equal + assert_eq!(cookie, cookie2); + + // left has more params + cookie.append("foo", "bar"); + assert_ne!(cookie, cookie2); + + // same len, different params + cookie2.append("bar", "foo"); + assert_ne!(cookie, cookie2); + + + // right has more params, and matching KV + cookie2.append("foo", "bar"); + assert_ne!(cookie, cookie2); + + // same params, different order + cookie.append("bar", "foo"); + assert_eq!(cookie, cookie2); + } + + #[test] + fn test_parse() { + let mut cookie = Cookie::new(); + + let parsed = Cookie::parse_header(&b"foo=bar".to_vec().into()).unwrap(); + cookie.append("foo", "bar"); + assert_eq!(cookie, parsed); + + let parsed = Cookie::parse_header(&b"foo=bar;".to_vec().into()).unwrap(); + assert_eq!(cookie, parsed); + + let parsed = Cookie::parse_header(&b"foo=bar; baz=quux".to_vec().into()).unwrap(); + cookie.append("baz", "quux"); + assert_eq!(cookie, parsed); + + let parsed = Cookie::parse_header(&b"foo=bar;; baz=quux".to_vec().into()).unwrap(); + assert_eq!(cookie, parsed); + + let parsed = Cookie::parse_header(&b"foo=bar; invalid ; bad; ;; baz=quux".to_vec().into()) + .unwrap(); + assert_eq!(cookie, parsed); + + let parsed = Cookie::parse_header(&b" foo = bar;baz= quux ".to_vec().into()).unwrap(); + assert_eq!(cookie, parsed); + + let parsed = + Cookie::parse_header(&vec![b"foo = bar".to_vec(), b"baz= quux ".to_vec()].into()) + .unwrap(); + assert_eq!(cookie, parsed); + + let parsed = Cookie::parse_header(&b"foo=bar; baz=quux ; empty=".to_vec().into()).unwrap(); + cookie.append("empty", ""); + assert_eq!(cookie, parsed); + + + let mut cookie = Cookie::new(); + + let parsed = Cookie::parse_header(&b"middle=equals=in=the=middle".to_vec().into()).unwrap(); + cookie.append("middle", "equals=in=the=middle"); + assert_eq!(cookie, parsed); + + let parsed = + Cookie::parse_header(&b"middle=equals=in=the=middle; double==2".to_vec().into()) + .unwrap(); + cookie.append("double", "=2"); + assert_eq!(cookie, parsed); + } + */ +} diff --git a/third_party/rust/headers/src/common/date.rs b/third_party/rust/headers/src/common/date.rs new file mode 100644 index 0000000000..96dd40f5b8 --- /dev/null +++ b/third_party/rust/headers/src/common/date.rs @@ -0,0 +1,46 @@ +use std::time::SystemTime; +use util::HttpDate; + +/// `Date` header, defined in [RFC7231](http://tools.ietf.org/html/rfc7231#section-7.1.1.2) +/// +/// The `Date` header field represents the date and time at which the +/// message was originated. +/// +/// ## ABNF +/// +/// ```text +/// Date = HTTP-date +/// ``` +/// +/// ## Example values +/// +/// * `Tue, 15 Nov 1994 08:12:31 GMT` +/// +/// # Example +/// +/// ``` +/// # extern crate headers; +/// use headers::Date; +/// use std::time::SystemTime; +/// +/// let date = Date::from(SystemTime::now()); +/// ``` +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct Date(HttpDate); + +derive_header! { + Date(_), + name: DATE +} + +impl From<SystemTime> for Date { + fn from(time: SystemTime) -> Date { + Date(time.into()) + } +} + +impl From<Date> for SystemTime { + fn from(date: Date) -> SystemTime { + date.0.into() + } +} diff --git a/third_party/rust/headers/src/common/etag.rs b/third_party/rust/headers/src/common/etag.rs new file mode 100644 index 0000000000..25846b76bb --- /dev/null +++ b/third_party/rust/headers/src/common/etag.rs @@ -0,0 +1,111 @@ +use std::str::FromStr; +use util::EntityTag; + +/// `ETag` header, defined in [RFC7232](http://tools.ietf.org/html/rfc7232#section-2.3) +/// +/// The `ETag` header field in a response provides the current entity-tag +/// for the selected representation, as determined at the conclusion of +/// handling the request. An entity-tag is an opaque validator for +/// differentiating between multiple representations of the same +/// resource, regardless of whether those multiple representations are +/// due to resource state changes over time, content negotiation +/// resulting in multiple representations being valid at the same time, +/// or both. An entity-tag consists of an opaque quoted string, possibly +/// prefixed by a weakness indicator. +/// +/// # ABNF +/// +/// ```text +/// ETag = entity-tag +/// ``` +/// +/// # Example values +/// +/// * `"xyzzy"` +/// * `W/"xyzzy"` +/// * `""` +/// +/// # Examples +/// +/// ``` +/// let etag = "\"xyzzy\"".parse::<headers::ETag>().unwrap(); +/// ``` +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct ETag(pub(super) EntityTag); + +derive_header! { + ETag(_), + name: ETAG +} + +impl ETag { + #[cfg(test)] + pub(crate) fn from_static(src: &'static str) -> ETag { + ETag(EntityTag::from_static(src)) + } +} + +error_type!(InvalidETag); + +impl FromStr for ETag { + type Err = InvalidETag; + fn from_str(src: &str) -> Result<Self, Self::Err> { + let val = src.parse().map_err(|_| InvalidETag { _inner: () })?; + + EntityTag::from_owned(val) + .map(ETag) + .ok_or_else(|| InvalidETag { _inner: () }) + } +} + +/* +test_etag { + // From the RFC + test_header!(test1, + vec![b"\"xyzzy\""], + Some(ETag(EntityTag::new(false, "xyzzy".to_owned())))); + test_header!(test2, + vec![b"W/\"xyzzy\""], + Some(ETag(EntityTag::new(true, "xyzzy".to_owned())))); + test_header!(test3, + vec![b"\"\""], + Some(ETag(EntityTag::new(false, "".to_owned())))); + // Own tests + test_header!(test4, + vec![b"\"foobar\""], + Some(ETag(EntityTag::new(false, "foobar".to_owned())))); + test_header!(test5, + vec![b"\"\""], + Some(ETag(EntityTag::new(false, "".to_owned())))); + test_header!(test6, + vec![b"W/\"weak-etag\""], + Some(ETag(EntityTag::new(true, "weak-etag".to_owned())))); + test_header!(test7, + vec![b"W/\"\x65\x62\""], + Some(ETag(EntityTag::new(true, "\u{0065}\u{0062}".to_owned())))); + test_header!(test8, + vec![b"W/\"\""], + Some(ETag(EntityTag::new(true, "".to_owned())))); + test_header!(test9, + vec![b"no-dquotes"], + None::<ETag>); + test_header!(test10, + vec![b"w/\"the-first-w-is-case-sensitive\""], + None::<ETag>); + test_header!(test11, + vec![b""], + None::<ETag>); + test_header!(test12, + vec![b"\"unmatched-dquotes1"], + None::<ETag>); + test_header!(test13, + vec![b"unmatched-dquotes2\""], + None::<ETag>); + test_header!(test14, + vec![b"matched-\"dquotes\""], + None::<ETag>); + test_header!(test15, + vec![b"\""], + None::<ETag>); +} +*/ diff --git a/third_party/rust/headers/src/common/expect.rs b/third_party/rust/headers/src/common/expect.rs new file mode 100644 index 0000000000..a1caf2530d --- /dev/null +++ b/third_party/rust/headers/src/common/expect.rs @@ -0,0 +1,86 @@ +use std::fmt; + +use util::IterExt; + +/// The `Expect` header. +/// +/// > The "Expect" header field in a request indicates a certain set of +/// > behaviors (expectations) that need to be supported by the server in +/// > order to properly handle this request. The only such expectation +/// > defined by this specification is 100-continue. +/// > +/// > Expect = "100-continue" +/// +/// # Example +/// +/// ``` +/// # extern crate headers; +/// use headers::Expect; +/// +/// let expect = Expect::CONTINUE; +/// ``` +#[derive(Clone, PartialEq)] +pub struct Expect(()); + +impl Expect { + /// "100-continue" + pub const CONTINUE: Expect = Expect(()); +} + +impl ::Header for Expect { + fn name() -> &'static ::HeaderName { + &::http::header::EXPECT + } + + fn decode<'i, I: Iterator<Item = &'i ::HeaderValue>>(values: &mut I) -> Result<Self, ::Error> { + values + .just_one() + .and_then(|value| { + if value == "100-continue" { + Some(Expect::CONTINUE) + } else { + None + } + }) + .ok_or_else(::Error::invalid) + } + + fn encode<E: Extend<::HeaderValue>>(&self, values: &mut E) { + values.extend(::std::iter::once(::HeaderValue::from_static( + "100-continue", + ))); + } +} + +impl fmt::Debug for Expect { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_tuple("Expect").field(&"100-continue").finish() + } +} + +#[cfg(test)] +mod tests { + use super::super::test_decode; + use super::Expect; + + #[test] + fn expect_continue() { + assert_eq!( + test_decode::<Expect>(&["100-continue"]), + Some(Expect::CONTINUE), + ); + } + + #[test] + fn expectation_failed() { + assert_eq!(test_decode::<Expect>(&["sandwich"]), None,); + } + + #[test] + fn too_many_values() { + assert_eq!( + test_decode::<Expect>(&["100-continue", "100-continue"]), + None, + ); + } +} diff --git a/third_party/rust/headers/src/common/expires.rs b/third_party/rust/headers/src/common/expires.rs new file mode 100644 index 0000000000..26a44eeba9 --- /dev/null +++ b/third_party/rust/headers/src/common/expires.rs @@ -0,0 +1,50 @@ +use std::time::SystemTime; +use util::HttpDate; + +/// `Expires` header, defined in [RFC7234](http://tools.ietf.org/html/rfc7234#section-5.3) +/// +/// The `Expires` header field gives the date/time after which the +/// response is considered stale. +/// +/// The presence of an Expires field does not imply that the original +/// resource will change or cease to exist at, before, or after that +/// time. +/// +/// # ABNF +/// +/// ```text +/// Expires = HTTP-date +/// ``` +/// +/// # Example values +/// * `Thu, 01 Dec 1994 16:00:00 GMT` +/// +/// # Example +/// +/// ``` +/// # extern crate headers; +/// use headers::Expires; +/// use std::time::{SystemTime, Duration}; +/// +/// let time = SystemTime::now() + Duration::from_secs(60 * 60 * 24); +/// let expires = Expires::from(time); +/// ``` +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct Expires(HttpDate); + +derive_header! { + Expires(_), + name: EXPIRES +} + +impl From<SystemTime> for Expires { + fn from(time: SystemTime) -> Expires { + Expires(time.into()) + } +} + +impl From<Expires> for SystemTime { + fn from(date: Expires) -> SystemTime { + date.0.into() + } +} diff --git a/third_party/rust/headers/src/common/host.rs b/third_party/rust/headers/src/common/host.rs new file mode 100644 index 0000000000..7c0d7acd1e --- /dev/null +++ b/third_party/rust/headers/src/common/host.rs @@ -0,0 +1,54 @@ +use std::convert::TryFrom; +use std::fmt; + +use http::uri::Authority; + +/// The `Host` header. +#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd)] +pub struct Host(Authority); + +impl Host { + /// Get the hostname, such as example.domain. + pub fn hostname(&self) -> &str { + self.0.host() + } + + /// Get the optional port number. + pub fn port(&self) -> Option<u16> { + self.0.port_u16() + } +} + +impl ::Header for Host { + fn name() -> &'static ::HeaderName { + &::http::header::HOST + } + + fn decode<'i, I: Iterator<Item = &'i ::HeaderValue>>(values: &mut I) -> Result<Self, ::Error> { + values + .next() + .cloned() + .and_then(|val| Authority::try_from(val.as_bytes()).ok()) + .map(Host) + .ok_or_else(::Error::invalid) + } + + fn encode<E: Extend<::HeaderValue>>(&self, values: &mut E) { + let bytes = self.0.as_str().as_bytes(); + let val = ::HeaderValue::from_bytes(bytes).expect("Authority is a valid HeaderValue"); + + values.extend(::std::iter::once(val)); + } +} + +impl From<Authority> for Host { + fn from(auth: Authority) -> Host { + Host(auth) + } +} + +impl fmt::Display for Host { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fmt::Display::fmt(&self.0, f) + } +} diff --git a/third_party/rust/headers/src/common/if_match.rs b/third_party/rust/headers/src/common/if_match.rs new file mode 100644 index 0000000000..5b9bdd95e3 --- /dev/null +++ b/third_party/rust/headers/src/common/if_match.rs @@ -0,0 +1,111 @@ +use super::ETag; +use util::EntityTagRange; +use HeaderValue; + +/// `If-Match` header, defined in +/// [RFC7232](https://tools.ietf.org/html/rfc7232#section-3.1) +/// +/// The `If-Match` header field makes the request method conditional on +/// the recipient origin server either having at least one current +/// representation of the target resource, when the field-value is "*", +/// or having a current representation of the target resource that has an +/// entity-tag matching a member of the list of entity-tags provided in +/// the field-value. +/// +/// An origin server MUST use the strong comparison function when +/// comparing entity-tags for `If-Match`, since the client +/// intends this precondition to prevent the method from being applied if +/// there have been any changes to the representation data. +/// +/// # ABNF +/// +/// ```text +/// If-Match = "*" / 1#entity-tag +/// ``` +/// +/// # Example values +/// +/// * `"xyzzy"` +/// * "xyzzy", "r2d2xxxx", "c3piozzzz" +/// +/// # Examples +/// +/// ``` +/// # extern crate headers; +/// use headers::IfMatch; +/// +/// let if_match = IfMatch::any(); +/// ``` +#[derive(Clone, Debug, PartialEq)] +pub struct IfMatch(EntityTagRange); + +derive_header! { + IfMatch(_), + name: IF_MATCH +} + +impl IfMatch { + /// Create a new `If-Match: *` header. + pub fn any() -> IfMatch { + IfMatch(EntityTagRange::Any) + } + + /// Returns whether this is `If-Match: *`, matching any entity tag. + pub fn is_any(&self) -> bool { + match self.0 { + EntityTagRange::Any => true, + EntityTagRange::Tags(..) => false, + } + } + + /// Checks whether the `ETag` strongly matches. + pub fn precondition_passes(&self, etag: &ETag) -> bool { + self.0.matches_strong(&etag.0) + } +} + +impl From<ETag> for IfMatch { + fn from(etag: ETag) -> IfMatch { + IfMatch(EntityTagRange::Tags(HeaderValue::from(etag.0).into())) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn is_any() { + assert!(IfMatch::any().is_any()); + assert!(!IfMatch::from(ETag::from_static("\"yolo\"")).is_any()); + } + + #[test] + fn precondition_fails() { + let if_match = IfMatch::from(ETag::from_static("\"foo\"")); + + let bar = ETag::from_static("\"bar\""); + let weak_foo = ETag::from_static("W/\"foo\""); + + assert!(!if_match.precondition_passes(&bar)); + assert!(!if_match.precondition_passes(&weak_foo)); + } + + #[test] + fn precondition_passes() { + let foo = ETag::from_static("\"foo\""); + + let if_match = IfMatch::from(foo.clone()); + + assert!(if_match.precondition_passes(&foo)); + } + + #[test] + fn precondition_any() { + let foo = ETag::from_static("\"foo\""); + + let if_match = IfMatch::any(); + + assert!(if_match.precondition_passes(&foo)); + } +} diff --git a/third_party/rust/headers/src/common/if_modified_since.rs b/third_party/rust/headers/src/common/if_modified_since.rs new file mode 100644 index 0000000000..db64ea75e0 --- /dev/null +++ b/third_party/rust/headers/src/common/if_modified_since.rs @@ -0,0 +1,75 @@ +use std::time::SystemTime; +use util::HttpDate; + +/// `If-Modified-Since` header, defined in +/// [RFC7232](http://tools.ietf.org/html/rfc7232#section-3.3) +/// +/// The `If-Modified-Since` header field makes a GET or HEAD request +/// method conditional on the selected representation's modification date +/// being more recent than the date provided in the field-value. +/// Transfer of the selected representation's data is avoided if that +/// data has not changed. +/// +/// # ABNF +/// +/// ```text +/// If-Modified-Since = HTTP-date +/// ``` +/// +/// # Example values +/// * `Sat, 29 Oct 1994 19:43:31 GMT` +/// +/// # Example +/// +/// ``` +/// # extern crate headers; +/// use headers::IfModifiedSince; +/// use std::time::{Duration, SystemTime}; +/// +/// let time = SystemTime::now() - Duration::from_secs(60 * 60 * 24); +/// let if_mod = IfModifiedSince::from(time); +/// ``` +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct IfModifiedSince(HttpDate); + +derive_header! { + IfModifiedSince(_), + name: IF_MODIFIED_SINCE +} + +impl IfModifiedSince { + /// Check if the supplied time means the resource has been modified. + pub fn is_modified(&self, last_modified: SystemTime) -> bool { + self.0 < last_modified.into() + } +} + +impl From<SystemTime> for IfModifiedSince { + fn from(time: SystemTime) -> IfModifiedSince { + IfModifiedSince(time.into()) + } +} + +impl From<IfModifiedSince> for SystemTime { + fn from(date: IfModifiedSince) -> SystemTime { + date.0.into() + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::time::Duration; + + #[test] + fn is_modified() { + let newer = SystemTime::now(); + let exact = newer - Duration::from_secs(2); + let older = newer - Duration::from_secs(4); + + let if_mod = IfModifiedSince::from(exact); + assert!(if_mod.is_modified(newer)); + assert!(!if_mod.is_modified(exact)); + assert!(!if_mod.is_modified(older)); + } +} diff --git a/third_party/rust/headers/src/common/if_none_match.rs b/third_party/rust/headers/src/common/if_none_match.rs new file mode 100644 index 0000000000..590609cb1d --- /dev/null +++ b/third_party/rust/headers/src/common/if_none_match.rs @@ -0,0 +1,111 @@ +use super::ETag; +use util::EntityTagRange; +use HeaderValue; + +/// `If-None-Match` header, defined in +/// [RFC7232](https://tools.ietf.org/html/rfc7232#section-3.2) +/// +/// The `If-None-Match` header field makes the request method conditional +/// on a recipient cache or origin server either not having any current +/// representation of the target resource, when the field-value is "*", +/// or having a selected representation with an entity-tag that does not +/// match any of those listed in the field-value. +/// +/// A recipient MUST use the weak comparison function when comparing +/// entity-tags for If-None-Match (Section 2.3.2), since weak entity-tags +/// can be used for cache validation even if there have been changes to +/// the representation data. +/// +/// # ABNF +/// +/// ```text +/// If-None-Match = "*" / 1#entity-tag +/// ``` +/// +/// # Example values +/// +/// * `"xyzzy"` +/// * `W/"xyzzy"` +/// * `"xyzzy", "r2d2xxxx", "c3piozzzz"` +/// * `W/"xyzzy", W/"r2d2xxxx", W/"c3piozzzz"` +/// * `*` +/// +/// # Examples +/// +/// ``` +/// # extern crate headers; +/// use headers::IfNoneMatch; +/// +/// let if_none_match = IfNoneMatch::any(); +/// ``` +#[derive(Clone, Debug, PartialEq)] +pub struct IfNoneMatch(EntityTagRange); + +derive_header! { + IfNoneMatch(_), + name: IF_NONE_MATCH +} + +impl IfNoneMatch { + /// Create a new `If-None-Match: *` header. + pub fn any() -> IfNoneMatch { + IfNoneMatch(EntityTagRange::Any) + } + + /// Checks whether the ETag passes this precondition. + pub fn precondition_passes(&self, etag: &ETag) -> bool { + !self.0.matches_weak(&etag.0) + } +} + +impl From<ETag> for IfNoneMatch { + fn from(etag: ETag) -> IfNoneMatch { + IfNoneMatch(EntityTagRange::Tags(HeaderValue::from(etag.0).into())) + } +} + +/* +test_if_none_match { + test_header!(test1, vec![b"\"xyzzy\""]); + test_header!(test2, vec![b"W/\"xyzzy\""]); + test_header!(test3, vec![b"\"xyzzy\", \"r2d2xxxx\", \"c3piozzzz\""]); + test_header!(test4, vec![b"W/\"xyzzy\", W/\"r2d2xxxx\", W/\"c3piozzzz\""]); + test_header!(test5, vec![b"*"]); +} +*/ + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn precondition_fails() { + let foo = ETag::from_static("\"foo\""); + let weak_foo = ETag::from_static("W/\"foo\""); + + let if_none = IfNoneMatch::from(foo.clone()); + + assert!(!if_none.precondition_passes(&foo)); + assert!(!if_none.precondition_passes(&weak_foo)); + } + + #[test] + fn precondition_passes() { + let if_none = IfNoneMatch::from(ETag::from_static("\"foo\"")); + + let bar = ETag::from_static("\"bar\""); + let weak_bar = ETag::from_static("W/\"bar\""); + + assert!(if_none.precondition_passes(&bar)); + assert!(if_none.precondition_passes(&weak_bar)); + } + + #[test] + fn precondition_any() { + let foo = ETag::from_static("\"foo\""); + + let if_none = IfNoneMatch::any(); + + assert!(!if_none.precondition_passes(&foo)); + } +} diff --git a/third_party/rust/headers/src/common/if_range.rs b/third_party/rust/headers/src/common/if_range.rs new file mode 100644 index 0000000000..e2675b431c --- /dev/null +++ b/third_party/rust/headers/src/common/if_range.rs @@ -0,0 +1,136 @@ +use std::time::SystemTime; + +use super::{ETag, LastModified}; +use util::{EntityTag, HttpDate}; +use HeaderValue; + +/// `If-Range` header, defined in [RFC7233](http://tools.ietf.org/html/rfc7233#section-3.2) +/// +/// If a client has a partial copy of a representation and wishes to have +/// an up-to-date copy of the entire representation, it could use the +/// Range header field with a conditional GET (using either or both of +/// If-Unmodified-Since and If-Match.) However, if the precondition +/// fails because the representation has been modified, the client would +/// then have to make a second request to obtain the entire current +/// representation. +/// +/// The `If-Range` header field allows a client to \"short-circuit\" the +/// second request. Informally, its meaning is as follows: if the +/// representation is unchanged, send me the part(s) that I am requesting +/// in Range; otherwise, send me the entire representation. +/// +/// # ABNF +/// +/// ```text +/// If-Range = entity-tag / HTTP-date +/// ``` +/// +/// # Example values +/// +/// * `Sat, 29 Oct 1994 19:43:31 GMT` +/// * `\"xyzzy\"` +/// +/// # Examples +/// +/// ``` +/// # extern crate headers; +/// use headers::IfRange; +/// use std::time::{SystemTime, Duration}; +/// +/// let fetched = SystemTime::now() - Duration::from_secs(60 * 60 * 24); +/// let if_range = IfRange::date(fetched); +/// ``` +#[derive(Clone, Debug, PartialEq)] +pub struct IfRange(IfRange_); + +derive_header! { + IfRange(_), + name: IF_RANGE +} + +impl IfRange { + /// Create an `IfRange` header with an entity tag. + pub fn etag(tag: ETag) -> IfRange { + IfRange(IfRange_::EntityTag(tag.0)) + } + + /// Create an `IfRange` header with a date value. + pub fn date(time: SystemTime) -> IfRange { + IfRange(IfRange_::Date(time.into())) + } + + /// Checks if the resource has been modified, or if the range request + /// can be served. + pub fn is_modified(&self, etag: Option<&ETag>, last_modified: Option<&LastModified>) -> bool { + match self.0 { + IfRange_::Date(since) => last_modified.map(|time| since < time.0).unwrap_or(true), + IfRange_::EntityTag(ref entity) => { + etag.map(|etag| !etag.0.strong_eq(entity)).unwrap_or(true) + } + } + } +} + +#[derive(Clone, Debug, PartialEq)] +enum IfRange_ { + /// The entity-tag the client has of the resource + EntityTag(EntityTag), + /// The date when the client retrieved the resource + Date(HttpDate), +} + +impl ::util::TryFromValues for IfRange_ { + fn try_from_values<'i, I>(values: &mut I) -> Result<Self, ::Error> + where + I: Iterator<Item = &'i HeaderValue>, + { + values + .next() + .and_then(|val| { + if let Some(tag) = EntityTag::from_val(val) { + return Some(IfRange_::EntityTag(tag)); + } + + let date = HttpDate::from_val(val)?; + Some(IfRange_::Date(date)) + }) + .ok_or_else(::Error::invalid) + } +} + +impl<'a> From<&'a IfRange_> for HeaderValue { + fn from(if_range: &'a IfRange_) -> HeaderValue { + match *if_range { + IfRange_::EntityTag(ref tag) => tag.into(), + IfRange_::Date(ref date) => date.into(), + } + } +} + +/* +#[cfg(test)] +mod tests { + use std::str; + use *; + use super::IfRange as HeaderField; + test_header!(test1, vec![b"Sat, 29 Oct 1994 19:43:31 GMT"]); + test_header!(test2, vec![b"\"xyzzy\""]); + test_header!(test3, vec![b"this-is-invalid"], None::<IfRange>); +} +*/ + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_is_modified_etag() { + let etag = ETag::from_static("\"xyzzy\""); + let if_range = IfRange::etag(etag.clone()); + + assert!(!if_range.is_modified(Some(&etag), None)); + + let etag = ETag::from_static("W/\"xyzzy\""); + assert!(if_range.is_modified(Some(&etag), None)); + } +} diff --git a/third_party/rust/headers/src/common/if_unmodified_since.rs b/third_party/rust/headers/src/common/if_unmodified_since.rs new file mode 100644 index 0000000000..d9b32107a1 --- /dev/null +++ b/third_party/rust/headers/src/common/if_unmodified_since.rs @@ -0,0 +1,76 @@ +use std::time::SystemTime; +use util::HttpDate; + +/// `If-Unmodified-Since` header, defined in +/// [RFC7232](http://tools.ietf.org/html/rfc7232#section-3.4) +/// +/// The `If-Unmodified-Since` header field makes the request method +/// conditional on the selected representation's last modification date +/// being earlier than or equal to the date provided in the field-value. +/// This field accomplishes the same purpose as If-Match for cases where +/// the user agent does not have an entity-tag for the representation. +/// +/// # ABNF +/// +/// ```text +/// If-Unmodified-Since = HTTP-date +/// ``` +/// +/// # Example values +/// +/// * `Sat, 29 Oct 1994 19:43:31 GMT` +/// +/// # Example +/// +/// ``` +/// # extern crate headers; +/// use headers::IfUnmodifiedSince; +/// use std::time::{SystemTime, Duration}; +/// +/// let time = SystemTime::now() - Duration::from_secs(60 * 60 * 24); +/// let if_unmod = IfUnmodifiedSince::from(time); +/// ``` +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct IfUnmodifiedSince(HttpDate); + +derive_header! { + IfUnmodifiedSince(_), + name: IF_UNMODIFIED_SINCE +} + +impl IfUnmodifiedSince { + /// Check if the supplied time passes the precondtion. + pub fn precondition_passes(&self, last_modified: SystemTime) -> bool { + self.0 >= last_modified.into() + } +} + +impl From<SystemTime> for IfUnmodifiedSince { + fn from(time: SystemTime) -> IfUnmodifiedSince { + IfUnmodifiedSince(time.into()) + } +} + +impl From<IfUnmodifiedSince> for SystemTime { + fn from(date: IfUnmodifiedSince) -> SystemTime { + date.0.into() + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::time::Duration; + + #[test] + fn precondition_passes() { + let newer = SystemTime::now(); + let exact = newer - Duration::from_secs(2); + let older = newer - Duration::from_secs(4); + + let if_unmod = IfUnmodifiedSince::from(exact); + assert!(!if_unmod.precondition_passes(newer)); + assert!(if_unmod.precondition_passes(exact)); + assert!(if_unmod.precondition_passes(older)); + } +} diff --git a/third_party/rust/headers/src/common/last_modified.rs b/third_party/rust/headers/src/common/last_modified.rs new file mode 100644 index 0000000000..c8f47dabdf --- /dev/null +++ b/third_party/rust/headers/src/common/last_modified.rs @@ -0,0 +1,51 @@ +use std::time::SystemTime; +use util::HttpDate; + +/// `Last-Modified` header, defined in +/// [RFC7232](http://tools.ietf.org/html/rfc7232#section-2.2) +/// +/// The `Last-Modified` header field in a response provides a timestamp +/// indicating the date and time at which the origin server believes the +/// selected representation was last modified, as determined at the +/// conclusion of handling the request. +/// +/// # ABNF +/// +/// ```text +/// Expires = HTTP-date +/// ``` +/// +/// # Example values +/// +/// * `Sat, 29 Oct 1994 19:43:31 GMT` +/// +/// # Example +/// +/// ``` +/// # extern crate headers; +/// use headers::LastModified; +/// use std::time::{Duration, SystemTime}; +/// +/// let modified = LastModified::from( +/// SystemTime::now() - Duration::from_secs(60 * 60 * 24) +/// ); +/// ``` +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct LastModified(pub(super) HttpDate); + +derive_header! { + LastModified(_), + name: LAST_MODIFIED +} + +impl From<SystemTime> for LastModified { + fn from(time: SystemTime) -> LastModified { + LastModified(time.into()) + } +} + +impl From<LastModified> for SystemTime { + fn from(date: LastModified) -> SystemTime { + date.0.into() + } +} diff --git a/third_party/rust/headers/src/common/location.rs b/third_party/rust/headers/src/common/location.rs new file mode 100644 index 0000000000..54a1cc513d --- /dev/null +++ b/third_party/rust/headers/src/common/location.rs @@ -0,0 +1,51 @@ +use HeaderValue; + +/// `Location` header, defined in +/// [RFC7231](http://tools.ietf.org/html/rfc7231#section-7.1.2) +/// +/// The `Location` header field is used in some responses to refer to a +/// specific resource in relation to the response. The type of +/// relationship is defined by the combination of request method and +/// status code semantics. +/// +/// # ABNF +/// +/// ```text +/// Location = URI-reference +/// ``` +/// +/// # Example values +/// * `/People.html#tim` +/// * `http://www.example.net/index.html` +/// +/// # Examples +/// +#[derive(Clone, Debug, PartialEq)] +pub struct Location(HeaderValue); + +derive_header! { + Location(_), + name: LOCATION +} + +#[cfg(test)] +mod tests { + use super::super::test_decode; + use super::*; + + #[test] + fn absolute_uri() { + let s = "http://www.example.net/index.html"; + let loc = test_decode::<Location>(&[s]).unwrap(); + + assert_eq!(loc, Location(HeaderValue::from_static(s))); + } + + #[test] + fn relative_uri_with_fragment() { + let s = "/People.html#tim"; + let loc = test_decode::<Location>(&[s]).unwrap(); + + assert_eq!(loc, Location(HeaderValue::from_static(s))); + } +} diff --git a/third_party/rust/headers/src/common/mod.rs b/third_party/rust/headers/src/common/mod.rs new file mode 100644 index 0000000000..2237ae8e64 --- /dev/null +++ b/third_party/rust/headers/src/common/mod.rs @@ -0,0 +1,190 @@ +//! A Collection of Header implementations for common HTTP Headers. +//! +//! ## Mime +//! +//! Several header fields use MIME values for their contents. Keeping with the +//! strongly-typed theme, the [mime](https://docs.rs/mime) crate +//! is used, such as `ContentType(pub Mime)`. + +//pub use self::accept_charset::AcceptCharset; +//pub use self::accept_encoding::AcceptEncoding; +//pub use self::accept_language::AcceptLanguage; +pub use self::accept_ranges::AcceptRanges; +//pub use self::accept::Accept; +pub use self::access_control_allow_credentials::AccessControlAllowCredentials; +pub use self::access_control_allow_headers::AccessControlAllowHeaders; +pub use self::access_control_allow_methods::AccessControlAllowMethods; +pub use self::access_control_allow_origin::AccessControlAllowOrigin; +pub use self::access_control_expose_headers::AccessControlExposeHeaders; +pub use self::access_control_max_age::AccessControlMaxAge; +pub use self::access_control_request_headers::AccessControlRequestHeaders; +pub use self::access_control_request_method::AccessControlRequestMethod; +pub use self::age::Age; +pub use self::allow::Allow; +pub use self::authorization::Authorization; +pub use self::cache_control::CacheControl; +pub use self::connection::Connection; +pub use self::content_disposition::ContentDisposition; +pub use self::content_encoding::ContentEncoding; +//pub use self::content_language::ContentLanguage; +pub use self::content_length::ContentLength; +pub use self::content_location::ContentLocation; +pub use self::content_range::ContentRange; +pub use self::content_type::ContentType; +pub use self::cookie::Cookie; +pub use self::date::Date; +pub use self::etag::ETag; +pub use self::expect::Expect; +pub use self::expires::Expires; +//pub use self::from::From; +pub use self::host::Host; +pub use self::if_match::IfMatch; +pub use self::if_modified_since::IfModifiedSince; +pub use self::if_none_match::IfNoneMatch; +pub use self::if_range::IfRange; +pub use self::if_unmodified_since::IfUnmodifiedSince; +//pub use self::last_event_id::LastEventId; +pub use self::last_modified::LastModified; +//pub use self::link::{Link, LinkValue, RelationType, MediaDesc}; +pub use self::location::Location; +pub use self::origin::Origin; +pub use self::pragma::Pragma; +//pub use self::prefer::{Prefer, Preference}; +//pub use self::preference_applied::PreferenceApplied; +pub use self::proxy_authorization::ProxyAuthorization; +pub use self::range::Range; +pub use self::referer::Referer; +pub use self::referrer_policy::ReferrerPolicy; +pub use self::retry_after::RetryAfter; +pub use self::sec_websocket_accept::SecWebsocketAccept; +pub use self::sec_websocket_key::SecWebsocketKey; +pub use self::sec_websocket_version::SecWebsocketVersion; +pub use self::server::Server; +pub use self::set_cookie::SetCookie; +pub use self::strict_transport_security::StrictTransportSecurity; +pub use self::te::Te; +pub use self::transfer_encoding::TransferEncoding; +pub use self::upgrade::Upgrade; +pub use self::user_agent::UserAgent; +pub use self::vary::Vary; +//pub use self::warning::Warning; + +#[cfg(test)] +fn test_decode<T: ::Header>(values: &[&str]) -> Option<T> { + use HeaderMapExt; + let mut map = ::http::HeaderMap::new(); + for val in values { + map.append(T::name(), val.parse().unwrap()); + } + map.typed_get() +} + +#[cfg(test)] +fn test_encode<T: ::Header>(header: T) -> ::http::HeaderMap { + use HeaderMapExt; + let mut map = ::http::HeaderMap::new(); + map.typed_insert(header); + map +} + +#[cfg(test)] +macro_rules! bench_header { + ($mod:ident, $ty:ident, $value:expr) => { + #[cfg(feature = "nightly")] + mod $mod { + use super::$ty; + use HeaderMapExt; + + #[bench] + fn bench_decode(b: &mut ::test::Bencher) { + let mut map = ::http::HeaderMap::new(); + map.append( + <$ty as ::Header>::name(), + $value.parse().expect("HeaderValue::from_str($value)"), + ); + b.bytes = $value.len() as u64; + b.iter(|| { + map.typed_get::<$ty>().unwrap(); + }); + } + + #[bench] + fn bench_encode(b: &mut ::test::Bencher) { + let mut map = ::http::HeaderMap::new(); + map.append( + <$ty as ::Header>::name(), + $value.parse().expect("HeaderValue::from_str($value)"), + ); + let typed = map.typed_get::<$ty>().unwrap(); + b.bytes = $value.len() as u64; + b.iter(|| { + map.typed_insert(typed.clone()); + map.clear(); + }); + } + } + }; +} + +//mod accept; +//mod accept_charset; +//mod accept_encoding; +//mod accept_language; +mod accept_ranges; +mod access_control_allow_credentials; +mod access_control_allow_headers; +mod access_control_allow_methods; +mod access_control_allow_origin; +mod access_control_expose_headers; +mod access_control_max_age; +mod access_control_request_headers; +mod access_control_request_method; +mod age; +mod allow; +pub mod authorization; +mod cache_control; +mod connection; +mod content_disposition; +mod content_encoding; +//mod content_language; +mod content_length; +mod content_location; +mod content_range; +mod content_type; +mod cookie; +mod date; +mod etag; +mod expect; +mod expires; +//mod from; +mod host; +mod if_match; +mod if_modified_since; +mod if_none_match; +mod if_range; +mod if_unmodified_since; +//mod last_event_id; +mod last_modified; +//mod link; +mod location; +mod origin; +mod pragma; +//mod prefer; +//mod preference_applied; +mod proxy_authorization; +mod range; +mod referer; +mod referrer_policy; +mod retry_after; +mod sec_websocket_accept; +mod sec_websocket_key; +mod sec_websocket_version; +mod server; +mod set_cookie; +mod strict_transport_security; +mod te; +mod transfer_encoding; +mod upgrade; +mod user_agent; +mod vary; +//mod warning; diff --git a/third_party/rust/headers/src/common/origin.rs b/third_party/rust/headers/src/common/origin.rs new file mode 100644 index 0000000000..6d4a022e00 --- /dev/null +++ b/third_party/rust/headers/src/common/origin.rs @@ -0,0 +1,210 @@ +use std::convert::TryFrom; +use std::fmt; + +use bytes::Bytes; +use http::uri::{self, Authority, Scheme, Uri}; + +use util::{IterExt, TryFromValues}; +use HeaderValue; + +/// The `Origin` header. +/// +/// The `Origin` header is a version of the `Referer` header that is used for all HTTP fetches and `POST`s whose CORS flag is set. +/// This header is often used to inform recipients of the security context of where the request was initiated. +/// +/// Following the spec, [https://fetch.spec.whatwg.org/#origin-header][url], the value of this header is composed of +/// a String (scheme), Host (host/port) +/// +/// [url]: https://fetch.spec.whatwg.org/#origin-header +/// +/// # Examples +/// +/// ``` +/// # extern crate headers; +/// use headers::Origin; +/// +/// let origin = Origin::NULL; +/// ``` +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub struct Origin(OriginOrNull); + +derive_header! { + Origin(_), + name: ORIGIN +} + +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +enum OriginOrNull { + Origin(Scheme, Authority), + Null, +} + +impl Origin { + /// The literal `null` Origin header. + pub const NULL: Origin = Origin(OriginOrNull::Null); + + /// Checks if `Origin` is `null`. + #[inline] + pub fn is_null(&self) -> bool { + match self.0 { + OriginOrNull::Null => true, + _ => false, + } + } + + /// Get the "scheme" part of this origin. + #[inline] + pub fn scheme(&self) -> &str { + match self.0 { + OriginOrNull::Origin(ref scheme, _) => scheme.as_str(), + OriginOrNull::Null => "", + } + } + + /// Get the "hostname" part of this origin. + #[inline] + pub fn hostname(&self) -> &str { + match self.0 { + OriginOrNull::Origin(_, ref auth) => auth.host(), + OriginOrNull::Null => "", + } + } + + /// Get the "port" part of this origin. + #[inline] + pub fn port(&self) -> Option<u16> { + match self.0 { + OriginOrNull::Origin(_, ref auth) => auth.port_u16(), + OriginOrNull::Null => None, + } + } + + /// Tries to build a `Origin` from three parts, the scheme, the host and an optional port. + pub fn try_from_parts( + scheme: &str, + host: &str, + port: impl Into<Option<u16>>, + ) -> Result<Self, InvalidOrigin> { + struct MaybePort(Option<u16>); + + impl fmt::Display for MaybePort { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + if let Some(port) = self.0 { + write!(f, ":{}", port) + } else { + Ok(()) + } + } + } + + let bytes = Bytes::from(format!("{}://{}{}", scheme, host, MaybePort(port.into()))); + HeaderValue::from_maybe_shared(bytes) + .ok() + .and_then(|val| Self::try_from_value(&val)) + .ok_or_else(|| InvalidOrigin { _inner: () }) + } + + // Used in AccessControlAllowOrigin + pub(super) fn try_from_value(value: &HeaderValue) -> Option<Self> { + OriginOrNull::try_from_value(value).map(Origin) + } + + pub(super) fn into_value(&self) -> HeaderValue { + (&self.0).into() + } +} + +impl fmt::Display for Origin { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self.0 { + OriginOrNull::Origin(ref scheme, ref auth) => write!(f, "{}://{}", scheme, auth), + OriginOrNull::Null => f.write_str("null"), + } + } +} + +error_type!(InvalidOrigin); + +impl OriginOrNull { + fn try_from_value(value: &HeaderValue) -> Option<Self> { + if value == "null" { + return Some(OriginOrNull::Null); + } + + let uri = Uri::try_from(value.as_bytes()).ok()?; + + let (scheme, auth) = match uri.into_parts() { + uri::Parts { + scheme: Some(scheme), + authority: Some(auth), + path_and_query: None, + .. + } => (scheme, auth), + uri::Parts { + scheme: Some(ref scheme), + authority: Some(ref auth), + path_and_query: Some(ref p), + .. + } if p == "/" => (scheme.clone(), auth.clone()), + _ => { + return None; + } + }; + + Some(OriginOrNull::Origin(scheme, auth)) + } +} + +impl TryFromValues for OriginOrNull { + fn try_from_values<'i, I>(values: &mut I) -> Result<Self, ::Error> + where + I: Iterator<Item = &'i HeaderValue>, + { + values + .just_one() + .and_then(OriginOrNull::try_from_value) + .ok_or_else(::Error::invalid) + } +} + +impl<'a> From<&'a OriginOrNull> for HeaderValue { + fn from(origin: &'a OriginOrNull) -> HeaderValue { + match origin { + OriginOrNull::Origin(ref scheme, ref auth) => { + let s = format!("{}://{}", scheme, auth); + let bytes = Bytes::from(s); + HeaderValue::from_maybe_shared(bytes) + .expect("Scheme and Authority are valid header values") + } + // Serialized as "null" per ASCII serialization of an origin + // https://html.spec.whatwg.org/multipage/browsers.html#ascii-serialisation-of-an-origin + OriginOrNull::Null => HeaderValue::from_static("null"), + } + } +} + +#[cfg(test)] +mod tests { + use super::super::{test_decode, test_encode}; + use super::*; + + #[test] + fn origin() { + let s = "http://web-platform.test:8000"; + let origin = test_decode::<Origin>(&[s]).unwrap(); + assert_eq!(origin.scheme(), "http"); + assert_eq!(origin.hostname(), "web-platform.test"); + assert_eq!(origin.port(), Some(8000)); + + let headers = test_encode(origin); + assert_eq!(headers["origin"], s); + } + + #[test] + fn null() { + assert_eq!(test_decode::<Origin>(&["null"]), Some(Origin::NULL),); + + let headers = test_encode(Origin::NULL); + assert_eq!(headers["origin"], "null"); + } +} diff --git a/third_party/rust/headers/src/common/pragma.rs b/third_party/rust/headers/src/common/pragma.rs new file mode 100644 index 0000000000..9b953978ff --- /dev/null +++ b/third_party/rust/headers/src/common/pragma.rs @@ -0,0 +1,61 @@ +use HeaderValue; + +/// The `Pragma` header defined by HTTP/1.0. +/// +/// > The "Pragma" header field allows backwards compatibility with +/// > HTTP/1.0 caches, so that clients can specify a "no-cache" request +/// > that they will understand (as Cache-Control was not defined until +/// > HTTP/1.1). When the Cache-Control header field is also present and +/// > understood in a request, Pragma is ignored. +/// > In HTTP/1.0, Pragma was defined as an extensible field for +/// > implementation-specified directives for recipients. This +/// > specification deprecates such extensions to improve interoperability. +/// +/// Spec: [https://tools.ietf.org/html/rfc7234#section-5.4][url] +/// +/// [url]: https://tools.ietf.org/html/rfc7234#section-5.4 +/// +/// # Examples +/// +/// ``` +/// # extern crate headers; +/// use headers::Pragma; +/// +/// let pragma = Pragma::no_cache(); +/// ``` +#[derive(Clone, Debug, PartialEq)] +pub struct Pragma(HeaderValue); + +derive_header! { + Pragma(_), + name: PRAGMA +} + +impl Pragma { + /// Construct the literal `no-cache` Pragma header. + pub fn no_cache() -> Pragma { + Pragma(HeaderValue::from_static("no-cache")) + } + + /// Return whether this pragma is `no-cache`. + pub fn is_no_cache(&self) -> bool { + self.0 == "no-cache" + } +} + +#[cfg(test)] +mod tests { + use super::super::test_decode; + use super::Pragma; + + #[test] + fn no_cache_is_no_cache() { + assert!(Pragma::no_cache().is_no_cache()); + } + + #[test] + fn etc_is_not_no_cache() { + let ext = test_decode::<Pragma>(&["dexter"]).unwrap(); + assert!(!ext.is_no_cache()); + } +} diff --git a/third_party/rust/headers/src/common/proxy_authorization.rs b/third_party/rust/headers/src/common/proxy_authorization.rs new file mode 100644 index 0000000000..50a6b0a4a8 --- /dev/null +++ b/third_party/rust/headers/src/common/proxy_authorization.rs @@ -0,0 +1,47 @@ +use super::authorization::{Authorization, Credentials}; + +/// `Proxy-Authorization` header, defined in [RFC7235](https://tools.ietf.org/html/rfc7235#section-4.4) +/// +/// The `Proxy-Authorization` header field allows a user agent to authenticate +/// itself with an HTTP proxy -- usually, but not necessarily, after +/// receiving a 407 (Proxy Authentication Required) response and the +/// `Proxy-Authenticate` header. Its value consists of credentials containing +/// the authentication information of the user agent for the realm of the +/// resource being requested. +/// +/// # ABNF +/// +/// ```text +/// Proxy-Authorization = credentials +/// ``` +/// +/// # Example values +/// * `Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==` +/// * `Bearer fpKL54jvWmEGVoRdCNjG` +/// +/// # Examples +/// +#[derive(Clone, PartialEq, Debug)] +pub struct ProxyAuthorization<C: Credentials>(pub C); + +impl<C: Credentials> ::Header for ProxyAuthorization<C> { + fn name() -> &'static ::HeaderName { + &::http::header::PROXY_AUTHORIZATION + } + + fn decode<'i, I: Iterator<Item = &'i ::HeaderValue>>(values: &mut I) -> Result<Self, ::Error> { + Authorization::decode(values).map(|auth| ProxyAuthorization(auth.0)) + } + + fn encode<E: Extend<::HeaderValue>>(&self, values: &mut E) { + let value = self.0.encode(); + debug_assert!( + value.as_bytes().starts_with(C::SCHEME.as_bytes()), + "Credentials::encode should include its scheme: scheme = {:?}, encoded = {:?}", + C::SCHEME, + value, + ); + + values.extend(::std::iter::once(value)); + } +} diff --git a/third_party/rust/headers/src/common/range.rs b/third_party/rust/headers/src/common/range.rs new file mode 100644 index 0000000000..29cc79d3c5 --- /dev/null +++ b/third_party/rust/headers/src/common/range.rs @@ -0,0 +1,418 @@ +use std::ops::{Bound, RangeBounds}; + +/// `Range` header, defined in [RFC7233](https://tools.ietf.org/html/rfc7233#section-3.1) +/// +/// The "Range" header field on a GET request modifies the method +/// semantics to request transfer of only one or more subranges of the +/// selected representation data, rather than the entire selected +/// representation data. +/// +/// # ABNF +/// +/// ```text +/// Range = byte-ranges-specifier / other-ranges-specifier +/// other-ranges-specifier = other-range-unit "=" other-range-set +/// other-range-set = 1*VCHAR +/// +/// bytes-unit = "bytes" +/// +/// byte-ranges-specifier = bytes-unit "=" byte-range-set +/// byte-range-set = 1#(byte-range-spec / suffix-byte-range-spec) +/// byte-range-spec = first-byte-pos "-" [last-byte-pos] +/// first-byte-pos = 1*DIGIT +/// last-byte-pos = 1*DIGIT +/// ``` +/// +/// # Example values +/// +/// * `bytes=1000-` +/// * `bytes=-2000` +/// * `bytes=0-1,30-40` +/// * `bytes=0-10,20-90,-100` +/// +/// # Examples +/// +/// ``` +/// # extern crate headers; +/// use headers::Range; +/// +/// +/// let range = Range::bytes(0..1234).unwrap(); +/// ``` +#[derive(Clone, Debug, PartialEq)] +pub struct Range(::HeaderValue); + +error_type!(InvalidRange); + +impl Range { + /// Creates a `Range` header from bounds. + pub fn bytes(bounds: impl RangeBounds<u64>) -> Result<Self, InvalidRange> { + let v = match (bounds.start_bound(), bounds.end_bound()) { + (Bound::Unbounded, Bound::Included(end)) => format!("bytes=-{}", end), + (Bound::Unbounded, Bound::Excluded(&end)) => format!("bytes=-{}", end - 1), + (Bound::Included(start), Bound::Included(end)) => format!("bytes={}-{}", start, end), + (Bound::Included(start), Bound::Excluded(&end)) => { + format!("bytes={}-{}", start, end - 1) + } + (Bound::Included(start), Bound::Unbounded) => format!("bytes={}-", start), + _ => return Err(InvalidRange { _inner: () }), + }; + + Ok(Range(::HeaderValue::from_str(&v).unwrap())) + } + + /// Iterate the range sets as a tuple of bounds. + pub fn iter<'a>(&'a self) -> impl Iterator<Item = (Bound<u64>, Bound<u64>)> + 'a { + let s = self + .0 + .to_str() + .expect("valid string checked in Header::decode()"); + + s["bytes=".len()..].split(',').filter_map(|spec| { + let mut iter = spec.trim().splitn(2, '-'); + Some((parse_bound(iter.next()?)?, parse_bound(iter.next()?)?)) + }) + } +} + +fn parse_bound(s: &str) -> Option<Bound<u64>> { + if s.is_empty() { + return Some(Bound::Unbounded); + } + + s.parse().ok().map(Bound::Included) +} + +impl ::Header for Range { + fn name() -> &'static ::HeaderName { + &::http::header::RANGE + } + + fn decode<'i, I: Iterator<Item = &'i ::HeaderValue>>(values: &mut I) -> Result<Self, ::Error> { + values + .next() + .and_then(|val| { + if val.to_str().ok()?.starts_with("bytes=") { + Some(Range(val.clone())) + } else { + None + } + }) + .ok_or_else(::Error::invalid) + } + + fn encode<E: Extend<::HeaderValue>>(&self, values: &mut E) { + values.extend(::std::iter::once(self.0.clone())); + } +} + +/* + +impl ByteRangeSpec { + /// Given the full length of the entity, attempt to normalize the byte range + /// into an satisfiable end-inclusive (from, to) range. + /// + /// The resulting range is guaranteed to be a satisfiable range within the bounds + /// of `0 <= from <= to < full_length`. + /// + /// If the byte range is deemed unsatisfiable, `None` is returned. + /// An unsatisfiable range is generally cause for a server to either reject + /// the client request with a `416 Range Not Satisfiable` status code, or to + /// simply ignore the range header and serve the full entity using a `200 OK` + /// status code. + /// + /// This function closely follows [RFC 7233][1] section 2.1. + /// As such, it considers ranges to be satisfiable if they meet the following + /// conditions: + /// + /// > If a valid byte-range-set includes at least one byte-range-spec with + /// a first-byte-pos that is less than the current length of the + /// representation, or at least one suffix-byte-range-spec with a + /// non-zero suffix-length, then the byte-range-set is satisfiable. + /// Otherwise, the byte-range-set is unsatisfiable. + /// + /// The function also computes remainder ranges based on the RFC: + /// + /// > If the last-byte-pos value is + /// absent, or if the value is greater than or equal to the current + /// length of the representation data, the byte range is interpreted as + /// the remainder of the representation (i.e., the server replaces the + /// value of last-byte-pos with a value that is one less than the current + /// length of the selected representation). + /// + /// [1]: https://tools.ietf.org/html/rfc7233 + pub fn to_satisfiable_range(&self, full_length: u64) -> Option<(u64, u64)> { + // If the full length is zero, there is no satisfiable end-inclusive range. + if full_length == 0 { + return None; + } + match self { + &ByteRangeSpec::FromTo(from, to) => { + if from < full_length && from <= to { + Some((from, ::std::cmp::min(to, full_length - 1))) + } else { + None + } + }, + &ByteRangeSpec::AllFrom(from) => { + if from < full_length { + Some((from, full_length - 1)) + } else { + None + } + }, + &ByteRangeSpec::Last(last) => { + if last > 0 { + // From the RFC: If the selected representation is shorter + // than the specified suffix-length, + // the entire representation is used. + if last > full_length { + Some((0, full_length - 1)) + } else { + Some((full_length - last, full_length - 1)) + } + } else { + None + } + } + } + } +} + +impl Range { + /// Get the most common byte range header ("bytes=from-to") + pub fn bytes(from: u64, to: u64) -> Range { + Range::Bytes(vec![ByteRangeSpec::FromTo(from, to)]) + } + + /// Get byte range header with multiple subranges + /// ("bytes=from1-to1,from2-to2,fromX-toX") + pub fn bytes_multi(ranges: Vec<(u64, u64)>) -> Range { + Range::Bytes(ranges.iter().map(|r| ByteRangeSpec::FromTo(r.0, r.1)).collect()) + } +} + + +impl fmt::Display for ByteRangeSpec { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + ByteRangeSpec::FromTo(from, to) => write!(f, "{}-{}", from, to), + ByteRangeSpec::Last(pos) => write!(f, "-{}", pos), + ByteRangeSpec::AllFrom(pos) => write!(f, "{}-", pos), + } + } +} + + +impl fmt::Display for Range { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + Range::Bytes(ref ranges) => { + try!(write!(f, "bytes=")); + + for (i, range) in ranges.iter().enumerate() { + if i != 0 { + try!(f.write_str(",")); + } + try!(Display::fmt(range, f)); + } + Ok(()) + }, + Range::Unregistered(ref unit, ref range_str) => { + write!(f, "{}={}", unit, range_str) + }, + } + } +} + +impl FromStr for Range { + type Err = ::Error; + + fn from_str(s: &str) -> ::Result<Range> { + let mut iter = s.splitn(2, '='); + + match (iter.next(), iter.next()) { + (Some("bytes"), Some(ranges)) => { + let ranges = from_comma_delimited(ranges); + if ranges.is_empty() { + return Err(::Error::Header); + } + Ok(Range::Bytes(ranges)) + } + (Some(unit), Some(range_str)) if unit != "" && range_str != "" => { + Ok(Range::Unregistered(unit.to_owned(), range_str.to_owned())) + + }, + _ => Err(::Error::Header) + } + } +} + +impl FromStr for ByteRangeSpec { + type Err = ::Error; + + fn from_str(s: &str) -> ::Result<ByteRangeSpec> { + let mut parts = s.splitn(2, '-'); + + match (parts.next(), parts.next()) { + (Some(""), Some(end)) => { + end.parse().or(Err(::Error::Header)).map(ByteRangeSpec::Last) + }, + (Some(start), Some("")) => { + start.parse().or(Err(::Error::Header)).map(ByteRangeSpec::AllFrom) + }, + (Some(start), Some(end)) => { + match (start.parse(), end.parse()) { + (Ok(start), Ok(end)) if start <= end => Ok(ByteRangeSpec::FromTo(start, end)), + _ => Err(::Error::Header) + } + }, + _ => Err(::Error::Header) + } + } +} + +fn from_comma_delimited<T: FromStr>(s: &str) -> Vec<T> { + s.split(',') + .filter_map(|x| match x.trim() { + "" => None, + y => Some(y) + }) + .filter_map(|x| x.parse().ok()) + .collect() +} + +impl Header for Range { + + fn header_name() -> &'static str { + static NAME: &'static str = "Range"; + NAME + } + + fn parse_header(raw: &Raw) -> ::Result<Range> { + from_one_raw_str(raw) + } + + fn fmt_header(&self, f: &mut ::Formatter) -> fmt::Result { + f.fmt_line(self) + } + +} + +#[test] +fn test_parse_bytes_range_valid() { + let r: Range = Header::parse_header(&"bytes=1-100".into()).unwrap(); + let r2: Range = Header::parse_header(&"bytes=1-100,-".into()).unwrap(); + let r3 = Range::bytes(1, 100); + assert_eq!(r, r2); + assert_eq!(r2, r3); + + let r: Range = Header::parse_header(&"bytes=1-100,200-".into()).unwrap(); + let r2: Range = Header::parse_header(&"bytes= 1-100 , 101-xxx, 200- ".into()).unwrap(); + let r3 = Range::Bytes( + vec![ByteRangeSpec::FromTo(1, 100), ByteRangeSpec::AllFrom(200)] + ); + assert_eq!(r, r2); + assert_eq!(r2, r3); + + let r: Range = Header::parse_header(&"bytes=1-100,-100".into()).unwrap(); + let r2: Range = Header::parse_header(&"bytes=1-100, ,,-100".into()).unwrap(); + let r3 = Range::Bytes( + vec![ByteRangeSpec::FromTo(1, 100), ByteRangeSpec::Last(100)] + ); + assert_eq!(r, r2); + assert_eq!(r2, r3); + + let r: Range = Header::parse_header(&"custom=1-100,-100".into()).unwrap(); + let r2 = Range::Unregistered("custom".to_owned(), "1-100,-100".to_owned()); + assert_eq!(r, r2); + +} + +#[test] +fn test_parse_unregistered_range_valid() { + let r: Range = Header::parse_header(&"custom=1-100,-100".into()).unwrap(); + let r2 = Range::Unregistered("custom".to_owned(), "1-100,-100".to_owned()); + assert_eq!(r, r2); + + let r: Range = Header::parse_header(&"custom=abcd".into()).unwrap(); + let r2 = Range::Unregistered("custom".to_owned(), "abcd".to_owned()); + assert_eq!(r, r2); + + let r: Range = Header::parse_header(&"custom=xxx-yyy".into()).unwrap(); + let r2 = Range::Unregistered("custom".to_owned(), "xxx-yyy".to_owned()); + assert_eq!(r, r2); +} + +#[test] +fn test_parse_invalid() { + let r: ::Result<Range> = Header::parse_header(&"bytes=1-a,-".into()); + assert_eq!(r.ok(), None); + + let r: ::Result<Range> = Header::parse_header(&"bytes=1-2-3".into()); + assert_eq!(r.ok(), None); + + let r: ::Result<Range> = Header::parse_header(&"abc".into()); + assert_eq!(r.ok(), None); + + let r: ::Result<Range> = Header::parse_header(&"bytes=1-100=".into()); + assert_eq!(r.ok(), None); + + let r: ::Result<Range> = Header::parse_header(&"bytes=".into()); + assert_eq!(r.ok(), None); + + let r: ::Result<Range> = Header::parse_header(&"custom=".into()); + assert_eq!(r.ok(), None); + + let r: ::Result<Range> = Header::parse_header(&"=1-100".into()); + assert_eq!(r.ok(), None); +} + +#[test] +fn test_fmt() { + use Headers; + + let mut headers = Headers::new(); + + headers.set( + Range::Bytes( + vec![ByteRangeSpec::FromTo(0, 1000), ByteRangeSpec::AllFrom(2000)] + )); + assert_eq!(&headers.to_string(), "Range: bytes=0-1000,2000-\r\n"); + + headers.clear(); + headers.set(Range::Bytes(vec![])); + + assert_eq!(&headers.to_string(), "Range: bytes=\r\n"); + + headers.clear(); + headers.set(Range::Unregistered("custom".to_owned(), "1-xxx".to_owned())); + + assert_eq!(&headers.to_string(), "Range: custom=1-xxx\r\n"); +} + +#[test] +fn test_byte_range_spec_to_satisfiable_range() { + assert_eq!(Some((0, 0)), ByteRangeSpec::FromTo(0, 0).to_satisfiable_range(3)); + assert_eq!(Some((1, 2)), ByteRangeSpec::FromTo(1, 2).to_satisfiable_range(3)); + assert_eq!(Some((1, 2)), ByteRangeSpec::FromTo(1, 5).to_satisfiable_range(3)); + assert_eq!(None, ByteRangeSpec::FromTo(3, 3).to_satisfiable_range(3)); + assert_eq!(None, ByteRangeSpec::FromTo(2, 1).to_satisfiable_range(3)); + assert_eq!(None, ByteRangeSpec::FromTo(0, 0).to_satisfiable_range(0)); + + assert_eq!(Some((0, 2)), ByteRangeSpec::AllFrom(0).to_satisfiable_range(3)); + assert_eq!(Some((2, 2)), ByteRangeSpec::AllFrom(2).to_satisfiable_range(3)); + assert_eq!(None, ByteRangeSpec::AllFrom(3).to_satisfiable_range(3)); + assert_eq!(None, ByteRangeSpec::AllFrom(5).to_satisfiable_range(3)); + assert_eq!(None, ByteRangeSpec::AllFrom(0).to_satisfiable_range(0)); + + assert_eq!(Some((1, 2)), ByteRangeSpec::Last(2).to_satisfiable_range(3)); + assert_eq!(Some((2, 2)), ByteRangeSpec::Last(1).to_satisfiable_range(3)); + assert_eq!(Some((0, 2)), ByteRangeSpec::Last(5).to_satisfiable_range(3)); + assert_eq!(None, ByteRangeSpec::Last(0).to_satisfiable_range(3)); + assert_eq!(None, ByteRangeSpec::Last(2).to_satisfiable_range(0)); +} + +bench_header!(bytes_multi, Range, { vec![b"bytes=1-1001,2001-3001,10001-".to_vec()]}); +bench_header!(custom_unit, Range, { vec![b"other=0-100000".to_vec()]}); +*/ diff --git a/third_party/rust/headers/src/common/referer.rs b/third_party/rust/headers/src/common/referer.rs new file mode 100644 index 0000000000..c85973b9cb --- /dev/null +++ b/third_party/rust/headers/src/common/referer.rs @@ -0,0 +1,60 @@ +use std::str::FromStr; + +use http::header::HeaderValue; + +/// `Referer` header, defined in +/// [RFC7231](http://tools.ietf.org/html/rfc7231#section-5.5.2) +/// +/// The `Referer` \[sic\] header field allows the user agent to specify a +/// URI reference for the resource from which the target URI was obtained +/// (i.e., the "referrer", though the field name is misspelled). A user +/// agent MUST NOT include the fragment and userinfo components of the +/// URI reference, if any, when generating the Referer field value. +/// +/// ## ABNF +/// +/// ```text +/// Referer = absolute-URI / partial-URI +/// ``` +/// +/// ## Example values +/// +/// * `http://www.example.org/hypertext/Overview.html` +/// +/// # Examples +/// +/// ``` +/// # extern crate headers; +/// use headers::Referer; +/// +/// let r = Referer::from_static("/People.html#tim"); +/// ``` +#[derive(Debug, Clone, PartialEq)] +pub struct Referer(HeaderValue); + +derive_header! { + Referer(_), + name: REFERER +} + +impl Referer { + /// Create a `Referer` with a static string. + /// + /// # Panic + /// + /// Panics if the string is not a legal header value. + pub fn from_static(s: &'static str) -> Referer { + Referer(HeaderValue::from_static(s)) + } +} + +error_type!(InvalidReferer); + +impl FromStr for Referer { + type Err = InvalidReferer; + fn from_str(src: &str) -> Result<Self, Self::Err> { + HeaderValue::from_str(src) + .map(Referer) + .map_err(|_| InvalidReferer { _inner: () }) + } +} diff --git a/third_party/rust/headers/src/common/referrer_policy.rs b/third_party/rust/headers/src/common/referrer_policy.rs new file mode 100644 index 0000000000..95bc36c056 --- /dev/null +++ b/third_party/rust/headers/src/common/referrer_policy.rs @@ -0,0 +1,190 @@ +use HeaderValue; + +/// `Referrer-Policy` header, part of +/// [Referrer Policy](https://www.w3.org/TR/referrer-policy/#referrer-policy-header) +/// +/// The `Referrer-Policy` HTTP header specifies the referrer +/// policy that the user agent applies when determining what +/// referrer information should be included with requests made, +/// and with browsing contexts created from the context of the +/// protected resource. +/// +/// # ABNF +/// +/// ```text +/// Referrer-Policy: 1#policy-token +/// policy-token = "no-referrer" / "no-referrer-when-downgrade" +/// / "same-origin" / "origin" +/// / "origin-when-cross-origin" / "unsafe-url" +/// ``` +/// +/// # Example values +/// +/// * `no-referrer` +/// +/// # Example +/// +/// ``` +/// # extern crate headers; +/// use headers::ReferrerPolicy; +/// +/// let rp = ReferrerPolicy::NO_REFERRER; +/// ``` +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub struct ReferrerPolicy(Policy); + +derive_header! { + ReferrerPolicy(_), + name: REFERRER_POLICY +} + +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +enum Policy { + NoReferrer, + NoReferrerWhenDowngrade, + SameOrigin, + Origin, + OriginWhenCrossOrigin, + UnsafeUrl, + StrictOrigin, + StrictOriginWhenCrossOrigin, +} + +impl ReferrerPolicy { + /// `no-referrer` + pub const NO_REFERRER: Self = ReferrerPolicy(Policy::NoReferrer); + + /// `no-referrer-when-downgrade` + pub const NO_REFERRER_WHEN_DOWNGRADE: Self = ReferrerPolicy(Policy::NoReferrerWhenDowngrade); + + /// `same-origin` + pub const SAME_ORIGIN: Self = ReferrerPolicy(Policy::SameOrigin); + + /// `origin` + pub const ORIGIN: Self = ReferrerPolicy(Policy::Origin); + + /// `origin-when-cross-origin` + pub const ORIGIN_WHEN_CROSS_ORIGIN: Self = ReferrerPolicy(Policy::OriginWhenCrossOrigin); + + /// `unsafe-url` + pub const UNSAFE_URL: Self = ReferrerPolicy(Policy::UnsafeUrl); + + /// `strict-origin` + pub const STRICT_ORIGIN: Self = ReferrerPolicy(Policy::StrictOrigin); + + ///`strict-origin-when-cross-origin` + pub const STRICT_ORIGIN_WHEN_CROSS_ORIGIN: Self = + ReferrerPolicy(Policy::StrictOriginWhenCrossOrigin); +} + +impl ::util::TryFromValues for Policy { + fn try_from_values<'i, I>(values: &mut I) -> Result<Self, ::Error> + where + I: Iterator<Item = &'i HeaderValue>, + { + // See https://www.w3.org/TR/referrer-policy/#determine-policy-for-token + // tl;dr - Pick *last* known policy in the list + let mut known = None; + for s in csv(values) { + known = Some(match s { + "no-referrer" | "never" => Policy::NoReferrer, + "no-referrer-when-downgrade" | "default" => Policy::NoReferrerWhenDowngrade, + "same-origin" => Policy::SameOrigin, + "origin" => Policy::Origin, + "origin-when-cross-origin" => Policy::OriginWhenCrossOrigin, + "strict-origin" => Policy::StrictOrigin, + "strict-origin-when-cross-origin" => Policy::StrictOriginWhenCrossOrigin, + "unsafe-url" | "always" => Policy::UnsafeUrl, + _ => continue, + }); + } + + known.ok_or_else(::Error::invalid) + } +} + +impl<'a> From<&'a Policy> for HeaderValue { + fn from(policy: &'a Policy) -> HeaderValue { + HeaderValue::from_static(match *policy { + Policy::NoReferrer => "no-referrer", + Policy::NoReferrerWhenDowngrade => "no-referrer-when-downgrade", + Policy::SameOrigin => "same-origin", + Policy::Origin => "origin", + Policy::OriginWhenCrossOrigin => "origin-when-cross-origin", + Policy::StrictOrigin => "strict-origin", + Policy::StrictOriginWhenCrossOrigin => "strict-origin-when-cross-origin", + Policy::UnsafeUrl => "unsafe-url", + }) + } +} + +fn csv<'i, I>(values: I) -> impl Iterator<Item = &'i str> +where + I: Iterator<Item = &'i HeaderValue>, +{ + values.flat_map(|value| { + value.to_str().into_iter().flat_map(|string| { + string.split(',').filter_map(|x| match x.trim() { + "" => None, + y => Some(y), + }) + }) + }) +} + +#[cfg(test)] +mod tests { + use super::super::test_decode; + use super::ReferrerPolicy; + + #[test] + fn decode_as_last_policy() { + assert_eq!( + test_decode::<ReferrerPolicy>(&["same-origin, origin"]), + Some(ReferrerPolicy::ORIGIN), + ); + + assert_eq!( + test_decode::<ReferrerPolicy>(&["origin", "same-origin"]), + Some(ReferrerPolicy::SAME_ORIGIN), + ); + } + + #[test] + fn decode_as_last_known() { + assert_eq!( + test_decode::<ReferrerPolicy>(&["origin, nope, nope, nope"]), + Some(ReferrerPolicy::ORIGIN), + ); + + assert_eq!( + test_decode::<ReferrerPolicy>(&["nope, origin, nope, nope"]), + Some(ReferrerPolicy::ORIGIN), + ); + + assert_eq!( + test_decode::<ReferrerPolicy>(&["nope, origin", "nope, nope"]), + Some(ReferrerPolicy::ORIGIN), + ); + + assert_eq!( + test_decode::<ReferrerPolicy>(&["nope", "origin", "nope, nope"]), + Some(ReferrerPolicy::ORIGIN), + ); + } + + #[test] + fn decode_unknown() { + assert_eq!(test_decode::<ReferrerPolicy>(&["nope"]), None,); + } + + #[test] + fn matching() { + let rp = ReferrerPolicy::ORIGIN; + + match rp { + ReferrerPolicy::ORIGIN => (), + _ => panic!("matched wrong"), + } + } +} diff --git a/third_party/rust/headers/src/common/retry_after.rs b/third_party/rust/headers/src/common/retry_after.rs new file mode 100644 index 0000000000..ec67aaf7da --- /dev/null +++ b/third_party/rust/headers/src/common/retry_after.rs @@ -0,0 +1,111 @@ +use std::time::{Duration, SystemTime}; + +use util::{HttpDate, Seconds, TryFromValues}; +use HeaderValue; + +/// The `Retry-After` header. +/// +/// The `Retry-After` response-header field can be used with a 503 (Service +/// Unavailable) response to indicate how long the service is expected to be +/// unavailable to the requesting client. This field MAY also be used with any +/// 3xx (Redirection) response to indicate the minimum time the user-agent is +/// asked wait before issuing the redirected request. The value of this field +/// can be either an HTTP-date or an integer number of seconds (in decimal) +/// after the time of the response. +/// +/// # Examples +/// ``` +/// # extern crate headers; +/// use std::time::{Duration, SystemTime}; +/// use headers::RetryAfter; +/// +/// let delay = RetryAfter::delay(Duration::from_secs(300)); +/// let date = RetryAfter::date(SystemTime::now()); +/// ``` + +/// Retry-After header, defined in [RFC7231](http://tools.ietf.org/html/rfc7231#section-7.1.3) +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct RetryAfter(After); + +derive_header! { + RetryAfter(_), + name: RETRY_AFTER +} + +#[derive(Debug, Clone, PartialEq, Eq)] +enum After { + /// Retry after the given DateTime + DateTime(HttpDate), + /// Retry after this duration has elapsed + Delay(Seconds), +} + +impl RetryAfter { + /// Create an `RetryAfter` header with a date value. + pub fn date(time: SystemTime) -> RetryAfter { + RetryAfter(After::DateTime(time.into())) + } + + /// Create an `RetryAfter` header with a date value. + pub fn delay(dur: Duration) -> RetryAfter { + RetryAfter(After::Delay(dur.into())) + } +} + +impl TryFromValues for After { + fn try_from_values<'i, I>(values: &mut I) -> Result<Self, ::Error> + where + I: Iterator<Item = &'i HeaderValue>, + { + values + .next() + .and_then(|val| { + if let Some(delay) = Seconds::from_val(val) { + return Some(After::Delay(delay)); + } + + let date = HttpDate::from_val(val)?; + Some(After::DateTime(date)) + }) + .ok_or_else(::Error::invalid) + } +} + +impl<'a> From<&'a After> for HeaderValue { + fn from(after: &'a After) -> HeaderValue { + match *after { + After::Delay(ref delay) => delay.into(), + After::DateTime(ref date) => date.into(), + } + } +} + +#[cfg(test)] +mod tests { + use super::super::test_decode; + use super::RetryAfter; + use std::time::Duration; + use util::HttpDate; + + #[test] + fn delay_decode() { + let r: RetryAfter = test_decode(&["1234"]).unwrap(); + assert_eq!(r, RetryAfter::delay(Duration::from_secs(1234)),); + } + + macro_rules! test_retry_after_datetime { + ($name:ident, $s:expr) => { + #[test] + fn $name() { + let r: RetryAfter = test_decode(&[$s]).unwrap(); + let dt = "Sun, 06 Nov 1994 08:49:37 GMT".parse::<HttpDate>().unwrap(); + + assert_eq!(r, RetryAfter(super::After::DateTime(dt))); + } + }; + } + + test_retry_after_datetime!(date_decode_rfc1123, "Sun, 06 Nov 1994 08:49:37 GMT"); + test_retry_after_datetime!(date_decode_rfc850, "Sunday, 06-Nov-94 08:49:37 GMT"); + test_retry_after_datetime!(date_decode_asctime, "Sun Nov 6 08:49:37 1994"); +} diff --git a/third_party/rust/headers/src/common/sec_websocket_accept.rs b/third_party/rust/headers/src/common/sec_websocket_accept.rs new file mode 100644 index 0000000000..89ec7c075f --- /dev/null +++ b/third_party/rust/headers/src/common/sec_websocket_accept.rs @@ -0,0 +1,67 @@ +use base64::engine::general_purpose::STANDARD as ENGINE; +use base64::Engine; +use bytes::Bytes; +use sha1::{Digest, Sha1}; + +use super::SecWebsocketKey; + +/// The `Sec-Websocket-Accept` header. +/// +/// This header is used in the Websocket handshake, sent back by the +/// server indicating a successful handshake. It is a signature +/// of the `Sec-Websocket-Key` header. +/// +/// # Example +/// +/// ```no_run +/// # extern crate headers; +/// use headers::{SecWebsocketAccept, SecWebsocketKey}; +/// +/// let sec_key: SecWebsocketKey = /* from request headers */ +/// # unimplemented!(); +/// +/// let sec_accept = SecWebsocketAccept::from(sec_key); +/// ``` +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub struct SecWebsocketAccept(::HeaderValue); + +derive_header! { + SecWebsocketAccept(_), + name: SEC_WEBSOCKET_ACCEPT +} + +impl From<SecWebsocketKey> for SecWebsocketAccept { + fn from(key: SecWebsocketKey) -> SecWebsocketAccept { + sign(key.0.as_bytes()) + } +} + +fn sign(key: &[u8]) -> SecWebsocketAccept { + let mut sha1 = Sha1::default(); + sha1.update(key); + sha1.update(&b"258EAFA5-E914-47DA-95CA-C5AB0DC85B11"[..]); + let b64 = Bytes::from(ENGINE.encode(&sha1.finalize())); + + let val = ::HeaderValue::from_maybe_shared(b64).expect("base64 is a valid value"); + + SecWebsocketAccept(val) +} + +#[cfg(test)] +mod tests { + use super::super::{test_decode, test_encode}; + use super::*; + + #[test] + fn key_to_accept() { + // From https://tools.ietf.org/html/rfc6455#section-1.2 + let key = test_decode::<SecWebsocketKey>(&["dGhlIHNhbXBsZSBub25jZQ=="]).expect("key"); + let accept = SecWebsocketAccept::from(key); + let headers = test_encode(accept); + + assert_eq!( + headers["sec-websocket-accept"], + "s3pPLMBiTxaQ9kYGzzhZRbK+xOo=" + ); + } +} diff --git a/third_party/rust/headers/src/common/sec_websocket_key.rs b/third_party/rust/headers/src/common/sec_websocket_key.rs new file mode 100644 index 0000000000..2c12dda4b2 --- /dev/null +++ b/third_party/rust/headers/src/common/sec_websocket_key.rs @@ -0,0 +1,8 @@ +/// The `Sec-Websocket-Key` header. +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub struct SecWebsocketKey(pub(super) ::HeaderValue); + +derive_header! { + SecWebsocketKey(_), + name: SEC_WEBSOCKET_KEY +} diff --git a/third_party/rust/headers/src/common/sec_websocket_version.rs b/third_party/rust/headers/src/common/sec_websocket_version.rs new file mode 100644 index 0000000000..d20c49c709 --- /dev/null +++ b/third_party/rust/headers/src/common/sec_websocket_version.rs @@ -0,0 +1,58 @@ +/// The `Sec-Websocket-Version` header. +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub struct SecWebsocketVersion(u8); + +impl SecWebsocketVersion { + /// `Sec-Websocket-Version: 13` + pub const V13: SecWebsocketVersion = SecWebsocketVersion(13); +} + +impl ::Header for SecWebsocketVersion { + fn name() -> &'static ::HeaderName { + &::http::header::SEC_WEBSOCKET_VERSION + } + + fn decode<'i, I: Iterator<Item = &'i ::HeaderValue>>(values: &mut I) -> Result<Self, ::Error> { + values + .next() + .and_then(|value| { + if value == "13" { + Some(SecWebsocketVersion::V13) + } else { + None + } + }) + .ok_or_else(::Error::invalid) + } + + fn encode<E: Extend<::HeaderValue>>(&self, values: &mut E) { + debug_assert_eq!(self.0, 13); + + values.extend(::std::iter::once(::HeaderValue::from_static("13"))); + } +} + +#[cfg(test)] +mod tests { + use super::super::{test_decode, test_encode}; + use super::SecWebsocketVersion; + + #[test] + fn decode_v13() { + assert_eq!( + test_decode::<SecWebsocketVersion>(&["13"]), + Some(SecWebsocketVersion::V13), + ); + } + + #[test] + fn decode_fail() { + assert_eq!(test_decode::<SecWebsocketVersion>(&["1"]), None,); + } + + #[test] + fn encode_v13() { + let headers = test_encode(SecWebsocketVersion::V13); + assert_eq!(headers["sec-websocket-version"], "13"); + } +} diff --git a/third_party/rust/headers/src/common/server.rs b/third_party/rust/headers/src/common/server.rs new file mode 100644 index 0000000000..b0e8f88099 --- /dev/null +++ b/third_party/rust/headers/src/common/server.rs @@ -0,0 +1,72 @@ +use std::fmt; +use std::str::FromStr; + +use util::HeaderValueString; + +/// `Server` header, defined in [RFC7231](http://tools.ietf.org/html/rfc7231#section-7.4.2) +/// +/// The `Server` header field contains information about the software +/// used by the origin server to handle the request, which is often used +/// by clients to help identify the scope of reported interoperability +/// problems, to work around or tailor requests to avoid particular +/// server limitations, and for analytics regarding server or operating +/// system use. An origin server MAY generate a Server field in its +/// responses. +/// +/// # ABNF +/// +/// ```text +/// Server = product *( RWS ( product / comment ) ) +/// ``` +/// +/// # Example values +/// * `CERN/3.0 libwww/2.17` +/// +/// # Example +/// +/// ``` +/// # extern crate headers; +/// use headers::Server; +/// +/// let server = Server::from_static("hyper/0.12.2"); +/// ``` +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct Server(HeaderValueString); + +derive_header! { + Server(_), + name: SERVER +} + +impl Server { + /// Construct a `Server` from a static string. + /// + /// # Panic + /// + /// Panics if the static string is not a legal header value. + pub fn from_static(s: &'static str) -> Server { + Server(HeaderValueString::from_static(s)) + } + + /// View this `Server` as a `&str`. + pub fn as_str(&self) -> &str { + self.0.as_str() + } +} + +error_type!(InvalidServer); + +impl FromStr for Server { + type Err = InvalidServer; + fn from_str(src: &str) -> Result<Self, Self::Err> { + HeaderValueString::from_str(src) + .map(Server) + .map_err(|_| InvalidServer { _inner: () }) + } +} + +impl fmt::Display for Server { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fmt::Display::fmt(&self.0, f) + } +} diff --git a/third_party/rust/headers/src/common/set_cookie.rs b/third_party/rust/headers/src/common/set_cookie.rs new file mode 100644 index 0000000000..32876ac7d8 --- /dev/null +++ b/third_party/rust/headers/src/common/set_cookie.rs @@ -0,0 +1,103 @@ +/// `Set-Cookie` header, defined [RFC6265](http://tools.ietf.org/html/rfc6265#section-4.1) +/// +/// The Set-Cookie HTTP response header is used to send cookies from the +/// server to the user agent. +/// +/// Informally, the Set-Cookie response header contains the header name +/// "Set-Cookie" followed by a ":" and a cookie. Each cookie begins with +/// a name-value-pair, followed by zero or more attribute-value pairs. +/// +/// # ABNF +/// +/// ```text +/// set-cookie-header = "Set-Cookie:" SP set-cookie-string +/// set-cookie-string = cookie-pair *( ";" SP cookie-av ) +/// cookie-pair = cookie-name "=" cookie-value +/// cookie-name = token +/// cookie-value = *cookie-octet / ( DQUOTE *cookie-octet DQUOTE ) +/// cookie-octet = %x21 / %x23-2B / %x2D-3A / %x3C-5B / %x5D-7E +/// ; US-ASCII characters excluding CTLs, +/// ; whitespace DQUOTE, comma, semicolon, +/// ; and backslash +/// token = <token, defined in [RFC2616], Section 2.2> +/// +/// cookie-av = expires-av / max-age-av / domain-av / +/// path-av / secure-av / httponly-av / +/// extension-av +/// expires-av = "Expires=" sane-cookie-date +/// sane-cookie-date = <rfc1123-date, defined in [RFC2616], Section 3.3.1> +/// max-age-av = "Max-Age=" non-zero-digit *DIGIT +/// ; In practice, both expires-av and max-age-av +/// ; are limited to dates representable by the +/// ; user agent. +/// non-zero-digit = %x31-39 +/// ; digits 1 through 9 +/// domain-av = "Domain=" domain-value +/// domain-value = <subdomain> +/// ; defined in [RFC1034], Section 3.5, as +/// ; enhanced by [RFC1123], Section 2.1 +/// path-av = "Path=" path-value +/// path-value = <any CHAR except CTLs or ";"> +/// secure-av = "Secure" +/// httponly-av = "HttpOnly" +/// extension-av = <any CHAR except CTLs or ";"> +/// ``` +/// +/// # Example values +/// +/// * `SID=31d4d96e407aad42` +/// * `lang=en-US; Expires=Wed, 09 Jun 2021 10:18:14 GMT` +/// * `lang=; Expires=Sun, 06 Nov 1994 08:49:37 GMT` +/// * `lang=en-US; Path=/; Domain=example.com` +/// +/// # Example +#[derive(Clone, Debug)] +pub struct SetCookie(Vec<::HeaderValue>); + +impl ::Header for SetCookie { + fn name() -> &'static ::HeaderName { + &::http::header::SET_COOKIE + } + + fn decode<'i, I: Iterator<Item = &'i ::HeaderValue>>(values: &mut I) -> Result<Self, ::Error> { + let vec = values.cloned().collect::<Vec<_>>(); + + if !vec.is_empty() { + Ok(SetCookie(vec)) + } else { + Err(::Error::invalid()) + } + } + + fn encode<E: Extend<::HeaderValue>>(&self, values: &mut E) { + values.extend(self.0.iter().cloned()); + } +} + +#[cfg(test)] +mod tests { + use super::super::{test_decode, test_encode}; + use super::*; + + #[test] + fn decode() { + let set_cookie = test_decode::<SetCookie>(&["foo=bar", "baz=quux"]).unwrap(); + assert_eq!(set_cookie.0.len(), 2); + assert_eq!(set_cookie.0[0], "foo=bar"); + assert_eq!(set_cookie.0[1], "baz=quux"); + } + + #[test] + fn encode() { + let set_cookie = SetCookie(vec![ + ::HeaderValue::from_static("foo=bar"), + ::HeaderValue::from_static("baz=quux"), + ]); + + let headers = test_encode(set_cookie); + let mut vals = headers.get_all("set-cookie").into_iter(); + assert_eq!(vals.next().unwrap(), "foo=bar"); + assert_eq!(vals.next().unwrap(), "baz=quux"); + assert_eq!(vals.next(), None); + } +} diff --git a/third_party/rust/headers/src/common/strict_transport_security.rs b/third_party/rust/headers/src/common/strict_transport_security.rs new file mode 100644 index 0000000000..628b800438 --- /dev/null +++ b/third_party/rust/headers/src/common/strict_transport_security.rs @@ -0,0 +1,247 @@ +use std::fmt; +use std::time::Duration; + +use util::{self, IterExt, Seconds}; + +/// `StrictTransportSecurity` header, defined in [RFC6797](https://tools.ietf.org/html/rfc6797) +/// +/// This specification defines a mechanism enabling web sites to declare +/// themselves accessible only via secure connections and/or for users to be +/// able to direct their user agent(s) to interact with given sites only over +/// secure connections. This overall policy is referred to as HTTP Strict +/// Transport Security (HSTS). The policy is declared by web sites via the +/// Strict-Transport-Security HTTP response header field and/or by other means, +/// such as user agent configuration, for example. +/// +/// # ABNF +/// +/// ```text +/// [ directive ] *( ";" [ directive ] ) +/// +/// directive = directive-name [ "=" directive-value ] +/// directive-name = token +/// directive-value = token | quoted-string +/// +/// ``` +/// +/// # Example values +/// +/// * `max-age=31536000` +/// * `max-age=15768000 ; includeSubdomains` +/// +/// # Example +/// +/// ``` +/// # extern crate headers; +/// use std::time::Duration; +/// use headers::StrictTransportSecurity; +/// +/// let sts = StrictTransportSecurity::including_subdomains(Duration::from_secs(31_536_000)); +/// ``` +#[derive(Clone, Debug, PartialEq)] +pub struct StrictTransportSecurity { + /// Signals the UA that the HSTS Policy applies to this HSTS Host as well as + /// any subdomains of the host's domain name. + include_subdomains: bool, + + /// Specifies the number of seconds, after the reception of the STS header + /// field, during which the UA regards the host (from whom the message was + /// received) as a Known HSTS Host. + max_age: Seconds, +} + +impl StrictTransportSecurity { + // NOTE: The two constructors exist to make a user *have* to decide if + // subdomains can be included or not, instead of forgetting due to an + // incorrect assumption about a default. + + /// Create an STS header that includes subdomains + pub fn including_subdomains(max_age: Duration) -> StrictTransportSecurity { + StrictTransportSecurity { + max_age: max_age.into(), + include_subdomains: true, + } + } + + /// Create an STS header that excludes subdomains + pub fn excluding_subdomains(max_age: Duration) -> StrictTransportSecurity { + StrictTransportSecurity { + max_age: max_age.into(), + include_subdomains: false, + } + } + + // getters + + /// Get whether this should include subdomains. + pub fn include_subdomains(&self) -> bool { + self.include_subdomains + } + + /// Get the max-age. + pub fn max_age(&self) -> Duration { + self.max_age.into() + } +} + +enum Directive { + MaxAge(u64), + IncludeSubdomains, + Unknown, +} + +fn from_str(s: &str) -> Result<StrictTransportSecurity, ::Error> { + s.split(';') + .map(str::trim) + .map(|sub| { + if sub.eq_ignore_ascii_case("includeSubdomains") { + Some(Directive::IncludeSubdomains) + } else { + let mut sub = sub.splitn(2, '='); + match (sub.next(), sub.next()) { + (Some(left), Some(right)) if left.trim().eq_ignore_ascii_case("max-age") => { + right + .trim() + .trim_matches('"') + .parse() + .ok() + .map(Directive::MaxAge) + } + _ => Some(Directive::Unknown), + } + } + }) + .fold(Some((None, None)), |res, dir| match (res, dir) { + (Some((None, sub)), Some(Directive::MaxAge(age))) => Some((Some(age), sub)), + (Some((age, None)), Some(Directive::IncludeSubdomains)) => Some((age, Some(()))), + (Some((Some(_), _)), Some(Directive::MaxAge(_))) + | (Some((_, Some(_))), Some(Directive::IncludeSubdomains)) + | (_, None) => None, + (res, _) => res, + }) + .and_then(|res| match res { + (Some(age), sub) => Some(StrictTransportSecurity { + max_age: Duration::from_secs(age).into(), + include_subdomains: sub.is_some(), + }), + _ => None, + }) + .ok_or_else(::Error::invalid) +} + +impl ::Header for StrictTransportSecurity { + fn name() -> &'static ::HeaderName { + &::http::header::STRICT_TRANSPORT_SECURITY + } + + fn decode<'i, I: Iterator<Item = &'i ::HeaderValue>>(values: &mut I) -> Result<Self, ::Error> { + values + .just_one() + .and_then(|v| v.to_str().ok()) + .map(from_str) + .unwrap_or_else(|| Err(::Error::invalid())) + } + + fn encode<E: Extend<::HeaderValue>>(&self, values: &mut E) { + struct Adapter<'a>(&'a StrictTransportSecurity); + + impl<'a> fmt::Display for Adapter<'a> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + if self.0.include_subdomains { + write!(f, "max-age={}; includeSubdomains", self.0.max_age) + } else { + write!(f, "max-age={}", self.0.max_age) + } + } + } + + values.extend(::std::iter::once(util::fmt(Adapter(self)))); + } +} + +#[cfg(test)] +mod tests { + use super::super::test_decode; + use super::StrictTransportSecurity; + use std::time::Duration; + + #[test] + fn test_parse_max_age() { + let h = test_decode::<StrictTransportSecurity>(&["max-age=31536000"]).unwrap(); + assert_eq!( + h, + StrictTransportSecurity { + include_subdomains: false, + max_age: Duration::from_secs(31536000).into(), + } + ); + } + + #[test] + fn test_parse_max_age_no_value() { + assert_eq!(test_decode::<StrictTransportSecurity>(&["max-age"]), None,); + } + + #[test] + fn test_parse_quoted_max_age() { + let h = test_decode::<StrictTransportSecurity>(&["max-age=\"31536000\""]).unwrap(); + assert_eq!( + h, + StrictTransportSecurity { + include_subdomains: false, + max_age: Duration::from_secs(31536000).into(), + } + ); + } + + #[test] + fn test_parse_spaces_max_age() { + let h = test_decode::<StrictTransportSecurity>(&["max-age = 31536000"]).unwrap(); + assert_eq!( + h, + StrictTransportSecurity { + include_subdomains: false, + max_age: Duration::from_secs(31536000).into(), + } + ); + } + + #[test] + fn test_parse_include_subdomains() { + let h = test_decode::<StrictTransportSecurity>(&["max-age=15768000 ; includeSubDomains"]) + .unwrap(); + assert_eq!( + h, + StrictTransportSecurity { + include_subdomains: true, + max_age: Duration::from_secs(15768000).into(), + } + ); + } + + #[test] + fn test_parse_no_max_age() { + assert_eq!( + test_decode::<StrictTransportSecurity>(&["includeSubdomains"]), + None, + ); + } + + #[test] + fn test_parse_max_age_nan() { + assert_eq!( + test_decode::<StrictTransportSecurity>(&["max-age = izzy"]), + None, + ); + } + + #[test] + fn test_parse_duplicate_directives() { + assert_eq!( + test_decode::<StrictTransportSecurity>(&["max-age=1; max-age=2"]), + None, + ); + } +} + +//bench_header!(bench, StrictTransportSecurity, { vec![b"max-age=15768000 ; includeSubDomains".to_vec()] }); diff --git a/third_party/rust/headers/src/common/te.rs b/third_party/rust/headers/src/common/te.rs new file mode 100644 index 0000000000..9a471b8a6f --- /dev/null +++ b/third_party/rust/headers/src/common/te.rs @@ -0,0 +1,41 @@ +use util::FlatCsv; + +/// `TE` header, defined in +/// [RFC7230](http://tools.ietf.org/html/rfc7230#section-4.3) +/// +/// As RFC7230 states, "The "TE" header field in a request indicates what transfer codings, +/// besides chunked, the client is willing to accept in response, and +/// whether or not the client is willing to accept trailer fields in a +/// chunked transfer coding." +/// +/// For HTTP/1.1 compliant clients `chunked` transfer codings are assumed to be acceptable and +/// so should never appear in this header. +/// +/// # ABNF +/// +/// ```text +/// TE = "TE" ":" #( t-codings ) +/// t-codings = "trailers" | ( transfer-extension [ accept-params ] ) +/// ``` +/// +/// # Example values +/// * `trailers` +/// * `trailers, deflate;q=0.5` +/// * `` +/// +/// # Examples +/// +#[derive(Clone, Debug, PartialEq)] +pub struct Te(FlatCsv); + +derive_header! { + Te(_), + name: TE +} + +impl Te { + /// Create a `TE: trailers` header. + pub fn trailers() -> Self { + Te(::HeaderValue::from_static("trailers").into()) + } +} diff --git a/third_party/rust/headers/src/common/transfer_encoding.rs b/third_party/rust/headers/src/common/transfer_encoding.rs new file mode 100644 index 0000000000..c3c49b15f1 --- /dev/null +++ b/third_party/rust/headers/src/common/transfer_encoding.rs @@ -0,0 +1,104 @@ +use util::FlatCsv; +use HeaderValue; + +/// `Transfer-Encoding` header, defined in +/// [RFC7230](http://tools.ietf.org/html/rfc7230#section-3.3.1) +/// +/// The `Transfer-Encoding` header field lists the transfer coding names +/// corresponding to the sequence of transfer codings that have been (or +/// will be) applied to the payload body in order to form the message +/// body. +/// +/// Note that setting this header will *remove* any previously set +/// `Content-Length` header, in accordance with +/// [RFC7230](http://tools.ietf.org/html/rfc7230#section-3.3.2): +/// +/// > A sender MUST NOT send a Content-Length header field in any message +/// > that contains a Transfer-Encoding header field. +/// +/// # ABNF +/// +/// ```text +/// Transfer-Encoding = 1#transfer-coding +/// ``` +/// +/// # Example values +/// +/// * `chunked` +/// * `gzip, chunked` +/// +/// # Example +/// +/// ``` +/// # extern crate headers; +/// use headers::TransferEncoding; +/// +/// let transfer = TransferEncoding::chunked(); +/// ``` +// This currently is just a `HeaderValue`, instead of a `Vec<Encoding>`, since +// the most common by far instance is simply the string `chunked`. It'd be a +// waste to need to allocate just for that. +#[derive(Clone, Debug)] +pub struct TransferEncoding(FlatCsv); + +derive_header! { + TransferEncoding(_), + name: TRANSFER_ENCODING +} + +impl TransferEncoding { + /// Constructor for the most common Transfer-Encoding, `chunked`. + pub fn chunked() -> TransferEncoding { + TransferEncoding(HeaderValue::from_static("chunked").into()) + } + + /// Returns whether this ends with the `chunked` encoding. + pub fn is_chunked(&self) -> bool { + self.0 + .value + //TODO(perf): use split and trim (not an actual method) on &[u8] + .to_str() + .map(|s| { + s.split(',') + .next_back() + .map(|encoding| encoding.trim() == "chunked") + .expect("split always has at least 1 item") + }) + .unwrap_or(false) + } +} + +#[cfg(test)] +mod tests { + use super::super::test_decode; + use super::TransferEncoding; + + #[test] + fn chunked_is_chunked() { + assert!(TransferEncoding::chunked().is_chunked()); + } + + #[test] + fn decode_gzip_chunked_is_chunked() { + let te = test_decode::<TransferEncoding>(&["gzip, chunked"]).unwrap(); + assert!(te.is_chunked()); + } + + #[test] + fn decode_chunked_gzip_is_not_chunked() { + let te = test_decode::<TransferEncoding>(&["chunked, gzip"]).unwrap(); + assert!(!te.is_chunked()); + } + + #[test] + fn decode_notchunked_is_not_chunked() { + let te = test_decode::<TransferEncoding>(&["notchunked"]).unwrap(); + assert!(!te.is_chunked()); + } + + #[test] + fn decode_multiple_is_chunked() { + let te = test_decode::<TransferEncoding>(&["gzip", "chunked"]).unwrap(); + assert!(te.is_chunked()); + } +} diff --git a/third_party/rust/headers/src/common/upgrade.rs b/third_party/rust/headers/src/common/upgrade.rs new file mode 100644 index 0000000000..8002b68b23 --- /dev/null +++ b/third_party/rust/headers/src/common/upgrade.rs @@ -0,0 +1,55 @@ +use HeaderValue; + +/// `Upgrade` header, defined in [RFC7230](http://tools.ietf.org/html/rfc7230#section-6.7) +/// +/// The `Upgrade` header field is intended to provide a simple mechanism +/// for transitioning from HTTP/1.1 to some other protocol on the same +/// connection. A client MAY send a list of protocols in the Upgrade +/// header field of a request to invite the server to switch to one or +/// more of those protocols, in order of descending preference, before +/// sending the final response. A server MAY ignore a received Upgrade +/// header field if it wishes to continue using the current protocol on +/// that connection. Upgrade cannot be used to insist on a protocol +/// change. +/// +/// ## ABNF +/// +/// ```text +/// Upgrade = 1#protocol +/// +/// protocol = protocol-name ["/" protocol-version] +/// protocol-name = token +/// protocol-version = token +/// ``` +/// +/// ## Example values +/// +/// * `HTTP/2.0, SHTTP/1.3, IRC/6.9, RTA/x11` +/// +/// # Note +/// +/// In practice, the `Upgrade` header is never that complicated. In most cases, +/// it is only ever a single value, such as `"websocket"`. +/// +/// # Examples +/// +/// ``` +/// # extern crate headers; +/// use headers::Upgrade; +/// +/// let ws = Upgrade::websocket(); +/// ``` +#[derive(Clone, Debug, PartialEq)] +pub struct Upgrade(HeaderValue); + +derive_header! { + Upgrade(_), + name: UPGRADE +} + +impl Upgrade { + /// Constructs an `Upgrade: websocket` header. + pub fn websocket() -> Upgrade { + Upgrade(HeaderValue::from_static("websocket")) + } +} diff --git a/third_party/rust/headers/src/common/user_agent.rs b/third_party/rust/headers/src/common/user_agent.rs new file mode 100644 index 0000000000..fc1d8ba458 --- /dev/null +++ b/third_party/rust/headers/src/common/user_agent.rs @@ -0,0 +1,81 @@ +use std::fmt; +use std::str::FromStr; + +use util::HeaderValueString; + +/// `User-Agent` header, defined in +/// [RFC7231](http://tools.ietf.org/html/rfc7231#section-5.5.3) +/// +/// The `User-Agent` header field contains information about the user +/// agent originating the request, which is often used by servers to help +/// identify the scope of reported interoperability problems, to work +/// around or tailor responses to avoid particular user agent +/// limitations, and for analytics regarding browser or operating system +/// use. A user agent SHOULD send a User-Agent field in each request +/// unless specifically configured not to do so. +/// +/// # ABNF +/// +/// ```text +/// User-Agent = product *( RWS ( product / comment ) ) +/// product = token ["/" product-version] +/// product-version = token +/// ``` +/// +/// # Example values +/// +/// * `CERN-LineMode/2.15 libwww/2.17b3` +/// * `Bunnies` +/// +/// # Notes +/// +/// * The parser does not split the value +/// +/// # Example +/// +/// ``` +/// # extern crate headers; +/// use headers::UserAgent; +/// +/// let ua = UserAgent::from_static("hyper/0.12.2"); +/// ``` +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct UserAgent(HeaderValueString); + +derive_header! { + UserAgent(_), + name: USER_AGENT +} + +impl UserAgent { + /// Create a `UserAgent` from a static string. + /// + /// # Panic + /// + /// Panics if the static string is not a legal header value. + pub fn from_static(src: &'static str) -> UserAgent { + UserAgent(HeaderValueString::from_static(src)) + } + + /// View this `UserAgent` as a `&str`. + pub fn as_str(&self) -> &str { + self.0.as_str() + } +} + +error_type!(InvalidUserAgent); + +impl FromStr for UserAgent { + type Err = InvalidUserAgent; + fn from_str(src: &str) -> Result<Self, Self::Err> { + HeaderValueString::from_str(src) + .map(UserAgent) + .map_err(|_| InvalidUserAgent { _inner: () }) + } +} + +impl fmt::Display for UserAgent { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fmt::Display::fmt(&self.0, f) + } +} diff --git a/third_party/rust/headers/src/common/vary.rs b/third_party/rust/headers/src/common/vary.rs new file mode 100644 index 0000000000..32b21fe7db --- /dev/null +++ b/third_party/rust/headers/src/common/vary.rs @@ -0,0 +1,84 @@ +use util::FlatCsv; + +use HeaderValue; + +/// `Vary` header, defined in [RFC7231](https://tools.ietf.org/html/rfc7231#section-7.1.4) +/// +/// The "Vary" header field in a response describes what parts of a +/// request message, aside from the method, Host header field, and +/// request target, might influence the origin server's process for +/// selecting and representing this response. The value consists of +/// either a single asterisk ("*") or a list of header field names +/// (case-insensitive). +/// +/// # ABNF +/// +/// ```text +/// Vary = "*" / 1#field-name +/// ``` +/// +/// # Example values +/// +/// * `accept-encoding, accept-language` +/// +/// # Example +/// +/// ``` +/// # extern crate headers; +/// use headers::Vary; +/// +/// let vary = Vary::any(); +/// ``` +#[derive(Debug, Clone, PartialEq)] +pub struct Vary(FlatCsv); + +derive_header! { + Vary(_), + name: VARY +} + +impl Vary { + /// Create a new `Very: *` header. + pub fn any() -> Vary { + Vary(HeaderValue::from_static("*").into()) + } + + /// Check if this includes `*`. + pub fn is_any(&self) -> bool { + self.0.iter().any(|val| val == "*") + } + + /// Iterate the header names of this `Vary`. + pub fn iter_strs(&self) -> impl Iterator<Item = &str> { + self.0.iter() + } +} + +/* +test_vary { + test_header!(test1, vec![b"accept-encoding, accept-language"]); + + #[test] + fn test2() { + let mut vary: ::Result<Vary>; + + vary = Header::parse_header(&"*".into()); + assert_eq!(vary.ok(), Some(Vary::Any)); + + vary = Header::parse_header(&"etag,cookie,allow".into()); + assert_eq!(vary.ok(), Some(Vary::Items(vec!["eTag".parse().unwrap(), + "cookIE".parse().unwrap(), + "AlLOw".parse().unwrap(),]))); + } +} +*/ + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn any_is_any() { + assert!(Vary::any().is_any()); + } +} 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() + })); + } +} diff --git a/third_party/rust/headers/src/lib.rs b/third_party/rust/headers/src/lib.rs new file mode 100644 index 0000000000..7d1ddaeded --- /dev/null +++ b/third_party/rust/headers/src/lib.rs @@ -0,0 +1,99 @@ +#![deny(missing_docs)] +#![deny(missing_debug_implementations)] +#![cfg_attr(test, deny(warnings))] +#![cfg_attr(all(test, feature = "nightly"), feature(test))] +#![doc(html_root_url = "https://docs.rs/headers/0.3.9")] + +//! # Typed HTTP Headers +//! +//! hyper has the opinion that headers should be strongly-typed, because that's +//! why we're using Rust in the first place. To set or get any header, an object +//! must implement the `Header` trait from this module. Several common headers +//! are already provided, such as `Host`, `ContentType`, `UserAgent`, and others. +//! +//! # Why Typed? +//! +//! Or, why not stringly-typed? Types give the following advantages: +//! +//! - More difficult to typo, since typos in types should be caught by the compiler +//! - Parsing to a proper type by default +//! +//! # Defining Custom Headers +//! +//! ## Implementing the `Header` trait +//! +//! Consider a Do Not Track header. It can be true or false, but it represents +//! that via the numerals `1` and `0`. +//! +//! ``` +//! extern crate http; +//! extern crate headers; +//! +//! use headers::{Header, HeaderName, HeaderValue}; +//! +//! struct Dnt(bool); +//! +//! impl Header for Dnt { +//! fn name() -> &'static HeaderName { +//! &http::header::DNT +//! } +//! +//! fn decode<'i, I>(values: &mut I) -> Result<Self, headers::Error> +//! where +//! I: Iterator<Item = &'i HeaderValue>, +//! { +//! let value = values +//! .next() +//! .ok_or_else(headers::Error::invalid)?; +//! +//! if value == "0" { +//! Ok(Dnt(false)) +//! } else if value == "1" { +//! Ok(Dnt(true)) +//! } else { +//! Err(headers::Error::invalid()) +//! } +//! } +//! +//! fn encode<E>(&self, values: &mut E) +//! where +//! E: Extend<HeaderValue>, +//! { +//! let s = if self.0 { +//! "1" +//! } else { +//! "0" +//! }; +//! +//! let value = HeaderValue::from_static(s); +//! +//! values.extend(std::iter::once(value)); +//! } +//! } +//! ``` + +extern crate base64; +extern crate bytes; +extern crate headers_core; +extern crate http; +extern crate httpdate; +extern crate mime; +extern crate sha1; +#[cfg(all(test, feature = "nightly"))] +extern crate test; + +pub use headers_core::{Error, Header}; + +#[doc(hidden)] +pub use http::HeaderMap; + +#[doc(hidden)] +pub use http::header::{HeaderName, HeaderValue}; + +#[macro_use] +mod util; +mod common; +mod map_ext; + +pub use self::common::*; +pub use self::map_ext::HeaderMapExt; diff --git a/third_party/rust/headers/src/map_ext.rs b/third_party/rust/headers/src/map_ext.rs new file mode 100644 index 0000000000..e80ccda9e3 --- /dev/null +++ b/third_party/rust/headers/src/map_ext.rs @@ -0,0 +1,88 @@ +use super::{Error, Header, HeaderValue}; +use http; + +/// An extension trait adding "typed" methods to `http::HeaderMap`. +pub trait HeaderMapExt: self::sealed::Sealed { + /// Inserts the typed `Header` into this `HeaderMap`. + fn typed_insert<H>(&mut self, header: H) + where + H: Header; + + /// Tries to find the header by name, and then decode it into `H`. + fn typed_get<H>(&self) -> Option<H> + where + H: Header; + + /// Tries to find the header by name, and then decode it into `H`. + fn typed_try_get<H>(&self) -> Result<Option<H>, Error> + where + H: Header; +} + +impl HeaderMapExt for http::HeaderMap { + fn typed_insert<H>(&mut self, header: H) + where + H: Header, + { + let entry = self.entry(H::name()); + let mut values = ToValues { + state: State::First(entry), + }; + header.encode(&mut values); + } + + fn typed_get<H>(&self) -> Option<H> + where + H: Header, + { + HeaderMapExt::typed_try_get(self).unwrap_or(None) + } + + fn typed_try_get<H>(&self) -> Result<Option<H>, Error> + where + H: Header, + { + let mut values = self.get_all(H::name()).iter(); + if values.size_hint() == (0, Some(0)) { + Ok(None) + } else { + H::decode(&mut values).map(Some) + } + } +} + +struct ToValues<'a> { + state: State<'a>, +} + +#[derive(Debug)] +enum State<'a> { + First(http::header::Entry<'a, HeaderValue>), + Latter(http::header::OccupiedEntry<'a, HeaderValue>), + Tmp, +} + +impl<'a> Extend<HeaderValue> for ToValues<'a> { + fn extend<T: IntoIterator<Item = HeaderValue>>(&mut self, iter: T) { + for value in iter { + let entry = match ::std::mem::replace(&mut self.state, State::Tmp) { + State::First(http::header::Entry::Occupied(mut e)) => { + e.insert(value); + e + } + State::First(http::header::Entry::Vacant(e)) => e.insert_entry(value), + State::Latter(mut e) => { + e.append(value); + e + } + State::Tmp => unreachable!("ToValues State::Tmp"), + }; + self.state = State::Latter(entry); + } + } +} + +mod sealed { + pub trait Sealed {} + impl Sealed for ::http::HeaderMap {} +} diff --git a/third_party/rust/headers/src/util/csv.rs b/third_party/rust/headers/src/util/csv.rs new file mode 100644 index 0000000000..2d0a8ed68d --- /dev/null +++ b/third_party/rust/headers/src/util/csv.rs @@ -0,0 +1,38 @@ +use std::fmt; + +/// Reads a comma-delimited raw header into a Vec. +pub(crate) fn from_comma_delimited<'i, I, T, E>(values: &mut I) -> Result<E, ::Error> +where + I: Iterator<Item = &'i ::HeaderValue>, + T: ::std::str::FromStr, + E: ::std::iter::FromIterator<T>, +{ + values + .flat_map(|value| { + value.to_str().into_iter().flat_map(|string| { + string + .split(',') + .filter_map(|x| match x.trim() { + "" => None, + y => Some(y), + }) + .map(|x| x.parse().map_err(|_| ::Error::invalid())) + }) + }) + .collect() +} + +/// Format an array into a comma-delimited string. +pub(crate) fn fmt_comma_delimited<T: fmt::Display>( + f: &mut fmt::Formatter, + mut iter: impl Iterator<Item = T>, +) -> fmt::Result { + if let Some(part) = iter.next() { + fmt::Display::fmt(&part, f)?; + } + for part in iter { + f.write_str(", ")?; + fmt::Display::fmt(&part, f)?; + } + Ok(()) +} diff --git a/third_party/rust/headers/src/util/entity.rs b/third_party/rust/headers/src/util/entity.rs new file mode 100644 index 0000000000..67604be4fc --- /dev/null +++ b/third_party/rust/headers/src/util/entity.rs @@ -0,0 +1,354 @@ +use std::fmt; + +use super::{FlatCsv, IterExt}; +use HeaderValue; + +/// An entity tag, defined in [RFC7232](https://tools.ietf.org/html/rfc7232#section-2.3) +/// +/// An entity tag consists of a string enclosed by two literal double quotes. +/// Preceding the first double quote is an optional weakness indicator, +/// which always looks like `W/`. Examples for valid tags are `"xyzzy"` and `W/"xyzzy"`. +/// +/// # ABNF +/// +/// ```text +/// entity-tag = [ weak ] opaque-tag +/// weak = %x57.2F ; "W/", case-sensitive +/// opaque-tag = DQUOTE *etagc DQUOTE +/// etagc = %x21 / %x23-7E / obs-text +/// ; VCHAR except double quotes, plus obs-text +/// ``` +/// +/// # Comparison +/// To check if two entity tags are equivalent in an application always use the `strong_eq` or +/// `weak_eq` methods based on the context of the Tag. Only use `==` to check if two tags are +/// identical. +/// +/// The example below shows the results for a set of entity-tag pairs and +/// both the weak and strong comparison function results: +/// +/// | ETag 1 | ETag 2 | Strong Comparison | Weak Comparison | +/// |---------|---------|-------------------|-----------------| +/// | `W/"1"` | `W/"1"` | no match | match | +/// | `W/"1"` | `W/"2"` | no match | no match | +/// | `W/"1"` | `"1"` | no match | match | +/// | `"1"` | `"1"` | match | match | +#[derive(Clone, Eq, PartialEq)] +pub(crate) struct EntityTag<T = HeaderValue>(T); + +#[derive(Clone, Debug, PartialEq)] +pub(crate) enum EntityTagRange { + Any, + Tags(FlatCsv), +} + +// ===== impl EntityTag ===== + +impl<T: AsRef<[u8]>> EntityTag<T> { + /// Get the tag. + pub(crate) fn tag(&self) -> &[u8] { + let bytes = self.0.as_ref(); + let end = bytes.len() - 1; + if bytes[0] == b'W' { + // W/"<tag>" + &bytes[3..end] + } else { + // "<tag>" + &bytes[1..end] + } + } + + /// Return if this is a "weak" tag. + pub(crate) fn is_weak(&self) -> bool { + self.0.as_ref()[0] == b'W' + } + + /// For strong comparison two entity-tags are equivalent if both are not weak and their + /// opaque-tags match character-by-character. + pub(crate) fn strong_eq<R>(&self, other: &EntityTag<R>) -> bool + where + R: AsRef<[u8]>, + { + !self.is_weak() && !other.is_weak() && self.tag() == other.tag() + } + + /// For weak comparison two entity-tags are equivalent if their + /// opaque-tags match character-by-character, regardless of either or + /// both being tagged as "weak". + pub(crate) fn weak_eq<R>(&self, other: &EntityTag<R>) -> bool + where + R: AsRef<[u8]>, + { + self.tag() == other.tag() + } + + /// The inverse of `EntityTag.strong_eq()`. + #[cfg(test)] + pub(crate) fn strong_ne(&self, other: &EntityTag) -> bool { + !self.strong_eq(other) + } + + /// The inverse of `EntityTag.weak_eq()`. + #[cfg(test)] + pub(crate) fn weak_ne(&self, other: &EntityTag) -> bool { + !self.weak_eq(other) + } + + pub(crate) fn parse(src: T) -> Option<Self> { + let slice = src.as_ref(); + let length = slice.len(); + + // Early exits if it doesn't terminate in a DQUOTE. + if length < 2 || slice[length - 1] != b'"' { + return None; + } + + let start = match slice[0] { + // "<tag>" + b'"' => 1, + // W/"<tag>" + b'W' => { + if length >= 4 && slice[1] == b'/' && slice[2] == b'"' { + 3 + } else { + return None; + } + } + _ => return None, + }; + + if check_slice_validity(&slice[start..length - 1]) { + Some(EntityTag(src)) + } else { + None + } + } +} + +impl EntityTag { + /* + /// Constructs a new EntityTag. + /// # Panics + /// If the tag contains invalid characters. + pub fn new(weak: bool, tag: String) -> EntityTag { + assert!(check_slice_validity(&tag), "Invalid tag: {:?}", tag); + EntityTag { weak: weak, tag: tag } + } + + /// Constructs a new weak EntityTag. + /// # Panics + /// If the tag contains invalid characters. + pub fn weak(tag: String) -> EntityTag { + EntityTag::new(true, tag) + } + + /// Constructs a new strong EntityTag. + /// # Panics + /// If the tag contains invalid characters. + pub fn strong(tag: String) -> EntityTag { + EntityTag::new(false, tag) + } + */ + + #[cfg(test)] + pub fn from_static(bytes: &'static str) -> EntityTag { + let val = HeaderValue::from_static(bytes); + match EntityTag::from_val(&val) { + Some(tag) => tag, + None => { + panic!("invalid static string for EntityTag: {:?}", bytes); + } + } + } + + pub(crate) fn from_owned(val: HeaderValue) -> Option<EntityTag> { + EntityTag::parse(val.as_bytes())?; + Some(EntityTag(val)) + } + + pub(crate) fn from_val(val: &HeaderValue) -> Option<EntityTag> { + EntityTag::parse(val.as_bytes()).map(|_entity| EntityTag(val.clone())) + } +} + +impl<T: fmt::Debug> fmt::Debug for EntityTag<T> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.0.fmt(f) + } +} + +impl super::TryFromValues for EntityTag { + fn try_from_values<'i, I>(values: &mut I) -> Result<Self, ::Error> + where + I: Iterator<Item = &'i HeaderValue>, + { + values + .just_one() + .and_then(EntityTag::from_val) + .ok_or_else(::Error::invalid) + } +} + +impl From<EntityTag> for HeaderValue { + fn from(tag: EntityTag) -> HeaderValue { + tag.0 + } +} + +impl<'a> From<&'a EntityTag> for HeaderValue { + fn from(tag: &'a EntityTag) -> HeaderValue { + tag.0.clone() + } +} + +/// check that each char in the slice is either: +/// 1. `%x21`, or +/// 2. in the range `%x23` to `%x7E`, or +/// 3. above `%x80` +fn check_slice_validity(slice: &[u8]) -> bool { + slice.iter().all(|&c| { + // HeaderValue already validates that this doesnt contain control + // characters, so we only need to look for DQUOTE (`"`). + // + // The debug_assert is just in case we use check_slice_validity in + // some new context that didnt come from a HeaderValue. + debug_assert!( + (c >= b'\x21' && c <= b'\x7e') | (c >= b'\x80'), + "EntityTag expects HeaderValue to have check for control characters" + ); + c != b'"' + }) +} + +// ===== impl EntityTagRange ===== + +impl EntityTagRange { + pub(crate) fn matches_strong(&self, entity: &EntityTag) -> bool { + self.matches_if(entity, |a, b| a.strong_eq(b)) + } + + pub(crate) fn matches_weak(&self, entity: &EntityTag) -> bool { + self.matches_if(entity, |a, b| a.weak_eq(b)) + } + + fn matches_if<F>(&self, entity: &EntityTag, func: F) -> bool + where + F: Fn(&EntityTag<&str>, &EntityTag) -> bool, + { + match *self { + EntityTagRange::Any => true, + EntityTagRange::Tags(ref tags) => tags + .iter() + .flat_map(EntityTag::<&str>::parse) + .any(|tag| func(&tag, entity)), + } + } +} + +impl super::TryFromValues for EntityTagRange { + fn try_from_values<'i, I>(values: &mut I) -> Result<Self, ::Error> + where + I: Iterator<Item = &'i HeaderValue>, + { + let flat = FlatCsv::try_from_values(values)?; + if flat.value == "*" { + Ok(EntityTagRange::Any) + } else { + Ok(EntityTagRange::Tags(flat)) + } + } +} + +impl<'a> From<&'a EntityTagRange> for HeaderValue { + fn from(tag: &'a EntityTagRange) -> HeaderValue { + match *tag { + EntityTagRange::Any => HeaderValue::from_static("*"), + EntityTagRange::Tags(ref tags) => tags.into(), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn parse(slice: &[u8]) -> Option<EntityTag> { + let val = HeaderValue::from_bytes(slice).ok()?; + EntityTag::from_val(&val) + } + + #[test] + fn test_etag_parse_success() { + // Expected success + let tag = parse(b"\"foobar\"").unwrap(); + assert!(!tag.is_weak()); + assert_eq!(tag.tag(), b"foobar"); + + let weak = parse(b"W/\"weaktag\"").unwrap(); + assert!(weak.is_weak()); + assert_eq!(weak.tag(), b"weaktag"); + } + + #[test] + fn test_etag_parse_failures() { + // Expected failures + macro_rules! fails { + ($slice:expr) => { + assert_eq!(parse($slice), None); + }; + } + + fails!(b"no-dquote"); + fails!(b"w/\"the-first-w-is-case sensitive\""); + fails!(b"W/\""); + fails!(b""); + fails!(b"\"unmatched-dquotes1"); + fails!(b"unmatched-dquotes2\""); + fails!(b"\"inner\"quotes\""); + } + + /* + #[test] + fn test_etag_fmt() { + assert_eq!(format!("{}", EntityTag::strong("foobar".to_owned())), "\"foobar\""); + assert_eq!(format!("{}", EntityTag::strong("".to_owned())), "\"\""); + assert_eq!(format!("{}", EntityTag::weak("weak-etag".to_owned())), "W/\"weak-etag\""); + assert_eq!(format!("{}", EntityTag::weak("\u{0065}".to_owned())), "W/\"\x65\""); + assert_eq!(format!("{}", EntityTag::weak("".to_owned())), "W/\"\""); + } + */ + + #[test] + fn test_cmp() { + // | ETag 1 | ETag 2 | Strong Comparison | Weak Comparison | + // |---------|---------|-------------------|-----------------| + // | `W/"1"` | `W/"1"` | no match | match | + // | `W/"1"` | `W/"2"` | no match | no match | + // | `W/"1"` | `"1"` | no match | match | + // | `"1"` | `"1"` | match | match | + let mut etag1 = EntityTag::from_static("W/\"1\""); + let mut etag2 = etag1.clone(); + assert!(!etag1.strong_eq(&etag2)); + assert!(etag1.weak_eq(&etag2)); + assert!(etag1.strong_ne(&etag2)); + assert!(!etag1.weak_ne(&etag2)); + + etag2 = EntityTag::from_static("W/\"2\""); + assert!(!etag1.strong_eq(&etag2)); + assert!(!etag1.weak_eq(&etag2)); + assert!(etag1.strong_ne(&etag2)); + assert!(etag1.weak_ne(&etag2)); + + etag2 = EntityTag::from_static("\"1\""); + assert!(!etag1.strong_eq(&etag2)); + assert!(etag1.weak_eq(&etag2)); + assert!(etag1.strong_ne(&etag2)); + assert!(!etag1.weak_ne(&etag2)); + + etag1 = EntityTag::from_static("\"1\""); + assert!(etag1.strong_eq(&etag2)); + assert!(etag1.weak_eq(&etag2)); + assert!(!etag1.strong_ne(&etag2)); + assert!(!etag1.weak_ne(&etag2)); + } +} diff --git a/third_party/rust/headers/src/util/flat_csv.rs b/third_party/rust/headers/src/util/flat_csv.rs new file mode 100644 index 0000000000..7be56c87db --- /dev/null +++ b/third_party/rust/headers/src/util/flat_csv.rs @@ -0,0 +1,199 @@ +use std::fmt; +use std::iter::FromIterator; +use std::marker::PhantomData; + +use bytes::BytesMut; +use util::TryFromValues; +use HeaderValue; + +// A single `HeaderValue` that can flatten multiple values with commas. +#[derive(Clone, PartialEq, Eq, Hash)] +pub(crate) struct FlatCsv<Sep = Comma> { + pub(crate) value: HeaderValue, + _marker: PhantomData<Sep>, +} + +pub(crate) trait Separator { + const BYTE: u8; + const CHAR: char; +} + +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub(crate) enum Comma {} + +impl Separator for Comma { + const BYTE: u8 = b','; + const CHAR: char = ','; +} + +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub(crate) enum SemiColon {} + +impl Separator for SemiColon { + const BYTE: u8 = b';'; + const CHAR: char = ';'; +} + +impl<Sep: Separator> FlatCsv<Sep> { + pub(crate) fn iter(&self) -> impl Iterator<Item = &str> { + self.value.to_str().ok().into_iter().flat_map(|value_str| { + let mut in_quotes = false; + value_str + .split(move |c| { + if in_quotes { + if c == '"' { + in_quotes = false; + } + false // dont split + } else { + if c == Sep::CHAR { + true // split + } else { + if c == '"' { + in_quotes = true; + } + false // dont split + } + } + }) + .map(|item| item.trim()) + }) + } +} + +impl<Sep: Separator> TryFromValues for FlatCsv<Sep> { + fn try_from_values<'i, I>(values: &mut I) -> Result<Self, ::Error> + where + I: Iterator<Item = &'i HeaderValue>, + { + let flat = values.collect(); + Ok(flat) + } +} + +impl<Sep> From<HeaderValue> for FlatCsv<Sep> { + fn from(value: HeaderValue) -> Self { + FlatCsv { + value, + _marker: PhantomData, + } + } +} + +impl<'a, Sep> From<&'a FlatCsv<Sep>> for HeaderValue { + fn from(flat: &'a FlatCsv<Sep>) -> HeaderValue { + flat.value.clone() + } +} + +impl<Sep> fmt::Debug for FlatCsv<Sep> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fmt::Debug::fmt(&self.value, f) + } +} + +impl<'a, Sep: Separator> FromIterator<&'a HeaderValue> for FlatCsv<Sep> { + fn from_iter<I>(iter: I) -> Self + where + I: IntoIterator<Item = &'a HeaderValue>, + { + let mut values = iter.into_iter(); + + // Common case is there is only 1 value, optimize for that + if let (1, Some(1)) = values.size_hint() { + return values + .next() + .expect("size_hint claimed 1 item") + .clone() + .into(); + } + + // Otherwise, there are multiple, so this should merge them into 1. + let mut buf = values + .next() + .cloned() + .map(|val| BytesMut::from(val.as_bytes())) + .unwrap_or_else(|| BytesMut::new()); + + for val in values { + buf.extend_from_slice(&[Sep::BYTE, b' ']); + buf.extend_from_slice(val.as_bytes()); + } + + let val = HeaderValue::from_maybe_shared(buf.freeze()) + .expect("comma separated HeaderValues are valid"); + + val.into() + } +} + +// TODO: would be great if there was a way to de-dupe these with above +impl<Sep: Separator> FromIterator<HeaderValue> for FlatCsv<Sep> { + fn from_iter<I>(iter: I) -> Self + where + I: IntoIterator<Item = HeaderValue>, + { + let mut values = iter.into_iter(); + + // Common case is there is only 1 value, optimize for that + if let (1, Some(1)) = values.size_hint() { + return values.next().expect("size_hint claimed 1 item").into(); + } + + // Otherwise, there are multiple, so this should merge them into 1. + let mut buf = values + .next() + .map(|val| BytesMut::from(val.as_bytes())) + .unwrap_or_else(|| BytesMut::new()); + + for val in values { + buf.extend_from_slice(&[Sep::BYTE, b' ']); + buf.extend_from_slice(val.as_bytes()); + } + + let val = HeaderValue::from_maybe_shared(buf.freeze()) + .expect("comma separated HeaderValues are valid"); + + val.into() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn comma() { + let val = HeaderValue::from_static("aaa, b; bb, ccc"); + let csv = FlatCsv::<Comma>::from(val); + + let mut values = csv.iter(); + assert_eq!(values.next(), Some("aaa")); + assert_eq!(values.next(), Some("b; bb")); + assert_eq!(values.next(), Some("ccc")); + assert_eq!(values.next(), None); + } + + #[test] + fn semicolon() { + let val = HeaderValue::from_static("aaa; b, bb; ccc"); + let csv = FlatCsv::<SemiColon>::from(val); + + let mut values = csv.iter(); + assert_eq!(values.next(), Some("aaa")); + assert_eq!(values.next(), Some("b, bb")); + assert_eq!(values.next(), Some("ccc")); + assert_eq!(values.next(), None); + } + + #[test] + fn quoted_text() { + let val = HeaderValue::from_static("foo=\"bar,baz\", sherlock=holmes"); + let csv = FlatCsv::<Comma>::from(val); + + let mut values = csv.iter(); + assert_eq!(values.next(), Some("foo=\"bar,baz\"")); + assert_eq!(values.next(), Some("sherlock=holmes")); + assert_eq!(values.next(), None); + } +} diff --git a/third_party/rust/headers/src/util/fmt.rs b/third_party/rust/headers/src/util/fmt.rs new file mode 100644 index 0000000000..c535c72925 --- /dev/null +++ b/third_party/rust/headers/src/util/fmt.rs @@ -0,0 +1,11 @@ +use std::fmt::Display; + +use HeaderValue; + +pub(crate) fn fmt<T: Display>(fmt: T) -> HeaderValue { + let s = fmt.to_string(); + match HeaderValue::from_maybe_shared(s) { + Ok(val) => val, + Err(err) => panic!("illegal HeaderValue; error = {:?}, fmt = \"{}\"", err, fmt), + } +} diff --git a/third_party/rust/headers/src/util/http_date.rs b/third_party/rust/headers/src/util/http_date.rs new file mode 100644 index 0000000000..da3f83969d --- /dev/null +++ b/third_party/rust/headers/src/util/http_date.rs @@ -0,0 +1,152 @@ +use std::fmt; +use std::str::FromStr; +use std::time::SystemTime; + +use bytes::Bytes; +use http::header::HeaderValue; +use httpdate; + +use super::IterExt; + +/// A timestamp with HTTP formatting and parsing +// Prior to 1995, there were three different formats commonly used by +// servers to communicate timestamps. For compatibility with old +// implementations, all three are defined here. The preferred format is +// a fixed-length and single-zone subset of the date and time +// specification used by the Internet Message Format [RFC5322]. +// +// HTTP-date = IMF-fixdate / obs-date +// +// An example of the preferred format is +// +// Sun, 06 Nov 1994 08:49:37 GMT ; IMF-fixdate +// +// Examples of the two obsolete formats are +// +// Sunday, 06-Nov-94 08:49:37 GMT ; obsolete RFC 850 format +// Sun Nov 6 08:49:37 1994 ; ANSI C's asctime() format +// +// A recipient that parses a timestamp value in an HTTP header field +// MUST accept all three HTTP-date formats. When a sender generates a +// header field that contains one or more timestamps defined as +// HTTP-date, the sender MUST generate those timestamps in the +// IMF-fixdate format. +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub(crate) struct HttpDate(httpdate::HttpDate); + +impl HttpDate { + pub(crate) fn from_val(val: &HeaderValue) -> Option<Self> { + val.to_str().ok()?.parse().ok() + } +} + +// TODO: remove this and FromStr? +#[derive(Debug)] +pub struct Error(()); + +impl super::TryFromValues for HttpDate { + fn try_from_values<'i, I>(values: &mut I) -> Result<Self, ::Error> + where + I: Iterator<Item = &'i HeaderValue>, + { + values + .just_one() + .and_then(HttpDate::from_val) + .ok_or_else(::Error::invalid) + } +} + +impl From<HttpDate> for HeaderValue { + fn from(date: HttpDate) -> HeaderValue { + (&date).into() + } +} + +impl<'a> From<&'a HttpDate> for HeaderValue { + fn from(date: &'a HttpDate) -> HeaderValue { + // TODO: could be just BytesMut instead of String + let s = date.to_string(); + let bytes = Bytes::from(s); + HeaderValue::from_maybe_shared(bytes).expect("HttpDate always is a valid value") + } +} + +impl FromStr for HttpDate { + type Err = Error; + fn from_str(s: &str) -> Result<HttpDate, Error> { + Ok(HttpDate(s.parse().map_err(|_| Error(()))?)) + } +} + +impl fmt::Debug for HttpDate { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fmt::Display::fmt(&self.0, f) + } +} + +impl fmt::Display for HttpDate { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fmt::Display::fmt(&self.0, f) + } +} + +impl From<SystemTime> for HttpDate { + fn from(sys: SystemTime) -> HttpDate { + HttpDate(sys.into()) + } +} + +impl From<HttpDate> for SystemTime { + fn from(date: HttpDate) -> SystemTime { + SystemTime::from(date.0) + } +} + +#[cfg(test)] +mod tests { + use super::HttpDate; + + use std::time::{Duration, UNIX_EPOCH}; + + // The old tests had Sunday, but 1994-11-07 is a Monday. + // See https://github.com/pyfisch/httpdate/pull/6#issuecomment-846881001 + fn nov_07() -> HttpDate { + HttpDate((UNIX_EPOCH + Duration::new(784198117, 0)).into()) + } + + #[test] + fn test_display_is_imf_fixdate() { + assert_eq!("Mon, 07 Nov 1994 08:48:37 GMT", &nov_07().to_string()); + } + + #[test] + fn test_imf_fixdate() { + assert_eq!( + "Mon, 07 Nov 1994 08:48:37 GMT".parse::<HttpDate>().unwrap(), + nov_07() + ); + } + + #[test] + fn test_rfc_850() { + assert_eq!( + "Monday, 07-Nov-94 08:48:37 GMT" + .parse::<HttpDate>() + .unwrap(), + nov_07() + ); + } + + #[test] + fn test_asctime() { + assert_eq!( + "Mon Nov 7 08:48:37 1994".parse::<HttpDate>().unwrap(), + nov_07() + ); + } + + #[test] + fn test_no_date() { + assert!("this-is-no-date".parse::<HttpDate>().is_err()); + } +} diff --git a/third_party/rust/headers/src/util/iter.rs b/third_party/rust/headers/src/util/iter.rs new file mode 100644 index 0000000000..03ed691ed6 --- /dev/null +++ b/third_party/rust/headers/src/util/iter.rs @@ -0,0 +1,11 @@ +pub trait IterExt: Iterator { + fn just_one(&mut self) -> Option<Self::Item> { + let one = self.next()?; + match self.next() { + Some(_) => None, + None => Some(one), + } + } +} + +impl<T: Iterator> IterExt for T {} diff --git a/third_party/rust/headers/src/util/mod.rs b/third_party/rust/headers/src/util/mod.rs new file mode 100644 index 0000000000..07fddbfb6c --- /dev/null +++ b/third_party/rust/headers/src/util/mod.rs @@ -0,0 +1,87 @@ +use HeaderValue; + +//pub use self::charset::Charset; +//pub use self::encoding::Encoding; +pub(crate) use self::entity::{EntityTag, EntityTagRange}; +pub(crate) use self::flat_csv::{FlatCsv, SemiColon}; +pub(crate) use self::fmt::fmt; +pub(crate) use self::http_date::HttpDate; +pub(crate) use self::iter::IterExt; +//pub use language_tags::LanguageTag; +//pub use self::quality_value::{Quality, QualityValue}; +pub(crate) use self::seconds::Seconds; +pub(crate) use self::value_string::HeaderValueString; + +//mod charset; +pub(crate) mod csv; +//mod encoding; +mod entity; +mod flat_csv; +mod fmt; +mod http_date; +mod iter; +//mod quality_value; +mod seconds; +mod value_string; + +macro_rules! error_type { + ($name:ident) => { + #[doc(hidden)] + pub struct $name { + _inner: (), + } + + impl ::std::fmt::Debug for $name { + fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { + f.debug_struct(stringify!($name)).finish() + } + } + + impl ::std::fmt::Display for $name { + fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { + f.write_str(stringify!($name)) + } + } + + impl ::std::error::Error for $name {} + }; +} + +macro_rules! derive_header { + ($type:ident(_), name: $name:ident) => { + impl crate::Header for $type { + fn name() -> &'static ::http::header::HeaderName { + &::http::header::$name + } + + fn decode<'i, I>(values: &mut I) -> Result<Self, ::Error> + where + I: Iterator<Item = &'i ::http::header::HeaderValue>, + { + ::util::TryFromValues::try_from_values(values).map($type) + } + + fn encode<E: Extend<::HeaderValue>>(&self, values: &mut E) { + values.extend(::std::iter::once((&self.0).into())); + } + } + }; +} + +/// A helper trait for use when deriving `Header`. +pub(crate) trait TryFromValues: Sized { + /// Try to convert from the values into an instance of `Self`. + fn try_from_values<'i, I>(values: &mut I) -> Result<Self, ::Error> + where + Self: Sized, + I: Iterator<Item = &'i HeaderValue>; +} + +impl TryFromValues for HeaderValue { + fn try_from_values<'i, I>(values: &mut I) -> Result<Self, ::Error> + where + I: Iterator<Item = &'i HeaderValue>, + { + values.next().cloned().ok_or_else(|| ::Error::invalid()) + } +} diff --git a/third_party/rust/headers/src/util/seconds.rs b/third_party/rust/headers/src/util/seconds.rs new file mode 100644 index 0000000000..a1a9194b0c --- /dev/null +++ b/third_party/rust/headers/src/util/seconds.rs @@ -0,0 +1,67 @@ +use std::fmt; +use std::time::Duration; + +use util::IterExt; +use HeaderValue; + +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub(crate) struct Seconds(Duration); + +impl Seconds { + pub(crate) fn from_val(val: &HeaderValue) -> Option<Self> { + let secs = val.to_str().ok()?.parse().ok()?; + + Some(Self::from_secs(secs)) + } + + pub(crate) fn from_secs(secs: u64) -> Self { + Self::from(Duration::from_secs(secs)) + } + + pub(crate) fn as_u64(&self) -> u64 { + self.0.as_secs() + } +} + +impl super::TryFromValues for Seconds { + fn try_from_values<'i, I>(values: &mut I) -> Result<Self, ::Error> + where + I: Iterator<Item = &'i HeaderValue>, + { + values + .just_one() + .and_then(Seconds::from_val) + .ok_or_else(::Error::invalid) + } +} + +impl<'a> From<&'a Seconds> for HeaderValue { + fn from(secs: &'a Seconds) -> HeaderValue { + secs.0.as_secs().into() + } +} + +impl From<Duration> for Seconds { + fn from(dur: Duration) -> Seconds { + debug_assert!(dur.subsec_nanos() == 0); + Seconds(dur) + } +} + +impl From<Seconds> for Duration { + fn from(secs: Seconds) -> Duration { + secs.0 + } +} + +impl fmt::Debug for Seconds { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}s", self.0.as_secs()) + } +} + +impl fmt::Display for Seconds { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fmt::Display::fmt(&self.0.as_secs(), f) + } +} diff --git a/third_party/rust/headers/src/util/value_string.rs b/third_party/rust/headers/src/util/value_string.rs new file mode 100644 index 0000000000..865a3558b9 --- /dev/null +++ b/third_party/rust/headers/src/util/value_string.rs @@ -0,0 +1,93 @@ +use std::{ + fmt, + str::{self, FromStr}, +}; + +use bytes::Bytes; +use http::header::HeaderValue; + +use super::IterExt; + +/// A value that is both a valid `HeaderValue` and `String`. +#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub(crate) struct HeaderValueString { + /// Care must be taken to only set this value when it is also + /// a valid `String`, since `as_str` will convert to a `&str` + /// in an unchecked manner. + value: HeaderValue, +} + +impl HeaderValueString { + pub(crate) fn from_val(val: &HeaderValue) -> Result<Self, ::Error> { + if val.to_str().is_ok() { + Ok(HeaderValueString { value: val.clone() }) + } else { + Err(::Error::invalid()) + } + } + + pub(crate) fn from_string(src: String) -> Option<Self> { + // A valid `str` (the argument)... + let bytes = Bytes::from(src); + HeaderValue::from_maybe_shared(bytes) + .ok() + .map(|value| HeaderValueString { value }) + } + + pub(crate) fn from_static(src: &'static str) -> HeaderValueString { + // A valid `str` (the argument)... + HeaderValueString { + value: HeaderValue::from_static(src), + } + } + + pub(crate) fn as_str(&self) -> &str { + // HeaderValueString is only created from HeaderValues + // that have validated they are also UTF-8 strings. + unsafe { str::from_utf8_unchecked(self.value.as_bytes()) } + } +} + +impl fmt::Debug for HeaderValueString { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fmt::Debug::fmt(self.as_str(), f) + } +} + +impl fmt::Display for HeaderValueString { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fmt::Display::fmt(self.as_str(), f) + } +} + +impl super::TryFromValues for HeaderValueString { + fn try_from_values<'i, I>(values: &mut I) -> Result<Self, ::Error> + where + I: Iterator<Item = &'i HeaderValue>, + { + values + .just_one() + .map(HeaderValueString::from_val) + .unwrap_or_else(|| Err(::Error::invalid())) + } +} + +impl<'a> From<&'a HeaderValueString> for HeaderValue { + fn from(src: &'a HeaderValueString) -> HeaderValue { + src.value.clone() + } +} + +#[derive(Debug)] +pub(crate) struct FromStrError(()); + +impl FromStr for HeaderValueString { + type Err = FromStrError; + + fn from_str(src: &str) -> Result<Self, Self::Err> { + // A valid `str` (the argument)... + src.parse() + .map(|value| HeaderValueString { value }) + .map_err(|_| FromStrError(())) + } +} |