Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement COOP and COEP #114

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,6 @@ httpdate = "1"

[features]
nightly = []

[patch.crates-io]
http = { git = "https://github.com/daxpedda/http", branch = "coop-coep" }
127 changes: 127 additions & 0 deletions src/common/cross_origin_embedder_policy.rs
Original file line number Diff line number Diff line change
@@ -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<Self, ::Error>
where
I: Iterator<Item = &'i HeaderValue>,
{
TryFromValues::try_from_values(values)
}

fn encode<E: Extend<HeaderValue>>(&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<Self, ::Error> {
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<Self, ::Error> {
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<Self, ::Error>
where
I: Iterator<Item = &'i HeaderValue>,
{
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::<CrossOriginEmbedderPolicy>(&["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::<CrossOriginEmbedderPolicy>(&["require-corp"]).unwrap();
assert_eq!(require_corp, CrossOriginEmbedderPolicy::RequireCorp);

let headers = test_encode(require_corp);
assert_eq!(headers["cross-origin-embedder-policy"], "require-corp");
}
}
153 changes: 153 additions & 0 deletions src/common/cross_origin_opener_policy.rs
Original file line number Diff line number Diff line change
@@ -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<Self, ::Error>
where
I: Iterator<Item = &'i HeaderValue>,
{
TryFromValues::try_from_values(values)
}

fn encode<E: Extend<HeaderValue>>(&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<Self, ::Error> {
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<Self, ::Error> {
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<Self, ::Error>
where
I: Iterator<Item = &'i HeaderValue>,
{
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::<CrossOriginOpenerPolicy>(&["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::<CrossOriginOpenerPolicy>(&["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::<CrossOriginOpenerPolicy>(&["same-origin"]).unwrap();
assert_eq!(same_origin, CrossOriginOpenerPolicy::SameOrigin);

let headers = test_encode(same_origin);
assert_eq!(headers["Cross-Origin-Opener-Policy"], "same-origin");
}
}
4 changes: 4 additions & 0 deletions src/common/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down