http_types_rs/security/
csp.rs

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