chore: checkpoint before Python removal

This commit is contained in:
2026-03-26 22:33:59 +00:00
parent 683cec9307
commit e568ddf82a
29972 changed files with 11269302 additions and 2 deletions

View File

@@ -0,0 +1,114 @@
use http::HeaderValue;
use crate::util::FlatCsv;
/// `Accept-Ranges` header, defined in [RFC7233](https://datatracker.ietf.org/doc/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
}
const ACCEPT_RANGES_BYTES: &str = "bytes";
const ACCEPT_RANGES_NONE: &str = "none";
impl AcceptRanges {
/// A constructor to easily create the common `Accept-Ranges: bytes` header.
pub fn bytes() -> Self {
AcceptRanges(HeaderValue::from_static(ACCEPT_RANGES_BYTES).into())
}
/// Check if the unit is `bytes`.
pub fn is_bytes(&self) -> bool {
self.0.value == ACCEPT_RANGES_BYTES
}
/// A constructor to easily create the common `Accept-Ranges: none` header.
pub fn none() -> Self {
AcceptRanges(HeaderValue::from_static(ACCEPT_RANGES_NONE).into())
}
/// Check if the unit is `none`.
pub fn is_none(&self) -> bool {
self.0.value == ACCEPT_RANGES_NONE
}
}
#[cfg(test)]
mod tests {
use super::super::test_decode;
use super::*;
fn accept_ranges(s: &str) -> AcceptRanges {
test_decode(&[s]).unwrap()
}
// bytes
#[test]
fn bytes_constructor() {
assert_eq!(accept_ranges("bytes"), AcceptRanges::bytes());
}
#[test]
fn is_bytes_method_successful_with_bytes_ranges() {
assert!(accept_ranges("bytes").is_bytes());
}
#[test]
fn is_bytes_method_successful_with_bytes_ranges_by_constructor() {
assert!(AcceptRanges::bytes().is_bytes());
}
#[test]
fn is_bytes_method_failed_with_not_bytes_ranges() {
assert!(!accept_ranges("dummy").is_bytes());
}
// none
#[test]
fn none_constructor() {
assert_eq!(accept_ranges("none"), AcceptRanges::none());
}
#[test]
fn is_none_method_successful_with_none_ranges() {
assert!(accept_ranges("none").is_none());
}
#[test]
fn is_none_method_successful_with_none_ranges_by_constructor() {
assert!(AcceptRanges::none().is_none());
}
#[test]
fn is_none_method_failed_with_not_none_ranges() {
assert!(!accept_ranges("dummy").is_none());
}
}

View File

@@ -0,0 +1,73 @@
use http::{HeaderName, HeaderValue};
use crate::{Error, Header};
/// `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
///
/// ```
/// 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());
}
}

View File

@@ -0,0 +1,98 @@
use std::iter::FromIterator;
use http::{HeaderName, HeaderValue};
use crate::util::FlatCsv;
/// `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 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(&self) -> impl Iterator<Item = HeaderName> + '_ {
self.0
.iter()
.map(|s| s.parse().ok())
.take_while(|val| val.is_some())
.flatten()
}
}
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());
}
}

View File

@@ -0,0 +1,90 @@
use std::iter::FromIterator;
use http::{HeaderValue, Method};
use crate::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 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(&self) -> impl Iterator<Item = Method> + '_ {
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");
}
}

View File

@@ -0,0 +1,170 @@
use std::convert::TryFrom;
use http::HeaderValue;
use super::origin::Origin;
use crate::util::{IterExt, TryFromValues};
use crate::Error;
/// 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
///
/// ```
/// 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.to_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");
}
}

View File

@@ -0,0 +1,88 @@
use std::iter::FromIterator;
use http::{HeaderName, HeaderValue};
use crate::util::FlatCsv;
/// `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 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(&self) -> impl Iterator<Item = HeaderName> + '_ {
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"
);
}
}

View File

@@ -0,0 +1,47 @@
use std::time::Duration;
use crate::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
///
/// ```
/// 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()
}
}

