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}