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