View File

@@ -0,0 +1,89 @@
use std::iter::FromIterator;
use http::{HeaderName, HeaderValue};
use crate::util::FlatCsv;
/// `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 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(&self) -> impl Iterator<Item = HeaderName> + '_ {
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"
);
}
}

View File

@@ -0,0 +1,73 @@
use http::{HeaderName, HeaderValue, Method};
use crate::{Error, Header};
/// `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 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
}
}

68
vendor/headers/src/common/age.rs vendored Normal file
View File

@@ -0,0 +1,68 @@
use std::time::Duration;
use crate::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
///
/// ```
/// 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()
}
}

67
vendor/headers/src/common/allow.rs vendored Normal file
View File

@@ -0,0 +1,67 @@
use std::iter::FromIterator;
use http::{HeaderValue, Method};
use crate::util::FlatCsv;
/// `Allow` header, defined in [RFC7231](https://datatracker.ietf.org/doc/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 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(&self) -> impl Iterator<Item = Method> + '_ {
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)
}
}

View File

@@ -0,0 +1,309 @@
//! Authorization header and types.
use base64::engine::general_purpose::STANDARD as ENGINE;
use base64::Engine;
use bytes::Bytes;
use http::{HeaderName, HeaderValue};
use crate::util::HeaderValueString;
use crate::{Error, Header};
/// `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
///
/// ```
/// 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(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.len() > C::SCHEME.len()
&& slice[C::SCHEME.len()] == b' '
&& slice[..C::SCHEME.len()].eq_ignore_ascii_case(C::SCHEME.as_bytes())
{
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()[..Self::SCHEME.len()].eq_ignore_ascii_case(Self::SCHEME.as_bytes()),
"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()..].trim_start()
}
}
impl Credentials for Bearer {
const SCHEME: &'static str = "Bearer";
fn decode(value: &HeaderValue) -> Option<Self> {
debug_assert!(
value.as_bytes()[..Self::SCHEME.len()].eq_ignore_ascii_case(Self::SCHEME.as_bytes()),
"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 http::header::HeaderMap;
use super::super::{test_decode, test_encode};
use super::{Authorization, Basic, Bearer};
use crate::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_case_insensitive() {
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_extra_whitespaces() {
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");
}
#[test]
fn bearer_decode_case_insensitive() {
let auth: Authorization<Bearer> = test_decode(&["bearer fpKL54jvWmEGVoRdCNjG"]).unwrap();
assert_eq!(auth.0.token().as_bytes(), b"fpKL54jvWmEGVoRdCNjG");
}
#[test]
fn bearer_decode_extra_whitespaces() {
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()] });

View File

@@ -0,0 +1,563 @@
use std::fmt;
use std::iter::FromIterator;
use std::str::FromStr;
use std::time::Duration;
use http::{HeaderName, HeaderValue};
use crate::util::{self, csv, Seconds};
use crate::{Error, Header};
/// `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
///
/// ```
/// 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 };
const MUST_UNDERSTAND: Self = Self { bits: 0b1000000000 };
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)
}
/// Check if the `must-revalidate` directive is set.
pub fn must_revalidate(&self) -> bool {
self.flags.contains(Flags::MUST_REVALIDATE)
}
/// Check if the `must-understand` directive is set.
pub fn must_understand(&self) -> bool {
self.flags.contains(Flags::MUST_UNDERSTAND)
}
/// 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 `must-revalidate` directive.
pub fn with_must_revalidate(mut self) -> Self {
self.flags.insert(Flags::MUST_REVALIDATE);
self
}
/// Set the `must-understand` directive.
pub fn with_must_understand(mut self) -> Self {
self.flags.insert(Flags::MUST_UNDERSTAND);
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::MustUnderstand => {
cc.flags.insert(Flags::MUST_UNDERSTAND);
}
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());
}
Directive::MaxStale(secs) => {
cc.max_stale = Some(Duration::from_secs(secs).into());
}
Directive::MinFresh(secs) => {
cc.min_fresh = Some(Duration::from_secs(secs).into());
}
Directive::SMaxAge(secs) => {
cc.s_max_age = Some(Duration::from_secs(secs).into());
}
}
}
FromIter(cc)
}
}
struct Fmt<'a>(&'a CacheControl);
impl fmt::Display for Fmt<'_> {
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::MUST_UNDERSTAND, Directive::MustUnderstand),
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,
MustUnderstand,
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::MustUnderstand => "must-understand",
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,
"must-understand" => Directive::MustUnderstand,
"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_must_revalidate() {
let cc = CacheControl::new().with_must_revalidate();
let headers = test_encode(cc.clone());
assert_eq!(headers["cache-control"], "must-revalidate");
assert_eq!(
test_decode::<CacheControl>(&["must-revalidate"]).unwrap(),
cc
);
assert!(cc.must_revalidate());
}
#[test]
fn test_must_understand() {
let cc = CacheControl::new().with_must_understand();
let headers = test_encode(cc.clone());
assert_eq!(headers["cache-control"], "must-understand");
assert_eq!(
test_decode::<CacheControl>(&["must-understand"]).unwrap(),
cc
);
assert!(cc.must_understand());
}
#[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");
}
}

