http_types/security/csp.rs
1use crate::Headers;
2use serde::{Deserialize, Serialize};
3use std::collections::HashMap;
4use std::fmt;
5
6/// Define source value
7///
8/// [read more](https://content-security-policy.com)
9#[derive(Debug, PartialEq, Eq, Copy, Clone, Hash)]
10pub enum Source {
11 /// Set source `'self'`
12 SameOrigin,
13 /// Set source `'src'`
14 Src,
15 /// Set source `'none'`
16 None,
17 /// Set source `'unsafe-inline'`
18 UnsafeInline,
19 /// Set source `data:`
20 Data,
21 /// Set source `mediastream:`
22 Mediastream,
23 /// Set source `https:`
24 Https,
25 /// Set source `blob:`
26 Blob,
27 /// Set source `filesystem:`
28 Filesystem,
29 /// Set source `'strict-dynamic'`
30 StrictDynamic,
31 /// Set source `'unsafe-eval'`
32 UnsafeEval,
33 /// Set source `*`
34 Wildcard,
35}
36
37impl fmt::Display for Source {
38 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
39 match *self {
40 Source::SameOrigin => write!(f, "'self'"),
41 Source::Src => write!(f, "'src'"),
42 Source::None => write!(f, "'none'"),
43 Source::UnsafeInline => write!(f, "'unsafe-inline'"),
44 Source::Data => write!(f, "data:"),
45 Source::Mediastream => write!(f, "mediastream:"),
46 Source::Https => write!(f, "https:"),
47 Source::Blob => write!(f, "blob:"),
48 Source::Filesystem => write!(f, "filesystem:"),
49 Source::StrictDynamic => write!(f, "'strict-dynamic'"),
50 Source::UnsafeEval => write!(f, "'unsafe-eval'"),
51 Source::Wildcard => write!(f, "*"),
52 }
53 }
54}
55
56impl AsRef<str> for Source {
57 fn as_ref(&self) -> &str {
58 match *self {
59 Source::SameOrigin => "'self'",
60 Source::Src => "'src'",
61 Source::None => "'none'",
62 Source::UnsafeInline => "'unsafe-inline'",
63 Source::Data => "data:",
64 Source::Mediastream => "mediastream:",
65 Source::Https => "https:",
66 Source::Blob => "blob:",
67 Source::Filesystem => "filesystem:",
68 Source::StrictDynamic => "'strict-dynamic'",
69 Source::UnsafeEval => "'unsafe-eval'",
70 Source::Wildcard => "*",
71 }
72 }
73}
74
75/// Define `report-to` directive value
76///
77/// [MDN | report-to](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/report-to)
78#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
79pub struct ReportTo {
80 #[serde(skip_serializing_if = "Option::is_none")]
81 group: Option<String>,
82 max_age: i32,
83 endpoints: Vec<ReportToEndpoint>,
84 #[serde(skip_serializing_if = "Option::is_none")]
85 include_subdomains: Option<bool>,
86}
87
88/// Define `endpoints` for `report-to` directive value
89///
90/// [MDN | report-to](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/report-to)
91#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
92pub struct ReportToEndpoint {
93 url: String,
94}
95
96/// Build a `Content-Security-Policy` header.
97///
98/// `Content-Security-Policy` (CSP) HTTP headers are used to prevent cross-site
99/// injections. [Read more](https://helmetjs.github.io/docs/csp/)
100///
101/// [Mozilla Developer Network](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy)
102///
103/// # Examples
104///
105/// ```
106/// use http_types::{headers, security, Response, StatusCode};
107///
108/// let mut policy = security::ContentSecurityPolicy::new();
109/// policy
110/// .default_src(security::Source::SameOrigin)
111/// .default_src("areweasyncyet.rs")
112/// .script_src(security::Source::SameOrigin)
113/// .script_src(security::Source::UnsafeInline)
114/// .object_src(security::Source::None)
115/// .base_uri(security::Source::None)
116/// .upgrade_insecure_requests();
117///
118/// let mut res = Response::new(StatusCode::Ok);
119/// res.set_body("Hello, Chashu!");
120///
121/// security::default(&mut res);
122/// policy.apply(&mut res);
123///
124/// assert_eq!(res["content-security-policy"], "base-uri 'none'; default-src 'self' areweasyncyet.rs; object-src 'none'; script-src 'self' 'unsafe-inline'; upgrade-insecure-requests");
125/// ```
126#[derive(Debug, Clone, PartialEq, Eq)]
127pub struct ContentSecurityPolicy {
128 policy: Vec<String>,
129 report_only_flag: bool,
130 directives: HashMap<String, Vec<String>>,
131}
132
133impl Default for ContentSecurityPolicy {
134 /// Sets the Content-Security-Policy default to "script-src 'self'; object-src 'self'"
135 fn default() -> Self {
136 let policy = String::from("script-src 'self'; object-src 'self'");
137 ContentSecurityPolicy {
138 policy: vec![policy],
139 report_only_flag: false,
140 directives: HashMap::new(),
141 }
142 }
143}
144
145impl ContentSecurityPolicy {
146 /// Create a new instance.
147 pub fn new() -> Self {
148 Self {
149 policy: Vec::new(),
150 report_only_flag: false,
151 directives: HashMap::new(),
152 }
153 }
154
155 fn insert_directive<T: AsRef<str>>(&mut self, directive: &str, source: T) {
156 let directive = String::from(directive);
157 let directives = self.directives.entry(directive).or_insert_with(Vec::new);
158 let source: String = source.as_ref().to_string();
159 directives.push(source);
160 }
161
162 /// Defines the Content-Security-Policy `base-uri` directive
163 ///
164 /// [MDN | base-uri](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/base-uri)
165 pub fn base_uri<T: AsRef<str>>(&mut self, source: T) -> &mut Self {
166 self.insert_directive("base-uri", source);
167 self
168 }
169
170 /// Defines the Content-Security-Policy `block-all-mixed-content` directive
171 ///
172 /// [MDN | block-all-mixed-content](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/block-all-mixed-content)
173 pub fn block_all_mixed_content(&mut self) -> &mut Self {
174 let policy = String::from("block-all-mixed-content");
175 self.policy.push(policy);
176 self
177 }
178
179 /// Defines the Content-Security-Policy `connect-src` directive
180 ///
181 /// [MDN | connect-src](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/connect-src)
182 pub fn connect_src<T: AsRef<str>>(&mut self, source: T) -> &mut Self {
183 self.insert_directive("connect-src", source);
184 self
185 }
186
187 /// Defines the Content-Security-Policy `default-src` directive
188 ///
189 /// [MDN | default-src](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/default-src)
190 pub fn default_src<T: AsRef<str>>(&mut self, source: T) -> &mut Self {
191 self.insert_directive("default-src", source);
192 self
193 }
194
195 /// Defines the Content-Security-Policy `font-src` directive
196 ///
197 /// [MDN | font-src](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/font-src)
198 pub fn font_src<T: AsRef<str>>(&mut self, source: T) -> &mut Self {
199 self.insert_directive("font-src", source);
200 self
201 }
202
203 /// Defines the Content-Security-Policy `form-action` directive
204 ///
205 /// [MDN | form-action](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/form-action)
206 pub fn form_action<T: AsRef<str>>(&mut self, source: T) -> &mut Self {
207 self.insert_directive("form-action", source);
208 self
209 }
210
211 /// Defines the Content-Security-Policy `frame-ancestors` directive
212 ///
213 /// [MDN | frame-ancestors](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/frame-ancestors)
214 pub fn frame_ancestors<T: AsRef<str>>(&mut self, source: T) -> &mut Self {
215 self.insert_directive("frame-ancestors", source);
216 self
217 }
218
219 /// Defines the Content-Security-Policy `frame-src` directive
220 ///
221 /// [MDN | frame-src](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/frame-src)
222 pub fn frame_src<T: AsRef<str>>(&mut self, source: T) -> &mut Self {
223 self.insert_directive("frame-src", source);
224 self
225 }
226
227 /// Defines the Content-Security-Policy `img-src` directive
228 ///
229 /// [MDN | img-src](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/img-src)
230 pub fn img_src<T: AsRef<str>>(&mut self, source: T) -> &mut Self {
231 self.insert_directive("img-src", source);
232 self
233 }
234
235 /// Defines the Content-Security-Policy `media-src` directive
236 ///
237 /// [MDN | media-src](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/media-src)
238 pub fn media_src<T: AsRef<str>>(&mut self, source: T) -> &mut Self {
239 self.insert_directive("media-src", source);
240 self
241 }
242
243 /// Defines the Content-Security-Policy `object-src` directive
244 ///
245 /// [MDN | object-src](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/object-src)
246 pub fn object_src<T: AsRef<str>>(&mut self, source: T) -> &mut Self {
247 self.insert_directive("object-src", source);
248 self
249 }
250
251 /// Defines the Content-Security-Policy `plugin-types` directive
252 ///
253 /// [MDN | plugin-types](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/plugin-types)
254 pub fn plugin_types<T: AsRef<str>>(&mut self, source: T) -> &mut Self {
255 self.insert_directive("plugin-types", source);
256 self
257 }
258
259 /// Defines the Content-Security-Policy `require-sri-for` directive
260 ///
261 /// [MDN | require-sri-for](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/require-sri-for)
262 pub fn require_sri_for<T: AsRef<str>>(&mut self, source: T) -> &mut Self {
263 self.insert_directive("require-sri-for", source);
264 self
265 }
266
267 /// Defines the Content-Security-Policy `report-uri` directive
268 ///
269 /// [MDN | report-uri](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/report-uri)
270 pub fn report_uri<T: AsRef<str>>(&mut self, uri: T) -> &mut Self {
271 self.insert_directive("report-uri", uri);
272 self
273 }
274
275 /// Defines the Content-Security-Policy `report-to` directive
276 ///
277 /// [MDN | report-to](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/report-to)
278 pub fn report_to(&mut self, endpoints: Vec<ReportTo>) -> &mut Self {
279 for endpoint in endpoints.iter() {
280 match serde_json::to_string(&endpoint) {
281 Ok(json) => {
282 let policy = format!("report-to {}", json);
283 self.policy.push(policy);
284 }
285 Err(error) => {
286 println!("{:?}", error);
287 }
288 }
289 }
290 self
291 }
292
293 /// Defines the Content-Security-Policy `sandbox` directive
294 ///
295 /// [MDN | sandbox](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/sandbox)
296 pub fn sandbox<T: AsRef<str>>(&mut self, source: T) -> &mut Self {
297 self.insert_directive("sandbox", source);
298 self
299 }
300
301 /// Defines the Content-Security-Policy `script-src` directive
302 ///
303 /// [MDN | script-src](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/script-src)
304 pub fn script_src<T: AsRef<str>>(&mut self, source: T) -> &mut Self {
305 self.insert_directive("script-src", source);
306 self
307 }
308
309 /// Defines the Content-Security-Policy `style-src` directive
310 ///
311 /// [MDN | style-src](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/style-src)
312 pub fn style_src<T: AsRef<str>>(&mut self, source: T) -> &mut Self {
313 self.insert_directive("style-src", source);
314 self
315 }
316
317 /// Defines the Content-Security-Policy `upgrade-insecure-requests` directive
318 ///
319 /// [MDN | upgrade-insecure-requests](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/upgrade-insecure-requests)
320 pub fn upgrade_insecure_requests(&mut self) -> &mut Self {
321 let policy = String::from("upgrade-insecure-requests");
322 self.policy.push(policy);
323 self
324 }
325
326 /// Defines the Content-Security-Policy `worker-src` directive
327 ///
328 /// [MDN | worker-src](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/worker-src)
329 pub fn worker_src<T: AsRef<str>>(&mut self, source: T) -> &mut Self {
330 self.insert_directive("worker-src", source);
331 self
332 }
333
334 /// Change the header to `Content-Security-Policy-Report-Only`
335 pub fn report_only(&mut self) -> &mut Self {
336 self.report_only_flag = true;
337 self
338 }
339
340 /// Create and retrieve the policy value
341 fn value(&mut self) -> String {
342 for (directive, sources) in &self.directives {
343 let policy = format!("{} {}", directive, sources.join(" "));
344 self.policy.push(policy);
345 self.policy.sort();
346 }
347 self.policy.join("; ")
348 }
349
350 /// Sets the `Content-Security-Policy` (CSP) HTTP header to prevent cross-site injections
351 pub fn apply(&mut self, mut headers: impl AsMut<Headers>) {
352 let name = if self.report_only_flag {
353 "Content-Security-Policy-Report-Only"
354 } else {
355 "Content-Security-Policy"
356 };
357 headers.as_mut().insert(name, self.value());
358 }
359}