rama_http_headers/common/
cross_origin_embedder_policy.rs1use std::borrow::Cow;
8use std::fmt;
9
10use rama_http_types::{HeaderName, HeaderValue};
11use rama_utils::macros::enums::enum_builder;
12
13use crate::util::{self, IterExt};
14use crate::{Error, HeaderDecode, HeaderEncode, TypedHeader};
15
16use super::cross_origin_policy_util::{
17 SingleTokenWithReportTo, format_single_token_with_report_to, parse_single_token_with_report_to,
18};
19
20enum_builder! {
21 @String
27 pub enum CrossOriginEmbedderPolicyValue {
28 UnsafeNone => "unsafe-none",
33 RequireCorp => "require-corp",
37 Credentialless => "credentialless",
40 }
41}
42
43#[derive(Clone, Debug, PartialEq, Eq, Hash)]
76pub struct CrossOriginEmbedderPolicy {
77 pub value: CrossOriginEmbedderPolicyValue,
78 pub report_to: Option<Cow<'static, str>>,
82}
83
84impl CrossOriginEmbedderPolicy {
85 #[must_use]
87 pub fn unsafe_none() -> Self {
88 Self {
89 value: CrossOriginEmbedderPolicyValue::UnsafeNone,
90 report_to: None,
91 }
92 }
93
94 #[must_use]
96 pub fn require_corp() -> Self {
97 Self {
98 value: CrossOriginEmbedderPolicyValue::RequireCorp,
99 report_to: None,
100 }
101 }
102
103 #[must_use]
105 pub fn credentialless() -> Self {
106 Self {
107 value: CrossOriginEmbedderPolicyValue::Credentialless,
108 report_to: None,
109 }
110 }
111
112 rama_utils::macros::generate_set_and_with! {
113 pub fn report_to(mut self, endpoint: impl Into<Cow<'static, str>>) -> Self {
114 self.report_to = Some(endpoint.into());
115 self
116 }
117 }
118}
119
120impl fmt::Display for CrossOriginEmbedderPolicy {
121 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
122 format_single_token_with_report_to(f, self.value.as_str(), self.report_to.as_deref())
123 }
124}
125
126impl TypedHeader for CrossOriginEmbedderPolicy {
127 fn name() -> &'static HeaderName {
128 &::rama_http_types::header::CROSS_ORIGIN_EMBEDDER_POLICY
129 }
130}
131
132impl HeaderDecode for CrossOriginEmbedderPolicy {
133 fn decode<'i, I: Iterator<Item = &'i HeaderValue>>(values: &mut I) -> Result<Self, Error> {
134 let raw = values
135 .just_one()
136 .and_then(|v| v.to_str().ok())
137 .ok_or_else(Error::invalid)?;
138 let SingleTokenWithReportTo { token, report_to } =
139 parse_single_token_with_report_to(raw).ok_or_else(Error::invalid)?;
140 let value =
141 CrossOriginEmbedderPolicyValue::strict_parse(token).ok_or_else(Error::invalid)?;
142 Ok(Self { value, report_to })
143 }
144}
145
146impl HeaderEncode for CrossOriginEmbedderPolicy {
147 fn encode<E: Extend<HeaderValue>>(&self, values: &mut E) {
148 values.extend(::std::iter::once(util::fmt(self)));
149 }
150}
151
152#[derive(Clone, Debug, PartialEq, Eq, Hash)]
160pub struct CrossOriginEmbedderPolicyReportOnly {
161 pub value: CrossOriginEmbedderPolicyValue,
162 pub report_to: Option<Cow<'static, str>>,
163}
164
165impl CrossOriginEmbedderPolicyReportOnly {
166 #[must_use]
169 pub fn from_enforcing(p: CrossOriginEmbedderPolicy) -> Self {
170 Self {
171 value: p.value,
172 report_to: p.report_to,
173 }
174 }
175
176 rama_utils::macros::generate_set_and_with! {
177 pub fn report_to(mut self, endpoint: impl Into<Cow<'static, str>>) -> Self {
178 self.report_to = Some(endpoint.into());
179 self
180 }
181 }
182}
183
184impl fmt::Display for CrossOriginEmbedderPolicyReportOnly {
185 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
186 format_single_token_with_report_to(f, self.value.as_str(), self.report_to.as_deref())
187 }
188}
189
190impl TypedHeader for CrossOriginEmbedderPolicyReportOnly {
191 fn name() -> &'static HeaderName {
192 &::rama_http_types::header::CROSS_ORIGIN_EMBEDDER_POLICY_REPORT_ONLY
193 }
194}
195
196impl HeaderDecode for CrossOriginEmbedderPolicyReportOnly {
197 fn decode<'i, I: Iterator<Item = &'i HeaderValue>>(values: &mut I) -> Result<Self, Error> {
198 let raw = values
199 .just_one()
200 .and_then(|v| v.to_str().ok())
201 .ok_or_else(Error::invalid)?;
202 let SingleTokenWithReportTo { token, report_to } =
203 parse_single_token_with_report_to(raw).ok_or_else(Error::invalid)?;
204 let value =
205 CrossOriginEmbedderPolicyValue::strict_parse(token).ok_or_else(Error::invalid)?;
206 Ok(Self { value, report_to })
207 }
208}
209
210impl HeaderEncode for CrossOriginEmbedderPolicyReportOnly {
211 fn encode<E: Extend<HeaderValue>>(&self, values: &mut E) {
212 values.extend(::std::iter::once(util::fmt(self)));
213 }
214}
215
216#[cfg(test)]
217mod tests {
218 use super::super::{test_decode, test_encode};
219 use super::*;
220
221 #[test]
222 fn round_trip_each_variant_no_report_to() {
223 for variant in [
224 CrossOriginEmbedderPolicyValue::UnsafeNone,
225 CrossOriginEmbedderPolicyValue::RequireCorp,
226 CrossOriginEmbedderPolicyValue::Credentialless,
227 ] {
228 let expected_str = variant.as_str().to_owned();
229 let v = CrossOriginEmbedderPolicy {
230 value: variant,
231 report_to: None,
232 };
233 let map = test_encode(v.clone());
234 let raw = map
235 .get(CrossOriginEmbedderPolicy::name())
236 .expect("set")
237 .to_str()
238 .unwrap()
239 .to_owned();
240 assert_eq!(raw, expected_str);
241 let parsed = test_decode::<CrossOriginEmbedderPolicy>(&[raw.as_str()]).expect("decode");
242 assert_eq!(parsed, v);
243 }
244 }
245
246 #[test]
247 fn round_trip_with_report_to() {
248 let v = CrossOriginEmbedderPolicy::require_corp().with_report_to("coep-endpoint");
249 let map = test_encode(v.clone());
250 let raw = map
251 .get(CrossOriginEmbedderPolicy::name())
252 .expect("set")
253 .to_str()
254 .unwrap()
255 .to_owned();
256 assert_eq!(raw, r#"require-corp; report-to="coep-endpoint""#);
257 let parsed = test_decode::<CrossOriginEmbedderPolicy>(&[raw.as_str()]).expect("decode");
258 assert_eq!(parsed, v);
259 }
260
261 #[test]
262 fn parser_case_insensitive_token() {
263 assert_eq!(
264 test_decode::<CrossOriginEmbedderPolicy>(&["Require-Corp"]),
265 Some(CrossOriginEmbedderPolicy::require_corp()),
266 );
267 }
268
269 #[test]
270 fn parser_rejects_unknown_token() {
271 assert!(test_decode::<CrossOriginEmbedderPolicy>(&["nope"]).is_none());
272 }
273
274 #[test]
275 fn parser_accepts_unquoted_report_to() {
276 let parsed =
277 test_decode::<CrossOriginEmbedderPolicy>(&["require-corp; report-to=coep-endpoint"])
278 .expect("decode unquoted");
279 assert_eq!(
280 parsed,
281 CrossOriginEmbedderPolicy::require_corp().with_report_to("coep-endpoint"),
282 );
283 }
284
285 #[test]
286 fn report_only_round_trip() {
287 let v = CrossOriginEmbedderPolicyReportOnly {
288 value: CrossOriginEmbedderPolicyValue::RequireCorp,
289 report_to: Some("ep".into()),
290 };
291 let map = test_encode(v.clone());
292 let raw = map
293 .get(CrossOriginEmbedderPolicyReportOnly::name())
294 .expect("set")
295 .to_str()
296 .unwrap()
297 .to_owned();
298 assert_eq!(raw, r#"require-corp; report-to="ep""#);
299 let parsed =
300 test_decode::<CrossOriginEmbedderPolicyReportOnly>(&[raw.as_str()]).expect("decode");
301 assert_eq!(parsed, v);
302 }
303
304 #[test]
305 fn enforcing_and_report_only_use_distinct_header_names() {
306 assert_ne!(
307 CrossOriginEmbedderPolicy::name(),
308 CrossOriginEmbedderPolicyReportOnly::name(),
309 );
310 }
311}