134
vendor/headers/src/common/connection.rs vendored Normal file
View File

@@ -0,0 +1,134 @@
use std::iter::FromIterator;
use http::{HeaderName, HeaderValue};
use self::sealed::AsConnectionOption;
use crate::util::FlatCsv;
/// `Connection` header, defined in
/// [RFC7230](https://datatracker.ietf.org/doc/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
///
/// ```
/// 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 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().any(|opt| opt.eq_ignore_ascii_case(s))
}
}
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 {
use http::HeaderName;
pub trait AsConnectionOption: Sealed {
fn as_connection_option(&self) -> &str;
}
pub trait Sealed {}
impl AsConnectionOption for &str {
fn as_connection_option(&self) -> &str {
self
}
}
impl Sealed for &str {}
impl AsConnectionOption for &HeaderName {
fn as_connection_option(&self) -> &str {
self.as_ref()
}
}
impl Sealed for &HeaderName {}
impl AsConnectionOption for HeaderName {
fn as_connection_option(&self) -> &str {
self.as_ref()
}
}
impl Sealed for HeaderName {}
}

View File

@@ -0,0 +1,326 @@
// # 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
use http::{HeaderName, HeaderValue};
use crate::{Error, Header};
/// 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
///
/// ```
/// 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);
}
}
*/

View File

@@ -0,0 +1,99 @@
use http::HeaderValue;
use self::sealed::AsCoding;
use crate::util::FlatCsv;
/// `Content-Encoding` header, defined in
/// [RFC7231](https://datatracker.ietf.org/doc/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`
/// * `br`
/// * `zstd`
///
/// # Examples
///
/// ```
/// 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())
}
/// A constructor to easily create a `Content-Encoding: br` header.
#[inline]
pub fn brotli() -> ContentEncoding {
ContentEncoding(HeaderValue::from_static("br").into())
}
/// A constructor to easily create a `Content-Encoding: zstd` header.
#[inline]
pub fn zstd() -> ContentEncoding {
ContentEncoding(HeaderValue::from_static("zstd").into())
}
/// Check if this header contains a given "coding".
///
/// This can be used with these argument types:
///
/// - `&str`
///
/// # Example
///
/// ```
/// 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().any(|opt| opt == s)
}
}
mod sealed {
pub trait AsCoding: Sealed {}
pub trait Sealed {
fn as_coding(&self) -> &str;
}
impl AsCoding for &str {}
impl Sealed for &str {
fn as_coding(&self) -> &str {
self
}
}
}

View File

@@ -0,0 +1,96 @@
use http::HeaderValue;
use crate::{Error, Header};
/// `Content-Length` header, defined in
/// [RFC7230](https://datatracker.ietf.org/doc/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](https://datatracker.ietf.org/doc/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
///
/// ```
/// 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);
});
*/

