Skip to main content

rama_http_headers/common/
x_frame_options.rs

1use rama_http_types::{HeaderName, HeaderValue};
2
3use crate::{Error, HeaderDecode, HeaderEncode, TypedHeader};
4
5/// `X-Frame-Options` header, as defined in RFC 7034
6/// ("HTTP Header Field X-Frame-Options"),
7/// vendored at `rama-http-headers/specifications/rfc7034.txt`.
8///
9/// Controls whether a browser is allowed to render a page in a `<frame>`,
10/// `<iframe>`, `<embed>` or `<object>`. The deprecated `ALLOW-FROM` directive
11/// is rejected on parse — it was removed from the modern HTML/Fetch specs and
12/// is no longer supported by any major browser. Use `Content-Security-Policy:
13/// frame-ancestors` for origin-scoped framing controls instead.
14///
15/// Token matching is ASCII case-insensitive on parse; encoding emits the
16/// canonical uppercase tokens.
17///
18/// # Example
19///
20/// ```
21/// use rama_http_headers::XFrameOptions;
22///
23/// let header = XFrameOptions::Deny;
24/// ```
25#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
26pub enum XFrameOptions {
27    /// `DENY`: the page cannot be displayed in a frame, regardless of origin.
28    Deny,
29    /// `SAMEORIGIN`: the page can only be displayed in a frame of the same origin.
30    SameOrigin,
31}
32
33impl XFrameOptions {
34    fn as_str(self) -> &'static str {
35        match self {
36            Self::Deny => "DENY",
37            Self::SameOrigin => "SAMEORIGIN",
38        }
39    }
40}
41
42impl TypedHeader for XFrameOptions {
43    fn name() -> &'static HeaderName {
44        &::rama_http_types::header::X_FRAME_OPTIONS
45    }
46}
47
48impl HeaderDecode for XFrameOptions {
49    fn decode<'i, I: Iterator<Item = &'i HeaderValue>>(values: &mut I) -> Result<Self, Error> {
50        values
51            .next()
52            .and_then(|value| {
53                let s = value.to_str().ok()?.trim();
54                if s.eq_ignore_ascii_case("DENY") {
55                    Some(Self::Deny)
56                } else if s.eq_ignore_ascii_case("SAMEORIGIN") {
57                    Some(Self::SameOrigin)
58                } else {
59                    // ALLOW-FROM is intentionally rejected (removed from the spec).
60                    None
61                }
62            })
63            .ok_or_else(Error::invalid)
64    }
65}
66
67impl HeaderEncode for XFrameOptions {
68    fn encode<E: Extend<HeaderValue>>(&self, values: &mut E) {
69        values.extend(::std::iter::once(HeaderValue::from_static(self.as_str())));
70    }
71}
72
73#[cfg(test)]
74mod tests {
75    use super::super::test_decode;
76    use super::*;
77
78    #[test]
79    fn decode_deny() {
80        assert_eq!(
81            test_decode::<XFrameOptions>(&["DENY"]),
82            Some(XFrameOptions::Deny),
83        );
84    }
85
86    #[test]
87    fn decode_sameorigin() {
88        assert_eq!(
89            test_decode::<XFrameOptions>(&["SAMEORIGIN"]),
90            Some(XFrameOptions::SameOrigin),
91        );
92    }
93
94    #[test]
95    fn decode_case_insensitive() {
96        assert_eq!(
97            test_decode::<XFrameOptions>(&["deny"]),
98            Some(XFrameOptions::Deny),
99        );
100        assert_eq!(
101            test_decode::<XFrameOptions>(&["SameOrigin"]),
102            Some(XFrameOptions::SameOrigin),
103        );
104    }
105
106    #[test]
107    fn decode_rejects_allow_from() {
108        assert_eq!(
109            test_decode::<XFrameOptions>(&["ALLOW-FROM https://example.com"]),
110            None,
111        );
112        assert_eq!(test_decode::<XFrameOptions>(&["ALLOW-FROM"]), None);
113        assert_eq!(test_decode::<XFrameOptions>(&["allow-from *"]), None);
114    }
115
116    #[test]
117    fn decode_rejects_other_values() {
118        assert_eq!(test_decode::<XFrameOptions>(&[""]), None);
119        assert_eq!(test_decode::<XFrameOptions>(&["allowall"]), None);
120        assert_eq!(test_decode::<XFrameOptions>(&["DENY, SAMEORIGIN"]), None);
121    }
122}