@@ -7,21 +7,45 @@ use rand::random;
7
7
use std:: fmt:: { Display , Formatter } ;
8
8
use std:: sync:: Arc ;
9
9
10
- use crate :: AppState ;
11
-
12
10
pub const DEFAULT_CONTENT_SECURITY_POLICY : & str = "script-src 'self' 'nonce-{NONCE}'" ;
13
11
14
12
#[ derive( Debug , Clone ) ]
15
13
pub struct ContentSecurityPolicy {
16
14
pub nonce : u64 ,
17
- app_state : Arc < AppState > ,
15
+ template : ContentSecurityPolicyTemplate ,
16
+ }
17
+
18
+ /// A template for the Content Security Policy header.
19
+ /// The template is a string that contains the nonce placeholder.
20
+ /// The nonce placeholder is replaced with the nonce value when the Content Security Policy is applied to a response.
21
+ /// This struct is cheap to clone.
22
+ #[ derive( Debug , Clone ) ]
23
+ pub struct ContentSecurityPolicyTemplate {
24
+ pub before_nonce : Arc < str > ,
25
+ pub after_nonce : Option < Arc < str > > ,
26
+ }
27
+
28
+ impl From < & str > for ContentSecurityPolicyTemplate {
29
+ fn from ( s : & str ) -> Self {
30
+ if let Some ( ( before, after) ) = s. split_once ( "{NONCE}" ) {
31
+ Self {
32
+ before_nonce : Arc :: from ( before) ,
33
+ after_nonce : Some ( Arc :: from ( after) ) ,
34
+ }
35
+ } else {
36
+ Self {
37
+ before_nonce : Arc :: from ( s) ,
38
+ after_nonce : None ,
39
+ }
40
+ }
41
+ }
18
42
}
19
43
20
44
impl ContentSecurityPolicy {
21
- pub fn new ( app_state : Arc < AppState > ) -> Self {
45
+ pub fn new ( template : ContentSecurityPolicyTemplate ) -> Self {
22
46
Self {
23
47
nonce : random ( ) ,
24
- app_state ,
48
+ template ,
25
49
}
26
50
}
27
51
@@ -31,26 +55,22 @@ impl ContentSecurityPolicy {
31
55
}
32
56
}
33
57
34
- fn template_string ( & self ) -> & str {
35
- & self . app_state . config . content_security_policy
36
- }
37
-
38
58
fn is_enabled ( & self ) -> bool {
39
- !self . app_state . config . content_security_policy . is_empty ( )
59
+ !self . template . before_nonce . is_empty ( )
40
60
}
41
61
}
42
62
43
63
impl Display for ContentSecurityPolicy {
44
64
fn fmt ( & self , f : & mut Formatter < ' _ > ) -> std:: fmt:: Result {
45
- let template = self . template_string ( ) ;
46
- if let Some ( ( before, after) ) = template. split_once ( "{NONCE}" ) {
47
- write ! ( f, "{before}{nonce}{after}" , nonce = self . nonce)
65
+ let before = self . template . before_nonce . as_ref ( ) ;
66
+ if let Some ( after) = & self . template . after_nonce {
67
+ let nonce = self . nonce ;
68
+ write ! ( f, "{before}{nonce}{after}" )
48
69
} else {
49
- write ! ( f, "{}" , template )
70
+ write ! ( f, "{before}" )
50
71
}
51
72
}
52
73
}
53
-
54
74
impl TryIntoHeaderPair for & ContentSecurityPolicy {
55
75
type Error = InvalidHeaderValue ;
56
76
@@ -61,3 +81,25 @@ impl TryIntoHeaderPair for &ContentSecurityPolicy {
61
81
) )
62
82
}
63
83
}
84
+
85
+ #[ cfg( test) ]
86
+ mod tests {
87
+ use super :: * ;
88
+
89
+ #[ test]
90
+ fn test_content_security_policy_display ( ) {
91
+ let template = ContentSecurityPolicyTemplate :: from (
92
+ "script-src 'self' 'nonce-{NONCE}' 'unsafe-inline'" ,
93
+ ) ;
94
+ let csp = ContentSecurityPolicy :: new ( template. clone ( ) ) ;
95
+ let csp_str = csp. to_string ( ) ;
96
+ assert ! ( csp_str. starts_with( "script-src 'self' 'nonce-" ) ) ;
97
+ assert ! ( csp_str. ends_with( "' 'unsafe-inline'" ) ) ;
98
+ let second_csp = ContentSecurityPolicy :: new ( template) ;
99
+ let second_csp_str = second_csp. to_string ( ) ;
100
+ assert_ne ! (
101
+ csp_str, second_csp_str,
102
+ "We should not generate the same nonce twice"
103
+ ) ;
104
+ }
105
+ }
0 commit comments