summaryrefslogtreecommitdiffstats
path: root/third_party/rust/headers/src
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /third_party/rust/headers/src
parentInitial commit. (diff)
downloadfirefox-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')
-rw-r--r--third_party/rust/headers/src/common/accept_ranges.rs42
-rw-r--r--third_party/rust/headers/src/common/access_control_allow_credentials.rs72
-rw-r--r--third_party/rust/headers/src/common/access_control_allow_headers.rs98
-rw-r--r--third_party/rust/headers/src/common/access_control_allow_methods.rs91
-rw-r--r--third_party/rust/headers/src/common/access_control_allow_origin.rs169
-rw-r--r--third_party/rust/headers/src/common/access_control_expose_headers.rs88
-rw-r--r--third_party/rust/headers/src/common/access_control_max_age.rs48
-rw-r--r--third_party/rust/headers/src/common/access_control_request_headers.rs89
-rw-r--r--third_party/rust/headers/src/common/access_control_request_method.rs73
-rw-r--r--third_party/rust/headers/src/common/age.rs69
-rw-r--r--third_party/rust/headers/src/common/allow.rs68
-rw-r--r--third_party/rust/headers/src/common/authorization.rs280
-rw-r--r--third_party/rust/headers/src/common/cache_control.rs508
-rw-r--r--third_party/rust/headers/src/common/connection.rs136
-rw-r--r--third_party/rust/headers/src/common/content_disposition.rs323
-rw-r--r--third_party/rust/headers/src/common/content_encoding.rs86
-rw-r--r--third_party/rust/headers/src/common/content_length.rs95
-rw-r--r--third_party/rust/headers/src/common/content_location.rs55
-rw-r--r--third_party/rust/headers/src/common/content_range.rs237
-rw-r--r--third_party/rust/headers/src/common/content_type.rs177
-rw-r--r--third_party/rust/headers/src/common/cookie.rs205
-rw-r--r--third_party/rust/headers/src/common/date.rs46
-rw-r--r--third_party/rust/headers/src/common/etag.rs111
-rw-r--r--third_party/rust/headers/src/common/expect.rs86
-rw-r--r--third_party/rust/headers/src/common/expires.rs50
-rw-r--r--third_party/rust/headers/src/common/host.rs54
-rw-r--r--third_party/rust/headers/src/common/if_match.rs111
-rw-r--r--third_party/rust/headers/src/common/if_modified_since.rs75
-rw-r--r--third_party/rust/headers/src/common/if_none_match.rs111
-rw-r--r--third_party/rust/headers/src/common/if_range.rs136
-rw-r--r--third_party/rust/headers/src/common/if_unmodified_since.rs76
-rw-r--r--third_party/rust/headers/src/common/last_modified.rs51
-rw-r--r--third_party/rust/headers/src/common/location.rs51
-rw-r--r--third_party/rust/headers/src/common/mod.rs190
-rw-r--r--third_party/rust/headers/src/common/origin.rs210
-rw-r--r--third_party/rust/headers/src/common/pragma.rs61
-rw-r--r--third_party/rust/headers/src/common/proxy_authorization.rs47
-rw-r--r--third_party/rust/headers/src/common/range.rs418
-rw-r--r--third_party/rust/headers/src/common/referer.rs60
-rw-r--r--third_party/rust/headers/src/common/referrer_policy.rs190
-rw-r--r--third_party/rust/headers/src/common/retry_after.rs111
-rw-r--r--third_party/rust/headers/src/common/sec_websocket_accept.rs67
-rw-r--r--third_party/rust/headers/src/common/sec_websocket_key.rs8
-rw-r--r--third_party/rust/headers/src/common/sec_websocket_version.rs58
-rw-r--r--third_party/rust/headers/src/common/server.rs72
-rw-r--r--third_party/rust/headers/src/common/set_cookie.rs103
-rw-r--r--third_party/rust/headers/src/common/strict_transport_security.rs247
-rw-r--r--third_party/rust/headers/src/common/te.rs41
-rw-r--r--third_party/rust/headers/src/common/transfer_encoding.rs104
-rw-r--r--third_party/rust/headers/src/common/upgrade.rs55
-rw-r--r--third_party/rust/headers/src/common/user_agent.rs81
-rw-r--r--third_party/rust/headers/src/common/vary.rs84
-rw-r--r--third_party/rust/headers/src/disabled/accept.rs150
-rw-r--r--third_party/rust/headers/src/disabled/accept_charset.rs57
-rw-r--r--third_party/rust/headers/src/disabled/accept_encoding.rs72
-rw-r--r--third_party/rust/headers/src/disabled/accept_language.rs72
-rw-r--r--third_party/rust/headers/src/disabled/content_language.rs35
-rw-r--r--third_party/rust/headers/src/disabled/from.rs29
-rw-r--r--third_party/rust/headers/src/disabled/last_event_id.rs40
-rw-r--r--third_party/rust/headers/src/disabled/link.rs1105
-rw-r--r--third_party/rust/headers/src/disabled/prefer.rs210
-rw-r--r--third_party/rust/headers/src/disabled/preference_applied.rs110
-rw-r--r--third_party/rust/headers/src/disabled/util/charset.rs229
-rw-r--r--third_party/rust/headers/src/disabled/util/encoding.rs57
-rw-r--r--third_party/rust/headers/src/disabled/util/extended_value.rs192
-rw-r--r--third_party/rust/headers/src/disabled/util/quality_value.rs268
-rw-r--r--third_party/rust/headers/src/disabled/warning.rs182
-rw-r--r--third_party/rust/headers/src/lib.rs99
-rw-r--r--third_party/rust/headers/src/map_ext.rs88
-rw-r--r--third_party/rust/headers/src/util/csv.rs38
-rw-r--r--third_party/rust/headers/src/util/entity.rs354
-rw-r--r--third_party/rust/headers/src/util/flat_csv.rs199
-rw-r--r--third_party/rust/headers/src/util/fmt.rs11
-rw-r--r--third_party/rust/headers/src/util/http_date.rs152
-rw-r--r--third_party/rust/headers/src/util/iter.rs11
-rw-r--r--third_party/rust/headers/src/util/mod.rs87
-rw-r--r--third_party/rust/headers/src/util/seconds.rs67
-rw-r--r--third_party/rust/headers/src/util/value_string.rs93
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(()))
+ }
+}