View File

@@ -0,0 +1,55 @@
use http::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)));
}
}

View File

@@ -0,0 +1,238 @@
use std::fmt;
use std::ops::{Bound, RangeBounds};
use http::{HeaderName, HeaderValue};
use crate::{util, Error, Header};
/// 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
///
/// ```
/// 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 fmt::Display for Adapter<'_> {
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>);
*/

View File

@@ -0,0 +1,179 @@
use std::fmt;
use http::{HeaderName, HeaderValue};
use mime::Mime;
use crate::{Error, Header};
/// `Content-Type` header, defined in
/// [RFC7231](https://datatracker.ietf.org/doc/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
///
/// ```
/// 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"
);
}

204
vendor/headers/src/common/cookie.rs vendored Normal file
View File

@@ -0,0 +1,204 @@
use crate::util::{FlatCsv, SemiColon};
/// `Cookie` header, defined in [RFC6265](https://datatracker.ietf.org/doc/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
///
/// ```
/// 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);
}
*/
}

46
vendor/headers/src/common/date.rs vendored Normal file
View File

@@ -0,0 +1,46 @@
use std::time::SystemTime;
use crate::util::HttpDate;
/// `Date` header, defined in [RFC7231](https://datatracker.ietf.org/doc/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
///
/// ```
/// 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()
}
}

112
vendor/headers/src/common/etag.rs vendored Normal file
View File

@@ -0,0 +1,112 @@
use std::str::FromStr;
use crate::util::EntityTag;
/// `ETag` header, defined in [RFC7232](https://datatracker.ietf.org/doc/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(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>);
}
*/

86
vendor/headers/src/common/expect.rs vendored Normal file
View File

@@ -0,0 +1,86 @@
use std::fmt;
use http::{HeaderName, HeaderValue};
use crate::util::IterExt;
use crate::{Error, Header};
/// 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
///
/// ```
/// 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,
);
}
}

50
vendor/headers/src/common/expires.rs vendored Normal file
View File

@@ -0,0 +1,50 @@
use std::time::SystemTime;
use crate::util::HttpDate;
/// `Expires` header, defined in [RFC7234](https://datatracker.ietf.org/doc/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
///
/// ```
/// 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()
}
}

57
vendor/headers/src/common/host.rs vendored Normal file
View File

@@ -0,0 +1,57 @@
use std::convert::TryFrom;
use std::fmt;
use http::uri::Authority;
use http::{HeaderName, HeaderValue};
use crate::{Error, Header};
/// 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)
}
}

111
vendor/headers/src/common/if_match.rs vendored Normal file
View File

@@ -0,0 +1,111 @@
use http::HeaderValue;
use super::ETag;
use crate::util::EntityTagRange;
/// `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
///
/// ```
/// 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));
}
}

View File

@@ -0,0 +1,74 @@
use crate::util::HttpDate;
use std::time::SystemTime;
/// `If-Modified-Since` header, defined in
/// [RFC7232](https://datatracker.ietf.org/doc/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
///
/// ```
/// 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));
}
}

View File

@@ -0,0 +1,111 @@
use http::HeaderValue;
use super::ETag;
use crate::util::EntityTagRange;
/// `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
///
/// ```
/// 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));
}
}

137
vendor/headers/src/common/if_range.rs vendored Normal file
View File

@@ -0,0 +1,137 @@
use std::time::SystemTime;
use http::HeaderValue;
use super::{ETag, LastModified};
use crate::util::{EntityTag, HttpDate, TryFromValues};
use crate::Error;
/// `If-Range` header, defined in [RFC7233](https://datatracker.ietf.org/doc/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
///
/// ```
/// 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 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));
}
}

View File

@@ -0,0 +1,75 @@
use crate::util::HttpDate;
use std::time::SystemTime;
/// `If-Unmodified-Since` header, defined in
/// [RFC7232](https://datatracker.ietf.org/doc/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
///
/// ```
/// 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));
}
}

