1use std::borrow::Cow;
2
3use http::{header::InvalidHeaderValue, HeaderValue};
4
5#[derive(Debug, Clone, PartialEq, Eq, Hash)]
8pub struct ContentSecurityPolicy {
9 pub default_src: Vec<CspSource>,
11 pub child_src: Vec<CspSource>,
12 pub connect_src: Vec<CspSource>,
13 pub font_src: Vec<CspSource>,
14 pub frame_src: Vec<CspSource>,
15 pub img_src: Vec<CspSource>,
16 pub manifest_src: Vec<CspSource>,
17 pub media_src: Vec<CspSource>,
18 pub object_src: Vec<CspSource>,
19 pub script_src: Vec<CspSource>,
20 pub script_src_elem: Vec<CspSource>,
21 pub script_src_attr: Vec<CspSource>,
22 pub style_src: Vec<CspSource>,
23 pub style_src_elem: Vec<CspSource>,
24 pub style_src_attr: Vec<CspSource>,
25 pub worker_src: Vec<CspSource>,
26 pub base_uri: Vec<CspSource>,
28 pub sandbox: Vec<CspSource>,
29 pub form_action: Vec<CspSource>,
31 pub frame_ancestors: Vec<CspSource>,
32 pub upgrade_insecure_requests: bool,
34}
35
36impl ContentSecurityPolicy {
37 pub const fn new_empty() -> Self {
39 Self {
40 default_src: vec![],
41 child_src: vec![],
42 connect_src: vec![],
43 font_src: vec![],
44 frame_src: vec![],
45 img_src: vec![],
46 manifest_src: vec![],
47 media_src: vec![],
48 object_src: vec![],
49 script_src: vec![],
50 script_src_elem: vec![],
51 script_src_attr: vec![],
52 style_src: vec![],
53 style_src_elem: vec![],
54 style_src_attr: vec![],
55 worker_src: vec![],
56 base_uri: vec![],
57 sandbox: vec![],
58 form_action: vec![],
59 frame_ancestors: vec![],
60 upgrade_insecure_requests: false,
61 }
62 }
63
64 pub fn strict_default() -> Self {
65 Self {
66 default_src: vec![CspSource::SelfOrigin],
67 base_uri: vec![CspSource::SelfOrigin],
68 font_src: vec![
69 CspSource::SelfOrigin,
70 CspSource::Scheme(CspSchemeSource::Https),
71 CspSource::Scheme(CspSchemeSource::Data),
72 ],
73 form_action: vec![CspSource::SelfOrigin],
74 frame_ancestors: vec![CspSource::SelfOrigin],
75 img_src: vec![
76 CspSource::SelfOrigin,
77 CspSource::Scheme(CspSchemeSource::Data),
78 ],
79 object_src: vec![CspSource::None],
80 script_src: vec![CspSource::SelfOrigin],
81 script_src_attr: vec![CspSource::None],
82 style_src: vec![
83 CspSource::SelfOrigin,
84 CspSource::Scheme(CspSchemeSource::Https),
85 CspSource::UnsafeInline,
86 ],
87 upgrade_insecure_requests: true,
88 ..Self::new_empty()
89 }
90 }
91}
92
93impl ContentSecurityPolicy {
94 pub fn value(&self, nonce: &str) -> Result<HeaderValue, InvalidHeaderValue> {
95 let mut output = String::with_capacity(256);
96 serialize_header(&mut output, nonce, "default-src", &self.default_src);
97 serialize_header(&mut output, nonce, "child-src", &self.child_src);
98 serialize_header(&mut output, nonce, "connect-src", &self.connect_src);
99 serialize_header(&mut output, nonce, "font-src", &self.font_src);
100 serialize_header(&mut output, nonce, "frame-src", &self.frame_src);
101 serialize_header(&mut output, nonce, "img-src", &self.img_src);
102 serialize_header(&mut output, nonce, "manifest-src", &self.manifest_src);
103 serialize_header(&mut output, nonce, "media-src", &self.media_src);
104 serialize_header(&mut output, nonce, "object-src", &self.object_src);
105 serialize_header(&mut output, nonce, "script-src", &self.script_src);
106 serialize_header(&mut output, nonce, "script-src-elem", &self.script_src_elem);
107 serialize_header(&mut output, nonce, "script-src-attr", &self.script_src_attr);
108 serialize_header(&mut output, nonce, "style-src", &self.style_src);
109 serialize_header(&mut output, nonce, "style-src-elem", &self.style_src_elem);
110 serialize_header(&mut output, nonce, "style-src-attr", &self.style_src_attr);
111 serialize_header(&mut output, nonce, "worker-src", &self.worker_src);
112 serialize_header(&mut output, nonce, "base-uri", &self.base_uri);
113 serialize_header(&mut output, nonce, "sandbox", &self.sandbox);
114 serialize_header(&mut output, nonce, "form-action", &self.form_action);
115 serialize_header(&mut output, nonce, "frame-ancestors", &self.frame_ancestors);
116 HeaderValue::from_str(output.as_str())
117 }
118}
119
120impl ContentSecurityPolicy {
121 pub fn upgrade_insecure_requests(self, doit: bool) -> Self {
122 Self {
123 upgrade_insecure_requests: doit,
124 ..self
125 }
126 }
127}
128
129macro_rules! csp_builder_add {
130 ($id:ident) => {
131 #[must_use]
132 pub fn $id(
133 self,
134 new: impl ::std::convert::Into<::std::vec::Vec<$crate::headers::csp::CspSource>>,
135 ) -> Self {
136 Self {
137 $id: ::std::convert::Into::into(new),
138 ..self
139 }
140 }
141 };
142}
143
144macro_rules! csp_builder_remove {
145 ($id:ident, $func:ident) => {
146 #[must_use]
147 pub fn $func(mut self) -> Self {
148 ::std::vec::Vec::clear(&mut self.$id);
149 self
150 }
151 };
152}
153
154#[rustfmt::skip]
155impl ContentSecurityPolicy {
156 csp_builder_add!(default_src);
157 csp_builder_add!(child_src);
158 csp_builder_add!(connect_src);
159 csp_builder_add!(font_src);
160 csp_builder_add!(frame_src);
161 csp_builder_add!(img_src);
162 csp_builder_add!(manifest_src);
163 csp_builder_add!(media_src);
164 csp_builder_add!(object_src);
165 csp_builder_add!(script_src);
166 csp_builder_add!(script_src_elem);
167 csp_builder_add!(script_src_attr);
168 csp_builder_add!(style_src);
169 csp_builder_add!(style_src_elem);
170 csp_builder_add!(style_src_attr);
171 csp_builder_add!(worker_src);
172 csp_builder_add!(base_uri);
173 csp_builder_add!(sandbox);
174 csp_builder_add!(form_action);
175 csp_builder_add!(frame_ancestors);
176 csp_builder_remove!(default_src, remove_default_src);
177 csp_builder_remove!(child_src, remove_child_src);
178 csp_builder_remove!(connect_src, remove_connect_src);
179 csp_builder_remove!(font_src, remove_font_src);
180 csp_builder_remove!(frame_src, remove_frame_src);
181 csp_builder_remove!(img_src, remove_img_src);
182 csp_builder_remove!(manifest_src, remove_manifest_src);
183 csp_builder_remove!(media_src, remove_media_src);
184 csp_builder_remove!(object_src, remove_object_src);
185 csp_builder_remove!(script_src, remove_script_src);
186 csp_builder_remove!(script_src_elem, remove_script_src_elem);
187 csp_builder_remove!(script_src_attr, remove_script_src_attr);
188 csp_builder_remove!(style_src, remove_style_src);
189 csp_builder_remove!(style_src_elem, remove_style_src_elem);
190 csp_builder_remove!(style_src_attr, remove_style_src_attr);
191 csp_builder_remove!(worker_src, remove_worker_src);
192 csp_builder_remove!(base_uri, remove_base_uri);
193 csp_builder_remove!(sandbox, remove_sandbox);
194 csp_builder_remove!(form_action, remove_form_action);
195 csp_builder_remove!(frame_ancestors, remove_frame_ancestors);
196}
197
198#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
199pub enum CspSchemeSource {
200 Data,
201 Mediastream,
202 Blob,
203 Filesystem,
204 Http,
205 Https,
206}
207
208impl AsRef<str> for CspSchemeSource {
209 fn as_ref(&self) -> &str {
210 match self {
211 Self::Data => "data:",
212 Self::Mediastream => "mediastream:",
213 Self::Blob => "blob:",
214 Self::Filesystem => "filesystem:",
215 Self::Http => "http:",
216 Self::Https => "https:",
217 }
218 }
219}
220
221impl From<CspSchemeSource> for CspSource {
222 fn from(value: CspSchemeSource) -> Self {
223 Self::Scheme(value)
224 }
225}
226
227#[derive(Debug, Clone, PartialEq, Eq, Hash)]
228pub enum CspHashAlgorithm {
229 Sha256,
230 Sha384,
231 Sha512,
232 Custom(String),
233}
234
235impl AsRef<str> for CspHashAlgorithm {
236 fn as_ref(&self) -> &str {
237 match self {
238 Self::Sha256 => "sha256",
239 Self::Sha384 => "sha384",
240 Self::Sha512 => "sha512",
241 Self::Custom(s) => s.as_ref(),
242 }
243 }
244}
245
246#[derive(Debug, Clone, PartialEq, Eq, Hash)]
247pub enum CspSource {
248 Host(String),
249 Scheme(CspSchemeSource),
250 Nonce,
252 Hash(CspHashAlgorithm, String),
253 SelfOrigin,
255 UnsafeEval,
256 WasmUnsafeEval,
257 UnsafeHashes,
258 UnsafeInline,
259 StrictDynamic,
260 ReportSample,
261 InlineSpeculationRules,
262 None,
263}
264
265impl CspSource {
266 fn as_cow(&self, nonce: &str) -> Cow<'_, str> {
267 let borrowed = match self {
268 Self::Host(s) => s.as_str(),
269 Self::Scheme(s) => s.as_ref(),
270 Self::Nonce => return Cow::Owned(format!("'nonce-{nonce}'")),
271 Self::Hash(algo, data) => return Cow::Owned(format!("'{}-{data}'", algo.as_ref())),
272 Self::SelfOrigin => "'self'",
273 Self::UnsafeEval => "'unsafe-eval'",
274 Self::WasmUnsafeEval => "'wasm-unsafe-eval'",
275 Self::UnsafeHashes => "'unsafe-hashes'",
276 Self::UnsafeInline => "'unsafe-inline'",
277 Self::StrictDynamic => "'strict-dynamic'",
278 Self::ReportSample => "'report-sample'",
279 Self::InlineSpeculationRules => "'inline-speculation-rules'",
280 Self::None => "'none'",
281 };
282 Cow::Borrowed(borrowed)
283 }
284}
285
286impl From<CspSource> for Vec<CspSource> {
287 fn from(value: CspSource) -> Self {
288 vec![value]
289 }
290}
291
292fn serialize_header(s: &mut String, nonce: &str, name: &str, sources: &[CspSource]) {
293 if sources.is_empty() {
294 return;
295 }
296 s.push_str(name);
297 for source in sources {
298 s.push(' ');
299 s.push_str(source.as_cow(nonce).as_ref());
300 }
301 s.push(';');
302}