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}