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}