use std::borrow::Cow;
use std::fmt;
use rama_http_types::{HeaderName, HeaderValue};
use rama_utils::macros::enums::enum_builder;
use crate::util::{self, IterExt};
use crate::{Error, HeaderDecode, HeaderEncode, TypedHeader};
use super::cross_origin_policy_util::{
SingleTokenWithReportTo, format_single_token_with_report_to, parse_single_token_with_report_to,
};
enum_builder! {
@String
pub enum CrossOriginEmbedderPolicyValue {
UnsafeNone => "unsafe-none",
RequireCorp => "require-corp",
Credentialless => "credentialless",
}
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct CrossOriginEmbedderPolicy {
pub value: CrossOriginEmbedderPolicyValue,
pub report_to: Option<Cow<'static, str>>,
}
impl CrossOriginEmbedderPolicy {
#[must_use]
pub fn unsafe_none() -> Self {
Self {
value: CrossOriginEmbedderPolicyValue::UnsafeNone,
report_to: None,
}
}
#[must_use]
pub fn require_corp() -> Self {
Self {
value: CrossOriginEmbedderPolicyValue::RequireCorp,
report_to: None,
}
}
#[must_use]
pub fn credentialless() -> Self {
Self {
value: CrossOriginEmbedderPolicyValue::Credentialless,
report_to: None,
}
}
rama_utils::macros::generate_set_and_with! {
pub fn report_to(mut self, endpoint: impl Into<Cow<'static, str>>) -> Self {
self.report_to = Some(endpoint.into());
self
}
}
}
impl fmt::Display for CrossOriginEmbedderPolicy {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
format_single_token_with_report_to(f, self.value.as_str(), self.report_to.as_deref())
}
}
impl TypedHeader for CrossOriginEmbedderPolicy {
fn name() -> &'static HeaderName {
&::rama_http_types::header::CROSS_ORIGIN_EMBEDDER_POLICY
}
}
impl HeaderDecode for CrossOriginEmbedderPolicy {
fn decode<'i, I: Iterator<Item = &'i HeaderValue>>(values: &mut I) -> Result<Self, Error> {
let raw = values
.just_one()
.and_then(|v| v.to_str().ok())
.ok_or_else(Error::invalid)?;
let SingleTokenWithReportTo { token, report_to } =
parse_single_token_with_report_to(raw).ok_or_else(Error::invalid)?;
let value =
CrossOriginEmbedderPolicyValue::strict_parse(token).ok_or_else(Error::invalid)?;
Ok(Self { value, report_to })
}
}
impl HeaderEncode for CrossOriginEmbedderPolicy {
fn encode<E: Extend<HeaderValue>>(&self, values: &mut E) {
values.extend(::std::iter::once(util::fmt(self)));
}
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct CrossOriginEmbedderPolicyReportOnly {
pub value: CrossOriginEmbedderPolicyValue,
pub report_to: Option<Cow<'static, str>>,
}
impl CrossOriginEmbedderPolicyReportOnly {
#[must_use]
pub fn from_enforcing(p: CrossOriginEmbedderPolicy) -> Self {
Self {
value: p.value,
report_to: p.report_to,
}
}
rama_utils::macros::generate_set_and_with! {
pub fn report_to(mut self, endpoint: impl Into<Cow<'static, str>>) -> Self {
self.report_to = Some(endpoint.into());
self
}
}
}
impl fmt::Display for CrossOriginEmbedderPolicyReportOnly {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
format_single_token_with_report_to(f, self.value.as_str(), self.report_to.as_deref())
}
}
impl TypedHeader for CrossOriginEmbedderPolicyReportOnly {
fn name() -> &'static HeaderName {
&::rama_http_types::header::CROSS_ORIGIN_EMBEDDER_POLICY_REPORT_ONLY
}
}
impl HeaderDecode for CrossOriginEmbedderPolicyReportOnly {
fn decode<'i, I: Iterator<Item = &'i HeaderValue>>(values: &mut I) -> Result<Self, Error> {
let raw = values
.just_one()
.and_then(|v| v.to_str().ok())
.ok_or_else(Error::invalid)?;
let SingleTokenWithReportTo { token, report_to } =
parse_single_token_with_report_to(raw).ok_or_else(Error::invalid)?;
let value =
CrossOriginEmbedderPolicyValue::strict_parse(token).ok_or_else(Error::invalid)?;
Ok(Self { value, report_to })
}
}
impl HeaderEncode for CrossOriginEmbedderPolicyReportOnly {
fn encode<E: Extend<HeaderValue>>(&self, values: &mut E) {
values.extend(::std::iter::once(util::fmt(self)));
}
}
#[cfg(test)]
mod tests {
use super::super::{test_decode, test_encode};
use super::*;
#[test]
fn round_trip_each_variant_no_report_to() {
for variant in [
CrossOriginEmbedderPolicyValue::UnsafeNone,
CrossOriginEmbedderPolicyValue::RequireCorp,
CrossOriginEmbedderPolicyValue::Credentialless,
] {
let expected_str = variant.as_str().to_owned();
let v = CrossOriginEmbedderPolicy {
value: variant,
report_to: None,
};
let map = test_encode(v.clone());
let raw = map
.get(CrossOriginEmbedderPolicy::name())
.expect("set")
.to_str()
.unwrap()
.to_owned();
assert_eq!(raw, expected_str);
let parsed = test_decode::<CrossOriginEmbedderPolicy>(&[raw.as_str()]).expect("decode");
assert_eq!(parsed, v);
}
}
#[test]
fn round_trip_with_report_to() {
let v = CrossOriginEmbedderPolicy::require_corp().with_report_to("coep-endpoint");
let map = test_encode(v.clone());
let raw = map
.get(CrossOriginEmbedderPolicy::name())
.expect("set")
.to_str()
.unwrap()
.to_owned();
assert_eq!(raw, r#"require-corp; report-to="coep-endpoint""#);
let parsed = test_decode::<CrossOriginEmbedderPolicy>(&[raw.as_str()]).expect("decode");
assert_eq!(parsed, v);
}
#[test]
fn parser_case_insensitive_token() {
assert_eq!(
test_decode::<CrossOriginEmbedderPolicy>(&["Require-Corp"]),
Some(CrossOriginEmbedderPolicy::require_corp()),
);
}
#[test]
fn parser_rejects_unknown_token() {
assert!(test_decode::<CrossOriginEmbedderPolicy>(&["nope"]).is_none());
}
#[test]
fn parser_accepts_unquoted_report_to() {
let parsed =
test_decode::<CrossOriginEmbedderPolicy>(&["require-corp; report-to=coep-endpoint"])
.expect("decode unquoted");
assert_eq!(
parsed,
CrossOriginEmbedderPolicy::require_corp().with_report_to("coep-endpoint"),
);
}
#[test]
fn report_only_round_trip() {
let v = CrossOriginEmbedderPolicyReportOnly {
value: CrossOriginEmbedderPolicyValue::RequireCorp,
report_to: Some("ep".into()),
};
let map = test_encode(v.clone());
let raw = map
.get(CrossOriginEmbedderPolicyReportOnly::name())
.expect("set")
.to_str()
.unwrap()
.to_owned();
assert_eq!(raw, r#"require-corp; report-to="ep""#);
let parsed =
test_decode::<CrossOriginEmbedderPolicyReportOnly>(&[raw.as_str()]).expect("decode");
assert_eq!(parsed, v);
}
#[test]
fn enforcing_and_report_only_use_distinct_header_names() {
assert_ne!(
CrossOriginEmbedderPolicy::name(),
CrossOriginEmbedderPolicyReportOnly::name(),
);
}
}