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}