Skip to main content

rama_http_headers/common/
sec_fetch_site.rs

1use std::borrow::Cow;
2
3use rama_http_types::{HeaderName, HeaderValue};
4
5use crate::{Error, HeaderDecode, HeaderEncode, TypedHeader};
6
7rama_utils::macros::enums::enum_builder! {
8    /// The `Sec-Fetch-Site` [fetch metadata request header][mdn].
9    ///
10    /// Sent by browsers to indicate the relationship between the origin that initiated the request
11    /// and the origin of the requested resource. It is the primary signal used by modern CSRF
12    /// protection (see [`CsrfLayer`]).
13    ///
14    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Sec-Fetch-Site
15    /// [`CsrfLayer`]: https://docs.rs/rama-http/latest/rama_http/layer/csrf/struct.CsrfLayer.html
16    ///
17    /// # Example values
18    ///
19    /// * `same-origin`
20    /// * `same-site`
21    /// * `cross-site`
22    /// * `none`
23    @String
24    pub enum SecFetchSite {
25        /// The request initiator and the target share the same origin.
26        SameOrigin => "same-origin",
27        /// The request initiator and the target share the same site (registrable domain) but not
28        /// the same origin.
29        SameSite => "same-site",
30        /// The request initiator and the target are cross-site.
31        CrossSite => "cross-site",
32        /// The request was initiated by the user (e.g. typing a URL), not by another origin.
33        None => "none",
34    }
35}
36
37impl TypedHeader for SecFetchSite {
38    fn name() -> &'static HeaderName {
39        &::rama_http_types::header::SEC_FETCH_SITE
40    }
41}
42
43impl HeaderDecode for SecFetchSite {
44    fn decode<'i, I>(values: &mut I) -> Result<Self, Error>
45    where
46        I: Iterator<Item = &'i HeaderValue>,
47    {
48        values
49            .next()
50            .and_then(|value| value.to_str().ok())
51            .and_then(|s| (!s.is_empty()).then(|| Self::from(s)))
52            .ok_or_else(Error::invalid)
53    }
54}
55
56impl HeaderEncode for SecFetchSite {
57    fn encode<E: Extend<HeaderValue>>(&self, values: &mut E) {
58        match self.as_static_str() {
59            Cow::Borrowed(s) => values.extend(std::iter::once(HeaderValue::from_static(s))),
60            Cow::Owned(s) => values.extend(HeaderValue::try_from(s).ok()),
61        }
62    }
63}
64
65#[cfg(test)]
66mod tests {
67    use super::super::{test_decode, test_encode};
68    use super::*;
69
70    #[test]
71    fn decode_known_values() {
72        assert_eq!(
73            test_decode::<SecFetchSite>(&["same-origin"]),
74            Some(SecFetchSite::SameOrigin)
75        );
76        assert_eq!(
77            test_decode::<SecFetchSite>(&["same-site"]),
78            Some(SecFetchSite::SameSite)
79        );
80        assert_eq!(
81            test_decode::<SecFetchSite>(&["cross-site"]),
82            Some(SecFetchSite::CrossSite)
83        );
84        assert_eq!(
85            test_decode::<SecFetchSite>(&["none"]),
86            Some(SecFetchSite::None)
87        );
88    }
89
90    #[test]
91    fn decode_accepts_unknown_and_empty() {
92        assert_eq!(
93            test_decode::<SecFetchSite>(&["nope"]),
94            Some(SecFetchSite::Unknown("nope".into()))
95        );
96        assert_eq!(test_decode::<SecFetchSite>(&[""]), None);
97        // The Fetch spec mandates lowercase; uppercase is recognised.
98        assert_eq!(
99            test_decode::<SecFetchSite>(&["Same-Origin"]),
100            Some(SecFetchSite::SameOrigin)
101        );
102    }
103
104    #[test]
105    fn encode_round_trip() {
106        let headers = test_encode(SecFetchSite::CrossSite);
107        assert_eq!(headers["sec-fetch-site"], "cross-site");
108    }
109}