diff --git a/Cargo.toml b/Cargo.toml index f0d8065e..bf67da40 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,3 +28,6 @@ httpdate = "1" [features] nightly = [] + +[patch.crates-io] +http = { git = "https://github.com/daxpedda/http", branch = "coop-coep" } diff --git a/src/common/cross_origin_embedder_policy.rs b/src/common/cross_origin_embedder_policy.rs new file mode 100644 index 00000000..53f53e32 --- /dev/null +++ b/src/common/cross_origin_embedder_policy.rs @@ -0,0 +1,127 @@ +use std::convert::TryFrom; + +use headers_core::HeaderName; +use util::{IterExt, TryFromValues}; +use Header; +use HeaderValue; + +/// Allows a server to declare an embedder policy for a given document. +/// +/// The HTTP `Cross-Origin-Embedder-Policy` (COEP) response header prevents a +/// document from loading any cross-origin resources that don't explicitly +/// grant the document permission (using CORP or CORS). +/// +/// ## ABNF +/// +/// ```text +/// Cross-Origin-Embedder-Policy = "Cross-Origin-Embedder-Policy" ":" unsafe-none | require-corp +/// ``` +/// +/// ## Possible values +/// * `unsafe-none` +/// * `require-corp` +/// +/// # Examples +/// +/// ``` +/// # extern crate headers; +/// use headers::CrossOriginEmbedderPolicy; +/// use std::convert::TryFrom; +/// +/// let no_corp = CrossOriginEmbedderPolicy::UnsafeNone; +/// let require_corp = CrossOriginEmbedderPolicy::RequireCorp; +/// let coep = CrossOriginEmbedderPolicy::try_from("require-corp"); +/// ``` +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub enum CrossOriginEmbedderPolicy { + /// `Cross-Origin-Embedder-Policy: require-corp` + RequireCorp, + /// `Cross-Origin-Embedder-Policy: unsafe-none` + UnsafeNone, +} + +impl Header for CrossOriginEmbedderPolicy { + fn name() -> &'static HeaderName { + &http::header::CROSS_ORIGIN_EMBEDDER_POLICY + } + + fn decode<'i, I>(values: &mut I) -> Result + where + I: Iterator, + { + TryFromValues::try_from_values(values) + } + + fn encode>(&self, values: &mut E) { + values.extend(std::iter::once(self.into())); + } +} + +impl TryFrom<&str> for CrossOriginEmbedderPolicy { + type Error = ::Error; + + fn try_from(s: &str) -> Result { + let header_value = HeaderValue::from_str(s).map_err(|_| ::Error::invalid())?; + Self::try_from(&header_value) + } +} + +impl TryFrom<&HeaderValue> for CrossOriginEmbedderPolicy { + type Error = ::Error; + + fn try_from(header_value: &HeaderValue) -> Result { + if header_value == "require-corp" { + Ok(Self::RequireCorp) + } else if header_value == "unsafe-none" { + Ok(Self::UnsafeNone) + } else { + Err(::Error::invalid()) + } + } +} + +impl TryFromValues for CrossOriginEmbedderPolicy { + fn try_from_values<'i, I>(values: &mut I) -> Result + where + I: Iterator, + { + values + .just_one() + .ok_or_else(::Error::invalid) + .and_then(Self::try_from) + } +} + +impl<'a> From<&'a CrossOriginEmbedderPolicy> for HeaderValue { + fn from(coep: &'a CrossOriginEmbedderPolicy) -> HeaderValue { + match coep { + CrossOriginEmbedderPolicy::RequireCorp => HeaderValue::from_static("require-corp"), + CrossOriginEmbedderPolicy::UnsafeNone => HeaderValue::from_static("unsafe-none"), + } + } +} + +#[cfg(test)] +mod tests { + + use super::super::{test_decode, test_encode}; + use super::*; + + #[test] + fn unsafe_none() { + let unsafe_none = test_decode::(&["unsafe-none"]).unwrap(); + assert_eq!(unsafe_none, CrossOriginEmbedderPolicy::UnsafeNone); + + let headers = test_encode(unsafe_none); + assert_eq!(headers["cross-origin-embedder-policy"], "unsafe-none"); + } + + #[test] + fn require_corp() { + let require_corp = test_decode::(&["require-corp"]).unwrap(); + assert_eq!(require_corp, CrossOriginEmbedderPolicy::RequireCorp); + + let headers = test_encode(require_corp); + assert_eq!(headers["cross-origin-embedder-policy"], "require-corp"); + } +} diff --git a/src/common/cross_origin_opener_policy.rs b/src/common/cross_origin_opener_policy.rs new file mode 100644 index 00000000..768359fc --- /dev/null +++ b/src/common/cross_origin_opener_policy.rs @@ -0,0 +1,153 @@ +use std::convert::TryFrom; + +use headers_core::HeaderName; +use util::{IterExt, TryFromValues}; +use Header; +use HeaderValue; + +/// Prevents other domains from opening/controlling a window. +/// +/// The HTTP `Cross-Origin-Opener-Policy` (COOP) response header allows you to +/// ensure a top-level document does not share a browsing context group with +/// cross-origin documents. +/// +/// COOP will process-isolate your document and potential attackers can't +/// access your global object if they were to open it in a popup, preventing a +/// set of cross-origin attacks dubbed XS-Leaks. +/// +/// If a cross-origin document with COOP is opened in a new window, the opening +/// document will not have a reference to it, and the `window.opener` property +/// of the new window will be `null`. This allows you to have more control over +/// references to a window than `rel=noopener`, which only affects outgoing +/// navigations. +/// +/// ## ABNF +/// +/// ```text +/// Cross-Origin-Opener-Policy = "Cross-Origin-Opener-Policy" ":" unsafe-none | same-origin-allow-popups | same-origin +/// ``` +/// +/// ## Possible values +/// * `unsafe-none` +/// * `same-origin-allow-popups` +/// * `same-origin` +/// +/// # Examples +/// +/// ``` +/// # extern crate headers; +/// use headers::CrossOriginOpenerPolicy; +/// use std::convert::TryFrom; +/// +/// let no_corp = CrossOriginOpenerPolicy::UnsafeNone; +/// let same_origin_allow_popups = CrossOriginOpenerPolicy::SameOriginAllowPopups; +/// let same_origin = CrossOriginOpenerPolicy::SameOrigin; +/// let coop = CrossOriginOpenerPolicy::try_from("same-origin"); +/// ``` +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub enum CrossOriginOpenerPolicy { + /// `Cross-Origin-Opener-Policy: same-origin` + SameOrigin, + /// `Cross-Origin-Opener-Policy: same-origin-allow-popups` + SameOriginAllowPopups, + /// `Cross-Origin-Opener-Policy: unsafe-none` + UnsafeNone, +} + +impl Header for CrossOriginOpenerPolicy { + fn name() -> &'static HeaderName { + &http::header::CROSS_ORIGIN_OPENER_POLICY + } + + fn decode<'i, I>(values: &mut I) -> Result + where + I: Iterator, + { + TryFromValues::try_from_values(values) + } + + fn encode>(&self, values: &mut E) { + values.extend(std::iter::once(self.into())); + } +} + +impl TryFrom<&str> for CrossOriginOpenerPolicy { + type Error = ::Error; + + fn try_from(s: &str) -> Result { + let header_value = HeaderValue::from_str(s).map_err(|_| ::Error::invalid())?; + Self::try_from(&header_value) + } +} + +impl TryFrom<&HeaderValue> for CrossOriginOpenerPolicy { + type Error = ::Error; + + fn try_from(header_value: &HeaderValue) -> Result { + if header_value == "same-origin" { + Ok(Self::SameOrigin) + } else if header_value == "same-origin-allow-popups" { + Ok(Self::SameOriginAllowPopups) + } else if header_value == "unsafe-none" { + Ok(Self::UnsafeNone) + } else { + Err(::Error::invalid()) + } + } +} + +impl TryFromValues for CrossOriginOpenerPolicy { + fn try_from_values<'i, I>(values: &mut I) -> Result + where + I: Iterator, + { + values + .just_one() + .ok_or_else(::Error::invalid) + .and_then(Self::try_from) + } +} + +impl<'a> From<&'a CrossOriginOpenerPolicy> for HeaderValue { + fn from(coop: &'a CrossOriginOpenerPolicy) -> HeaderValue { + match coop { + CrossOriginOpenerPolicy::SameOrigin => HeaderValue::from_static("same-origin"), + CrossOriginOpenerPolicy::SameOriginAllowPopups => HeaderValue::from_static("same-origin-allow-popups"), + CrossOriginOpenerPolicy::UnsafeNone => HeaderValue::from_static("unsafe-none"), + } + } +} + +#[cfg(test)] +mod tests { + + use super::super::{test_decode, test_encode}; + use super::*; + + #[test] + fn unsafe_none() { + let unsafe_none = test_decode::(&["unsafe-none"]).unwrap(); + assert_eq!(unsafe_none, CrossOriginOpenerPolicy::UnsafeNone); + + let headers = test_encode(unsafe_none); + assert_eq!(headers["Cross-Origin-Opener-Policy"], "unsafe-none"); + } + + #[test] + fn same_origin_allow_popups() { + let same_origin_allow_popups = test_decode::(&["same-origin-allow-popups"]).unwrap(); + assert_eq!(same_origin_allow_popups, CrossOriginOpenerPolicy::SameOriginAllowPopups); + + let headers = test_encode(same_origin_allow_popups); + assert_eq!(headers["Cross-Origin-Opener-Policy"], "same-origin-allow-popups"); + } + + #[test] + fn same_origin() { + let same_origin = test_decode::(&["same-origin"]).unwrap(); + assert_eq!(same_origin, CrossOriginOpenerPolicy::SameOrigin); + + let headers = test_encode(same_origin); + assert_eq!(headers["Cross-Origin-Opener-Policy"], "same-origin"); + } +} diff --git a/src/common/mod.rs b/src/common/mod.rs index 2237ae8e..45d9b90c 100644 --- a/src/common/mod.rs +++ b/src/common/mod.rs @@ -32,6 +32,8 @@ 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::cross_origin_embedder_policy::CrossOriginEmbedderPolicy; +pub use self::cross_origin_opener_policy::CrossOriginOpenerPolicy; pub use self::date::Date; pub use self::etag::ETag; pub use self::expect::Expect; @@ -152,6 +154,8 @@ mod content_location; mod content_range; mod content_type; mod cookie; +mod cross_origin_embedder_policy; +mod cross_origin_opener_policy; mod date; mod etag; mod expect;