From 79f863b31b32be44359a7741dff17ff476da2b40 Mon Sep 17 00:00:00 2001 From: Yoshua Wuyts Date: Sat, 25 Jul 2020 19:25:20 +0200 Subject: [PATCH 1/3] init Timing-Allow-Origin headers --- src/headers/constants.rs | 4 ++ src/lib.rs | 2 +- src/trace/allow_origin.rs | 100 ++++++++++++++++++++++++++++++++++++++ src/trace/mod.rs | 7 ++- 4 files changed, 110 insertions(+), 3 deletions(-) create mode 100644 src/trace/allow_origin.rs diff --git a/src/headers/constants.rs b/src/headers/constants.rs index e3cec8f6..91fca693 100644 --- a/src/headers/constants.rs +++ b/src/headers/constants.rs @@ -125,6 +125,7 @@ pub const PRAGMA: HeaderName = HeaderName::from_lowercase_str("pragma"); /// The `Proxy-Authenticate` Header pub const PROXY_AUTHENTICATE: HeaderName = HeaderName::from_lowercase_str("proxy-authenticate"); + /// The `Proxy-Authorization` Header pub const PROXY_AUTHORIZATION: HeaderName = HeaderName::from_lowercase_str("proxy-authorization"); @@ -143,6 +144,9 @@ pub const SERVER_TIMING: HeaderName = HeaderName::from_lowercase_str("server-tim /// The `Te` Header pub const TE: HeaderName = HeaderName::from_lowercase_str("te"); +/// The `Timing-Allow-Origin` Header +pub const TIMING_ALLOW_ORIGIN: HeaderName = HeaderName::from_lowercase_str("timing-allow-origin"); + /// The `Traceparent` Header pub const TRACEPARENT: HeaderName = HeaderName::from_lowercase_str("traceparent"); diff --git a/src/lib.rs b/src/lib.rs index 213cf885..08373476 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -131,9 +131,9 @@ mod status; mod status_code; mod version; +pub mod trace; cfg_unstable! { pub mod upgrade; - pub mod trace; mod client; mod server; diff --git a/src/trace/allow_origin.rs b/src/trace/allow_origin.rs new file mode 100644 index 00000000..88659d7b --- /dev/null +++ b/src/trace/allow_origin.rs @@ -0,0 +1,100 @@ +use crate::headers::{HeaderName, HeaderValue, Headers, ToHeaderValues, TIMING_ALLOW_ORIGIN}; +use crate::Url; +use std::option; + +/// `Timing-Allow-Origin` header. +/// +/// # Specifications +/// +/// - [W3C Timing-Allow-Origin header](https://w3c.github.io/resource-timing/#sec-timing-allow-origin) +/// - [WhatWG Fetch Origin header](https://fetch.spec.whatwg.org/#origin-header) +#[derive(Debug, Clone, Eq, PartialEq)] +pub struct AllowOrigin { + origins: Vec, +} + +impl AllowOrigin { + /// Create a new instance of `AllowOrigin`. + pub fn new() -> Self { + Self { origins: vec![] } + } + + /// Create an instance of `AllowOrigin` from a `Headers` instance. + /// + /// # Implementation note + /// + /// If a `"null"` value is found + pub fn from_headers(headers: impl AsRef) -> crate::Result> { + let allow_origin = match headers.as_ref().get(TIMING_ALLOW_ORIGIN) { + Some(header) => header, + None => return Ok(None), + }; + + allow_origin.as_str().split(","); + todo!(); + } + + /// Append an origin to the list of origins. + pub fn push(&mut self, origin: impl Into) { + self.origins.push(origin.into()); + } + + /// Insert a `HeaderName` + `HeaderValue` pair into a `Headers` instance. + pub fn apply(&self, headers: impl AsMut) { + todo!(); + } + + /// Get the `HeaderName`. + pub fn name(&self) -> HeaderName { + todo!(); + } + + /// Get the `HeaderValue`. + pub fn value(&self) -> HeaderValue { + todo!(); + } +} + +// Conversion from `AllowOrigin` -> `HeaderValue`. +impl ToHeaderValues for AllowOrigin { + type Iter = option::IntoIter; + fn to_header_values(&self) -> crate::Result { + todo!() + } +} + +/// An origin passed into `AllowOrigin`. +/// +/// Values can either be `Url` or `Wildcard`. `"null"` values are skipped during parsing. +// +// NOTE: this origin is different than the origin in the fetch spec. It needs to +// be its own type. +#[derive(Debug, Clone, Eq, PartialEq)] +pub enum Origin { + /// An origin URL. + Url(Url), + /// Allow all origins. + Wildcard, +} + +impl From for Origin { + fn from(url: Url) -> Self { + Origin::Url(url) + } +} + +// Conversion from `AllowOrigin` -> `HeaderValue`. +impl ToHeaderValues for Origin { + type Iter = option::IntoIter; + fn to_header_values(&self) -> crate::Result { + let res = unsafe { + match self { + Self::Url(url) => { + HeaderValue::from_bytes_unchecked(format!("{}", url).into_bytes()) + } + Self::Wildcard => HeaderValue::from_bytes_unchecked(String::from("*").into_bytes()), + } + }; + Ok(Some(res).into_iter()) + } +} diff --git a/src/trace/mod.rs b/src/trace/mod.rs index 571e782c..1e7b9f9e 100644 --- a/src/trace/mod.rs +++ b/src/trace/mod.rs @@ -6,12 +6,15 @@ //! //! # Specifications //! -//! - [W3C Trace-Context headers](https://w3c.github.io/trace-context/) -//! - [W3C Server-Timing headers](https://w3c.github.io/server-timing/#the-server-timing-header-field) +//! - [W3C Trace-Context header](https://w3c.github.io/trace-context/) +//! - [W3C Server-Timing header](https://w3c.github.io/server-timing/#the-server-timing-header-field) +//! - [W3C Timing-Allow-Origin header](https://w3c.github.io/resource-timing/#sec-timing-allow-origin) +mod allow_origin; pub mod server_timing; mod trace_context; +pub use allow_origin::{AllowOrigin, Origin}; #[doc(inline)] pub use server_timing::{Metric, ServerTiming}; pub use trace_context::TraceContext; From 43aea7157b54d70c25974ca6a6accb8932ccca36 Mon Sep 17 00:00:00 2001 From: Yoshua Wuyts Date: Thu, 30 Jul 2020 23:32:07 +0200 Subject: [PATCH 2/3] Finalize Allow-Origin --- src/trace/allow_origin.rs | 273 ++++++++++++++++++++++++++++++++++---- src/trace/mod.rs | 3 +- 2 files changed, 247 insertions(+), 29 deletions(-) diff --git a/src/trace/allow_origin.rs b/src/trace/allow_origin.rs index 88659d7b..1ec6809f 100644 --- a/src/trace/allow_origin.rs +++ b/src/trace/allow_origin.rs @@ -1,14 +1,42 @@ +//! Specify origins that are allowed to see values via the Resource Timing API. +//! +//! # Specifications +//! +//! - [W3C Timing-Allow-Origin header](https://w3c.github.io/resource-timing/#sec-timing-allow-origin) +//! - [WhatWG Fetch Origin header](https://fetch.spec.whatwg.org/#origin-header) +//! +//! # Examples +//! +//! ``` +//! # fn main() -> http_types::Result<()> { +//! # +//! use http_types::Response; +//! use http_types::trace::{AllowOrigin, Origin}; +//! +//! let mut origins = AllowOrigin::new(); +//! origins.push(Origin::Wildcard); +//! +//! let mut res = Response::new(200); +//! origins.apply(&mut res); +//! +//! let origins = AllowOrigin::from_headers(res)?.unwrap(); +//! let origin = origins.iter().next().unwrap(); +//! assert_eq!(origin, &Origin::Wildcard); +//! # +//! # Ok(()) } +//! ``` + use crate::headers::{HeaderName, HeaderValue, Headers, ToHeaderValues, TIMING_ALLOW_ORIGIN}; -use crate::Url; +use crate::{Status, Url}; + +use std::fmt::Write; +use std::fmt::{self, Debug}; +use std::iter::Iterator; use std::option; +use std::slice; -/// `Timing-Allow-Origin` header. -/// -/// # Specifications -/// -/// - [W3C Timing-Allow-Origin header](https://w3c.github.io/resource-timing/#sec-timing-allow-origin) -/// - [WhatWG Fetch Origin header](https://fetch.spec.whatwg.org/#origin-header) -#[derive(Debug, Clone, Eq, PartialEq)] +/// Specify origins that are allowed to see values via the Resource Timing API. +#[derive(Clone, Eq, PartialEq)] pub struct AllowOrigin { origins: Vec, } @@ -23,15 +51,28 @@ impl AllowOrigin { /// /// # Implementation note /// - /// If a `"null"` value is found + /// A header value of `"null"` is treated the same as if no header was sent. pub fn from_headers(headers: impl AsRef) -> crate::Result> { - let allow_origin = match headers.as_ref().get(TIMING_ALLOW_ORIGIN) { - Some(header) => header, + let headers = match headers.as_ref().get(TIMING_ALLOW_ORIGIN) { + Some(headers) => headers, None => return Ok(None), }; - allow_origin.as_str().split(","); - todo!(); + let mut origins = vec![]; + for header in headers { + for origin in header.as_str().split(",") { + match origin.trim_start() { + "*" => origins.push(Origin::Wildcard), + r#""null""# => continue, + origin => { + let url = Url::parse(origin).status(400)?; + origins.push(Origin::Url(url)); + } + } + } + } + + Ok(Some(Self { origins })) } /// Append an origin to the list of origins. @@ -40,18 +81,136 @@ impl AllowOrigin { } /// Insert a `HeaderName` + `HeaderValue` pair into a `Headers` instance. - pub fn apply(&self, headers: impl AsMut) { - todo!(); + pub fn apply(&self, mut headers: impl AsMut) { + headers.as_mut().insert(TIMING_ALLOW_ORIGIN, self.value()); } /// Get the `HeaderName`. pub fn name(&self) -> HeaderName { - todo!(); + TIMING_ALLOW_ORIGIN } /// Get the `HeaderValue`. pub fn value(&self) -> HeaderValue { - todo!(); + let mut output = String::new(); + for (n, origin) in self.origins.iter().enumerate() { + let origin: HeaderValue = origin.clone().into(); + match n { + 0 => write!(output, "{}", origin).unwrap(), + _ => write!(output, ", {}", origin).unwrap(), + }; + } + + // SAFETY: the internal string is validated to be ASCII. + unsafe { HeaderValue::from_bytes_unchecked(output.into()) } + } + + /// An iterator visiting all server timings. + pub fn into_iter(self) -> IntoIter { + IntoIter { + inner: self.origins.into_iter(), + } + } + + /// An iterator visiting all server timings. + pub fn iter(&self) -> Iter<'_> { + Iter { + inner: self.origins.iter(), + } + } + + /// An iterator visiting all server timings. + pub fn iter_mut(&mut self) -> IterMut<'_> { + IterMut { + inner: self.origins.iter_mut(), + } + } +} + +impl IntoIterator for AllowOrigin { + type Item = Origin; + type IntoIter = IntoIter; + + #[inline] + fn into_iter(self) -> Self::IntoIter { + self.into_iter() + } +} + +impl<'a> IntoIterator for &'a AllowOrigin { + type Item = &'a Origin; + type IntoIter = Iter<'a>; + + // #[inline]serv + fn into_iter(self) -> Self::IntoIter { + self.iter() + } +} + +impl<'a> IntoIterator for &'a mut AllowOrigin { + type Item = &'a mut Origin; + type IntoIter = IterMut<'a>; + + #[inline] + fn into_iter(self) -> Self::IntoIter { + self.iter_mut() + } +} + +/// A borrowing iterator over entries in `AllowOrigin`. +#[derive(Debug)] +pub struct IntoIter { + inner: std::vec::IntoIter, +} + +impl Iterator for IntoIter { + type Item = Origin; + + fn next(&mut self) -> Option { + self.inner.next() + } + + #[inline] + fn size_hint(&self) -> (usize, Option) { + self.inner.size_hint() + } +} + +/// A lending iterator over entries in `AllowOrigin`. +#[derive(Debug)] +pub struct Iter<'a> { + inner: slice::Iter<'a, Origin>, +} + +impl<'a> Iterator for Iter<'a> { + type Item = &'a Origin; + + fn next(&mut self) -> Option { + self.inner.next() + } + + #[inline] + fn size_hint(&self) -> (usize, Option) { + self.inner.size_hint() + } +} + +/// A mutable iterator over entries in `AllowOrigin`. +#[derive(Debug)] +pub struct IterMut<'a> { + inner: slice::IterMut<'a, Origin>, +} + +impl<'a> Iterator for IterMut<'a> { + type Item = &'a mut Origin; + + fn next(&mut self) -> Option { + self.inner.next() + } + + #[inline] + fn size_hint(&self) -> (usize, Option) { + self.inner.size_hint() } } @@ -59,7 +218,17 @@ impl AllowOrigin { impl ToHeaderValues for AllowOrigin { type Iter = option::IntoIter; fn to_header_values(&self) -> crate::Result { - todo!() + Ok(self.value().to_header_values().unwrap()) + } +} + +impl Debug for AllowOrigin { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut list = f.debug_list(); + for origin in &self.origins { + list.entry(origin); + } + list.finish() } } @@ -83,18 +252,66 @@ impl From for Origin { } } -// Conversion from `AllowOrigin` -> `HeaderValue`. -impl ToHeaderValues for Origin { - type Iter = option::IntoIter; - fn to_header_values(&self) -> crate::Result { - let res = unsafe { - match self { - Self::Url(url) => { +impl From for HeaderValue { + fn from(entry: Origin) -> HeaderValue { + unsafe { + match entry { + Origin::Url(url) => { HeaderValue::from_bytes_unchecked(format!("{}", url).into_bytes()) } - Self::Wildcard => HeaderValue::from_bytes_unchecked(String::from("*").into_bytes()), + Origin::Wildcard => { + HeaderValue::from_bytes_unchecked(String::from("*").into_bytes()) + } } - }; - Ok(Some(res).into_iter()) + } + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::headers::Headers; + + #[test] + fn smoke() -> crate::Result<()> { + let mut origins = AllowOrigin::new(); + origins.push(Origin::Wildcard); + + let mut headers = Headers::new(); + origins.apply(&mut headers); + + let origins = AllowOrigin::from_headers(headers)?.unwrap(); + let origin = origins.iter().next().unwrap(); + assert_eq!(origin, &Origin::Wildcard); + Ok(()) + } + + #[test] + fn multi() -> crate::Result<()> { + let mut origins = AllowOrigin::new(); + origins.push(Origin::Wildcard); + origins.push(Origin::Url(Url::parse("https://mozilla.org/")?)); + + let mut headers = Headers::new(); + origins.apply(&mut headers); + + let origins = AllowOrigin::from_headers(headers)?.unwrap(); + let mut origins = origins.iter(); + let origin = origins.next().unwrap(); + assert!(matches!(origin, Origin::Wildcard)); + + let origin = origins.next().unwrap(); + let rhs = Url::parse("https://mozilla.org/")?; + assert_eq!(origin, &Origin::Url(rhs)); + Ok(()) + } + + #[test] + fn bad_request_on_parse_error() -> crate::Result<()> { + let mut headers = Headers::new(); + headers.insert(TIMING_ALLOW_ORIGIN, "server; "); + let err = AllowOrigin::from_headers(headers).unwrap_err(); + assert_eq!(err.status(), 400); + Ok(()) } } diff --git a/src/trace/mod.rs b/src/trace/mod.rs index 1e7b9f9e..b351e5de 100644 --- a/src/trace/mod.rs +++ b/src/trace/mod.rs @@ -10,10 +10,11 @@ //! - [W3C Server-Timing header](https://w3c.github.io/server-timing/#the-server-timing-header-field) //! - [W3C Timing-Allow-Origin header](https://w3c.github.io/resource-timing/#sec-timing-allow-origin) -mod allow_origin; +pub mod allow_origin; pub mod server_timing; mod trace_context; +#[doc(inline)] pub use allow_origin::{AllowOrigin, Origin}; #[doc(inline)] pub use server_timing::{Metric, ServerTiming}; From babd65ec7b0e854454be5cb3723e2eb3ba23328e Mon Sep 17 00:00:00 2001 From: Yoshua Wuyts Date: Thu, 30 Jul 2020 23:40:41 +0200 Subject: [PATCH 3/3] Fix clippy warnings --- src/trace/allow_origin.rs | 34 +++++++++++++++++++++++-------- src/trace/server_timing/metric.rs | 2 +- src/trace/server_timing/mod.rs | 11 +++------- 3 files changed, 29 insertions(+), 18 deletions(-) diff --git a/src/trace/allow_origin.rs b/src/trace/allow_origin.rs index 1ec6809f..5daf16d7 100644 --- a/src/trace/allow_origin.rs +++ b/src/trace/allow_origin.rs @@ -36,6 +36,27 @@ use std::option; use std::slice; /// Specify origins that are allowed to see values via the Resource Timing API. +/// +/// # Examples +/// +/// ``` +/// # fn main() -> http_types::Result<()> { +/// # +/// use http_types::Response; +/// use http_types::trace::{AllowOrigin, Origin}; +/// +/// let mut origins = AllowOrigin::new(); +/// origins.push(Origin::Wildcard); +/// +/// let mut res = Response::new(200); +/// origins.apply(&mut res); +/// +/// let origins = AllowOrigin::from_headers(res)?.unwrap(); +/// let origin = origins.iter().next().unwrap(); +/// assert_eq!(origin, &Origin::Wildcard); +/// # +/// # Ok(()) } +/// ``` #[derive(Clone, Eq, PartialEq)] pub struct AllowOrigin { origins: Vec, @@ -60,7 +81,7 @@ impl AllowOrigin { let mut origins = vec![]; for header in headers { - for origin in header.as_str().split(",") { + for origin in header.as_str().split(',') { match origin.trim_start() { "*" => origins.push(Origin::Wildcard), r#""null""# => continue, @@ -105,13 +126,6 @@ impl AllowOrigin { unsafe { HeaderValue::from_bytes_unchecked(output.into()) } } - /// An iterator visiting all server timings. - pub fn into_iter(self) -> IntoIter { - IntoIter { - inner: self.origins.into_iter(), - } - } - /// An iterator visiting all server timings. pub fn iter(&self) -> Iter<'_> { Iter { @@ -133,7 +147,9 @@ impl IntoIterator for AllowOrigin { #[inline] fn into_iter(self) -> Self::IntoIter { - self.into_iter() + IntoIter { + inner: self.origins.into_iter(), + } } } diff --git a/src/trace/server_timing/metric.rs b/src/trace/server_timing/metric.rs index 4f2fc836..bf4f8fee 100644 --- a/src/trace/server_timing/metric.rs +++ b/src/trace/server_timing/metric.rs @@ -48,7 +48,7 @@ impl Metric { /// The timing description. pub fn description(&self) -> Option<&str> { - self.desc.as_ref().map(|s| s.as_str()) + self.desc.as_deref() } } diff --git a/src/trace/server_timing/mod.rs b/src/trace/server_timing/mod.rs index 0c28ff66..ed0fc664 100644 --- a/src/trace/server_timing/mod.rs +++ b/src/trace/server_timing/mod.rs @@ -117,13 +117,6 @@ impl ServerTiming { self.timings.push(entry); } - /// An iterator visiting all server timings. - pub fn into_iter(self) -> IntoIter { - IntoIter { - inner: self.timings.into_iter(), - } - } - /// An iterator visiting all server timings. pub fn iter(&self) -> Iter<'_> { Iter { @@ -145,7 +138,9 @@ impl IntoIterator for ServerTiming { #[inline] fn into_iter(self) -> Self::IntoIter { - self.into_iter() + IntoIter { + inner: self.timings.into_iter(), + } } }