csp/lib.rs
1//! This crate is a helper to quickly construct a CSP and then turn it into a
2//! String.
3//!
4//! This library can help you when you don't want to remember some weird
5//! formatting rules of CSP, and want to avoid typos. And it certainly can be
6//! handy if you need to re-use things, for example a list of sources (just
7//! .clone() them everywhere and you're good to go!).
8//!
9//! WARNING: this library does not care if you create invalid CSP rules, and
10//! happily allows them and turns them into Strings. But it does force you to
11//! use a typed structure, so it'll be harder to mess up than when manually
12//! writing CSP. Another thing that this crate does not do: It does not do any
13//! base64 or percent encoding or anything like that.
14//!
15//! # Example usage
16//! ```rust
17//! use csp::{CSP, Directive, Sources, Source};
18//!
19//! let csp = CSP::new()
20//! .push(Directive::ImgSrc(
21//! Sources::new_with(Source::Self_)
22//! .push(Source::Host("https://*.example.org"))
23//! .push(Source::Host("https://shields.io")),
24//! ))
25//! .push(Directive::ConnectSrc(
26//! Sources::new()
27//! .push(Source::Host("http://crates.io"))
28//! .push(Source::Scheme("https"))
29//! .push(Source::Self_),
30//! ))
31//! .push(Directive::StyleSrc(
32//! Sources::new_with(Source::Self_).push(Source::UnsafeInline),
33//! ))
34//! .push(Directive::ObjectSrc(Sources::new()));
35//!
36//! let csp_header = "Content-Security-Policy: ".to_owned() + &csp.to_string();
37//! ```
38//! # Copyright notice for this crate's docs:
39//! Most of the comments for various CSP things are from [MDN](https://developer.mozilla.org/en-US/docs/MDN/About), so they licensed under [CC-BY-SA 2.5](https://creativecommons.org/licenses/by-sa/2.5/)
40//! So attribution of most of the docs goes to [Mozilla Contributors](https://developer.mozilla.org/en-US/docs/MDN/Writing_guidelines/contributors.txt).
41//!
42//! Please go to MDN to read up to date docs, as these ones might not be up to
43//! date.
44
45#![deny(clippy::all)]
46#![deny(unsafe_code)]
47#![deny(clippy::cargo)]
48#![warn(missing_docs)]
49#![deny(rustdoc::invalid_html_tags)]
50#![warn(clippy::pedantic)]
51#![warn(clippy::nursery)]
52
53use std::fmt;
54
55#[derive(Debug, Default, Clone)]
56/// The starting point for building a Content Security Policy.
57///
58/// You'll add [`Directive`] into this struct, and later on call `.to_string()`
59/// on it to get it as a header compatible string. Doesn't include
60/// content-security-policy: part in it though.
61///
62/// [`Directive`]: Directive
63pub struct CSP<'a>(Vec<Directive<'a>>);
64
65#[derive(Debug, Default, Clone)]
66/// A struct to give source(s) to a [`Directive`] which might require it.
67///
68/// # Example usage
69/// ```rust
70/// use csp::{Sources, Source};
71///
72/// let sources = Sources::new().push(Source::Self_).push(Source::Scheme("data"));
73///
74/// assert_eq!(sources.to_string(), "'self' data:");
75/// ```
76///
77/// [`Directive`]: Directive
78pub struct Sources<'a>(Vec<Source<'a>>);
79
80#[derive(Debug, Default, Clone)]
81/// Used for `PluginTypes` [`Directive`].
82///
83/// # Example usage
84/// ```rust
85/// let flash = csp::Plugins::new().push(("application", "x-shockwave-flash"));
86/// ```
87/// to get `application/x-shockwave-flash`
88///
89/// [`Directive`]: Directive
90pub struct Plugins<'a>(Vec<(&'a str, &'a str)>);
91
92#[derive(Debug, Default, Clone)]
93/// Used for `ReportUri` [`Directive`].
94///
95/// # Example usage
96/// ```rust
97/// let report_uris = csp::ReportUris::new().push("https://example.org/report");
98/// ```
99///
100/// [`Directive`]: Directive
101pub struct ReportUris<'a>(Vec<&'a str>);
102
103#[derive(Debug, Default, Clone)]
104/// Used for `Sandbox` [`Directive`].
105///
106/// [`Directive`]: Directive
107pub struct SandboxAllowedList(Vec<SandboxAllow>);
108
109#[derive(Debug, Clone)]
110/// Used for `RequireSriFor` [`Directive`].
111///
112/// [`Directive`]: Directive
113pub enum SriFor {
114 /// Requires SRI for scripts.
115 Script,
116 /// Requires SRI for style sheets.
117 Style,
118 /// Requires SRI for both, scripts and style sheets.
119 ScriptStyle,
120}
121
122#[derive(Debug, Clone)]
123/// The source that a bunch of directives can have multiple of.
124///
125/// If nothing gets added, becomes 'none'.
126pub enum Source<'a> {
127 /// Internet hosts by name or IP address, as well as an optional URL scheme
128 /// and/or port number.
129 ///
130 /// The site's address may include an optional leading wildcard (the
131 /// asterisk character, '*'), and you may use a wildcard (again, '*') as the
132 /// port number, indicating that all legal ports are valid for the source.
133 /// Examples:
134 /// - `http://*.example.com`: Matches all attempts to load from any subdomain
135 /// of example.com using the `http:` URL scheme.
136 /// - `mail.example.com:443`: Matches all attempts to access port 443 on
137 /// mail.example.com.
138 /// - `https://store.example.com`: Matches all attempts to access
139 /// store.example.com using https:.
140 Host(&'a str),
141 /// A schema such as 'http' or 'https'.
142 ///
143 /// The colon is automatically added to the end. You can also specify data
144 /// schemas (not recommended).
145 /// - `data` Allows data: URIs to be used as a content source. This is
146 /// insecure; an attacker can also inject arbitrary data: URIs. Use this
147 /// sparingly and definitely not for scripts.
148 /// - `mediastream` Allows `mediastream:` URIs to be used as a content source.
149 /// - `blob` Allows `blob:` URIs to be used as a content source.
150 /// - `filesystem` Allows `filesystem:` URIs to be used as a content source.
151 Scheme(&'a str),
152 /// Refers to the origin from which the protected document is being served,
153 /// including the same URL scheme and port number.
154 ///
155 /// Some browsers specifically exclude `blob` and `filesystem` from source
156 /// directives. Sites needing to allow these content types can specify them
157 /// using the Data attribute.
158 Self_,
159 /// Allows the use of `eval()` and similar methods for creating code from
160 /// strings.
161 UnsafeEval,
162 /// Allows the compilation and instantiation of WebAssembly.
163 WasmUnsafeEval,
164 /// Allows to enable specific inline event handlers. If you only need to
165 /// allow inline event handlers and not inline `<script>` elements or
166 /// `javascript:` URLs, this is a safer method compared to using the
167 /// `unsafe-inline` expression.
168 UnsafeHashes,
169 /// Allows the use of inline resources, such as inline `<script>` elements,
170 /// javascript: URLs, inline event handlers, and inline <\style> elements.
171 UnsafeInline,
172 /// A whitelist for specific inline scripts using a cryptographic nonce
173 /// (number used once). The server must generate a unique nonce value each
174 /// time it transmits a policy. It is critical to provide an unguessable
175 /// nonce, as bypassing a resource’s policy is otherwise trivial. See unsafe
176 /// inline script for an example. Specifying nonce makes a modern browser
177 /// ignore `'unsafe-inline'` which could still be set for older browsers
178 /// without nonce support.
179 Nonce(&'a str),
180 /// A sha256, sha384 or sha512 hash of scripts or styles. The use of this
181 /// source consists of two portions separated by a dash: the encryption
182 /// algorithm used to create the hash and the base64-encoded hash of the
183 /// script or style. When generating the hash, don't include the `<script>`
184 /// or `<style>` tags and note that capitalization and whitespace matter,
185 /// including leading or trailing whitespace. See unsafe inline script for
186 /// an example. In CSP 2.0 this applied only to inline scripts. CSP 3.0
187 /// allows it in the case of `script-src` for external scripts.
188 Hash((&'a str, &'a str)),
189 /// The `strict-dynamic` source expression specifies that the trust
190 /// explicitly given to a script present in the markup, by accompanying it
191 /// with a nonce or a hash, shall be propagated to all the scripts loaded by
192 /// that root script. At the same time, any whitelist or source expressions
193 /// such as `'self'` or `'unsafe-inline'` will be ignored. See script-src
194 /// for an example.
195 StrictDynamic,
196 /// Requires a sample of the violating code to be included in the violation
197 /// report.
198 ReportSample,
199}
200
201#[derive(Debug, Clone)]
202/// Optionally used for the `Sandbox` directive. Not using it but using the
203/// sandbox directive disallows everything that you could allow with the
204/// optional values.
205pub enum SandboxAllow {
206 /// Allows for downloads to occur without a gesture from the user.
207 DownloadsWithoutUserActivation,
208 /// Allows the embedded browsing context to submit forms. If this keyword is
209 /// not used, this operation is not allowed.
210 Forms,
211 /// Allows the embedded browsing context to open modal windows.
212 Modals,
213 /// Allows the embedded browsing context to disable the ability to lock the
214 /// screen orientation.
215 OrientationLock,
216 /// Allows the embedded browsing context to use the Pointer Lock API.
217 PointerLock,
218 /// Allows popups (like from window.open, target="_blank", showModalDialog).
219 /// If this keyword is not used, that functionality will silently fail.
220 Popups,
221 /// Allows a sandboxed document to open new windows without forcing the
222 /// sandboxing flags upon them. This will allow, for example, a third-party
223 /// advertisement to be safely sandboxed without forcing the same
224 /// restrictions upon a landing page.
225 PopupsToEscapeSandbox,
226 /// Allows embedders to have control over whether an iframe can start a
227 /// presentation session.
228 Presentation,
229 /// Allows the content to be treated as being from its normal origin. If
230 /// this keyword is not used, the embedded content is treated as being from
231 /// a unique origin.
232 SameOrigin,
233 /// Allows the embedded browsing context to run scripts (but not create
234 /// pop-up windows). If this keyword is not used, this operation is not
235 /// allowed.
236 Scripts,
237 /// Lets the resource request access to the parent's storage capabilities
238 /// with the Storage Access API.
239 StorageAccessByUserActivation,
240 /// Allows the embedded browsing context to navigate (load) content to the
241 /// top-level browsing context. If this keyword is not used, this operation
242 /// is not allowed.
243 TopNavigation,
244 /// Lets the resource navigate the top-level browsing context, but only if
245 /// initiated by a user gesture.
246 TopNavigationByUserActivation,
247}
248
249#[derive(Debug, Clone)]
250/// A CSP directive.
251pub enum Directive<'a> {
252 /// Restricts the URLs which can be used in a document's `<base>` element.
253 ///
254 /// If this value is absent, then any URI is allowed. If this directive is
255 /// absent, the user agent will use the value in the `<base>` element.
256 BaseUri(Sources<'a>),
257 /// Prevents loading any assets using HTTP when the page is loaded using
258 /// HTTPS.
259 ///
260 ///All mixed content resource requests are blocked, including both active
261 /// and passive mixed content. This also applies to `<iframe>` documents,
262 /// ensuring the entire page is mixed content free.
263 /// The upgrade-insecure-requests directive is evaluated before
264 /// block-all-mixed-content and If the former is set, the latter is
265 /// effectively a no-op. It is recommended to set one directive or the other
266 /// – not both, unless you want to force HTTPS on older browsers that do not
267 /// force it after a redirect to HTTP.
268 BlockAllMixedContent,
269 /// Defines the valid sources for web workers and nested browsing contexts
270 /// loaded using elements such as `<frame>` and `<iframe>`.
271 ///
272 /// For workers, non-compliant requests are treated as fatal network errors
273 /// by the user agent.
274 ChildSrc(Sources<'a>),
275 /// restricts the URLs which can be loaded using script interfaces. The APIs
276 /// that are restricted are:
277 ///
278 /// - `<a>` ping,
279 /// - Fetch,
280 /// - XMLHttpRequest,
281 /// - WebSocket,
282 /// - EventSource,
283 /// - Navigator.sendBeacon().
284 ///
285 /// Note: connect-src 'self' does not resolve to websocket schemas in all browsers, more info: <https://github.com/w3c/webappsec-csp/issues/7>
286 ConnectSrc(Sources<'a>),
287 /// Serves as a fallback for the other CSP fetch directives.
288 ///
289 /// For each of the following directives that are absent, the user agent
290 /// will look for the default-src directive and will use this value for it:
291 /// - child-src
292 /// - connect-src
293 /// - font-src
294 /// - frame-src
295 /// - img-src
296 /// - manifest-src
297 /// - media-src
298 /// - object-src
299 /// - prefetch-src
300 /// - script-src
301 /// - script-src-elem
302 /// - script-src-attr
303 /// - style-src
304 /// - style-src-elem
305 /// - style-src-attr
306 /// - worker-src
307 DefaultSrc(Sources<'a>),
308 /// Specifies valid sources for fonts loaded using @font-face.
309 FontSrc(Sources<'a>),
310 /// Restricts the URLs which can be used as the target of a form submissions
311 /// from a given context.
312 ///
313 /// Whether form-action should block redirects after a form submission is
314 /// debated and browser implementations of this aspect are inconsistent
315 /// (e.g. Firefox 57 doesn't block the redirects whereas Chrome 63 does).
316 FormAction(Sources<'a>),
317 /// specifies valid parents that may embed a page using `<frame>`, `<iframe>`,
318 /// `<object>`, `<embed>`, or `<applet>`.
319 ///
320 /// Setting this directive to 'none' is similar to X-Frame-Options: deny
321 /// (which is also supported in older browsers).
322 FrameAncestors(Sources<'a>),
323 /// Specifies valid sources for nested browsing contexts loading using
324 /// elements such as `<frame>` and `<iframe>`.
325 FrameSrc(Sources<'a>),
326 /// Specifies valid sources of images and favicons.
327 ImgSrc(Sources<'a>),
328 /// Specifies which manifest can be applied to the resource.
329 ManifestSrc(Sources<'a>),
330 /// Specifies valid sources for loading media using the `<audio>` and
331 /// `<video>` elements.
332 MediaSrc(Sources<'a>),
333 /// restricts the URLs to which a document can initiate navigations by any
334 /// means including `<form>` (if form-action is not specified), `<a>`,
335 /// window.location, window.open, etc.
336 ///
337 /// This is an enforcement on what navigations this document initiates not
338 /// on what this document is allowed to navigate to.
339 ///
340 /// Note: If the form-action directive is present, the navigate-to directive
341 /// will not act on navigations that are form submissions.
342 NavigateTo(Sources<'a>),
343 /// specifies valid sources for the `<object>`, `<embed>`, and `<applet>`
344 /// elements.
345 ///
346 /// To set allowed types for `<object>`, `<embed>`, and `<applet>` elements,
347 /// use the PluginTypes.
348 ///
349 /// Elements controlled by object-src are perhaps coincidentally considered
350 /// legacy HTML elements and aren't receiving new standardized features
351 /// (such as the security attributes sandbox or allow for `<iframe>`).
352 /// Therefore it is recommended to restrict this fetch-directive (e.g.
353 /// explicitly set object-src 'none' if possible).
354 ObjectSrc(Sources<'a>),
355 /// Restricts the set of plugins that can be embedded into a document by
356 /// limiting the types of resources which can be loaded.
357 ///
358 /// Instantiation of an `<embed>`, `<object>` or `<applet>` element will fail
359 /// if:
360 /// - the element to load does not declare a valid MIME type,
361 /// - the declared type does not match one of specified types in the
362 /// plugin-types directive,
363 /// - the fetched resource does not match the declared type.
364 PluginTypes(Plugins<'a>),
365 /// Specifies valid resources that may be prefetched or prerendered.
366 PrefetchSrc(Sources<'a>),
367 /// Instructs the user agent to store reporting endpoints for an origin.
368 ///
369 /// ```text
370 /// Content-Security-Policy: ...; report-to groupname
371 /// ```
372 ///
373 /// The directive has no effect in and of itself, but only gains meaning in
374 /// combination with other directives.
375 ReportTo(&'a str),
376 /// Deprecated.
377 ///
378 /// Instructs the user agent to report attempts to violate the Content
379 /// Security Policy. These violation reports consist of JSON documents sent
380 /// via an HTTP POST request to the specified URI.
381 ///
382 /// This feature is no longer recommended. Though some browsers might still
383 /// support it, it may have already been removed from the relevant web
384 /// standards, may be in the process of being dropped, or may only be kept
385 /// for compatibility purposes. Avoid using it, and update existing code if
386 /// possible.
387 ///
388 /// Though the report-to directive is intended to replace the deprecated
389 /// report-uri directive, report-to isn’t supported in most browsers yet. So
390 /// for compatibility with current browsers while also adding forward
391 /// compatibility when browsers get report-to support, you can specify both
392 /// report-uri and report-to:
393 ///
394 /// > `Content-Security-Policy: ...; report-uri <https://endpoint.com>;
395 /// > report-to groupname`
396 ///
397 /// In browsers that support report-to, the report-uri directive will be
398 /// ignored.
399 ReportUri(ReportUris<'a>),
400 /// Instructs the client to require the use of Subresource Integrity for
401 /// scripts or styles on the page.
402 RequireSriFor(SriFor),
403 /// Enables a sandbox for the requested resource similar to the `<iframe>`
404 /// sandbox attribute.
405 ///
406 /// It applies restrictions to a page's actions including preventing popups,
407 /// preventing the execution of plugins and scripts, and enforcing a
408 /// same-origin policy.
409 ///
410 /// You can leave the SandboxAllowedList empty
411 /// (`SandboxAllowedList::new_empty()`) to disallow everything.
412 Sandbox(SandboxAllowedList),
413 /// Specifies valid sources for JavaScript.
414 ///
415 /// This includes not only URLs loaded directly into `<script>` elements, but
416 /// also things like inline script event handlers (onclick) and XSLT
417 /// stylesheets which can trigger script execution.
418 ScriptSrc(Sources<'a>),
419 /// Specifies valid sources for JavaScript.
420 ///
421 /// This includes not only URLs loaded directly into `<script>` elements, but
422 /// also things like inline script event handlers (onclick) and XSLT
423 /// stylesheets which can trigger script execution.
424 ScriptSrcAttr(Sources<'a>),
425 /// Specifies valid sources for JavaScript `<script>` elements, but not
426 /// inline script event handlers like onclick.
427 ScriptSrcElem(Sources<'a>),
428 /// specifies valid sources for stylesheets.
429 StyleSrc(Sources<'a>),
430 /// Specifies valid sources for inline styles applied to individual DOM
431 /// elements.
432 StyleSrcAttr(Sources<'a>),
433 /// Specifies valid sources for stylesheets `<style>` elements and `<link>`
434 /// elements with rel="stylesheet".
435 StyleSrcElem(Sources<'a>),
436 /// Instructs user agents to restrict usage of known DOM XSS sinks to a
437 /// predefined set of functions that only accept non-spoofable, typed values
438 /// in place of strings.
439 ///
440 /// This allows authors to define rules guarding writing values to the DOM
441 /// and thus reducing the DOM XSS attack surface to small, isolated parts of
442 /// the web application codebase, facilitating their monitoring and code
443 /// review. This directive declares a white-list of trusted type policy
444 /// names created with TrustedTypes.createPolicy from Trusted Types API.
445 TrustedTypes(Vec<&'a str>),
446 /// Instructs user agents to treat all of a site's insecure URLs (those
447 /// served over HTTP) as though they have been replaced with secure URLs
448 /// (those served over HTTPS).
449 ///
450 /// This directive is intended for web sites with large numbers of insecure
451 /// legacy URLs that need to be rewritten.
452 ///
453 /// The upgrade-insecure-requests directive is evaluated before
454 /// block-all-mixed-content and if it is set, the latter is effectively a
455 /// no-op. It is recommended to set either directive, but not both, unless
456 /// you want to force HTTPS on older browsers that do not force it after a
457 /// redirect to HTTP. The upgrade-insecure-requests directive will not
458 /// ensure that users visiting your site via links on third-party sites will
459 /// be upgraded to HTTPS for the top-level navigation and thus does not
460 /// replace the Strict-Transport-Security (HSTS) header, which should still
461 /// be set with an appropriate max-age to ensure that users are not subject
462 /// to SSL stripping attacks.
463 UpgradeInsecureRequests,
464 /// Instructs user agents to restrict usage of known DOM XSS sinks to a
465 /// predefined set of functions that only accept non-spoofable, typed values
466 /// in place of strings.
467 ///
468 /// This allows authors to define rules guarding writing values to the DOM
469 /// and thus reducing the DOM XSS attack surface to small, isolated parts of
470 /// the web application codebase, facilitating their monitoring and code
471 /// review. This directive declares a white-list of trusted type policy
472 /// names created with TrustedTypes.createPolicy from Trusted Types API.
473 WorkerSrc(Sources<'a>),
474}
475
476impl<'a> CSP<'a> {
477 #[must_use]
478 /// Creates a new empty CSP
479 pub fn new() -> Self {
480 Self::default()
481 }
482
483 #[must_use]
484 /// Creates a new CSP with a given directive
485 pub fn new_with(directive: Directive<'a>) -> Self {
486 Self(vec![directive])
487 }
488
489 #[deprecated(since = "1.0.0", note = "please use `push_borrowed` instead")]
490 #[allow(missing_docs)]
491 pub fn add_borrowed<'b>(&'b mut self, directive: Directive<'a>) -> &'b mut Self {
492 self.push_borrowed(directive);
493 self
494 }
495
496 /// Pushes a directive to the end of the borrowed CSP
497 pub fn push_borrowed<'b>(&'b mut self, directive: Directive<'a>) -> &'b mut Self {
498 self.0.push(directive);
499 self
500 }
501
502 #[allow(clippy::should_implement_trait)]
503 #[deprecated(since = "1.0.0", note = "please use `push` instead")]
504 #[must_use]
505 #[allow(missing_docs)]
506 pub fn add(self, directive: Directive<'a>) -> Self {
507 self.push(directive)
508 }
509
510 #[must_use]
511 /// Pushes a directive to the end of the CSP
512 pub fn push(mut self, directive: Directive<'a>) -> Self {
513 self.0.push(directive);
514 self
515 }
516}
517
518impl<'a> Sources<'a> {
519 #[must_use]
520 /// Creates a new empty Sources
521 pub const fn new() -> Self {
522 Self(vec![])
523 }
524
525 #[must_use]
526 /// Creates new Sources with a source
527 pub fn new_with(source: Source<'a>) -> Self {
528 Self(vec![source])
529 }
530
531 #[deprecated(since = "1.0.0", note = "please use `push_borrowed` instead")]
532 #[allow(missing_docs)]
533 pub fn add_borrowed<'b>(&'b mut self, source: Source<'a>) -> &'b mut Self {
534 self.push_borrowed(source);
535 self
536 }
537
538 /// Pushes a source to the end of the borrowed Sources
539 pub fn push_borrowed<'b>(&'b mut self, source: Source<'a>) -> &'b mut Self {
540 self.0.push(source);
541 self
542 }
543
544 #[allow(clippy::should_implement_trait)]
545 #[deprecated(since = "1.0.0", note = "please use `push` instead")]
546 #[must_use]
547 #[allow(missing_docs)]
548 pub fn add(self, source: Source<'a>) -> Self {
549 self.push(source)
550 }
551
552 #[must_use]
553 /// Pushes a source to the end of the Sources
554 pub fn push(mut self, source: Source<'a>) -> Self {
555 self.0.push(source);
556 self
557 }
558}
559
560impl<'a> Plugins<'a> {
561 #[must_use]
562 /// Creates a new Plugins with a plugin
563 pub fn new_with(plugin: (&'a str, &'a str)) -> Self {
564 Self(vec![plugin])
565 }
566
567 #[must_use]
568 /// Creates a new empty plugins
569 pub const fn new() -> Self {
570 Self(vec![])
571 }
572
573 #[deprecated(since = "1.0.0", note = "please use `push_borrowed` instead")]
574 #[allow(missing_docs)]
575 pub fn add_borrowed<'b>(&'b mut self, plugin: (&'a str, &'a str)) -> &'b mut Self {
576 self.push_borrowed(plugin);
577 self
578 }
579
580 /// Pushes a plugin to the end of the borrowed Plugins
581 pub fn push_borrowed<'b>(&'b mut self, plugin: (&'a str, &'a str)) -> &'b mut Self {
582 self.0.push(plugin);
583 self
584 }
585
586 #[allow(clippy::should_implement_trait)]
587 #[deprecated(since = "1.0.0", note = "please use `push` instead")]
588 #[must_use]
589 #[allow(missing_docs)]
590 pub fn add(self, plugin: (&'a str, &'a str)) -> Self {
591 self.push(plugin)
592 }
593
594 #[must_use]
595 /// Pushes a plugin to the end of the Plugins
596 pub fn push(mut self, plugin: (&'a str, &'a str)) -> Self {
597 self.0.push(plugin);
598 self
599 }
600}
601
602impl SandboxAllowedList {
603 #[must_use]
604 /// Creates a new `SandboxAllowedList` with only a certain sandbox allowance
605 pub fn new_with(sandbox_allow: SandboxAllow) -> Self {
606 Self(vec![sandbox_allow])
607 }
608
609 #[must_use]
610 /// Creates a new empty `SandboxAllowedList`
611 pub const fn new() -> Self {
612 Self(vec![])
613 }
614
615 #[deprecated(since = "1.0.0", note = "please use `push_borrowed` instead")]
616 #[allow(missing_docs)]
617 pub fn add_borrowed(&'_ mut self, sandbox_allow: SandboxAllow) -> &'_ mut Self {
618 self.push_borrowed(sandbox_allow);
619 self
620 }
621
622 /// Pushes a sandbox allow type to the end of the borrowed
623 /// `SandboxAllowedList`
624 pub fn push_borrowed(&'_ mut self, sandbox_allow: SandboxAllow) -> &'_ mut Self {
625 self.0.push(sandbox_allow);
626 self
627 }
628
629 #[allow(clippy::should_implement_trait)]
630 #[deprecated(since = "1.0.0", note = "please use `push` instead")]
631 #[must_use]
632 #[allow(missing_docs)]
633 pub fn add(self, sandbox_allow: SandboxAllow) -> Self {
634 self.push(sandbox_allow)
635 }
636
637 #[must_use]
638 /// Pushes a sandbox allow type to the end of the `SandboxAllowedList`
639 pub fn push(mut self, sandbox_allow: SandboxAllow) -> Self {
640 self.0.push(sandbox_allow);
641 self
642 }
643}
644
645impl<'a> ReportUris<'a> {
646 #[must_use]
647 /// Creates a new `ReportUris` with a certain uri
648 pub fn new_with(report_uri: &'a str) -> Self {
649 ReportUris(vec![report_uri])
650 }
651
652 #[must_use]
653 /// Creates a new empty `ReportUris`
654 pub const fn new() -> Self {
655 ReportUris(vec![])
656 }
657
658 #[deprecated(since = "1.0.0", note = "please use `push_borrowed` instead")]
659 #[allow(missing_docs)]
660 pub fn add_borrowed<'b>(&'b mut self, report_uri: &'a str) -> &'b mut Self {
661 self.push_borrowed(report_uri);
662 self
663 }
664
665 /// Pushes a report uri to the end of the borrowed `ReportUris`
666 pub fn push_borrowed<'b>(&'b mut self, report_uri: &'a str) -> &'b mut Self {
667 self.0.push(report_uri);
668 self
669 }
670
671 #[allow(clippy::should_implement_trait)]
672 #[deprecated(since = "1.0.0", note = "please use `push` instead")]
673 #[must_use]
674 #[allow(missing_docs)]
675 pub fn add(self, report_uri: &'a str) -> Self {
676 self.push(report_uri)
677 }
678
679 #[must_use]
680 /// Pushes a report uri to the end of the `ReportUris`
681 pub fn push(mut self, report_uri: &'a str) -> Self {
682 self.0.push(report_uri);
683 self
684 }
685}
686
687impl<'a> fmt::Display for Source<'a> {
688 fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
689 match self {
690 Self::Host(s) => write!(fmt, "{}", s),
691 Self::Scheme(s) => write!(fmt, "{}:", s),
692 Self::Self_ => write!(fmt, "'self'"),
693 Self::UnsafeEval => write!(fmt, "'unsafe-eval'"),
694 Self::WasmUnsafeEval => write!(fmt, "'wasm-unsafe-eval'"),
695 Self::UnsafeHashes => write!(fmt, "'unsafe-hashes'"),
696 Self::UnsafeInline => write!(fmt, "'unsafe-inline'"),
697 Self::Nonce(s) => write!(fmt, "'nonce-{}'", s),
698 Self::Hash((algo, hash)) => write!(fmt, "'{}-{}'", algo, hash),
699 Self::StrictDynamic => write!(fmt, "'strict-dynamic'"),
700 Self::ReportSample => write!(fmt, "'report-sample'"),
701 }
702 }
703}
704
705impl fmt::Display for SandboxAllow {
706 fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
707 match self {
708 Self::DownloadsWithoutUserActivation => {
709 write!(fmt, "allow-downloads-without-user-activation")
710 }
711 Self::Forms => write!(fmt, "allow-forms"),
712 Self::Modals => write!(fmt, "allow-modals"),
713 Self::OrientationLock => write!(fmt, "allow-orientation-lock"),
714 Self::PointerLock => write!(fmt, "allow-pointer-lock"),
715 Self::Popups => write!(fmt, "allow-popups"),
716 Self::PopupsToEscapeSandbox => write!(fmt, "allow-popups-to-escape-sandbox"),
717 Self::Presentation => write!(fmt, "allow-presentation"),
718 Self::SameOrigin => write!(fmt, "allow-same-origin"),
719 Self::Scripts => write!(fmt, "allow-scripts"),
720 Self::StorageAccessByUserActivation => {
721 write!(fmt, "allow-storage-access-by-user-activation")
722 }
723 Self::TopNavigation => write!(fmt, "allow-top-navigation"),
724 Self::TopNavigationByUserActivation => {
725 write!(fmt, "allow-top-navigation-by-user-activation")
726 }
727 }
728 }
729}
730
731impl fmt::Display for SriFor {
732 fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
733 match self {
734 Self::Script => write!(fmt, "script"),
735 Self::Style => write!(fmt, "style"),
736 Self::ScriptStyle => write!(fmt, "script style"),
737 }
738 }
739}
740
741impl<'a> fmt::Display for Directive<'a> {
742 fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
743 match self {
744 Self::BaseUri(s) => write!(fmt, "base-uri {}", s),
745 Self::BlockAllMixedContent => write!(fmt, "block-all-mixed-content"),
746 Self::ChildSrc(s) => write!(fmt, "child-src {}", s),
747 Self::ConnectSrc(s) => write!(fmt, "connect-src {}", s),
748 Self::DefaultSrc(s) => write!(fmt, "default-src {}", s),
749 Self::FontSrc(s) => write!(fmt, "font-src {}", s),
750 Self::FormAction(s) => write!(fmt, "form-action {}", s),
751 Self::FrameAncestors(s) => write!(fmt, "frame-ancestors {}", s),
752 Self::FrameSrc(s) => write!(fmt, "frame-src {}", s),
753 Self::ImgSrc(s) => write!(fmt, "img-src {}", s),
754 Self::ManifestSrc(s) => write!(fmt, "manifest-src {}", s),
755 Self::MediaSrc(s) => write!(fmt, "media-src {}", s),
756 Self::NavigateTo(s) => write!(fmt, "navigate-to {}", s),
757 Self::ObjectSrc(s) => write!(fmt, "object-src {}", s),
758 Self::PluginTypes(s) => write!(fmt, "plugin-types {}", s),
759 Self::PrefetchSrc(s) => write!(fmt, "prefetch-src {}", s),
760 Self::ReportTo(s) => write!(fmt, "report-to {}", s),
761 Self::ReportUri(uris) => {
762 write!(fmt, "report-uri ")?;
763
764 for uri in &uris.0[0..uris.0.len() - 1] {
765 write!(fmt, "{} ", uri)?;
766 }
767
768 let last = uris.0[uris.0.len() - 1];
769 write!(fmt, "{}", last)
770 }
771 Self::RequireSriFor(s) => write!(fmt, "require-sri-for {}", s),
772 Self::Sandbox(s) => {
773 if s.0.is_empty() {
774 write!(fmt, "sandbox")
775 } else {
776 write!(fmt, "sandbox {}", s)
777 }
778 }
779 Self::ScriptSrc(s) => write!(fmt, "script-src {}", s),
780 Self::ScriptSrcAttr(s) => write!(fmt, "script-src-attr {}", s),
781 Self::ScriptSrcElem(s) => write!(fmt, "script-src-elem {}", s),
782 Self::StyleSrc(s) => write!(fmt, "style-src {}", s),
783 Self::StyleSrcAttr(s) => write!(fmt, "style-src-attr {}", s),
784 Self::StyleSrcElem(s) => write!(fmt, "style-src-elem {}", s),
785 Self::TrustedTypes(trusted_types) => {
786 write!(fmt, "trusted-types ")?;
787
788 for trusted_type in &trusted_types[0..trusted_types.len() - 1] {
789 write!(fmt, "{} ", trusted_type)?;
790 }
791
792 let last = trusted_types[trusted_types.len() - 1];
793 write!(fmt, "{}", last)
794 }
795 Self::UpgradeInsecureRequests => write!(fmt, "upgrade-insecure-requests"),
796 Self::WorkerSrc(s) => write!(fmt, "worker-src {}", s),
797 }
798 }
799}
800
801impl<'a> fmt::Display for Plugins<'a> {
802 fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
803 if self.0.is_empty() {
804 return write!(fmt, "");
805 }
806
807 for plugin in &self.0[0..self.0.len() - 1] {
808 write!(fmt, "{}/{} ", plugin.0, plugin.1)?;
809 }
810
811 let last = &self.0[self.0.len() - 1];
812 write!(fmt, "{}/{}", last.0, last.1)
813 }
814}
815
816impl<'a> fmt::Display for Sources<'a> {
817 fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
818 if self.0.is_empty() {
819 return write!(fmt, "'none'");
820 }
821
822 for source in &self.0[0..self.0.len() - 1] {
823 write!(fmt, "{} ", source)?;
824 }
825
826 let last = &self.0[self.0.len() - 1];
827 write!(fmt, "{}", last)
828 }
829}
830
831impl fmt::Display for SandboxAllowedList {
832 fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
833 if self.0.is_empty() {
834 return write!(fmt, "");
835 }
836
837 for directive in &self.0[0..self.0.len() - 1] {
838 write!(fmt, "{} ", directive)?;
839 }
840
841 let last = &self.0[self.0.len() - 1];
842 write!(fmt, "{}", last)
843 }
844}
845
846impl<'a> fmt::Display for CSP<'a> {
847 fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
848 if self.0.is_empty() {
849 return write!(fmt, "");
850 }
851
852 for directive in &self.0[0..self.0.len() - 1] {
853 write!(fmt, "{}; ", directive)?;
854 }
855
856 let last = &self.0[self.0.len() - 1];
857 write!(fmt, "{}", last)
858 }
859}
860
861#[cfg(test)]
862mod tests {
863 use super::*;
864
865 #[test]
866 /// Tests combining different Directives and sources, and makes sure that
867 /// spaces and semicolons are inserted correctly.
868 fn large_csp() {
869 let font_src = Source::Host("https://cdn.example.org");
870
871 let mut csp = CSP::new()
872 .push(Directive::ImgSrc(
873 Sources::new_with(Source::Self_)
874 .push(Source::Scheme("https"))
875 .push(Source::Host("http://shields.io")),
876 ))
877 .push(Directive::ConnectSrc(
878 Sources::new().push(Source::Host("https://crates.io")).push(Source::Self_),
879 ))
880 .push(Directive::StyleSrc(
881 Sources::new_with(Source::Self_)
882 .push(Source::UnsafeInline)
883 .push(font_src.clone()),
884 ));
885
886 csp.push_borrowed(Directive::FontSrc(Sources::new_with(font_src)));
887
888 println!("{}", csp);
889
890 let csp = csp.to_string();
891
892 assert_eq!(
893 csp,
894 "img-src 'self' https: http://shields.io; connect-src https://crates.io 'self'; style-src 'self' 'unsafe-inline' https://cdn.example.org; font-src https://cdn.example.org"
895 );
896 }
897
898 #[test]
899 /// Tests all the possible source variations.
900 fn all_sources() {
901 let csp = CSP::new().push(Directive::ScriptSrc(
902 Sources::new()
903 .push(Source::Hash(("sha256", "1234a")))
904 .push(Source::Nonce("5678b"))
905 .push(Source::ReportSample)
906 .push(Source::StrictDynamic)
907 .push(Source::UnsafeEval)
908 .push(Source::WasmUnsafeEval)
909 .push(Source::UnsafeHashes)
910 .push(Source::UnsafeInline)
911 .push(Source::Scheme("data"))
912 .push(Source::Host("https://example.org"))
913 .push(Source::Self_),
914 ));
915
916 assert_eq!(
917 csp.to_string(),
918 "script-src 'sha256-1234a' 'nonce-5678b' 'report-sample' 'strict-dynamic' 'unsafe-eval' 'wasm-unsafe-eval' 'unsafe-hashes' 'unsafe-inline' data: https://example.org 'self'"
919 );
920 }
921
922 #[test]
923 fn empty_values() {
924 let csp = CSP::new();
925
926 assert_eq!(csp.to_string(), "");
927
928 let csp = CSP::new().push(Directive::ImgSrc(Sources::new()));
929
930 assert_eq!(csp.to_string(), "img-src 'none'");
931 }
932
933 #[test]
934 fn sandbox() {
935 let csp = CSP::new().push(Directive::Sandbox(SandboxAllowedList::new()));
936
937 assert_eq!(csp.to_string(), "sandbox");
938
939 let csp = CSP::new()
940 .push(Directive::Sandbox(SandboxAllowedList::new().push(SandboxAllow::Scripts)));
941
942 assert_eq!(csp.to_string(), "sandbox allow-scripts");
943 assert_eq!(
944 csp.to_string(),
945 "sandbox ".to_owned() + &SandboxAllow::Scripts.to_string()
946 );
947 }
948
949 #[test]
950 fn special() {
951 let mut csp = CSP::new();
952 let sri_directive = Directive::RequireSriFor(SriFor::Script);
953
954 csp.push_borrowed(sri_directive);
955
956 assert_eq!(csp.to_string(), "require-sri-for script");
957
958 let csp = CSP::new_with(Directive::BlockAllMixedContent);
959 assert_eq!(csp.to_string(), "block-all-mixed-content");
960
961 let csp = CSP::new_with(Directive::PluginTypes(
962 Plugins::new().push(("application", "x-java-applet")),
963 ));
964 assert_eq!(csp.to_string(), "plugin-types application/x-java-applet");
965
966 let csp = CSP::new_with(Directive::ReportTo("endpoint-1"));
967 assert_eq!(csp.to_string(), "report-to endpoint-1");
968
969 let csp = CSP::new_with(Directive::ReportUri(
970 ReportUris::new_with("https://r1.example.org").push("https://r2.example.org"),
971 ));
972 assert_eq!(
973 csp.to_string(),
974 "report-uri https://r1.example.org https://r2.example.org"
975 );
976
977 let csp = CSP::new_with(Directive::TrustedTypes(vec!["hello", "hello2"]));
978 assert_eq!(csp.to_string(), "trusted-types hello hello2");
979
980 let csp = CSP::new_with(Directive::UpgradeInsecureRequests);
981 assert_eq!(csp.to_string(), "upgrade-insecure-requests");
982 }
983}