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