View File

@@ -0,0 +1,50 @@
use crate::util::HttpDate;
use std::time::SystemTime;
/// `Last-Modified` header, defined in
/// [RFC7232](https://datatracker.ietf.org/doc/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
///
/// ```
/// 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()
}
}

51
vendor/headers/src/common/location.rs vendored Normal file
View File

@@ -0,0 +1,51 @@
use http::HeaderValue;
/// `Location` header, defined in
/// [RFC7231](https://datatracker.ietf.org/doc/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)));
}
}

190
vendor/headers/src/common/mod.rs vendored Normal file
View File

@@ -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: crate::Header>(values: &[&str]) -> Option<T> {
use crate::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: crate::Header>(header: T) -> ::http::HeaderMap {
use crate::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 crate::HeaderMapExt;
#[bench]
fn bench_decode(b: &mut ::test::Bencher) {
let mut map = ::http::HeaderMap::new();
map.append(
<$ty as crate::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 crate::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;

207
vendor/headers/src/common/origin.rs vendored Normal file
View File

@@ -0,0 +1,207 @@
use std::convert::TryFrom;
use std::fmt;
use bytes::Bytes;
use http::uri::{self, Authority, Scheme, Uri};
use http::HeaderValue;
use crate::util::{IterExt, TryFromValues};
use crate::Error;
/// 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
///
/// ```
/// 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 {
matches!(self.0, OriginOrNull::Null)
}
/// 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(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 to_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");
}
}

60
vendor/headers/src/common/pragma.rs vendored Normal file
View File

@@ -0,0 +1,60 @@
use http::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
///
/// ```
/// 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());
}
}

View File

@@ -0,0 +1,50 @@
use http::{HeaderName, HeaderValue};
use super::authorization::{Authorization, Credentials};
use crate::{Error, Header};
/// `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));
}
}

458
vendor/headers/src/common/range.rs vendored Normal file
View File

@@ -0,0 +1,458 @@
use std::ops::{Bound, RangeBounds};
use http::{HeaderName, HeaderValue};
use crate::{Error, Header};
/// `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
///
/// ```
/// 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::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),
// These do not directly translate.
//(Bound::Unbounded, Bound::Included(end)) => format!("bytes=-{}", end),
//(Bound::Unbounded, Bound::Excluded(&end)) => format!("bytes=-{}", end - 1),
_ => return Err(InvalidRange { _inner: () }),
};
Ok(Range(HeaderValue::from_str(&v).unwrap()))
}
/// Iterate the range sets as a tuple of bounds, if valid with length.
///
/// The length of the content is passed as an argument, and all ranges
/// that can be satisfied will be iterated.
pub fn satisfiable_ranges(
&self,
len: u64,
) -> impl Iterator<Item = (Bound<u64>, Bound<u64>)> + '_ {
let s = self
.0
.to_str()
.expect("valid string checked in Header::decode()");
s["bytes=".len()..].split(',').filter_map(move |spec| {
let mut iter = spec.trim().splitn(2, '-');
let start = parse_bound(iter.next()?)?;
let end = parse_bound(iter.next()?)?;
// Unbounded ranges in HTTP are actually a suffix
// For example, `-100` means the last 100 bytes.
if let Bound::Unbounded = start {
if let Bound::Included(end) = end {
if len < end {
// Last N bytes is larger than available!
return None;
}
return Some((Bound::Included(len - end), Bound::Unbounded));
}
// else fall through
}
Some((start, end))
})
}
}
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()]});
*/
#[test]
fn test_to_satisfiable_range_suffix() {
let range = super::test_decode::<Range>(&["bytes=-100"]).unwrap();
let bounds = range.satisfiable_ranges(350).next().unwrap();
assert_eq!(bounds, (Bound::Included(250), Bound::Unbounded));
}
#[test]
fn test_to_unsatisfiable_range_suffix() {
let range = super::test_decode::<Range>(&["bytes=-350"]).unwrap();
let bounds = range.satisfiable_ranges(100).next();
assert_eq!(bounds, None);
}

