rocket_community/shield/policy.rs
1//! Module containing the [`Policy`] trait and types that implement it.
2
3use std::borrow::Cow;
4use std::fmt;
5
6use indexmap::IndexMap;
7use time::Duration;
8
9use crate::http::{uncased::Uncased, uri::Absolute, Header};
10
11/// Trait implemented by security and privacy policy headers.
12///
13/// Types that implement this trait can be [`enable()`]d and [`disable()`]d on
14/// instances of [`Shield`].
15///
16/// [`Shield`]: crate::shield::Shield
17/// [`enable()`]: crate::shield::Shield::enable()
18/// [`disable()`]: crate::shield::Shield::disable()
19pub trait Policy: Default + Send + Sync + 'static {
20 /// The actual name of the HTTP header.
21 ///
22 /// This name must uniquely identify the header as it is used to determine
23 /// whether two implementations of `Policy` are for the same header. Use the
24 /// real HTTP header's name.
25 ///
26 /// # Example
27 ///
28 /// ```rust
29 /// # extern crate rocket_community as rocket;
30 /// # use rocket::http::Header;
31 /// use rocket::shield::Policy;
32 ///
33 /// #[derive(Default)]
34 /// struct MyPolicy;
35 ///
36 /// impl Policy for MyPolicy {
37 /// const NAME: &'static str = "X-My-Policy";
38 /// # fn header(&self) -> Header<'static> { unimplemented!() }
39 /// }
40 /// ```
41 const NAME: &'static str;
42
43 /// Returns the [`Header`](../../rocket/http/struct.Header.html) to attach
44 /// to all outgoing responses.
45 ///
46 /// # Example
47 ///
48 /// ```rust
49 /// # extern crate rocket_community as rocket;
50 /// use rocket::http::Header;
51 /// use rocket::shield::Policy;
52 ///
53 /// #[derive(Default)]
54 /// struct MyPolicy;
55 ///
56 /// impl Policy for MyPolicy {
57 /// # const NAME: &'static str = "X-My-Policy";
58 /// fn header(&self) -> Header<'static> {
59 /// Header::new(Self::NAME, "value-to-enable")
60 /// }
61 /// }
62 /// ```
63 fn header(&self) -> Header<'static>;
64}
65
66macro_rules! impl_policy {
67 ($T:ty, $name:expr) => {
68 impl Policy for $T {
69 const NAME: &'static str = $name;
70
71 fn header(&self) -> Header<'static> {
72 self.into()
73 }
74 }
75 };
76}
77
78// Keep this in-sync with the top-level module docs.
79impl_policy!(XssFilter, "X-XSS-Protection");
80impl_policy!(NoSniff, "X-Content-Type-Options");
81impl_policy!(Frame, "X-Frame-Options");
82impl_policy!(Hsts, "Strict-Transport-Security");
83impl_policy!(ExpectCt, "Expect-CT");
84impl_policy!(Referrer, "Referrer-Policy");
85impl_policy!(Prefetch, "X-DNS-Prefetch-Control");
86impl_policy!(Permission, "Permissions-Policy");
87
88/// The [Referrer-Policy] header: controls the value set by the browser for the
89/// [Referer] header.
90///
91/// Tells the browser if it should send all or part of URL of the current page
92/// to the next site the user navigates to via the [Referer] header. This can be
93/// important for security as the URL itself might expose sensitive data, such
94/// as a hidden file path or personal identifier.
95///
96/// [Referrer-Policy]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referrer-Policy
97/// [Referer]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referer
98pub enum Referrer {
99 /// Omits the `Referer` header (returned by [`Referrer::default()`]).
100 NoReferrer,
101
102 /// Omits the `Referer` header on connection downgrade i.e. following HTTP
103 /// link from HTTPS site (_Browser default_).
104 NoReferrerWhenDowngrade,
105
106 /// Only send the origin of part of the URL, e.g. the origin of
107 /// `https://foo.com/bob.html` is `https://foo.com`.
108 Origin,
109
110 /// Send full URL for same-origin requests, only send origin part when
111 /// replying to [cross-origin] requests.
112 ///
113 /// [cross-origin]: https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS
114 OriginWhenCrossOrigin,
115
116 /// Send full URL for same-origin requests only.
117 SameOrigin,
118
119 /// Only send origin part of URL, only send if protocol security level
120 /// remains the same e.g. HTTPS to HTTPS.
121 StrictOrigin,
122
123 /// Send full URL for same-origin requests. For cross-origin requests, only
124 /// send origin part of URL if protocol security level remains the same e.g.
125 /// HTTPS to HTTPS.
126 StrictOriginWhenCrossOrigin,
127
128 /// Send full URL for same-origin or cross-origin requests. _This will leak
129 /// the full URL of TLS protected resources to insecure origins. Use with
130 /// caution._
131 UnsafeUrl,
132}
133
134/// Defaults to [`Referrer::NoReferrer`]. Tells the browser to omit the
135/// `Referer` header.
136impl Default for Referrer {
137 fn default() -> Referrer {
138 Referrer::NoReferrer
139 }
140}
141
142impl From<&Referrer> for Header<'static> {
143 fn from(referrer: &Referrer) -> Self {
144 let policy_string = match referrer {
145 Referrer::NoReferrer => "no-referrer",
146 Referrer::NoReferrerWhenDowngrade => "no-referrer-when-downgrade",
147 Referrer::Origin => "origin",
148 Referrer::OriginWhenCrossOrigin => "origin-when-cross-origin",
149 Referrer::SameOrigin => "same-origin",
150 Referrer::StrictOrigin => "strict-origin",
151 Referrer::StrictOriginWhenCrossOrigin => "strict-origin-when-cross-origin",
152 Referrer::UnsafeUrl => "unsafe-url",
153 };
154
155 Header::new(Referrer::NAME, policy_string)
156 }
157}
158
159/// The [Expect-CT] header: enables reporting and/or enforcement of [Certificate
160/// Transparency].
161///
162/// [Certificate Transparency] can detect and prevent the use of incorrectly
163/// issued malicious, or revoked TLS certificates. It solves a variety of
164/// problems with public TLS/SSL certificate management and is valuable measure
165/// for all public TLS applications.
166///
167/// If you're just [getting started] with certificate transparency, ensure that
168/// your [site is in compliance][getting started] before you enable enforcement
169/// with [`ExpectCt::Enforce`] or [`ExpectCt::ReportAndEnforce`]. Failure to do
170/// so will result in the browser refusing to communicate with your application.
171/// _You have been warned_.
172///
173/// [Expect-CT]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Expect-CT
174/// [Certificate Transparency]: http://www.certificate-transparency.org/what-is-ct
175/// [getting started]: http://www.certificate-transparency.org/getting-started
176pub enum ExpectCt {
177 /// Enforce certificate compliance for the next [`Duration`]. Ensure that
178 /// your certificates are in compliance before turning on enforcement.
179 /// (_Shield_ default).
180 Enforce(Duration),
181
182 /// Report to `Absolute`, but do not enforce, compliance violations for the
183 /// next [`Duration`]. Doesn't provide any protection but is a good way make
184 /// sure things are working correctly before turning on enforcement in
185 /// production.
186 Report(Duration, Absolute<'static>),
187
188 /// Enforce compliance and report violations to `Absolute` for the next
189 /// [`Duration`].
190 ReportAndEnforce(Duration, Absolute<'static>),
191}
192
193/// Defaults to [`ExpectCt::Enforce`] with a 30 day duration, enforce CT
194/// compliance, see [draft] standard for more.
195///
196/// [draft]: https://tools.ietf.org/html/draft-ietf-httpbis-expect-ct-03#page-15
197impl Default for ExpectCt {
198 fn default() -> ExpectCt {
199 ExpectCt::Enforce(Duration::days(30))
200 }
201}
202
203impl From<&ExpectCt> for Header<'static> {
204 fn from(expect: &ExpectCt) -> Self {
205 let policy_string = match expect {
206 ExpectCt::Enforce(age) => format!("max-age={}, enforce", age.whole_seconds()),
207 ExpectCt::Report(age, uri) => {
208 format!(r#"max-age={}, report-uri="{}""#, age.whole_seconds(), uri)
209 }
210 ExpectCt::ReportAndEnforce(age, uri) => {
211 format!(
212 "max-age={}, enforce, report-uri=\"{}\"",
213 age.whole_seconds(),
214 uri
215 )
216 }
217 };
218
219 Header::new(ExpectCt::NAME, policy_string)
220 }
221}
222
223/// The [X-Content-Type-Options] header: turns off [mime sniffing] which can
224/// prevent certain [attacks].
225///
226/// [mime sniffing]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types#MIME_sniffing
227/// [X-Content-Type-Options]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Content-Type-Options
228/// [attacks]: https://blog.mozilla.org/security/2016/08/26/mitigating-mime-confusion-attacks-in-firefox/
229pub enum NoSniff {
230 /// Turns off mime sniffing.
231 Enable,
232}
233
234/// Defaults to [`NoSniff::Enable`], turns off mime sniffing.
235impl Default for NoSniff {
236 fn default() -> NoSniff {
237 NoSniff::Enable
238 }
239}
240
241impl From<&NoSniff> for Header<'static> {
242 fn from(_: &NoSniff) -> Self {
243 Header::new(NoSniff::NAME, "nosniff")
244 }
245}
246
247/// The HTTP [Strict-Transport-Security] (HSTS) header: enforces strict HTTPS
248/// usage.
249///
250/// HSTS tells the browser that the site should only be accessed using HTTPS
251/// instead of HTTP. HSTS prevents a variety of downgrading attacks and should
252/// always be used when [TLS] is enabled. `Shield` will turn HSTS on and issue a
253/// warning if you enable TLS without enabling HSTS when the application is run
254/// in non-debug profiles.
255///
256/// While HSTS is important for HTTPS security, incorrectly configured HSTS can
257/// lead to problems as you are disallowing access to non-HTTPS enabled parts of
258/// your site. [Yelp engineering] has good discussion of potential challenges
259/// that can arise and how to roll this out in a large scale setting. So, if
260/// you use TLS, use HSTS, but roll it out with care.
261///
262/// [TLS]: https://rocket.rs/guide/configuration/#configuring-tls
263/// [Strict-Transport-Security]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Strict-Transport-Security
264/// [Yelp engineering]: https://engineeringblog.yelp.com/2017/09/the-road-to-hsts.html
265#[derive(PartialEq, Copy, Clone)]
266pub enum Hsts {
267 /// Browser should only permit this site to be accesses by HTTPS for the
268 /// next [`Duration`].
269 Enable(Duration),
270
271 /// Like [`Hsts::Enable`], but also apply to all of the site's subdomains.
272 IncludeSubDomains(Duration),
273
274 /// Send a "preload" HSTS header, which requests inclusion in the HSTS
275 /// preload list. This variant implies [`Hsts::IncludeSubDomains`], which
276 /// implies [`Hsts::Enable`].
277 ///
278 /// The provided `Duration` must be _at least_ 365 days. If the duration
279 /// provided is less than 365 days, the header will be written out with a
280 /// `max-age` of 365 days.
281 ///
282 /// # Details
283 ///
284 /// Google maintains an [HSTS preload service] that can be used to prevent
285 /// the browser from ever connecting to your site over an insecure
286 /// connection. Read more at [MDN]. Don't enable this before you have
287 /// registered your site and you ensure that it meets the requirements
288 /// specified by the preload service.
289 ///
290 /// [HSTS preload service]: https://hstspreload.org/
291 /// [MDN]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Strict-Transport-Security#Preloading_Strict_Transport_Security
292 Preload(Duration),
293}
294
295/// Defaults to `Hsts::Enable(Duration::days(365))`.
296impl Default for Hsts {
297 fn default() -> Hsts {
298 Hsts::Enable(Duration::days(365))
299 }
300}
301
302impl From<&Hsts> for Header<'static> {
303 fn from(hsts: &Hsts) -> Self {
304 if hsts == &Hsts::default() {
305 static DEFAULT: Header<'static> = Header {
306 name: Uncased::from_borrowed(Hsts::NAME),
307 value: Cow::Borrowed("max-age=31536000"),
308 };
309
310 return DEFAULT.clone();
311 }
312
313 let policy_string = match hsts {
314 Hsts::Enable(age) => format!("max-age={}", age.whole_seconds()),
315 Hsts::IncludeSubDomains(age) => {
316 format!("max-age={}; includeSubDomains", age.whole_seconds())
317 }
318 Hsts::Preload(age) => {
319 // Google says it needs to be >= 365 days for preload list.
320 static YEAR: Duration = Duration::seconds(31536000);
321
322 format!(
323 "max-age={}; includeSubDomains; preload",
324 age.max(&YEAR).whole_seconds()
325 )
326 }
327 };
328
329 Header::new(Hsts::NAME, policy_string)
330 }
331}
332
333/// The [X-Frame-Options] header: helps prevent [clickjacking] attacks.
334///
335/// Controls whether the browser should allow the page to render in a `<frame>`,
336/// [`<iframe>`][iframe] or `<object>`. This can be used to prevent
337/// [clickjacking] attacks.
338///
339/// [X-Frame-Options]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options
340/// [clickjacking]: https://en.wikipedia.org/wiki/Clickjacking
341/// [owasp-clickjacking]: https://www.owasp.org/index.php/Clickjacking_Defense_Cheat_Sheet
342/// [iframe]: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe
343pub enum Frame {
344 /// Page cannot be displayed in a frame.
345 Deny,
346
347 /// Page can only be displayed in a frame if the page trying to render it is
348 /// in the same origin. Interpretation of same-origin is [browser
349 /// dependent][X-Frame-Options].
350 ///
351 /// [X-Frame-Options]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options
352 SameOrigin,
353}
354
355/// Defaults to [`Frame::SameOrigin`].
356impl Default for Frame {
357 fn default() -> Frame {
358 Frame::SameOrigin
359 }
360}
361
362impl From<&Frame> for Header<'static> {
363 fn from(frame: &Frame) -> Self {
364 let policy_string: &'static str = match frame {
365 Frame::Deny => "DENY",
366 Frame::SameOrigin => "SAMEORIGIN",
367 };
368
369 Header::new(Frame::NAME, policy_string)
370 }
371}
372
373/// The [X-XSS-Protection] header: filters some forms of reflected [XSS]
374/// attacks. Modern browsers do not support or enforce this header.
375///
376/// [X-XSS-Protection]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-XSS-Protection
377/// [XSS]: https://developer.mozilla.org/en-US/docs/Glossary/Cross-site_scripting
378pub enum XssFilter {
379 /// Disables XSS filtering.
380 Disable,
381
382 /// Enables XSS filtering. If XSS is detected, the browser will sanitize
383 /// before rendering the page (_Shield default_).
384 Enable,
385
386 /// Enables XSS filtering. If XSS is detected, the browser will not
387 /// render the page.
388 EnableBlock,
389}
390
391/// Defaults to [`XssFilter::Enable`].
392impl Default for XssFilter {
393 fn default() -> XssFilter {
394 XssFilter::Enable
395 }
396}
397
398impl From<&XssFilter> for Header<'static> {
399 fn from(filter: &XssFilter) -> Self {
400 let policy_string: &'static str = match filter {
401 XssFilter::Disable => "0",
402 XssFilter::Enable => "1",
403 XssFilter::EnableBlock => "1; mode=block",
404 };
405
406 Header::new(XssFilter::NAME, policy_string)
407 }
408}
409
410/// The [X-DNS-Prefetch-Control] header: controls browser DNS prefetching.
411///
412/// Tells the browser if it should perform domain name resolution on both links
413/// that the user may choose to follow as well as URLs for items referenced by
414/// the document including images, CSS, JavaScript, and so forth. Disabling
415/// prefetching is useful if you don't control the link on the pages, or know
416/// that you don't want to leak information to these domains.
417///
418/// [X-DNS-Prefetch-Control]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-DNS-Prefetch-Control
419#[derive(Default)]
420pub enum Prefetch {
421 /// Enables DNS prefetching. This is the browser default.
422 On,
423 /// Disables DNS prefetching. This is the shield policy default.
424 #[default]
425 Off,
426}
427
428impl From<&Prefetch> for Header<'static> {
429 fn from(prefetch: &Prefetch) -> Self {
430 let policy_string = match prefetch {
431 Prefetch::On => "on",
432 Prefetch::Off => "off",
433 };
434
435 Header::new(Prefetch::NAME, policy_string)
436 }
437}
438
439/// The [Permissions-Policy] header: allow or block the use of browser features.
440///
441/// Tells the browser to allow or block the use of a browser feature in the
442/// top-level page as well as allow or block _requesting access to_ (via the
443/// `allow` `iframe` attribute) features in embedded iframes.
444///
445/// By default, the top-level page may access ~all features and any embedded
446/// iframes may request access to ~any feature. This header allows the server to
447/// control exactly _which_ (if any) origins may access or request access to
448/// browser features.
449///
450/// Features are enabled via the [`Permission::allowed()`] constructor and
451/// chainable [`allow()`](Self::allow()) build method. Features can be blocked
452/// via the [`Permission::blocked()`] and chainable [`block()`](Self::block())
453/// builder method.
454///
455/// ```rust
456/// # #[macro_use] extern crate rocket_community as rocket;
457/// use rocket::shield::{Shield, Permission, Feature, Allow};
458///
459/// // In addition to defaults, block access to geolocation and USB features.
460/// // Enable camera and microphone features only for the serving origin. Enable
461/// // payment request access for the current origin and `https://rocket.rs`.
462/// let permission = Permission::default()
463/// .block(Feature::Geolocation)
464/// .block(Feature::Usb)
465/// .allow(Feature::Camera, Allow::This)
466/// .allow(Feature::Microphone, Allow::This)
467/// .allow(Feature::Payment, [Allow::This, Allow::Origin(uri!("https://rocket.rs"))]);
468///
469/// rocket::build().attach(Shield::default().enable(permission));
470/// ```
471///
472/// # Default
473///
474/// The default returned via [`Permission::default()`] blocks access to the
475/// `interest-cohort` feature, otherwise known as FLoC, which disables using the
476/// current site in ad targeting tracking computations.
477///
478/// [Permissions-Policy]: https://github.com/w3c/webappsec-permissions-policy/blob/a45df7b237e2a85e1909d7f226ca4eb4ce5095ba/permissions-policy-explainer.md
479#[derive(PartialEq, Clone)]
480pub struct Permission(IndexMap<Feature, Vec<Allow>>);
481
482impl Default for Permission {
483 /// The default `Permission` policy blocks access to the `interest-cohort`
484 /// feature, otherwise known as FLoC, which disables using the current site
485 /// in ad targeting tracking computations.
486 fn default() -> Self {
487 Permission::blocked(Feature::InterestCohort)
488 }
489}
490
491impl Permission {
492 /// Constructs a new `Permission` policy with only `feature` allowed for the
493 /// set of origins in `allow` which may be a single [`Allow`], a slice
494 /// (`[Allow]` or `&[Allow]`), or a vector (`Vec<Allow>`).
495 ///
496 /// If `allow` is empty, the use of the feature is blocked unless another
497 /// call to `allow()` allows it. If `allow` contains [`Allow::Any`], the
498 /// feature is allowable for all origins. Otherwise, the feature is
499 /// allowable only for the origin specified in `allow`.
500 ///
501 /// # Panics
502 ///
503 /// Panics if an `Absolute` URI in an `Allow::Origin` does not contain a
504 /// host part.
505 ///
506 /// # Example
507 ///
508 /// ```rust
509 /// # #[macro_use] extern crate rocket_community as rocket;
510 /// use rocket::shield::{Permission, Feature, Allow};
511 ///
512 /// let rocket = Allow::Origin(uri!("https://rocket.rs"));
513 ///
514 /// let perm = Permission::allowed(Feature::Usb, Allow::This);
515 /// let perm = Permission::allowed(Feature::Usb, Allow::Any);
516 /// let perm = Permission::allowed(Feature::Usb, [Allow::This, rocket]);
517 /// ```
518 pub fn allowed<L>(feature: Feature, allow: L) -> Self
519 where
520 L: IntoIterator<Item = Allow>,
521 {
522 Permission(IndexMap::new()).allow(feature, allow)
523 }
524
525 /// Constructs a new `Permission` policy with only `feature` blocked.
526 ///
527 /// # Example
528 ///
529 /// ```rust
530 /// # extern crate rocket_community as rocket;
531 ///
532 /// use rocket::shield::{Permission, Feature};
533 ///
534 /// let perm = Permission::blocked(Feature::Usb);
535 /// let perm = Permission::blocked(Feature::Payment);
536 /// ```
537 pub fn blocked(feature: Feature) -> Self {
538 Permission(IndexMap::new()).block(feature)
539 }
540
541 /// Adds `feature` as allowable for the set of origins in `allow` which may
542 /// be a single [`Allow`], a slice (`[Allow]` or `&[Allow]`), or a vector
543 /// (`Vec<Allow>`).
544 ///
545 /// This policy supersedes any previous policy set for `feature`.
546 ///
547 /// If `allow` is empty, the use of the feature is blocked unless another
548 /// call to `allow()` allows it. If `allow` contains [`Allow::Any`], the
549 /// feature is allowable for all origins. Otherwise, the feature is
550 /// allowable only for the origin specified in `allow`.
551 ///
552 /// # Panics
553 ///
554 /// Panics if an `Absolute` URI in an `Allow::Origin` does not contain a
555 /// host part.
556 ///
557 /// # Example
558 ///
559 /// ```rust
560 /// # #[macro_use] extern crate rocket_community as rocket;
561 /// use rocket::shield::{Permission, Feature, Allow};
562 ///
563 /// let rocket = Allow::Origin(uri!("https://rocket.rs"));
564 /// let perm = Permission::allowed(Feature::Usb, Allow::This)
565 /// .allow(Feature::Payment, [rocket, Allow::This]);
566 /// ```
567 pub fn allow<L>(mut self, feature: Feature, allow: L) -> Self
568 where
569 L: IntoIterator<Item = Allow>,
570 {
571 let mut allow: Vec<_> = allow.into_iter().collect();
572 for allow in &allow {
573 if let Allow::Origin(absolute) = allow {
574 let auth = absolute.authority();
575 if auth.is_none() || matches!(auth, Some(a) if a.host().is_empty()) {
576 panic!("...")
577 }
578 }
579 }
580
581 if allow.contains(&Allow::Any) {
582 allow = vec![Allow::Any];
583 }
584
585 self.0.insert(feature, allow);
586 self
587 }
588
589 /// Blocks `feature`. This policy supersedes any previous policy set for
590 /// `feature`.
591 ///
592 /// # Example
593 ///
594 /// ```rust
595 /// # extern crate rocket_community as rocket;
596 ///
597 /// use rocket::shield::{Permission, Feature};
598 ///
599 /// let perm = Permission::default()
600 /// .block(Feature::Usb)
601 /// .block(Feature::Payment);
602 /// ```
603 pub fn block(mut self, feature: Feature) -> Self {
604 self.0.insert(feature, vec![]);
605 self
606 }
607
608 /// Returns the allow list (so far) for `feature` if feature is allowed.
609 ///
610 /// # Example
611 ///
612 /// ```rust
613 /// # extern crate rocket_community as rocket;
614 ///
615 /// use rocket::shield::{Permission, Feature, Allow};
616 ///
617 /// let perm = Permission::default();
618 /// assert!(perm.get(Feature::Usb).is_none());
619 ///
620 /// let perm = perm.allow(Feature::Usb, Allow::Any);
621 /// assert_eq!(perm.get(Feature::Usb).unwrap(), &[Allow::Any]);
622 /// ```
623 pub fn get(&self, feature: Feature) -> Option<&[Allow]> {
624 Some(self.0.get(&feature)?)
625 }
626
627 /// Returns an iterator over the pairs of features and their allow lists,
628 /// empty if the feature is blocked.
629 ///
630 /// Features are returned in the order in which they were first added.
631 ///
632 /// # Example
633 ///
634 /// ```rust
635 /// # #[macro_use] extern crate rocket_community as rocket;;
636 /// use rocket::shield::{Permission, Feature, Allow};
637 ///
638 /// let foo = uri!("https://foo.com:1234");
639 /// let perm = Permission::blocked(Feature::Camera)
640 /// .allow(Feature::Gyroscope, [Allow::This, Allow::Origin(foo.clone())])
641 /// .block(Feature::Payment)
642 /// .allow(Feature::Camera, Allow::Any);
643 ///
644 /// let perms: Vec<_> = perm.iter().collect();
645 /// assert_eq!(perms.len(), 3);
646 /// assert_eq!(perms, vec![
647 /// (Feature::Camera, &[Allow::Any][..]),
648 /// (Feature::Gyroscope, &[Allow::This, Allow::Origin(foo)][..]),
649 /// (Feature::Payment, &[][..]),
650 /// ]);
651 /// ```
652 pub fn iter(&self) -> impl Iterator<Item = (Feature, &[Allow])> {
653 self.0.iter().map(|(feature, list)| (*feature, &**list))
654 }
655}
656
657impl From<&Permission> for Header<'static> {
658 fn from(perm: &Permission) -> Self {
659 if perm == &Permission::default() {
660 static DEFAULT: Header<'static> = Header {
661 name: Uncased::from_borrowed(Permission::NAME),
662 value: Cow::Borrowed("interest-cohort=()"),
663 };
664
665 return DEFAULT.clone();
666 }
667
668 let value = perm
669 .0
670 .iter()
671 .map(|(feature, allow)| {
672 let list = allow
673 .iter()
674 .map(|origin| origin.rendered())
675 .collect::<Vec<_>>()
676 .join(" ");
677
678 format!("{}=({})", feature, list)
679 })
680 .collect::<Vec<_>>()
681 .join(", ");
682
683 Header::new(Permission::NAME, value)
684 }
685}
686
687/// Specifies the origin(s) allowed to access a browser [`Feature`] via
688/// [`Permission`].
689#[allow(clippy::large_enum_variant)]
690#[derive(Debug, PartialEq, Clone)]
691pub enum Allow {
692 /// Allow this specific origin. The feature is allowed only for this
693 /// specific origin.
694 ///
695 /// The `user_info`, `path`, and `query` parts of the URI, if any, are
696 /// ignored.
697 Origin(Absolute<'static>),
698 /// Any origin at all.
699 ///
700 /// The feature will be allowed in all browsing contexts regardless of their
701 /// origin.
702 Any,
703 /// The current origin.
704 ///
705 /// The feature will be allowed in the immediately returned document and in
706 /// all nested browsing contexts (iframes) in the same origin.
707 This,
708}
709
710impl Allow {
711 fn rendered(&self) -> Cow<'static, str> {
712 match self {
713 Allow::Origin(uri) => {
714 let mut string = String::with_capacity(32);
715 string.push('"');
716 string.push_str(uri.scheme());
717
718 // This should never fail when rendering a header for `Shield`
719 // due to `panic` in `.allow()`.
720 if let Some(auth) = uri.authority() {
721 use std::fmt::Write;
722
723 let _ = write!(string, "://{}", auth.host());
724 if let Some(port) = auth.port() {
725 let _ = write!(string, ":{}", port);
726 }
727 }
728
729 string.push('"');
730 string.into()
731 }
732 Allow::Any => "*".into(),
733 Allow::This => "self".into(),
734 }
735 }
736}
737
738impl IntoIterator for Allow {
739 type Item = Self;
740
741 type IntoIter = std::iter::Once<Self>;
742
743 fn into_iter(self) -> Self::IntoIter {
744 std::iter::once(self)
745 }
746}
747
748/// A browser feature that can be enabled or blocked via [`Permission`].
749#[derive(Debug, PartialEq, Eq, Hash, Copy, Clone)]
750#[non_exhaustive]
751pub enum Feature {
752 // Standardized.
753 /// The "accelerometer" feature.
754 Accelerometer,
755 /// The "ambient-light-sensor" feature.
756 AmbientLightSensor,
757 /// The "autoplay" feature.
758 Autoplay,
759 /// The "battery" feature.
760 Battery,
761 /// The "camera" feature.
762 Camera,
763 /// The "cross-origin-isolated" feature.
764 CrossOriginIsolated,
765 /// The "display-capture" feature.
766 Displaycapture,
767 /// The "document-domain" feature.
768 DocumentDomain,
769 /// The "encrypted-media" feature.
770 EncryptedMedia,
771 /// The "execution-while-not-rendered" feature.
772 ExecutionWhileNotRendered,
773 /// The "execution-while-out-of-viewport" feature.
774 ExecutionWhileOutOfviewport,
775 /// The "fullscreen" feature.
776 Fullscreen,
777 /// The "geolocation" feature.
778 Geolocation,
779 /// The "gyroscope" feature.
780 Gyroscope,
781 /// The "magnetometer" feature.
782 Magnetometer,
783 /// The "microphone" feature.
784 Microphone,
785 /// The "midi" feature.
786 Midi,
787 /// The "navigation-override" feature.
788 NavigationOverride,
789 /// The "payment" feature.
790 Payment,
791 /// The "picture-in-picture" feature.
792 PictureInPicture,
793 /// The "publickey-credentials-get" feature.
794 PublickeyCredentialsGet,
795 /// The "screen-wake-lock" feature.
796 ScreenWakeLock,
797 /// The "sync-xhr" feature.
798 SyncXhr,
799 /// The "usb" feature.
800 Usb,
801 /// The "web-share" feature.
802 WebShare,
803 /// The "xr-spatial-tracking" feature.
804 XrSpatialTracking,
805
806 // Proposed.
807 /// The "clipboard-read" feature.
808 ClipboardRead,
809 /// The "clipboard-write" feature.
810 ClipboardWrite,
811 /// The "gamepad" feature.
812 Gamepad,
813 /// The "speaker-selection" feature.
814 SpeakerSelection,
815 /// The "interest-cohort" feature.
816 InterestCohort,
817
818 // Experimental.
819 /// The "conversion-measurement" feature.
820 ConversionMeasurement,
821 /// The "focus-without-user-activation" feature.
822 FocusWithoutUserActivation,
823 /// The "hid" feature.
824 Hid,
825 /// The "idle-detection" feature.
826 IdleDetection,
827 /// The "serial" feature.
828 Serial,
829 /// The "sync-script" feature.
830 SyncScript,
831 /// The "trust-token-redemption" feature.
832 TrustTokenRedemption,
833 /// The "vertical-scroll" feature.
834 VerticalScroll,
835}
836
837impl Feature {
838 /// Returns the feature string as it appears in the header.
839 ///
840 /// # Example
841 ///
842 /// ```rust
843 /// # extern crate rocket_community as rocket;
844 ///
845 /// use rocket::shield::Feature;
846 ///
847 /// assert_eq!(Feature::Camera.as_str(), "camera");
848 /// assert_eq!(Feature::SyncScript.as_str(), "sync-script");
849 /// ```
850 pub const fn as_str(self) -> &'static str {
851 use Feature::*;
852
853 match self {
854 Accelerometer => "accelerometer",
855 AmbientLightSensor => "ambient-light-sensor",
856 Autoplay => "autoplay",
857 Battery => "battery",
858 Camera => "camera",
859 CrossOriginIsolated => "cross-origin-isolated",
860 Displaycapture => "display-capture",
861 DocumentDomain => "document-domain",
862 EncryptedMedia => "encrypted-media",
863 ExecutionWhileNotRendered => "execution-while-not-rendered",
864 ExecutionWhileOutOfviewport => "execution-while-out-of-viewport",
865 Fullscreen => "fullscreen",
866 Geolocation => "geolocation",
867 Gyroscope => "gyroscope",
868 Magnetometer => "magnetometer",
869 Microphone => "microphone",
870 Midi => "midi",
871 NavigationOverride => "navigation-override",
872 Payment => "payment",
873 PictureInPicture => "picture-in-picture",
874 PublickeyCredentialsGet => "publickey-credentials-get",
875 ScreenWakeLock => "screen-wake-lock",
876 SyncXhr => "sync-xhr",
877 Usb => "usb",
878 WebShare => "web-share",
879 XrSpatialTracking => "xr-spatial-tracking",
880
881 ClipboardRead => "clipboard-read",
882 ClipboardWrite => "clipboard-write",
883 Gamepad => "gamepad",
884 SpeakerSelection => "speaker-selection",
885 InterestCohort => "interest-cohort",
886
887 ConversionMeasurement => "conversion-measurement",
888 FocusWithoutUserActivation => "focus-without-user-activation",
889 Hid => "hid",
890 IdleDetection => "idle-detection",
891 Serial => "serial",
892 SyncScript => "sync-script",
893 TrustTokenRedemption => "trust-token-redemption",
894 VerticalScroll => "vertical-scroll",
895 }
896 }
897}
898
899impl fmt::Display for Feature {
900 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
901 self.as_str().fmt(f)
902 }
903}