Skip to main content

http_security_headers/policy/
cross_origin.rs

1//! Cross-Origin policy headers configuration.
2//!
3//! These headers help protect against Spectre-like attacks and control how resources
4//! are shared across origins.
5
6use crate::error::{Error, Result};
7
8/// Cross-Origin-Opener-Policy (COOP) header value.
9///
10/// COOP allows you to ensure a top-level document does not share a browsing context
11/// group with cross-origin documents.
12///
13/// # Examples
14///
15/// ```
16/// use http_security_headers::CrossOriginOpenerPolicy;
17///
18/// let policy = CrossOriginOpenerPolicy::SameOrigin;
19/// let policy = CrossOriginOpenerPolicy::SameOriginAllowPopups;
20/// ```
21#[derive(Debug, Clone, Copy, PartialEq, Eq)]
22pub enum CrossOriginOpenerPolicy {
23    /// Isolates the browsing context exclusively to same-origin documents.
24    SameOrigin,
25    /// Retains references to newly opened windows or tabs which don't set COOP or opt out
26    /// by setting COOP to `unsafe-none`.
27    SameOriginAllowPopups,
28    /// This is the default value and allows the document to be added to its opener's
29    /// browsing context group unless the opener itself has a COOP of `same-origin` or
30    /// `same-origin-allow-popups`.
31    UnsafeNone,
32}
33
34impl CrossOriginOpenerPolicy {
35    /// Converts the policy to its header value string.
36    pub fn as_str(&self) -> &'static str {
37        match self {
38            Self::SameOrigin => "same-origin",
39            Self::SameOriginAllowPopups => "same-origin-allow-popups",
40            Self::UnsafeNone => "unsafe-none",
41        }
42    }
43}
44
45impl std::str::FromStr for CrossOriginOpenerPolicy {
46    type Err = Error;
47
48    fn from_str(s: &str) -> Result<Self> {
49        match s.to_lowercase().as_str() {
50            "same-origin" => Ok(Self::SameOrigin),
51            "same-origin-allow-popups" => Ok(Self::SameOriginAllowPopups),
52            "unsafe-none" => Ok(Self::UnsafeNone),
53            _ => Err(Error::InvalidCoop(format!(
54                "Unknown Cross-Origin-Opener-Policy: '{}'",
55                s
56            ))),
57        }
58    }
59}
60
61impl std::fmt::Display for CrossOriginOpenerPolicy {
62    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
63        write!(f, "{}", self.as_str())
64    }
65}
66
67/// Cross-Origin-Embedder-Policy (COEP) header value.
68///
69/// COEP prevents a document from loading any cross-origin resources that don't explicitly
70/// grant the document permission to be loaded.
71///
72/// # Examples
73///
74/// ```
75/// use http_security_headers::CrossOriginEmbedderPolicy;
76///
77/// let policy = CrossOriginEmbedderPolicy::RequireCorp;
78/// let policy = CrossOriginEmbedderPolicy::UnsafeNone;
79/// ```
80#[derive(Debug, Clone, Copy, PartialEq, Eq)]
81pub enum CrossOriginEmbedderPolicy {
82    /// This is the default value and allows the document to fetch cross-origin resources
83    /// without giving explicit permission through the CORS protocol or CORP header.
84    UnsafeNone,
85    /// A document can only load resources from the same origin, or resources explicitly
86    /// marked as loadable from another origin.
87    RequireCorp,
88    /// A more permissive variant of `require-corp` that reports (but doesn't block)
89    /// violations and allows cross-origin resources without CORP headers to load.
90    Credentialless,
91}
92
93impl CrossOriginEmbedderPolicy {
94    /// Converts the policy to its header value string.
95    pub fn as_str(&self) -> &'static str {
96        match self {
97            Self::UnsafeNone => "unsafe-none",
98            Self::RequireCorp => "require-corp",
99            Self::Credentialless => "credentialless",
100        }
101    }
102}
103
104impl std::str::FromStr for CrossOriginEmbedderPolicy {
105    type Err = Error;
106
107    fn from_str(s: &str) -> Result<Self> {
108        match s.to_lowercase().as_str() {
109            "unsafe-none" => Ok(Self::UnsafeNone),
110            "require-corp" => Ok(Self::RequireCorp),
111            "credentialless" => Ok(Self::Credentialless),
112            _ => Err(Error::InvalidCoep(format!(
113                "Unknown Cross-Origin-Embedder-Policy: '{}'",
114                s
115            ))),
116        }
117    }
118}
119
120impl std::fmt::Display for CrossOriginEmbedderPolicy {
121    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
122        write!(f, "{}", self.as_str())
123    }
124}
125
126/// Cross-Origin-Resource-Policy (CORP) header value.
127///
128/// CORP allows you to control the set of origins that are empowered to include a resource.
129///
130/// # Examples
131///
132/// ```
133/// use http_security_headers::CrossOriginResourcePolicy;
134///
135/// let policy = CrossOriginResourcePolicy::SameOrigin;
136/// let policy = CrossOriginResourcePolicy::SameSite;
137/// let policy = CrossOriginResourcePolicy::CrossOrigin;
138/// ```
139#[derive(Debug, Clone, Copy, PartialEq, Eq)]
140pub enum CrossOriginResourcePolicy {
141    /// Only requests from the same origin can read the resource.
142    SameOrigin,
143    /// Only requests from the same site can read the resource.
144    SameSite,
145    /// Requests from any origin can read the resource.
146    CrossOrigin,
147}
148
149impl CrossOriginResourcePolicy {
150    /// Converts the policy to its header value string.
151    pub fn as_str(&self) -> &'static str {
152        match self {
153            Self::SameOrigin => "same-origin",
154            Self::SameSite => "same-site",
155            Self::CrossOrigin => "cross-origin",
156        }
157    }
158}
159
160impl std::str::FromStr for CrossOriginResourcePolicy {
161    type Err = Error;
162
163    fn from_str(s: &str) -> Result<Self> {
164        match s.to_lowercase().as_str() {
165            "same-origin" => Ok(Self::SameOrigin),
166            "same-site" => Ok(Self::SameSite),
167            "cross-origin" => Ok(Self::CrossOrigin),
168            _ => Err(Error::InvalidCorp(format!(
169                "Unknown Cross-Origin-Resource-Policy: '{}'",
170                s
171            ))),
172        }
173    }
174}
175
176impl std::fmt::Display for CrossOriginResourcePolicy {
177    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
178        write!(f, "{}", self.as_str())
179    }
180}
181
182#[cfg(test)]
183mod tests {
184    use super::*;
185    use std::str::FromStr;
186
187    // COOP tests
188    #[test]
189    fn test_coop_as_str() {
190        assert_eq!(CrossOriginOpenerPolicy::SameOrigin.as_str(), "same-origin");
191        assert_eq!(
192            CrossOriginOpenerPolicy::SameOriginAllowPopups.as_str(),
193            "same-origin-allow-popups"
194        );
195        assert_eq!(CrossOriginOpenerPolicy::UnsafeNone.as_str(), "unsafe-none");
196    }
197
198    #[test]
199    fn test_coop_from_str() {
200        assert_eq!(
201            CrossOriginOpenerPolicy::from_str("same-origin").unwrap(),
202            CrossOriginOpenerPolicy::SameOrigin
203        );
204        assert_eq!(
205            CrossOriginOpenerPolicy::from_str("same-origin-allow-popups").unwrap(),
206            CrossOriginOpenerPolicy::SameOriginAllowPopups
207        );
208        assert_eq!(
209            CrossOriginOpenerPolicy::from_str("unsafe-none").unwrap(),
210            CrossOriginOpenerPolicy::UnsafeNone
211        );
212        assert!(CrossOriginOpenerPolicy::from_str("invalid").is_err());
213    }
214
215    // COEP tests
216    #[test]
217    fn test_coep_as_str() {
218        assert_eq!(
219            CrossOriginEmbedderPolicy::UnsafeNone.as_str(),
220            "unsafe-none"
221        );
222        assert_eq!(
223            CrossOriginEmbedderPolicy::RequireCorp.as_str(),
224            "require-corp"
225        );
226        assert_eq!(
227            CrossOriginEmbedderPolicy::Credentialless.as_str(),
228            "credentialless"
229        );
230    }
231
232    #[test]
233    fn test_coep_from_str() {
234        assert_eq!(
235            CrossOriginEmbedderPolicy::from_str("unsafe-none").unwrap(),
236            CrossOriginEmbedderPolicy::UnsafeNone
237        );
238        assert_eq!(
239            CrossOriginEmbedderPolicy::from_str("require-corp").unwrap(),
240            CrossOriginEmbedderPolicy::RequireCorp
241        );
242        assert_eq!(
243            CrossOriginEmbedderPolicy::from_str("credentialless").unwrap(),
244            CrossOriginEmbedderPolicy::Credentialless
245        );
246        assert!(CrossOriginEmbedderPolicy::from_str("invalid").is_err());
247    }
248
249    // CORP tests
250    #[test]
251    fn test_corp_as_str() {
252        assert_eq!(
253            CrossOriginResourcePolicy::SameOrigin.as_str(),
254            "same-origin"
255        );
256        assert_eq!(CrossOriginResourcePolicy::SameSite.as_str(), "same-site");
257        assert_eq!(
258            CrossOriginResourcePolicy::CrossOrigin.as_str(),
259            "cross-origin"
260        );
261    }
262
263    #[test]
264    fn test_corp_from_str() {
265        assert_eq!(
266            CrossOriginResourcePolicy::from_str("same-origin").unwrap(),
267            CrossOriginResourcePolicy::SameOrigin
268        );
269        assert_eq!(
270            CrossOriginResourcePolicy::from_str("same-site").unwrap(),
271            CrossOriginResourcePolicy::SameSite
272        );
273        assert_eq!(
274            CrossOriginResourcePolicy::from_str("cross-origin").unwrap(),
275            CrossOriginResourcePolicy::CrossOrigin
276        );
277        assert!(CrossOriginResourcePolicy::from_str("invalid").is_err());
278    }
279
280    #[test]
281    fn test_display() {
282        assert_eq!(
283            CrossOriginOpenerPolicy::SameOrigin.to_string(),
284            "same-origin"
285        );
286        assert_eq!(
287            CrossOriginEmbedderPolicy::RequireCorp.to_string(),
288            "require-corp"
289        );
290        assert_eq!(
291            CrossOriginResourcePolicy::SameSite.to_string(),
292            "same-site"
293        );
294    }
295}