rocket/shield/policy.rs
1//! Module containing the [`Policy`] trait and types that implement it.
2
3use std::fmt;
4use std::borrow::Cow;
5
6use indexmap::IndexMap;
7use time::Duration;
8
9use crate::http::{Header, uri::Absolute, uncased::Uncased};
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;
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;
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!("max-age={}, enforce, report-uri=\"{}\"", age.whole_seconds(), uri)
212 }
213 };
214
215 Header::new(ExpectCt::NAME, policy_string)
216 }
217}
218
219/// The [X-Content-Type-Options] header: turns off [mime sniffing] which can
220/// prevent certain [attacks].
221///
222/// [mime sniffing]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types#MIME_sniffing
223/// [X-Content-Type-Options]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Content-Type-Options
224/// [attacks]: https://blog.mozilla.org/security/2016/08/26/mitigating-mime-confusion-attacks-in-firefox/
225pub enum NoSniff {
226 /// Turns off mime sniffing.
227 Enable,
228}
229
230/// Defaults to [`NoSniff::Enable`], turns off mime sniffing.
231impl Default for NoSniff {
232 fn default() -> NoSniff {
233 NoSniff::Enable
234 }
235}
236
237impl From<&NoSniff> for Header<'static> {
238 fn from(_: &NoSniff) -> Self {
239 Header::new(NoSniff::NAME, "nosniff")
240 }
241}
242
243/// The HTTP [Strict-Transport-Security] (HSTS) header: enforces strict HTTPS
244/// usage.
245///
246/// HSTS tells the browser that the site should only be accessed using HTTPS
247/// instead of HTTP. HSTS prevents a variety of downgrading attacks and should
248/// always be used when [TLS] is enabled. `Shield` will turn HSTS on and issue a
249/// warning if you enable TLS without enabling HSTS when the application is run
250/// in non-debug profiles.
251///
252/// While HSTS is important for HTTPS security, incorrectly configured HSTS can
253/// lead to problems as you are disallowing access to non-HTTPS enabled parts of
254/// your site. [Yelp engineering] has good discussion of potential challenges
255/// that can arise and how to roll this out in a large scale setting. So, if
256/// you use TLS, use HSTS, but roll it out with care.
257///
258/// [TLS]: https://rocket.rs/guide/configuration/#configuring-tls
259/// [Strict-Transport-Security]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Strict-Transport-Security
260/// [Yelp engineering]: https://engineeringblog.yelp.com/2017/09/the-road-to-hsts.html
261#[derive(PartialEq, Copy, Clone)]
262pub enum Hsts {
263 /// Browser should only permit this site to be accesses by HTTPS for the
264 /// next [`Duration`].
265 Enable(Duration),
266
267 /// Like [`Hsts::Enable`], but also apply to all of the site's subdomains.
268 IncludeSubDomains(Duration),
269
270 /// Send a "preload" HSTS header, which requests inclusion in the HSTS
271 /// preload list. This variant implies [`Hsts::IncludeSubDomains`], which
272 /// implies [`Hsts::Enable`].
273 ///
274 /// The provided `Duration` must be _at least_ 365 days. If the duration
275 /// provided is less than 365 days, the header will be written out with a
276 /// `max-age` of 365 days.
277 ///
278 /// # Details
279 ///
280 /// Google maintains an [HSTS preload service] that can be used to prevent
281 /// the browser from ever connecting to your site over an insecure
282 /// connection. Read more at [MDN]. Don't enable this before you have
283 /// registered your site and you ensure that it meets the requirements
284 /// specified by the preload service.
285 ///
286 /// [HSTS preload service]: https://hstspreload.org/
287 /// [MDN]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Strict-Transport-Security#Preloading_Strict_Transport_Security
288 Preload(Duration),
289}
290
291/// Defaults to `Hsts::Enable(Duration::days(365))`.
292impl Default for Hsts {
293 fn default() -> Hsts {
294 Hsts::Enable(Duration::days(365))
295 }
296}
297
298impl From<&Hsts> for Header<'static> {
299 fn from(hsts: &Hsts) -> Self {
300 if hsts == &Hsts::default() {
301 static DEFAULT: Header<'static> = Header {
302 name: Uncased::from_borrowed(Hsts::NAME),
303 value: Cow::Borrowed("max-age=31536000")
304 };
305
306 return DEFAULT.clone();
307 }
308
309 let policy_string = match hsts {
310 Hsts::Enable(age) => format!("max-age={}", age.whole_seconds()),
311 Hsts::IncludeSubDomains(age) => {
312 format!("max-age={}; includeSubDomains", age.whole_seconds())
313 }
314 Hsts::Preload(age) => {
315 // Google says it needs to be >= 365 days for preload list.
316 static YEAR: Duration = Duration::seconds(31536000);
317
318 format!("max-age={}; includeSubDomains; preload", age.max(&YEAR).whole_seconds())
319 }
320 };
321
322 Header::new(Hsts::NAME, policy_string)
323 }
324}
325
326/// The [X-Frame-Options] header: helps prevent [clickjacking] attacks.
327///
328/// Controls whether the browser should allow the page to render in a `<frame>`,
329/// [`<iframe>`][iframe] or `<object>`. This can be used to prevent
330/// [clickjacking] attacks.
331///
332/// [X-Frame-Options]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options
333/// [clickjacking]: https://en.wikipedia.org/wiki/Clickjacking
334/// [owasp-clickjacking]: https://www.owasp.org/index.php/Clickjacking_Defense_Cheat_Sheet
335/// [iframe]: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe
336pub enum Frame {
337 /// Page cannot be displayed in a frame.
338 Deny,
339
340 /// Page can only be displayed in a frame if the page trying to render it is
341 /// in the same origin. Interpretation of same-origin is [browser
342 /// dependent][X-Frame-Options].
343 ///
344 /// [X-Frame-Options]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options
345 SameOrigin,
346}
347
348/// Defaults to [`Frame::SameOrigin`].
349impl Default for Frame {
350 fn default() -> Frame {
351 Frame::SameOrigin
352 }
353}
354
355impl From<&Frame> for Header<'static> {
356 fn from(frame: &Frame) -> Self {
357 let policy_string: &'static str = match frame {
358 Frame::Deny => "DENY",
359 Frame::SameOrigin => "SAMEORIGIN",
360 };
361
362 Header::new(Frame::NAME, policy_string)
363 }
364}
365
366/// The [X-XSS-Protection] header: filters some forms of reflected [XSS]
367/// attacks. Modern browsers do not support or enforce this header.
368///
369/// [X-XSS-Protection]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-XSS-Protection
370/// [XSS]: https://developer.mozilla.org/en-US/docs/Glossary/Cross-site_scripting
371pub enum XssFilter {
372 /// Disables XSS filtering.
373 Disable,
374
375 /// Enables XSS filtering. If XSS is detected, the browser will sanitize
376 /// before rendering the page (_Shield default_).
377 Enable,
378
379 /// Enables XSS filtering. If XSS is detected, the browser will not
380 /// render the page.
381 EnableBlock,
382}
383
384/// Defaults to [`XssFilter::Enable`].
385impl Default for XssFilter {
386 fn default() -> XssFilter {
387 XssFilter::Enable
388 }
389}
390
391impl From<&XssFilter> for Header<'static> {
392 fn from(filter: &XssFilter) -> Self {
393 let policy_string: &'static str = match filter {
394 XssFilter::Disable => "0",
395 XssFilter::Enable => "1",
396 XssFilter::EnableBlock => "1; mode=block",
397 };
398
399 Header::new(XssFilter::NAME, policy_string)
400 }
401}
402
403/// The [X-DNS-Prefetch-Control] header: controls browser DNS prefetching.
404///
405/// Tells the browser if it should perform domain name resolution on both links
406/// that the user may choose to follow as well as URLs for items referenced by
407/// the document including images, CSS, JavaScript, and so forth. Disabling
408/// prefetching is useful if you don't control the link on the pages, or know
409/// that you don't want to leak information to these domains.
410///
411/// [X-DNS-Prefetch-Control]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-DNS-Prefetch-Control
412#[derive(Default)]
413pub enum Prefetch {
414 /// Enables DNS prefetching. This is the browser default.
415 On,
416 /// Disables DNS prefetching. This is the shield policy default.
417 #[default]
418 Off,
419}
420
421impl From<&Prefetch> for Header<'static> {
422 fn from(prefetch: &Prefetch) -> Self {
423 let policy_string = match prefetch {
424 Prefetch::On => "on",
425 Prefetch::Off => "off",
426 };
427
428 Header::new(Prefetch::NAME, policy_string)
429 }
430}
431
432/// The [Permissions-Policy] header: allow or block the use of browser features.
433///
434/// Tells the browser to allow or block the use of a browser feature in the
435/// top-level page as well as allow or block _requesting access to_ (via the
436/// `allow` `iframe` attribute) features in embedded iframes.
437///
438/// By default, the top-level page may access ~all features and any embedded
439/// iframes may request access to ~any feature. This header allows the server to
440/// control exactly _which_ (if any) origins may access or request access to
441/// browser features.
442///
443/// Features are enabled via the [`Permission::allowed()`] constructor and
444/// chainable [`allow()`](Self::allow()) build method. Features can be blocked
445/// via the [`Permission::blocked()`] and chainable [`block()`](Self::block())
446/// builder method.
447///
448/// ```rust
449 /// # #[macro_use] extern crate rocket;
450/// use rocket::shield::{Shield, Permission, Feature, Allow};
451///
452/// // In addition to defaults, block access to geolocation and USB features.
453/// // Enable camera and microphone features only for the serving origin. Enable
454/// // payment request access for the current origin and `https://rocket.rs`.
455/// let permission = Permission::default()
456/// .block(Feature::Geolocation)
457/// .block(Feature::Usb)
458/// .allow(Feature::Camera, Allow::This)
459/// .allow(Feature::Microphone, Allow::This)
460/// .allow(Feature::Payment, [Allow::This, Allow::Origin(uri!("https://rocket.rs"))]);
461///
462/// rocket::build().attach(Shield::default().enable(permission));
463/// ```
464///
465/// # Default
466///
467/// The default returned via [`Permission::default()`] blocks access to the
468/// `interest-cohort` feature, otherwise known as FLoC, which disables using the
469/// current site in ad targeting tracking computations.
470///
471/// [Permissions-Policy]: https://github.com/w3c/webappsec-permissions-policy/blob/a45df7b237e2a85e1909d7f226ca4eb4ce5095ba/permissions-policy-explainer.md
472#[derive(PartialEq, Clone)]
473pub struct Permission(IndexMap<Feature, Vec<Allow>>);
474
475impl Default for Permission {
476 /// The default `Permission` policy blocks access to the `interest-cohort`
477 /// feature, otherwise known as FLoC, which disables using the current site
478 /// in ad targeting tracking computations.
479 fn default() -> Self {
480 Permission::blocked(Feature::InterestCohort)
481 }
482}
483
484impl Permission {
485 /// Constructs a new `Permission` policy with only `feature` allowed for the
486 /// set of origins in `allow` which may be a single [`Allow`], a slice
487 /// (`[Allow]` or `&[Allow]`), or a vector (`Vec<Allow>`).
488 ///
489 /// If `allow` is empty, the use of the feature is blocked unless another
490 /// call to `allow()` allows it. If `allow` contains [`Allow::Any`], the
491 /// feature is allowable for all origins. Otherwise, the feature is
492 /// allowable only for the origin specified in `allow`.
493 ///
494 /// # Panics
495 ///
496 /// Panics if an `Absolute` URI in an `Allow::Origin` does not contain a
497 /// host part.
498 ///
499 /// # Example
500 ///
501 /// ```rust
502 /// # #[macro_use] extern crate rocket;
503 /// use rocket::shield::{Permission, Feature, Allow};
504 ///
505 /// let rocket = Allow::Origin(uri!("https://rocket.rs"));
506 ///
507 /// let perm = Permission::allowed(Feature::Usb, Allow::This);
508 /// let perm = Permission::allowed(Feature::Usb, Allow::Any);
509 /// let perm = Permission::allowed(Feature::Usb, [Allow::This, rocket]);
510 /// ```
511 pub fn allowed<L>(feature: Feature, allow: L) -> Self
512 where L: IntoIterator<Item = Allow>
513 {
514 Permission(IndexMap::new()).allow(feature, allow)
515 }
516
517 /// Constructs a new `Permission` policy with only `feature` blocked.
518 ///
519 /// # Example
520 ///
521 /// ```rust
522 /// use rocket::shield::{Permission, Feature};
523 ///
524 /// let perm = Permission::blocked(Feature::Usb);
525 /// let perm = Permission::blocked(Feature::Payment);
526 /// ```
527 pub fn blocked(feature: Feature) -> Self {
528 Permission(IndexMap::new()).block(feature)
529 }
530
531 /// Adds `feature` as allowable for the set of origins in `allow` which may
532 /// be a single [`Allow`], a slice (`[Allow]` or `&[Allow]`), or a vector
533 /// (`Vec<Allow>`).
534 ///
535 /// This policy supersedes any previous policy set for `feature`.
536 ///
537 /// If `allow` is empty, the use of the feature is blocked unless another
538 /// call to `allow()` allows it. If `allow` contains [`Allow::Any`], the
539 /// feature is allowable for all origins. Otherwise, the feature is
540 /// allowable only for the origin specified in `allow`.
541 ///
542 /// # Panics
543 ///
544 /// Panics if an `Absolute` URI in an `Allow::Origin` does not contain a
545 /// host part.
546 ///
547 /// # Example
548 ///
549 /// ```rust
550 /// # #[macro_use] extern crate rocket;
551 /// use rocket::shield::{Permission, Feature, Allow};
552 ///
553 /// let rocket = Allow::Origin(uri!("https://rocket.rs"));
554 /// let perm = Permission::allowed(Feature::Usb, Allow::This)
555 /// .allow(Feature::Payment, [rocket, Allow::This]);
556 /// ```
557 pub fn allow<L>(mut self, feature: Feature, allow: L) -> Self
558 where L: IntoIterator<Item = Allow>
559 {
560 let mut allow: Vec<_> = allow.into_iter().collect();
561 for allow in &allow {
562 if let Allow::Origin(absolute) = allow {
563 let auth = absolute.authority();
564 if auth.is_none() || matches!(auth, Some(a) if a.host().is_empty()) {
565 panic!("...")
566 }
567 }
568 }
569
570 if allow.contains(&Allow::Any) {
571 allow = vec![Allow::Any];
572 }
573
574 self.0.insert(feature, allow);
575 self
576 }
577
578 /// Blocks `feature`. This policy supersedes any previous policy set for
579 /// `feature`.
580 ///
581 /// # Example
582 ///
583 /// ```rust
584 /// use rocket::shield::{Permission, Feature};
585 ///
586 /// let perm = Permission::default()
587 /// .block(Feature::Usb)
588 /// .block(Feature::Payment);
589 /// ```
590 pub fn block(mut self, feature: Feature) -> Self {
591 self.0.insert(feature, vec![]);
592 self
593 }
594
595 /// Returns the allow list (so far) for `feature` if feature is allowed.
596 ///
597 /// # Example
598 ///
599 /// ```rust
600 /// use rocket::shield::{Permission, Feature, Allow};
601 ///
602 /// let perm = Permission::default();
603 /// assert!(perm.get(Feature::Usb).is_none());
604 ///
605 /// let perm = perm.allow(Feature::Usb, Allow::Any);
606 /// assert_eq!(perm.get(Feature::Usb).unwrap(), &[Allow::Any]);
607 /// ```
608 pub fn get(&self, feature: Feature) -> Option<&[Allow]> {
609 Some(self.0.get(&feature)?)
610 }
611
612 /// Returns an iterator over the pairs of features and their allow lists,
613 /// empty if the feature is blocked.
614 ///
615 /// Features are returned in the order in which they were first added.
616 ///
617 /// # Example
618 ///
619 /// ```rust
620 /// # #[macro_use] extern crate rocket;
621 /// use rocket::shield::{Permission, Feature, Allow};
622 ///
623 /// let foo = uri!("https://foo.com:1234");
624 /// let perm = Permission::blocked(Feature::Camera)
625 /// .allow(Feature::Gyroscope, [Allow::This, Allow::Origin(foo.clone())])
626 /// .block(Feature::Payment)
627 /// .allow(Feature::Camera, Allow::Any);
628 ///
629 /// let perms: Vec<_> = perm.iter().collect();
630 /// assert_eq!(perms.len(), 3);
631 /// assert_eq!(perms, vec![
632 /// (Feature::Camera, &[Allow::Any][..]),
633 /// (Feature::Gyroscope, &[Allow::This, Allow::Origin(foo)][..]),
634 /// (Feature::Payment, &[][..]),
635 /// ]);
636 /// ```
637 pub fn iter(&self) -> impl Iterator<Item = (Feature, &[Allow])> {
638 self.0.iter().map(|(feature, list)| (*feature, &**list))
639 }
640}
641
642impl From<&Permission> for Header<'static> {
643 fn from(perm: &Permission) -> Self {
644 if perm == &Permission::default() {
645 static DEFAULT: Header<'static> = Header {
646 name: Uncased::from_borrowed(Permission::NAME),
647 value: Cow::Borrowed("interest-cohort=()")
648 };
649
650 return DEFAULT.clone();
651 }
652
653 let value = perm.0.iter()
654 .map(|(feature, allow)| {
655 let list = allow.iter()
656 .map(|origin| origin.rendered())
657 .collect::<Vec<_>>()
658 .join(" ");
659
660 format!("{}=({})", feature, list)
661 })
662 .collect::<Vec<_>>()
663 .join(", ");
664
665 Header::new(Permission::NAME, value)
666 }
667}
668
669/// Specifies the origin(s) allowed to access a browser [`Feature`] via
670/// [`Permission`].
671#[derive(Debug, PartialEq, Clone)]
672pub enum Allow {
673 /// Allow this specific origin. The feature is allowed only for this
674 /// specific origin.
675 ///
676 /// The `user_info`, `path`, and `query` parts of the URI, if any, are
677 /// ignored.
678 Origin(Absolute<'static>),
679 /// Any origin at all.
680 ///
681 /// The feature will be allowed in all browsing contexts regardless of their
682 /// origin.
683 Any,
684 /// The current origin.
685 ///
686 /// The feature will be allowed in the immediately returned document and in
687 /// all nested browsing contexts (iframes) in the same origin.
688 This,
689}
690
691impl Allow {
692 fn rendered(&self) -> Cow<'static, str> {
693 match self {
694 Allow::Origin(uri) => {
695 let mut string = String::with_capacity(32);
696 string.push('"');
697 string.push_str(uri.scheme());
698
699 // This should never fail when rendering a header for `Shield`
700 // due to `panic` in `.allow()`.
701 if let Some(auth) = uri.authority() {
702 use std::fmt::Write;
703
704 let _ = write!(string, "://{}", auth.host());
705 if let Some(port) = auth.port() {
706 let _ = write!(string, ":{}", port);
707 }
708 }
709
710 string.push('"');
711 string.into()
712 }
713 Allow::Any => "*".into(),
714 Allow::This => "self".into(),
715 }
716 }
717}
718
719impl IntoIterator for Allow {
720 type Item = Self;
721
722 type IntoIter = std::iter::Once<Self>;
723
724 fn into_iter(self) -> Self::IntoIter {
725 std::iter::once(self)
726 }
727}
728
729/// A browser feature that can be enabled or blocked via [`Permission`].
730#[derive(Debug, PartialEq, Eq, Hash, Copy, Clone)]
731#[non_exhaustive]
732pub enum Feature {
733 // Standardized.
734
735 /// The "accelerometer" feature.
736 Accelerometer,
737 /// The "ambient-light-sensor" feature.
738 AmbientLightSensor,
739 /// The "autoplay" feature.
740 Autoplay,
741 /// The "battery" feature.
742 Battery,
743 /// The "camera" feature.
744 Camera,
745 /// The "cross-origin-isolated" feature.
746 CrossOriginIsolated,
747 /// The "display-capture" feature.
748 Displaycapture,
749 /// The "document-domain" feature.
750 DocumentDomain,
751 /// The "encrypted-media" feature.
752 EncryptedMedia,
753 /// The "execution-while-not-rendered" feature.
754 ExecutionWhileNotRendered,
755 /// The "execution-while-out-of-viewport" feature.
756 ExecutionWhileOutOfviewport,
757 /// The "fullscreen" feature.
758 Fullscreen,
759 /// The "geolocation" feature.
760 Geolocation,
761 /// The "gyroscope" feature.
762 Gyroscope,
763 /// The "magnetometer" feature.
764 Magnetometer,
765 /// The "microphone" feature.
766 Microphone,
767 /// The "midi" feature.
768 Midi,
769 /// The "navigation-override" feature.
770 NavigationOverride,
771 /// The "payment" feature.
772 Payment,
773 /// The "picture-in-picture" feature.
774 PictureInPicture,
775 /// The "publickey-credentials-get" feature.
776 PublickeyCredentialsGet,
777 /// The "screen-wake-lock" feature.
778 ScreenWakeLock,
779 /// The "sync-xhr" feature.
780 SyncXhr,
781 /// The "usb" feature.
782 Usb,
783 /// The "web-share" feature.
784 WebShare,
785 /// The "xr-spatial-tracking" feature.
786 XrSpatialTracking,
787
788 // Proposed.
789
790 /// The "clipboard-read" feature.
791 ClipboardRead,
792 /// The "clipboard-write" feature.
793 ClipboardWrite,
794 /// The "gamepad" feature.
795 Gamepad,
796 /// The "speaker-selection" feature.
797 SpeakerSelection,
798 /// The "interest-cohort" feature.
799 InterestCohort,
800
801 // Experimental.
802
803 /// The "conversion-measurement" feature.
804 ConversionMeasurement,
805 /// The "focus-without-user-activation" feature.
806 FocusWithoutUserActivation,
807 /// The "hid" feature.
808 Hid,
809 /// The "idle-detection" feature.
810 IdleDetection,
811 /// The "serial" feature.
812 Serial,
813 /// The "sync-script" feature.
814 SyncScript,
815 /// The "trust-token-redemption" feature.
816 TrustTokenRedemption,
817 /// The "vertical-scroll" feature.
818 VerticalScroll,
819}
820
821impl Feature {
822 /// Returns the feature string as it appears in the header.
823 ///
824 /// # Example
825 ///
826 /// ```rust
827 /// use rocket::shield::Feature;
828 ///
829 /// assert_eq!(Feature::Camera.as_str(), "camera");
830 /// assert_eq!(Feature::SyncScript.as_str(), "sync-script");
831 /// ```
832 pub const fn as_str(self) -> &'static str {
833 use Feature::*;
834
835 match self {
836 Accelerometer => "accelerometer",
837 AmbientLightSensor => "ambient-light-sensor",
838 Autoplay => "autoplay",
839 Battery => "battery",
840 Camera => "camera",
841 CrossOriginIsolated => "cross-origin-isolated",
842 Displaycapture => "display-capture",
843 DocumentDomain => "document-domain",
844 EncryptedMedia => "encrypted-media",
845 ExecutionWhileNotRendered => "execution-while-not-rendered",
846 ExecutionWhileOutOfviewport => "execution-while-out-of-viewport",
847 Fullscreen => "fullscreen",
848 Geolocation => "geolocation",
849 Gyroscope => "gyroscope",
850 Magnetometer => "magnetometer",
851 Microphone => "microphone",
852 Midi => "midi",
853 NavigationOverride => "navigation-override",
854 Payment => "payment",
855 PictureInPicture => "picture-in-picture",
856 PublickeyCredentialsGet => "publickey-credentials-get",
857 ScreenWakeLock => "screen-wake-lock",
858 SyncXhr => "sync-xhr",
859 Usb => "usb",
860 WebShare => "web-share",
861 XrSpatialTracking => "xr-spatial-tracking",
862
863 ClipboardRead => "clipboard-read",
864 ClipboardWrite => "clipboard-write",
865 Gamepad => "gamepad",
866 SpeakerSelection => "speaker-selection",
867 InterestCohort => "interest-cohort",
868
869 ConversionMeasurement => "conversion-measurement",
870 FocusWithoutUserActivation => "focus-without-user-activation",
871 Hid => "hid",
872 IdleDetection => "idle-detection",
873 Serial => "serial",
874 SyncScript => "sync-script",
875 TrustTokenRedemption => "trust-token-redemption",
876 VerticalScroll => "vertical-scroll",
877 }
878 }
879}
880
881impl fmt::Display for Feature {
882 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
883 self.as_str().fmt(f)
884 }
885}