Skip to main content

rama_http_headers/common/content_security_policy/
directive.rs

1use std::fmt;
2
3use rama_utils::macros::enums::enum_builder;
4
5use super::source_list::SourceList;
6
7enum_builder! {
8    /// Names of the CSP directives this crate knows about. Unknown
9    /// directives round-trip through the `Unknown(String)` variant.
10    @String
11    pub enum DirectiveName {
12        /// Fallback source-list for fetch directives that don't have
13        /// their own entry. The closest thing CSP has to a global lock.
14        DefaultSrc => "default-src",
15        /// Where executable script may be fetched / inlined from.
16        ScriptSrc => "script-src",
17        /// `script-src` restricted to `<script>` element loads only.
18        ScriptSrcElem => "script-src-elem",
19        /// `script-src` restricted to inline event handlers / `javascript:`
20        /// URIs only.
21        ScriptSrcAttr => "script-src-attr",
22        /// Where stylesheets may be fetched / inlined from.
23        StyleSrc => "style-src",
24        /// `style-src` restricted to `<style>` / `<link rel=stylesheet>` loads.
25        StyleSrcElem => "style-src-elem",
26        /// `style-src` restricted to inline `style="…"` attributes.
27        StyleSrcAttr => "style-src-attr",
28        /// Where images may be fetched from.
29        ImgSrc => "img-src",
30        /// Where fonts may be fetched from.
31        FontSrc => "font-src",
32        /// Where the protected resource may open XHR / fetch / WebSocket
33        /// / EventSource connections.
34        ConnectSrc => "connect-src",
35        /// Where audio / video may be fetched from.
36        MediaSrc => "media-src",
37        /// Where `<object>` / `<embed>` / `<applet>` may be fetched from.
38        ObjectSrc => "object-src",
39        /// Where `<frame>` / `<iframe>` documents may be fetched from.
40        FrameSrc => "frame-src",
41        /// Who may embed the protected resource in a frame. Note:
42        /// nonces/hashes/`'unsafe-inline'` are NOT valid here.
43        FrameAncestors => "frame-ancestors",
44        /// Fallback for `frame-src` + `worker-src` (legacy).
45        ChildSrc => "child-src",
46        /// Where workers (`Worker`, `SharedWorker`, `ServiceWorker`)
47        /// may be fetched from.
48        WorkerSrc => "worker-src",
49        /// Where the page manifest may be fetched from.
50        ManifestSrc => "manifest-src",
51        /// Where the browser is permitted to prefetch resources from.
52        PrefetchSrc => "prefetch-src",
53        /// Permissible form submission targets.
54        FormAction => "form-action",
55        /// Permissible values for the `<base href>` element.
56        BaseUri => "base-uri",
57        /// Restrict the URLs the document may navigate to (CSP3 draft;
58        /// limited browser support).
59        NavigateTo => "navigate-to",
60        /// Endpoint to POST violation reports to (deprecated in favour
61        /// of `report-to`, but still the most widely supported).
62        ReportUri => "report-uri",
63        /// Reporting-API group name to deliver violation reports to.
64        ReportTo => "report-to",
65        /// Apply HTML5 iframe-sandbox flags to the protected resource
66        /// itself.
67        Sandbox => "sandbox",
68        /// Force-rewrite all `http:` subresource URLs to `https:`.
69        /// Valueless.
70        UpgradeInsecureRequests => "upgrade-insecure-requests",
71        /// Block all mixed-content loads (legacy precursor to
72        /// `upgrade-insecure-requests`). Valueless.
73        BlockAllMixedContent => "block-all-mixed-content",
74        /// Restrict which sink-types require a Trusted Type.
75        RequireTrustedTypesFor => "require-trusted-types-for",
76        /// Whitelist of Trusted-Types policy names.
77        TrustedTypes => "trusted-types",
78    }
79}
80
81/// One CSP directive — a `(name, source-list)` pair. Render via
82/// [`fmt::Display`] for the wire form.
83#[derive(Debug, Clone, PartialEq, Eq)]
84pub struct Directive {
85    pub name: DirectiveName,
86    pub sources: SourceList,
87}
88
89impl fmt::Display for Directive {
90    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
91        if self.sources.as_slice().is_empty() {
92            self.name.fmt(f)
93        } else {
94            write!(f, "{} {}", self.name, self.sources)
95        }
96    }
97}
98
99#[cfg(test)]
100mod tests {
101    use super::*;
102
103    #[test]
104    fn known_directive_names_round_trip() {
105        for known in [
106            "default-src",
107            "script-src",
108            "frame-ancestors",
109            "upgrade-insecure-requests",
110            "require-trusted-types-for",
111        ] {
112            let d: DirectiveName = known.into();
113            assert_eq!(d.as_str(), known);
114            // The strict parser agrees with `From<&str>` for known names.
115            assert!(DirectiveName::strict_parse(known).is_some());
116        }
117    }
118
119    #[test]
120    fn unknown_directive_name_falls_through_to_unknown() {
121        let d: DirectiveName = "experimental-thing".into();
122        assert!(matches!(d, DirectiveName::Unknown(ref s) if s.as_ref() == "experimental-thing"));
123        assert_eq!(d.as_str(), "experimental-thing");
124        // Strict parser rejects unknowns.
125        assert!(DirectiveName::strict_parse("experimental-thing").is_none());
126    }
127
128    #[test]
129    fn directive_display_with_sources() {
130        let d = Directive {
131            name: DirectiveName::DefaultSrc,
132            sources: SourceList::self_origin(),
133        };
134        assert_eq!(d.to_string(), "default-src 'self'");
135    }
136
137    #[test]
138    fn directive_display_valueless() {
139        let d = Directive {
140            name: DirectiveName::UpgradeInsecureRequests,
141            sources: SourceList::empty(),
142        };
143        assert_eq!(d.to_string(), "upgrade-insecure-requests");
144    }
145}