helmet_core/lib.rs
1//! Helmet is a collection of HTTP headers that help secure your app by setting various HTTP headers.
2//!
3//! `helmet-core` provides the core functionality of Helmet, vie convenient builders to configure the library.
4//!
5//! The library can be adapted to different frameworks by wrapping the `Helmet` struct in a way that suits the framework. For reference implementations see the [ntex-helmet](https://crates.io/crates/ntex-helmet) crate or the [axum-helmet](https://crates.io/crates/axum-helmet) crate.
6//!
7//! It is based on the [Helmet](https://helmetjs.github.io/) library for Node.js and is highly configurable.
8//!
9//! # Usage
10//!
11//! ```no_run
12//! use helmet_core::{ContentSecurityPolicy, CrossOriginOpenerPolicy, Helmet};
13//!
14//! let helmet = Helmet::new()
15//! .add(
16//! ContentSecurityPolicy::new()
17//! .child_src(vec!["'self'", "https://youtube.com"])
18//! .connect_src(vec!["'self'", "https://youtube.com"])
19//! .default_src(vec!["'self'", "https://youtube.com"])
20//! .font_src(vec!["'self'", "https://youtube.com"]),
21//! )
22//! .add(CrossOriginOpenerPolicy::same_origin_allow_popups());
23//! ```
24//!
25//! By default Helmet will set the following headers:
26//!
27//! ```text
28//! Content-Security-Policy: default-src 'self'; base-uri 'self'; font-src 'self' https: data:; form-action 'self'; frame-ancestors 'self'; img-src 'self' data:; object-src 'none'; script-src 'self'; script-src-attr 'none'; style-src 'self' https: 'unsafe-inline'; upgrade-insecure-requests
29//! Cross-Origin-Opener-Policy: same-origin
30//! Cross-Origin-Resource-Policy: same-origin
31//! Origin-Agent-Cluster: ?1
32//! Referrer-Policy: no-referrer
33//! Strict-Transport-Security: max-age=15552000; includeSubDomains
34//! X-Content-Type-Options: nosniff
35//! X-DNS-Prefetch-Control: off
36//! X-Download-Options: noopen
37//! X-Frame-Options: sameorigin
38//! X-Permitted-Cross-Domain-Policies: none
39//! X-XSS-Protection: 0
40//! ```
41//!
42//! This might be a good starting point for most users, but it is highly recommended to spend some time with the documentation for each header, and adjust them to your needs.
43//!
44//! # Configuration
45//!
46//! By default if you construct a new instance of `Helmet` it will not set any headers.
47//!
48//! The `helmet-core` crate helps you configure Helmet by providing convenient builders for each header.
49use core::fmt::Display;
50
51/// Header trait
52///
53/// Allows custom headers to be added to the response
54///
55/// # Examples
56///
57/// ```
58/// use helmet_core::Header;
59///
60/// struct MyHeader;
61///
62/// impl Into<Header> for MyHeader {
63/// fn into(self) -> Header {
64/// ("My-Header", "My-Value".to_owned())
65/// }
66/// }
67/// ```
68pub type Header = (&'static str, String);
69
70/// Manages `Cross-Origin-Embedder-Policy` header
71///
72/// The Cross-Origin-Embedder-Policy HTTP response header prevents a document from loading any cross-origin resources that do not explicitly grant the document permission (via CORS headers) to load them.
73///
74/// # Values
75///
76/// - unsafe-none: The document is not subject to any Cross-Origin-Embedder-Policy restrictions.
77/// - require-corp: The document is subject to Cross-Origin-Embedder-Policy restrictions.
78/// - credentialless: The document is subject to Cross-Origin-Embedder-Policy restrictions, and is not allowed to request credentials (e.g. cookies, certificates, HTTP authentication) from the user.
79///
80/// # Examples
81///
82/// ```
83/// use helmet_core::CrossOriginEmbedderPolicy;
84///
85/// let cross_origin_embedder_policy = CrossOriginEmbedderPolicy::unsafe_none();
86///
87/// let cross_origin_embedder_policy = CrossOriginEmbedderPolicy::require_corp();
88///
89/// let cross_origin_embedder_policy = CrossOriginEmbedderPolicy::credentialless();
90/// ```
91#[derive(Clone)]
92pub enum CrossOriginEmbedderPolicy {
93 UnsafeNone,
94 RequireCorp,
95 Credentialless,
96}
97
98impl CrossOriginEmbedderPolicy {
99 pub fn unsafe_none() -> Self {
100 Self::UnsafeNone
101 }
102
103 pub fn require_corp() -> Self {
104 Self::RequireCorp
105 }
106
107 pub fn credentialless() -> Self {
108 Self::Credentialless
109 }
110}
111
112impl CrossOriginEmbedderPolicy {
113 pub fn as_str(&self) -> &'static str {
114 match self {
115 CrossOriginEmbedderPolicy::UnsafeNone => "unsafe-none",
116 CrossOriginEmbedderPolicy::RequireCorp => "require-corp",
117 CrossOriginEmbedderPolicy::Credentialless => "credentialless",
118 }
119 }
120}
121
122impl From<CrossOriginEmbedderPolicy> for Header {
123 fn from(val: CrossOriginEmbedderPolicy) -> Self {
124 ("Cross-Origin-Embedder-Policy", val.as_str().to_owned())
125 }
126}
127
128/// Manages `Cross-Origin-Opener-Policy` header
129///
130/// The Cross-Origin-Opener-Policy HTTP response header restricts how selected resources are allowed to interact with the document's browsing context in response to user navigation. Each resource can declare an opener policy which applies to the resource's corresponding browsing context.
131///
132/// # Values
133///
134/// - same-origin: The resource's browsing context is the same-origin as the document's browsing context.
135/// - same-origin-allow-popups: The resource's browsing context is the same-origin as the document's browsing context, and the resource is allowed to open new browsing contexts.
136/// - unsafe-none: The resource's browsing context is cross-origin with the document's browsing context.
137///
138/// # Examples
139///
140/// ```
141/// use helmet_core::CrossOriginOpenerPolicy;
142///
143/// let cross_origin_opener_policy = CrossOriginOpenerPolicy::same_origin();
144/// ```
145#[derive(Clone)]
146pub enum CrossOriginOpenerPolicy {
147 SameOrigin,
148 SameOriginAllowPopups,
149 UnsafeNone,
150}
151
152impl CrossOriginOpenerPolicy {
153 pub fn same_origin() -> Self {
154 Self::SameOrigin
155 }
156
157 pub fn same_origin_allow_popups() -> Self {
158 Self::SameOriginAllowPopups
159 }
160
161 pub fn unsafe_none() -> Self {
162 Self::UnsafeNone
163 }
164}
165
166impl CrossOriginOpenerPolicy {
167 fn as_str(&self) -> &'static str {
168 match self {
169 CrossOriginOpenerPolicy::SameOrigin => "same-origin",
170 CrossOriginOpenerPolicy::SameOriginAllowPopups => "same-origin-allow-popups",
171 CrossOriginOpenerPolicy::UnsafeNone => "unsafe-none",
172 }
173 }
174}
175
176impl From<CrossOriginOpenerPolicy> for Header {
177 fn from(val: CrossOriginOpenerPolicy) -> Self {
178 ("Cross-Origin-Opener-Policy", val.as_str().to_owned())
179 }
180}
181
182/// Manages `Cross-Origin-Resource-Policy` header
183///
184/// The Cross-Origin-Resource-Policy HTTP response header conveys a desire that the browser blocks no-cors cross-origin/cross-site requests to the given resource.
185///
186/// # Values
187///
188/// - same-origin: The resource is same-origin to the document.
189/// - same-site: The resource is same-site to the document.
190/// - cross-origin: The resource is cross-origin to the document.
191///
192/// # Examples
193///
194/// ```
195/// use helmet_core::CrossOriginResourcePolicy;
196///
197/// let cross_origin_resource_policy = CrossOriginResourcePolicy::same_origin();
198/// ```
199#[derive(Clone)]
200pub enum CrossOriginResourcePolicy {
201 SameOrigin,
202 SameSite,
203 CrossOrigin,
204}
205
206impl CrossOriginResourcePolicy {
207 pub fn same_origin() -> Self {
208 Self::SameOrigin
209 }
210
211 pub fn same_site() -> Self {
212 Self::SameSite
213 }
214
215 pub fn cross_origin() -> Self {
216 Self::CrossOrigin
217 }
218}
219
220impl CrossOriginResourcePolicy {
221 fn as_str(&self) -> &'static str {
222 match self {
223 CrossOriginResourcePolicy::SameOrigin => "same-origin",
224 CrossOriginResourcePolicy::SameSite => "same-site",
225 CrossOriginResourcePolicy::CrossOrigin => "cross-origin",
226 }
227 }
228}
229
230impl From<CrossOriginResourcePolicy> for Header {
231 fn from(val: CrossOriginResourcePolicy) -> Self {
232 ("Cross-Origin-Resource-Policy", val.as_str().to_owned())
233 }
234}
235
236/// Manages `Origin-Agent-Cluster` header
237///
238/// The Origin-Agent-Cluster HTTP request header indicates that the client prefers an "origin agent cluster" (OAC) for the origin of the resource being requested. An OAC is a cluster of servers that are controlled by the same entity as the origin server, and that are geographically close to the client. The OAC is used to provide the client with a better experience, for example by serving content from a server that is close to the client, or by serving content that is optimized for the client's device.
239///
240/// # Values
241///
242/// - 0: The client does not prefer an OAC.
243/// - 1: The client prefers an OAC.
244///
245/// # Examples
246///
247/// ```
248/// use helmet_core::OriginAgentCluster;
249///
250/// let origin_agent_cluster = OriginAgentCluster::new(true);
251/// ```
252#[derive(Clone)]
253pub struct OriginAgentCluster(bool);
254
255impl OriginAgentCluster {
256 pub fn new(prefer_mobile_experience: bool) -> Self {
257 Self(prefer_mobile_experience)
258 }
259}
260
261impl OriginAgentCluster {
262 fn as_str(&self) -> &'static str {
263 if self.0 {
264 "?1"
265 } else {
266 "?0"
267 }
268 }
269}
270
271impl From<OriginAgentCluster> for Header {
272 fn from(val: OriginAgentCluster) -> Self {
273 ("Origin-Agent-Cluster", val.as_str().to_owned())
274 }
275}
276
277/// Manages `Referrer-Policy` header
278///
279/// The Referrer-Policy HTTP response header controls how much referrer information (sent via the Referer header) should be included with requests.
280///
281/// # Values
282///
283/// - no-referrer: The Referer header will be omitted entirely. No referrer information is sent along with requests.
284/// - no-referrer-when-downgrade: The Referer header will be omitted entirely. However, if the protected resource URL scheme is HTTPS, then the full path will still be sent as a referrer.
285/// - origin: Only send the origin of the document as the referrer in all cases. The document https://example.com/page.html will send the referrer https://example.com/.
286/// - origin-when-cross-origin: Send a full URL when performing a same-origin request, but only send the origin of the document for other cases.
287/// - same-origin: A referrer will be sent for same-site origins, but cross-origin requests will contain no referrer information.
288/// - strict-origin: Only send the origin of the document as the referrer when the protocol security level stays the same (HTTPS→HTTPS), but don't send it to a less secure destination (HTTPS→HTTP).
289/// - strict-origin-when-cross-origin: Send a full URL when performing a same-origin request, only send the origin when the protocol security level stays the same (HTTPS→HTTPS), and send no header to a less secure destination (HTTPS→HTTP).
290/// - unsafe-url: Send a full URL (stripped from parameters) when performing a same-origin or cross-origin request. This policy will leak origins and paths from TLS-protected resources to insecure origins. Carefully consider the impact of this setting.
291///
292/// # Examples
293///
294/// ```
295/// use helmet_core::ReferrerPolicy;
296///
297/// let referrer_policy = ReferrerPolicy::no_referrer();
298/// ```
299#[derive(Clone)]
300pub enum ReferrerPolicy {
301 NoReferrer,
302 NoReferrerWhenDowngrade,
303 Origin,
304 OriginWhenCrossOrigin,
305 SameOrigin,
306 StrictOrigin,
307 StrictOriginWhenCrossOrigin,
308 UnsafeUrl,
309}
310
311impl ReferrerPolicy {
312 pub fn no_referrer() -> Self {
313 Self::NoReferrer
314 }
315
316 pub fn no_referrer_when_downgrade() -> Self {
317 Self::NoReferrerWhenDowngrade
318 }
319
320 pub fn origin() -> Self {
321 Self::Origin
322 }
323
324 pub fn origin_when_cross_origin() -> Self {
325 Self::OriginWhenCrossOrigin
326 }
327
328 pub fn same_origin() -> Self {
329 Self::SameOrigin
330 }
331
332 pub fn strict_origin() -> Self {
333 Self::StrictOrigin
334 }
335
336 pub fn strict_origin_when_cross_origin() -> Self {
337 Self::StrictOriginWhenCrossOrigin
338 }
339
340 pub fn unsafe_url() -> Self {
341 Self::UnsafeUrl
342 }
343}
344
345impl ReferrerPolicy {
346 fn as_str(&self) -> &'static str {
347 match self {
348 ReferrerPolicy::NoReferrer => "no-referrer",
349 ReferrerPolicy::NoReferrerWhenDowngrade => "no-referrer-when-downgrade",
350 ReferrerPolicy::Origin => "origin",
351 ReferrerPolicy::OriginWhenCrossOrigin => "origin-when-cross-origin",
352 ReferrerPolicy::SameOrigin => "same-origin",
353 ReferrerPolicy::StrictOrigin => "strict-origin",
354 ReferrerPolicy::StrictOriginWhenCrossOrigin => "strict-origin-when-cross-origin",
355 ReferrerPolicy::UnsafeUrl => "unsafe-url",
356 }
357 }
358}
359
360impl From<ReferrerPolicy> for Header {
361 fn from(val: ReferrerPolicy) -> Self {
362 ("Referrer-Policy", val.as_str().to_owned())
363 }
364}
365
366/// Manages `Strict-Transport-Security` header
367///
368/// The Strict-Transport-Security HTTP response header (often abbreviated as HSTS) lets a web site tell browsers that it should only be accessed using HTTPS, instead of using HTTP.
369///
370/// # Values
371///
372/// - max-age: The time, in seconds, that the browser should remember that a site is only to be accessed using HTTPS.
373/// - includeSubDomains: If this optional parameter is specified, this rule applies to all of the site's subdomains as well.
374/// - preload: If this optional parameter is specified, this rule applies to all of the site's subdomains as well.
375///
376/// # Examples
377///
378/// ```
379/// use helmet_core::StrictTransportSecurity;
380///
381/// let strict_transport_security = StrictTransportSecurity::default();
382///
383/// let custom_strict_transport_security = StrictTransportSecurity::default()
384/// .max_age(31536000)
385/// .include_sub_domains()
386/// .preload();
387/// ```
388#[derive(Clone)]
389pub struct StrictTransportSecurity {
390 max_age: u32,
391 include_sub_domains: bool,
392 preload: bool,
393}
394
395impl StrictTransportSecurity {
396 pub fn new() -> Self {
397 Self::default()
398 }
399
400 pub fn max_age(mut self, max_age: u32) -> Self {
401 self.max_age = max_age;
402 self
403 }
404
405 pub fn include_sub_domains(mut self) -> Self {
406 self.include_sub_domains = true;
407 self
408 }
409
410 pub fn preload(mut self) -> Self {
411 self.preload = true;
412 self
413 }
414}
415
416impl Default for StrictTransportSecurity {
417 fn default() -> Self {
418 Self {
419 max_age: 31536000,
420 include_sub_domains: false,
421 preload: false,
422 }
423 }
424}
425
426impl Display for StrictTransportSecurity {
427 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
428 write!(f, "max-age={}", self.max_age)?;
429 if self.include_sub_domains {
430 write!(f, "; includeSubDomains")?;
431 }
432 if self.preload {
433 write!(f, "; preload")?;
434 }
435 Ok(())
436 }
437}
438
439impl From<StrictTransportSecurity> for Header {
440 fn from(val: StrictTransportSecurity) -> Self {
441 ("Strict-Transport-Security", val.to_string())
442 }
443}
444
445/// Manages `X-Content-Type-Options` header
446///
447/// The X-Content-Type-Options response HTTP header is a marker used by the server to indicate that the MIME types advertised in the Content-Type headers should not be changed and be followed. This allows to opt-out of MIME type sniffing, or, in other words, it is a way to say that the webmasters knew what they were doing.
448///
449/// # Values
450///
451/// - nosniff: Prevents the browser from MIME-sniffing a response away from the declared content-type. This also applies to Google Chrome, when downloading extensions.
452///
453/// # Examples
454///
455/// ```
456/// use helmet_core::XContentTypeOptions;
457///
458/// let x_content_type_options = XContentTypeOptions::nosniff();
459/// ```
460#[derive(Clone)]
461pub enum XContentTypeOptions {
462 NoSniff,
463}
464
465impl XContentTypeOptions {
466 pub fn nosniff() -> Self {
467 Self::NoSniff
468 }
469}
470
471impl XContentTypeOptions {
472 fn as_str(&self) -> &'static str {
473 match self {
474 XContentTypeOptions::NoSniff => "nosniff",
475 }
476 }
477}
478
479impl Display for XContentTypeOptions {
480 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
481 match self {
482 XContentTypeOptions::NoSniff => write!(f, "nosniff"),
483 }
484 }
485}
486
487impl From<XContentTypeOptions> for Header {
488 fn from(val: XContentTypeOptions) -> Self {
489 ("X-Content-Type-Options", val.as_str().to_owned())
490 }
491}
492
493/// Manages `X-DNS-Prefetch-Control` header
494///
495/// The X-DNS-Prefetch-Control HTTP response header controls DNS prefetching, a feature by which browsers proactively perform domain name resolution on both links that the user may choose to follow as well as URLs for items referenced by the document, including images, CSS, JavaScript, and so forth.
496///
497/// # Values
498///
499/// - off: Disable DNS prefetching.
500/// - on: Enable DNS prefetching, allowing the browser to proactively perform domain name resolution on both links that the user may choose to follow as well as URLs for items referenced by the document, including images, CSS, JavaScript, and so forth.
501///
502/// # Examples
503///
504/// ```
505/// use helmet_core::XDNSPrefetchControl;
506///
507/// let x_dns_prefetch_control = XDNSPrefetchControl::off();
508/// ```
509#[derive(Clone)]
510pub enum XDNSPrefetchControl {
511 Off,
512 On,
513}
514
515impl XDNSPrefetchControl {
516 pub fn off() -> Self {
517 Self::Off
518 }
519
520 pub fn on() -> Self {
521 Self::On
522 }
523}
524
525impl XDNSPrefetchControl {
526 fn as_str(&self) -> &'static str {
527 match self {
528 XDNSPrefetchControl::Off => "off",
529 XDNSPrefetchControl::On => "on",
530 }
531 }
532}
533
534impl Display for XDNSPrefetchControl {
535 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
536 match self {
537 XDNSPrefetchControl::Off => write!(f, "off"),
538 XDNSPrefetchControl::On => write!(f, "on"),
539 }
540 }
541}
542
543impl From<XDNSPrefetchControl> for Header {
544 fn from(val: XDNSPrefetchControl) -> Self {
545 ("X-DNS-Prefetch-Control", val.as_str().to_owned())
546 }
547}
548
549/// Manages `X-Download-Options` header
550///
551/// The X-Download-Options HTTP response header indicates that the browser (Internet Explorer) should not display the option to "Open" a file that has been downloaded from an application, to prevent phishing attacks that could trick users into opening potentially malicious content that could infect their computer.
552///
553/// # Values
554///
555/// - noopen: Prevents Internet Explorer from executing downloads in your site’s context.
556///
557/// # Examples
558///
559/// ```
560/// use helmet_core::XDownloadOptions;
561///
562/// let x_download_options = XDownloadOptions::noopen();
563/// ```
564#[derive(Clone)]
565pub enum XDownloadOptions {
566 NoOpen,
567}
568
569impl XDownloadOptions {
570 pub fn noopen() -> Self {
571 Self::NoOpen
572 }
573}
574
575impl XDownloadOptions {
576 fn as_str(&self) -> &'static str {
577 match self {
578 XDownloadOptions::NoOpen => "noopen",
579 }
580 }
581}
582
583impl Display for XDownloadOptions {
584 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
585 match self {
586 XDownloadOptions::NoOpen => write!(f, "noopen"),
587 }
588 }
589}
590
591impl From<XDownloadOptions> for Header {
592 fn from(val: XDownloadOptions) -> Self {
593 ("X-Download-Options", val.as_str().to_owned())
594 }
595}
596
597/// Manages `X-Frame-Options` header
598///
599/// The X-Frame-Options HTTP response header can be used to to avoid click-jacking attacks by preventing the content to be included in other websites.
600///
601/// # Values
602///
603/// - deny: The page cannot be displayed in a frame, regardless of the site attempting to do so.
604/// - sameorigin: The page can only be displayed in a frame on the same origin as the page itself.
605/// - allow-from: **Deprecated.** Ignored by all modern browsers. Use `ContentSecurityPolicy::new().frame_ancestors(...)` instead.
606///
607/// # Examples
608///
609/// ```
610/// use helmet_core::XFrameOptions;
611///
612/// let x_frame_options = XFrameOptions::deny();
613///
614/// let x_frame_options = XFrameOptions::same_origin();
615/// ```
616#[derive(Clone)]
617pub enum XFrameOptions {
618 Deny,
619 SameOrigin,
620 #[deprecated(
621 note = "ALLOW-FROM is ignored by modern browsers. Use ContentSecurityPolicy::new().frame_ancestors(...) instead."
622 )]
623 AllowFrom(String),
624}
625
626impl XFrameOptions {
627 pub fn deny() -> Self {
628 Self::Deny
629 }
630
631 pub fn same_origin() -> Self {
632 Self::SameOrigin
633 }
634
635 #[deprecated(
636 note = "ALLOW-FROM is ignored by modern browsers. Use ContentSecurityPolicy::new().frame_ancestors(...) instead."
637 )]
638 pub fn allow_from(uri: &str) -> Self {
639 #[allow(deprecated)]
640 Self::AllowFrom(uri.to_string())
641 }
642}
643
644impl Display for XFrameOptions {
645 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
646 #[allow(deprecated)]
647 match self {
648 XFrameOptions::Deny => write!(f, "DENY"),
649 XFrameOptions::SameOrigin => write!(f, "SAMEORIGIN"),
650 XFrameOptions::AllowFrom(uri) => write!(f, "ALLOW-FROM {}", uri),
651 }
652 }
653}
654
655impl From<XFrameOptions> for Header {
656 fn from(val: XFrameOptions) -> Self {
657 ("X-Frame-Options", val.to_string())
658 }
659}
660
661/// Manages `X-Permitted-Cross-Domain-Policies` header
662///
663/// The X-Permitted-Cross-Domain-Policies HTTP response header determines whether cross-domain policy files (crossdomain.xml and clientaccesspolicy.xml) will be ignored by Flash and Adobe Acrobat in subsequent requests.
664///
665/// # Values
666///
667/// - none: No policy file is allowed.
668/// - master-only: Only a master policy file, but no other policy files, is allowed.
669/// - by-content-type: A policy file is allowed if its MIME type matches the Content-Type of the requested resource.
670/// - by-ftp-filename: A policy file is allowed if its URL matches the URL of the requested resource.
671/// - all: Any policy file is allowed.
672///
673/// # Examples
674///
675/// ```
676/// use helmet_core::XPermittedCrossDomainPolicies;
677///
678/// let x_permitted_cross_domain_policies = XPermittedCrossDomainPolicies::none();
679///
680/// let x_permitted_cross_domain_policies = XPermittedCrossDomainPolicies::master_only();
681///
682/// let x_permitted_cross_domain_policies = XPermittedCrossDomainPolicies::by_content_type();
683///
684/// let x_permitted_cross_domain_policies = XPermittedCrossDomainPolicies::by_ftp_filename();
685///
686/// let x_permitted_cross_domain_policies = XPermittedCrossDomainPolicies::all();
687/// ```
688#[derive(Clone)]
689pub enum XPermittedCrossDomainPolicies {
690 None,
691 MasterOnly,
692 ByContentType,
693 ByFtpFilename,
694 All,
695}
696
697impl XPermittedCrossDomainPolicies {
698 pub fn none() -> Self {
699 Self::None
700 }
701
702 pub fn master_only() -> Self {
703 Self::MasterOnly
704 }
705
706 pub fn by_content_type() -> Self {
707 Self::ByContentType
708 }
709
710 pub fn by_ftp_filename() -> Self {
711 Self::ByFtpFilename
712 }
713
714 pub fn all() -> Self {
715 Self::All
716 }
717}
718
719impl XPermittedCrossDomainPolicies {
720 fn as_str(&self) -> &'static str {
721 match self {
722 XPermittedCrossDomainPolicies::None => "none",
723 XPermittedCrossDomainPolicies::MasterOnly => "master-only",
724 XPermittedCrossDomainPolicies::ByContentType => "by-content-type",
725 XPermittedCrossDomainPolicies::ByFtpFilename => "by-ftp-filename",
726 XPermittedCrossDomainPolicies::All => "all",
727 }
728 }
729}
730
731impl Display for XPermittedCrossDomainPolicies {
732 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
733 match self {
734 XPermittedCrossDomainPolicies::None => write!(f, "none"),
735 XPermittedCrossDomainPolicies::MasterOnly => write!(f, "master-only"),
736 XPermittedCrossDomainPolicies::ByContentType => write!(f, "by-content-type"),
737 XPermittedCrossDomainPolicies::ByFtpFilename => write!(f, "by-ftp-filename"),
738 XPermittedCrossDomainPolicies::All => write!(f, "all"),
739 }
740 }
741}
742
743impl From<XPermittedCrossDomainPolicies> for Header {
744 fn from(val: XPermittedCrossDomainPolicies) -> Self {
745 ("X-Permitted-Cross-Domain-Policies", val.as_str().to_owned())
746 }
747}
748
749/// Manages `X-XSS-Protection` header
750///
751/// The HTTP X-XSS-Protection response header is a feature of Internet Explorer, Chrome and Safari that stops pages from loading when they detect reflected cross-site scripting (XSS) attacks. Although these protections are largely unnecessary in modern browsers when sites implement a strong Content-Security-Policy that disables the use of inline JavaScript ('unsafe-inline'), they can still provide protections for users of older web browsers that don't yet support CSP.
752///
753/// # Values
754///
755/// - 0: Disables XSS filtering.
756/// - 1: Enables XSS filtering (usually default in browsers).
757/// - 1; mode=block: Enables XSS filtering. Rather than sanitizing the page, the browser will prevent rendering of the page if an attack is detected.
758/// - 1; report=<reporting-URI>: Enables XSS filtering. If a cross-site scripting attack is detected, the browser will sanitize the page and report the violation. This uses the functionality of the CSP report-uri directive to send a report.
759///
760/// # Examples
761///
762/// ```
763/// use helmet_core::XXSSProtection;
764///
765/// let x_xss_protection = XXSSProtection::on();
766///
767/// let x_xss_protection = XXSSProtection::off();
768///
769/// let x_xss_protection = XXSSProtection::on().mode_block();
770///
771/// let x_xss_protection = XXSSProtection::on().report("https://example.com");
772///
773/// let x_xss_protection = XXSSProtection::on().mode_block().report("https://example.com");
774/// ```
775#[derive(Clone)]
776pub struct XXSSProtection {
777 on: bool,
778 mode_block: bool,
779 report: Option<String>,
780}
781
782impl XXSSProtection {
783 /// Disables XSS filtering.
784 pub fn off() -> Self {
785 Self {
786 on: false,
787 mode_block: false,
788 report: None,
789 }
790 }
791
792 /// Enables XSS filtering (usually default in browsers).
793 pub fn on() -> Self {
794 Self {
795 on: true,
796 mode_block: false,
797 report: None,
798 }
799 }
800
801 /// Enables XSS filtering. Rather than sanitizing the page, the browser will prevent rendering of the page if an attack is detected.
802 pub fn mode_block(mut self) -> Self {
803 self.mode_block = true;
804 self
805 }
806
807 /// Enables XSS filtering. If a cross-site scripting attack is detected, the browser will sanitize the page and report the violation. This uses the functionality of the CSP report-uri directive to send a report.
808 pub fn report(mut self, report: &str) -> Self {
809 self.report = Some(report.to_string());
810 self
811 }
812}
813
814impl Display for XXSSProtection {
815 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
816 if self.on {
817 write!(f, "1")?;
818 if self.mode_block {
819 write!(f, "; mode=block")?;
820 }
821 if let Some(report) = &self.report {
822 write!(f, "; report={}", report)?;
823 }
824 } else {
825 write!(f, "0")?;
826 }
827 Ok(())
828 }
829}
830
831impl From<XXSSProtection> for Header {
832 fn from(val: XXSSProtection) -> Self {
833 ("X-XSS-Protection", val.to_string())
834 }
835}
836
837/// Manages `X-Powered-By` header
838///
839/// ntex does not set `X-Powered-By` header by default.
840/// Instead of silencing the header, Helmet allows you to set it to a custom value.
841/// This can be useful against primitive fingerprinting.
842///
843/// # Examples
844///
845/// ```
846/// use helmet_core::XPoweredBy;
847///
848/// let x_powered_by = XPoweredBy::new("PHP 4.2.0");
849/// ```
850#[derive(Clone)]
851pub struct XPoweredBy(String);
852
853impl XPoweredBy {
854 /// Set the `X-Powered-By` header to a custom value.
855 pub fn new(comment: &str) -> Self {
856 Self(comment.to_string())
857 }
858}
859
860impl Display for XPoweredBy {
861 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
862 write!(f, "{}", self.0)?;
863 Ok(())
864 }
865}
866
867impl From<XPoweredBy> for Header {
868 fn from(val: XPoweredBy) -> Self {
869 ("X-Powered-By", val.to_string())
870 }
871}
872
873/// Manages `Content-Security-Policy` header
874///
875/// The HTTP Content-Security-Policy response header allows web site administrators to control resources the user agent is allowed to load for a given page. With a few exceptions, policies mostly involve specifying server origins and script endpoints. This helps guard against cross-site scripting attacks (XSS).
876///
877/// # Directives
878///
879/// - child-src: Defines valid sources for web workers and nested browsing contexts loaded using elements such as `<frame>` and `<iframe>`.
880/// - connect-src: Applies to XMLHttpRequest (AJAX), WebSocket or EventSource. If not allowed the browser emulates a 400 HTTP status code.
881/// - default-src: The default-src is the default policy for loading content such as JavaScript, Images, CSS, Font's, AJAX requests, Frames, HTML5 Media. See the list of directives to see which values are allowed as default.
882/// - font-src: Defines valid sources for fonts loaded using @font-face.
883/// - frame-src: Defines valid sources for nested browsing contexts loading using elements such as `<frame>` and `<iframe>`.
884/// - img-src: Defines valid sources of images and favicons.
885/// - manifest-src: Specifies which manifest can be applied to the resource.
886/// - media-src: Defines valid sources for loading media using the `<audio>` and `<video>` elements.
887/// - object-src: Defines valid sources for the `<object>`, `<embed>`, and `<applet>` elements.
888/// - prefetch-src: Specifies which referrer to use when fetching the resource.
889/// - script-src: Defines valid sources for JavaScript.
890/// - script-src-elem: Defines valid sources for JavaScript inline event handlers.
891/// - script-src-attr: Defines valid sources for JavaScript inline event handlers.
892/// - style-src: Defines valid sources for stylesheets.
893/// - style-src-elem: Defines valid sources for stylesheets inline event handlers.
894/// - style-src-attr: Defines valid sources for stylesheets inline event handlers.
895/// - worker-src: Defines valid sources for Worker, SharedWorker, or ServiceWorker scripts.
896/// - base-uri: Restricts the URLs which can be used in a document's `<base>` element.
897/// - sandbox: Enables a sandbox for the requested resource similar to the iframe sandbox attribute. The sandbox applies a same origin policy, prevents popups, plugins and script execution is blocked. You can keep the sandbox value empty to keep all restrictions in place, or add values: allow-forms allow-same-origin allow-scripts allow-popups, allow-modals, allow-orientation-lock, allow-pointer-lock, allow-presentation, allow-popups-to-escape-sandbox, allow-top-navigation, allow-top-navigation-by-user-activation.
898/// - form-action: Restricts the URLs which can be used as the target of a form submissions from a given context.
899/// - frame-ancestors: Specifies valid parents that may embed a page using `<frame>`, `<iframe>`, `<object>`, `<embed>`, or `<applet>`.
900/// - report-to: Specifies the endpoint name (defined via `Reporting-Endpoints` header) to send violation reports to.
901/// - report-uri: Specifies URL(s) to send violation reports to (deprecated but still widely supported).
902/// - require-trusted-types-for: Specifies which trusted types are required by a resource.
903/// - trusted-types: Specifies which trusted types are defined by a resource.
904/// - upgrade-insecure-requests: Block HTTP requests on insecure elements.
905///
906/// # Examples
907///
908/// ```
909/// use helmet_core::ContentSecurityPolicy;
910///
911/// let content_security_policy = ContentSecurityPolicy::default()
912/// .child_src(vec!["'self'", "https://youtube.com"])
913/// .connect_src(vec!["'self'", "https://youtube.com"])
914/// .default_src(vec!["'self'", "https://youtube.com"])
915/// .font_src(vec!["'self'", "https://youtube.com"]);
916/// ```
917///
918/// ## Report only
919///
920/// In report only mode, the browser will not block the request, but will send a report to the specified URI.
921///
922/// Make sure to set the `report-to` and/or `report-uri` directives.
923///
924/// ```
925/// use helmet_core::ContentSecurityPolicy;
926///
927/// let content_security_policy = ContentSecurityPolicy::default()
928/// .child_src(vec!["'self'", "https://youtube.com"])
929/// .report_to("csp-endpoint")
930/// .report_uri(vec!["https://example.com/report"])
931/// .report_only();
932/// ```
933#[derive(Clone)]
934pub struct ContentSecurityPolicy<'a> {
935 child_src: Option<Vec<&'a str>>,
936 connect_src: Option<Vec<&'a str>>,
937 default_src: Option<Vec<&'a str>>,
938 font_src: Option<Vec<&'a str>>,
939 frame_src: Option<Vec<&'a str>>,
940 img_src: Option<Vec<&'a str>>,
941 manifest_src: Option<Vec<&'a str>>,
942 media_src: Option<Vec<&'a str>>,
943 object_src: Option<Vec<&'a str>>,
944 prefetch_src: Option<Vec<&'a str>>,
945 script_src: Option<Vec<&'a str>>,
946 script_src_elem: Option<Vec<&'a str>>,
947 script_src_attr: Option<Vec<&'a str>>,
948 style_src: Option<Vec<&'a str>>,
949 style_src_elem: Option<Vec<&'a str>>,
950 style_src_attr: Option<Vec<&'a str>>,
951 worker_src: Option<Vec<&'a str>>,
952 base_uri: Option<Vec<&'a str>>,
953 sandbox: Option<Vec<&'a str>>,
954 form_action: Option<Vec<&'a str>>,
955 frame_ancestors: Option<Vec<&'a str>>,
956 report_to: Option<&'a str>,
957 report_uri: Option<Vec<&'a str>>,
958 require_trusted_types_for: Option<Vec<&'a str>>,
959 trusted_types: Option<Vec<&'a str>>,
960 upgrade_insecure_requests: bool,
961 report_only: bool,
962}
963
964impl<'a> ContentSecurityPolicy<'a> {
965 pub fn new() -> Self {
966 Self {
967 child_src: None,
968 connect_src: None,
969 default_src: None,
970 font_src: None,
971 frame_src: None,
972 img_src: None,
973 manifest_src: None,
974 media_src: None,
975 object_src: None,
976 prefetch_src: None,
977 script_src: None,
978 script_src_elem: None,
979 script_src_attr: None,
980 style_src: None,
981 style_src_elem: None,
982 style_src_attr: None,
983 worker_src: None,
984 base_uri: None,
985 sandbox: None,
986 form_action: None,
987 frame_ancestors: None,
988 report_to: None,
989 report_uri: None,
990 require_trusted_types_for: None,
991 trusted_types: None,
992 upgrade_insecure_requests: false,
993 report_only: false,
994 }
995 }
996
997 /// child-src: Defines valid sources for web workers and nested browsing contexts loaded using elements such as `<frame>` and `<iframe>`.
998 pub fn child_src(mut self, values: Vec<&'a str>) -> Self {
999 self.child_src = Some(values);
1000 self
1001 }
1002
1003 /// connect-src: Applies to XMLHttpRequest (AJAX), WebSocket or EventSource. If not allowed the browser emulates a 400 HTTP status code.
1004 pub fn connect_src(mut self, values: Vec<&'a str>) -> Self {
1005 self.connect_src = Some(values);
1006 self
1007 }
1008
1009 /// default-src: The default-src is the default policy for loading content such as JavaScript, Images, CSS, Font's, AJAX requests, Frames, HTML5 Media. See the list of directives to see which values are allowed as default.
1010 pub fn default_src(mut self, values: Vec<&'a str>) -> Self {
1011 self.default_src = Some(values);
1012 self
1013 }
1014
1015 /// font-src: Defines valid sources for fonts loaded using @font-face.
1016 pub fn font_src(mut self, values: Vec<&'a str>) -> Self {
1017 self.font_src = Some(values);
1018 self
1019 }
1020
1021 /// frame-src: Defines valid sources for nested browsing contexts loading using elements such as `<frame>` and `<iframe>`.
1022 pub fn frame_src(mut self, values: Vec<&'a str>) -> Self {
1023 self.frame_src = Some(values);
1024 self
1025 }
1026
1027 /// img-src: Defines valid sources of images and favicons.
1028 pub fn img_src(mut self, values: Vec<&'a str>) -> Self {
1029 self.img_src = Some(values);
1030 self
1031 }
1032
1033 /// manifest-src: Specifies which manifest can be applied to the resource.
1034 pub fn manifest_src(mut self, values: Vec<&'a str>) -> Self {
1035 self.manifest_src = Some(values);
1036 self
1037 }
1038
1039 /// media-src: Defines valid sources for loading media using the `<audio>` and `<video>` elements.
1040 pub fn media_src(mut self, values: Vec<&'a str>) -> Self {
1041 self.media_src = Some(values);
1042 self
1043 }
1044
1045 /// object-src: Defines valid sources for the `<object>`, `<embed>`, and `<applet>` elements.
1046 pub fn object_src(mut self, values: Vec<&'a str>) -> Self {
1047 self.object_src = Some(values);
1048 self
1049 }
1050
1051 /// prefetch-src: Specifies which referrer to use when fetching the resource.
1052 pub fn prefetch_src(mut self, values: Vec<&'a str>) -> Self {
1053 self.prefetch_src = Some(values);
1054 self
1055 }
1056
1057 /// script-src: Defines valid sources for JavaScript.
1058 pub fn script_src(mut self, values: Vec<&'a str>) -> Self {
1059 self.script_src = Some(values);
1060 self
1061 }
1062
1063 /// script-src-elem: Defines valid sources for JavaScript inline event handlers.
1064 pub fn script_src_elem(mut self, values: Vec<&'a str>) -> Self {
1065 self.script_src_elem = Some(values);
1066 self
1067 }
1068
1069 /// script-src-attr: Defines valid sources for JavaScript inline event handlers.
1070 pub fn script_src_attr(mut self, values: Vec<&'a str>) -> Self {
1071 self.script_src_attr = Some(values);
1072 self
1073 }
1074
1075 /// style-src: Defines valid sources for stylesheets.
1076 pub fn style_src(mut self, values: Vec<&'a str>) -> Self {
1077 self.style_src = Some(values);
1078 self
1079 }
1080
1081 /// style-src-elem: Defines valid sources for stylesheets inline event handlers.
1082 pub fn style_src_elem(mut self, values: Vec<&'a str>) -> Self {
1083 self.style_src_elem = Some(values);
1084 self
1085 }
1086
1087 /// style-src-attr: Defines valid sources for stylesheets inline event handlers.
1088 pub fn style_src_attr(mut self, values: Vec<&'a str>) -> Self {
1089 self.style_src_attr = Some(values);
1090 self
1091 }
1092
1093 /// worker-src: Defines valid sources for Worker, SharedWorker, or ServiceWorker scripts.
1094 pub fn worker_src(mut self, values: Vec<&'a str>) -> Self {
1095 self.worker_src = Some(values);
1096 self
1097 }
1098
1099 /// base-uri: Restricts the URLs which can be used in a document's `<base>` element.
1100 pub fn base_uri(mut self, values: Vec<&'a str>) -> Self {
1101 self.base_uri = Some(values);
1102 self
1103 }
1104
1105 /// sandbox: Enables a sandbox for the requested resource similar to the iframe sandbox attribute. The sandbox applies a same origin policy, prevents popups, plugins and script execution is blocked. You can keep the sandbox value empty to keep all restrictions in place, or add values: allow-forms allow-same-origin allow-scripts allow-popups, allow-modals, allow-orientation-lock, allow-pointer-lock, allow-presentation, allow-popups-to-escape-sandbox, allow-top-navigation, allow-top-navigation-by-user-activation.
1106 pub fn sandbox(mut self, values: Vec<&'a str>) -> Self {
1107 self.sandbox = Some(values);
1108 self
1109 }
1110
1111 /// form-action: Restricts the URLs which can be used as the target of a form submissions from a given context.
1112 pub fn form_action(mut self, values: Vec<&'a str>) -> Self {
1113 self.form_action = Some(values);
1114 self
1115 }
1116
1117 /// frame-ancestors: Specifies valid parents that may embed a page using `<frame>`, `<iframe>`, `<object>`, `<embed>`, or `<applet>`.
1118 pub fn frame_ancestors(mut self, values: Vec<&'a str>) -> Self {
1119 self.frame_ancestors = Some(values);
1120 self
1121 }
1122
1123 /// report-to: Specifies the endpoint name (defined via `Reporting-Endpoints` header) to send violation reports to.
1124 pub fn report_to(mut self, endpoint: &'a str) -> Self {
1125 self.report_to = Some(endpoint);
1126 self
1127 }
1128
1129 /// report-uri: Specifies URL(s) to send violation reports to (deprecated but still widely supported).
1130 pub fn report_uri(mut self, values: Vec<&'a str>) -> Self {
1131 self.report_uri = Some(values);
1132 self
1133 }
1134
1135 /// require-trusted-types-for: Specifies which trusted types are required by a resource.
1136 pub fn require_trusted_types_for(mut self, values: Vec<&'a str>) -> Self {
1137 self.require_trusted_types_for = Some(values);
1138 self
1139 }
1140
1141 /// trusted-types: Specifies which trusted types are defined by a resource.
1142 pub fn trusted_types(mut self, values: Vec<&'a str>) -> Self {
1143 self.trusted_types = Some(values);
1144 self
1145 }
1146
1147 /// Block HTTP requests on insecure elements.
1148 pub fn upgrade_insecure_requests(mut self) -> Self {
1149 self.upgrade_insecure_requests = true;
1150 self
1151 }
1152
1153 /// Enable report only mode
1154 ///
1155 /// When set to true, the `Content-Security-Policy-Report-Only` header is set instead of `Content-Security-Policy`.
1156 ///
1157 /// Defaults to false.
1158 ///
1159 /// See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy-Report-Only
1160 pub fn report_only(mut self) -> Self {
1161 self.report_only = true;
1162 self
1163 }
1164}
1165
1166impl Display for ContentSecurityPolicy<'_> {
1167 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1168 let mut directives: Vec<String> = Vec::new();
1169 let directive = |name: &str, values: &[&str]| format!("{} {}", name, values.join(" "));
1170
1171 if let Some(v) = &self.default_src {
1172 directives.push(directive("default-src", v));
1173 }
1174 if let Some(v) = &self.base_uri {
1175 directives.push(directive("base-uri", v));
1176 }
1177 if let Some(v) = &self.child_src {
1178 directives.push(directive("child-src", v));
1179 }
1180 if let Some(v) = &self.connect_src {
1181 directives.push(directive("connect-src", v));
1182 }
1183 if let Some(v) = &self.font_src {
1184 directives.push(directive("font-src", v));
1185 }
1186 if let Some(v) = &self.form_action {
1187 directives.push(directive("form-action", v));
1188 }
1189 if let Some(v) = &self.frame_ancestors {
1190 directives.push(directive("frame-ancestors", v));
1191 }
1192 if let Some(v) = &self.frame_src {
1193 directives.push(directive("frame-src", v));
1194 }
1195 if let Some(v) = &self.img_src {
1196 directives.push(directive("img-src", v));
1197 }
1198 if let Some(v) = &self.manifest_src {
1199 directives.push(directive("manifest-src", v));
1200 }
1201 if let Some(v) = &self.media_src {
1202 directives.push(directive("media-src", v));
1203 }
1204 if let Some(v) = &self.object_src {
1205 directives.push(directive("object-src", v));
1206 }
1207 if let Some(v) = &self.prefetch_src {
1208 directives.push(directive("prefetch-src", v));
1209 }
1210 if let Some(v) = &self.script_src {
1211 directives.push(directive("script-src", v));
1212 }
1213 if let Some(v) = &self.script_src_elem {
1214 directives.push(directive("script-src-elem", v));
1215 }
1216 if let Some(v) = &self.script_src_attr {
1217 directives.push(directive("script-src-attr", v));
1218 }
1219 if let Some(v) = &self.style_src {
1220 directives.push(directive("style-src", v));
1221 }
1222 if let Some(v) = &self.style_src_elem {
1223 directives.push(directive("style-src-elem", v));
1224 }
1225 if let Some(v) = &self.style_src_attr {
1226 directives.push(directive("style-src-attr", v));
1227 }
1228 if let Some(v) = &self.worker_src {
1229 directives.push(directive("worker-src", v));
1230 }
1231 if let Some(v) = &self.sandbox {
1232 directives.push(directive("sandbox", v));
1233 }
1234 if let Some(v) = &self.report_to {
1235 directives.push(format!("report-to {}", v));
1236 }
1237 if let Some(v) = &self.report_uri {
1238 directives.push(directive("report-uri", v));
1239 }
1240 if let Some(v) = &self.require_trusted_types_for {
1241 directives.push(directive("require-trusted-types-for", v));
1242 }
1243 if let Some(v) = &self.trusted_types {
1244 directives.push(directive("trusted-types", v));
1245 }
1246 if self.upgrade_insecure_requests {
1247 directives.push("upgrade-insecure-requests".to_string());
1248 }
1249
1250 write!(f, "{}", directives.join("; "))
1251 }
1252}
1253
1254impl Default for ContentSecurityPolicy<'_> {
1255 /// Default policy for the Content-Security-Policy header.
1256 ///
1257 /// values:
1258 /// ```text
1259 /// default-src 'self';
1260 /// base-uri 'self';
1261 /// font-src 'self' https: data:;
1262 /// form-action 'self';
1263 /// frame-ancestors 'self';
1264 /// img-src 'self' data:;
1265 /// object-src 'none';
1266 /// script-src 'self';
1267 /// script-src-attr 'none';
1268 /// style-src 'self' https: 'unsafe-inline';
1269 /// upgrade-insecure-requests
1270 /// ```
1271 fn default() -> Self {
1272 Self::new()
1273 .default_src(vec!["'self'"])
1274 .base_uri(vec!["'self'"])
1275 .font_src(vec!["'self'", "https:", "data:"])
1276 .form_action(vec!["'self'"])
1277 .frame_ancestors(vec!["'self'"])
1278 .img_src(vec!["'self'", "data:"])
1279 .object_src(vec!["'none'"])
1280 .script_src(vec!["'self'"])
1281 .script_src_attr(vec!["'none'"])
1282 .style_src(vec!["'self'", "https:", "'unsafe-inline'"])
1283 .upgrade_insecure_requests()
1284 }
1285}
1286
1287impl From<ContentSecurityPolicy<'_>> for Header {
1288 fn from(val: ContentSecurityPolicy<'_>) -> Self {
1289 (
1290 if val.report_only {
1291 "Content-Security-Policy-Report-Only"
1292 } else {
1293 "Content-Security-Policy"
1294 },
1295 val.to_string(),
1296 )
1297 }
1298}
1299
1300/// Error returned when a header name or value cannot be converted to a valid HTTP header.
1301#[derive(Debug)]
1302pub enum HelmetError {
1303 /// The header name is not a valid HTTP header name.
1304 InvalidHeaderName(String),
1305 /// The header value is not a valid HTTP header value.
1306 InvalidHeaderValue(String),
1307}
1308
1309impl std::fmt::Display for HelmetError {
1310 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1311 match self {
1312 HelmetError::InvalidHeaderName(name) => write!(f, "invalid header name: {}", name),
1313 HelmetError::InvalidHeaderValue(msg) => write!(f, "invalid header value: {}", msg),
1314 }
1315 }
1316}
1317
1318impl std::error::Error for HelmetError {}
1319
1320/// Helmet security headers middleware for ntex services
1321///
1322/// # Examples
1323///
1324/// ```
1325/// use helmet_core::Helmet;
1326///
1327/// let helmet = Helmet::default();
1328/// ```
1329///
1330/// ## Adding custom headers
1331///
1332/// ```
1333/// use helmet_core::{Helmet, StrictTransportSecurity};
1334///
1335/// let helmet = Helmet::new()
1336/// .add(StrictTransportSecurity::new().max_age(31536000).include_sub_domains());
1337/// ```
1338#[derive(Clone)]
1339pub struct Helmet {
1340 pub headers: Vec<Header>,
1341}
1342
1343#[allow(clippy::should_implement_trait)]
1344impl Helmet {
1345 /// Create new `Helmet` instance without any headers applied
1346 pub fn new() -> Self {
1347 Self {
1348 headers: Vec::new(),
1349 }
1350 }
1351
1352 /// Add header to the middleware
1353 pub fn add(mut self, header: impl Into<Header>) -> Self {
1354 self.headers.push(header.into());
1355 self
1356 }
1357}
1358
1359impl Default for Helmet {
1360 /// Default `Helmet` instance with all headers applied
1361 ///
1362 /// ```text
1363 /// Content-Security-Policy: default-src 'self'; base-uri 'self'; font-src 'self' https: data:; form-action 'self'; frame-ancestors 'self'; img-src 'self' data:; object-src 'none'; script-src 'self'; script-src-attr 'none'; style-src 'self' https: 'unsafe-inline'; upgrade-insecure-requests
1364 /// Cross-Origin-Opener-Policy: same-origin
1365 /// Cross-Origin-Resource-Policy: same-origin
1366 /// Origin-Agent-Cluster: ?1
1367 /// Referrer-Policy: no-referrer
1368 /// Strict-Transport-Security: max-age=15552000; includeSubDomains
1369 /// X-Content-Type-Options: nosniff
1370 /// X-DNS-Prefetch-Control: off
1371 /// X-Download-Options: noopen
1372 /// X-Frame-Options: sameorigin
1373 /// X-Permitted-Cross-Domain-Policies: none
1374 /// X-XSS-Protection: 0
1375 /// ```
1376 fn default() -> Self {
1377 Self::new()
1378 .add(ContentSecurityPolicy::default())
1379 .add(CrossOriginOpenerPolicy::same_origin())
1380 .add(CrossOriginResourcePolicy::same_origin())
1381 .add(OriginAgentCluster(true))
1382 .add(ReferrerPolicy::no_referrer())
1383 .add(
1384 StrictTransportSecurity::new()
1385 .max_age(15552000)
1386 .include_sub_domains(),
1387 )
1388 .add(XContentTypeOptions::nosniff())
1389 .add(XDNSPrefetchControl::off())
1390 .add(XDownloadOptions::noopen())
1391 .add(XFrameOptions::same_origin())
1392 .add(XPermittedCrossDomainPolicies::none())
1393 .add(XXSSProtection::off())
1394 }
1395}
1396
1397#[cfg(test)]
1398mod tests {
1399 use super::*;
1400
1401 #[test]
1402 fn csp_default_output() {
1403 assert_eq!(
1404 ContentSecurityPolicy::default().to_string(),
1405 "default-src 'self'; base-uri 'self'; font-src 'self' https: data:; form-action 'self'; frame-ancestors 'self'; img-src 'self' data:; object-src 'none'; script-src 'self'; script-src-attr 'none'; style-src 'self' https: 'unsafe-inline'; upgrade-insecure-requests"
1406 );
1407 }
1408
1409 #[test]
1410 fn csp_default_override_replaces_directive() {
1411 assert_eq!(
1412 ContentSecurityPolicy::default()
1413 .script_src(vec!["'self'", "'unsafe-inline'", "'unsafe-eval'"])
1414 .to_string(),
1415 "default-src 'self'; base-uri 'self'; font-src 'self' https: data:; form-action 'self'; frame-ancestors 'self'; img-src 'self' data:; object-src 'none'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; script-src-attr 'none'; style-src 'self' https: 'unsafe-inline'; upgrade-insecure-requests"
1416 );
1417 }
1418
1419 #[test]
1420 fn csp_default_override_multiple_directives() {
1421 assert_eq!(
1422 ContentSecurityPolicy::default()
1423 .script_src(vec!["'self'", "'unsafe-inline'"])
1424 .img_src(vec!["*"])
1425 .to_string(),
1426 "default-src 'self'; base-uri 'self'; font-src 'self' https: data:; form-action 'self'; frame-ancestors 'self'; img-src *; object-src 'none'; script-src 'self' 'unsafe-inline'; script-src-attr 'none'; style-src 'self' https: 'unsafe-inline'; upgrade-insecure-requests"
1427 );
1428 }
1429
1430 #[test]
1431 fn csp_new_is_empty() {
1432 assert_eq!(ContentSecurityPolicy::new().to_string(), "");
1433 }
1434}