66
vendor/headers/src/common/referer.rs vendored Normal file
View File

@@ -0,0 +1,66 @@
use std::fmt;
use std::str::FromStr;
use crate::util::HeaderValueString;
/// `Referer` header, defined in
/// [RFC7231](https://datatracker.ietf.org/doc/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
///
/// ```
/// use headers::Referer;
///
/// let r = Referer::from_static("/People.html#tim");
/// ```
#[derive(Debug, Clone, PartialEq)]
pub struct Referer(HeaderValueString);
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 const fn from_static(s: &'static str) -> Referer {
Referer(HeaderValueString::from_static(s))
}
}
error_type!(InvalidReferer);
impl FromStr for Referer {
type Err = InvalidReferer;
fn from_str(src: &str) -> Result<Self, Self::Err> {
HeaderValueString::from_str(src)
.map(Referer)
.map_err(|_| InvalidReferer { _inner: () })
}
}
impl fmt::Display for Referer {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::Display::fmt(&self.0, f)
}
}

View File

@@ -0,0 +1,192 @@
use http::HeaderValue;
use crate::util::TryFromValues;
use crate::Error;
/// `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
///
/// ```
/// 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 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"),
}
}
}

113
vendor/headers/src/common/retry_after.rs vendored Normal file
View File

@@ -0,0 +1,113 @@
use std::time::{Duration, SystemTime};
use http::HeaderValue;
use crate::util::{HttpDate, Seconds, TryFromValues};
use crate::Error;
/// 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
/// ```
/// 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](https://datatracker.ietf.org/doc/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 std::time::Duration;
use super::super::test_decode;
use super::RetryAfter;
use crate::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");
}

View File

@@ -0,0 +1,67 @@
use base64::engine::general_purpose::STANDARD as ENGINE;
use base64::Engine;
use bytes::Bytes;
use http::HeaderValue;
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
/// 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="
);
}
}

View File

@@ -0,0 +1,30 @@
use base64::{engine::general_purpose::STANDARD, Engine};
use http::HeaderValue;
/// The `Sec-Websocket-Key` header.
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct SecWebsocketKey(pub(super) HeaderValue);
derive_header! {
SecWebsocketKey(_),
name: SEC_WEBSOCKET_KEY
}
impl From<[u8; 16]> for SecWebsocketKey {
fn from(bytes: [u8; 16]) -> Self {
let mut value = HeaderValue::from_str(&STANDARD.encode(bytes)).unwrap();
value.set_sensitive(true);
Self(value)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn from_bytes() {
let bytes: [u8; 16] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];
let _ = SecWebsocketKey::from(bytes);
}
}

View File

@@ -0,0 +1,62 @@
use http::{HeaderName, HeaderValue};
use crate::{Error, Header};
/// 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");
}
}

71
vendor/headers/src/common/server.rs vendored Normal file
View File

@@ -0,0 +1,71 @@
use std::fmt;
use std::str::FromStr;
use crate::util::HeaderValueString;
/// `Server` header, defined in [RFC7231](https://datatracker.ietf.org/doc/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
///
/// ```
/// 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 const 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)
}
}

107
vendor/headers/src/common/set_cookie.rs vendored Normal file
View File

@@ -0,0 +1,107 @@
use http::{HeaderName, HeaderValue};
use crate::{Error, Header};
/// `Set-Cookie` header, defined [RFC6265](https://datatracker.ietf.org/doc/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);
}
}

View File

@@ -0,0 +1,249 @@
use std::fmt;
use std::time::Duration;
use http::{HeaderName, HeaderValue};
use crate::util::{self, IterExt, Seconds};
use crate::{Error, Header};
/// `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
///
/// ```
/// 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),
}
}
})
.try_fold((None, None), |res, dir| match (res, dir) {
((None, sub), Some(Directive::MaxAge(age))) => Some((Some(age), sub)),
((age, None), Some(Directive::IncludeSubdomains)) => Some((age, Some(()))),
((Some(_), _), Some(Directive::MaxAge(_)))
| ((_, Some(_)), Some(Directive::IncludeSubdomains))
| (_, None) => None,
(res, _) => Some(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 fmt::Display for Adapter<'_> {
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()] });

43
vendor/headers/src/common/te.rs vendored Normal file
View File

@@ -0,0 +1,43 @@
use http::HeaderValue;
use crate::util::FlatCsv;
/// `TE` header, defined in
/// [RFC7230](https://datatracker.ietf.org/doc/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())
}
}

View File

@@ -0,0 +1,104 @@
use http::HeaderValue;
use crate::util::FlatCsv;
/// `Transfer-Encoding` header, defined in
/// [RFC7230](https://datatracker.ietf.org/doc/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](https://datatracker.ietf.org/doc/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
///
/// ```
/// 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());
}
}

54
vendor/headers/src/common/upgrade.rs vendored Normal file
View File

@@ -0,0 +1,54 @@
use http::HeaderValue;
/// `Upgrade` header, defined in [RFC7230](https://datatracker.ietf.org/doc/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
///
/// ```
/// 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"))
}
}

80
vendor/headers/src/common/user_agent.rs vendored Normal file
View File

@@ -0,0 +1,80 @@
use std::fmt;
use std::str::FromStr;
use crate::util::HeaderValueString;
/// `User-Agent` header, defined in
/// [RFC7231](https://datatracker.ietf.org/doc/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
///
/// ```
/// 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 const 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)
}
}

89
vendor/headers/src/common/vary.rs vendored Normal file
View File

@@ -0,0 +1,89 @@
use http::{HeaderName, HeaderValue};
use crate::util::FlatCsv;
/// `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
///
/// ```
/// 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()
}
}
impl From<HeaderName> for Vary {
fn from(name: HeaderName) -> Self {
Vary(HeaderValue::from(name).into())
}
}
/*
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());
}
}

147
vendor/headers/src/disabled/accept.rs vendored Normal file
View File

@@ -0,0 +1,147 @@
use mime::{self, Mime};
use {QualityItem, qitem};
header! {
/// `Accept` header, defined in [RFC7231](https://datatracker.ietf.org/doc/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 mime;
/// use headers::{Headers, Accept, qitem};
///
/// let mut headers = Headers::new();
///
/// headers.set(
/// Accept(vec![
/// qitem(mime::TEXT_HTML),
/// ])
/// );
/// ```
///
/// ```
/// extern crate mime;
/// use headers::{Headers, Accept, qitem};
///
/// let mut headers = Headers::new();
/// headers.set(
/// Accept(vec![
/// qitem(mime::APPLICATION_JSON),
/// ])
/// );
/// ```
/// ```
/// 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()] });

View File

@@ -0,0 +1,57 @@
use {Charset, QualityItem};
header! {
/// `Accept-Charset` header, defined in
/// [RFC7231](https://datatracker.ietf.org/doc/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"]);
}
}

View File

@@ -0,0 +1,72 @@
use {Encoding, QualityItem};
header! {
/// `Accept-Encoding` header, defined in
/// [RFC7231](https://datatracker.ietf.org/doc/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"]);
}
}

View File

@@ -0,0 +1,71 @@
use language_tags::LanguageTag;
use QualityItem;
header! {
/// `Accept-Language` header, defined in
/// [RFC7231](https://datatracker.ietf.org/doc/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),
/// ])
/// );
/// ```
///
/// ```
/// # #[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()] });

View File

@@ -0,0 +1,34 @@
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
///
/// ```
/// #[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);

29
vendor/headers/src/disabled/from.rs vendored Normal file
View File

@@ -0,0 +1,29 @@
header! {
/// `From` header, defined in [RFC7231](https://datatracker.ietf.org/doc/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"]);
}
}

View File

@@ -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())));
*/
}

1105
vendor/headers/src/disabled/link.rs vendored Normal file

File diff suppressed because it is too large Load Diff

210
vendor/headers/src/disabled/prefer.rs vendored Normal file
View File

@@ -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](https://datatracker.ietf.org/doc/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()] });

View File

@@ -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](https://datatracker.ietf.org/doc/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()] });

View File

@@ -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));
}

View File

@@ -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()))
}
}
}

View File

@@ -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));
}
}

View File

@@ -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())
}
}

182
vendor/headers/src/disabled/warning.rs vendored Normal file
View File

@@ -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()
}));
}
}

93
vendor/headers/src/lib.rs vendored Normal file
View File

@@ -0,0 +1,93 @@
#![deny(missing_docs)]
#![deny(missing_debug_implementations)]
#![cfg_attr(test, deny(warnings))]
#![cfg_attr(all(test, feature = "nightly"), feature(test))]
//! # 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));
//! }
//! }
//! ```
#[cfg(all(test, feature = "nightly"))]
extern crate test;
pub use headers_core::{Error, Header};
pub use mime::Mime;
#[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;

87
vendor/headers/src/map_ext.rs vendored Normal file
View File

@@ -0,0 +1,87 @@
use super::{Error, Header, HeaderValue};
/// 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 Extend<HeaderValue> for ToValues<'_> {
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 {}
}

42
vendor/headers/src/util/csv.rs vendored Normal file
View File

@@ -0,0 +1,42 @@
use std::fmt;
use http::HeaderValue;
use crate::Error;
/// 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(())
}

356
vendor/headers/src/util/entity.rs vendored Normal file
View File

@@ -0,0 +1,356 @@
use std::fmt;
use http::HeaderValue;
use super::{FlatCsv, IterExt};
use crate::Error;
/// 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!(
(b'\x21'..=b'\x7e').contains(&c) | (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));
}
}

202
vendor/headers/src/util/flat_csv.rs vendored Normal file
View File

@@ -0,0 +1,202 @@
use std::fmt;
use std::iter::FromIterator;
use std::marker::PhantomData;
use bytes::BytesMut;
use http::HeaderValue;
use crate::util::TryFromValues;
use crate::Error;
// 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| {
#[allow(clippy::collapsible_else_if)]
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_default();
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_default();
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);
}
}

11
vendor/headers/src/util/fmt.rs vendored Normal file
View File

@@ -0,0 +1,11 @@
use std::fmt::Display;
use http::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),
}
}

151
vendor/headers/src/util/http_date.rs vendored Normal file
View File

@@ -0,0 +1,151 @@
use std::fmt;
use std::str::FromStr;
use std::time::SystemTime;
use bytes::Bytes;
use http::header::HeaderValue;
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, crate::Error>
where
I: Iterator<Item = &'i HeaderValue>,
{
values
.just_one()
.and_then(HttpDate::from_val)
.ok_or_else(crate::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());
}
}

11
vendor/headers/src/util/iter.rs vendored Normal file
View File

@@ -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 {}

89
vendor/headers/src/util/mod.rs vendored Normal file
View File

@@ -0,0 +1,89 @@
use http::HeaderValue;
use crate::Error;
//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, crate::Error>
where
I: Iterator<Item = &'i ::http::header::HeaderValue>,
{
crate::util::TryFromValues::try_from_values(values).map($type)
}
fn encode<E: Extend<crate::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)
}
}

69
vendor/headers/src/util/seconds.rs vendored Normal file
View File

@@ -0,0 +1,69 @@
use std::fmt;
use std::time::Duration;
use http::HeaderValue;
use crate::util::IterExt;
use crate::Error;
#[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)
}
}

94
vendor/headers/src/util/value_string.rs vendored Normal file
View File

@@ -0,0 +1,94 @@
use std::{
fmt,
str::{self, FromStr},
};
use bytes::Bytes;
use http::header::HeaderValue;
use super::IterExt;
use crate::Error;
/// 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) const 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(()))
}
}