1
- use actix_web:: http:: header:: {
2
- HeaderName , HeaderValue , TryIntoHeaderPair , CONTENT_SECURITY_POLICY ,
3
- } ;
1
+ use actix_web:: http:: header:: CONTENT_SECURITY_POLICY ;
4
2
use actix_web:: HttpResponseBuilder ;
5
- use awc:: http:: header:: InvalidHeaderValue ;
6
3
use rand:: random;
7
4
use serde:: Deserialize ;
8
- use std:: fmt:: { Display , Formatter } ;
9
- use std:: sync:: Arc ;
10
5
11
6
pub const DEFAULT_CONTENT_SECURITY_POLICY : & str = "script-src 'self' 'nonce-{NONCE}'" ;
7
+ pub const NONCE_PLACEHOLDER : & str = "{NONCE}" ;
12
8
13
9
#[ derive( Debug , Clone ) ]
14
10
pub struct ContentSecurityPolicy {
15
11
pub nonce : u64 ,
16
- template : ContentSecurityPolicyTemplate ,
17
12
}
18
13
19
14
/// A template for the Content Security Policy header.
@@ -22,8 +17,28 @@ pub struct ContentSecurityPolicy {
22
17
/// This struct is cheap to clone.
23
18
#[ derive( Debug , Clone , PartialEq , Eq ) ]
24
19
pub struct ContentSecurityPolicyTemplate {
25
- pub before_nonce : Arc < str > ,
26
- pub after_nonce : Option < Arc < str > > ,
20
+ pub template : String ,
21
+ pub nonce_position : Option < usize > ,
22
+ }
23
+
24
+ impl ContentSecurityPolicyTemplate {
25
+ #[ must_use]
26
+ pub fn is_enabled ( & self ) -> bool {
27
+ self . nonce_position . is_some ( )
28
+ }
29
+
30
+ fn format_nonce ( & self , nonce : u64 ) -> String {
31
+ if let Some ( pos) = self . nonce_position {
32
+ format ! (
33
+ "{}{}{}" ,
34
+ & self . template[ ..pos] ,
35
+ nonce,
36
+ & self . template[ pos + NONCE_PLACEHOLDER . len( ) ..]
37
+ )
38
+ } else {
39
+ self . template . clone ( )
40
+ }
41
+ }
27
42
}
28
43
29
44
impl Default for ContentSecurityPolicyTemplate {
@@ -34,16 +49,10 @@ impl Default for ContentSecurityPolicyTemplate {
34
49
35
50
impl From < & str > for ContentSecurityPolicyTemplate {
36
51
fn from ( s : & str ) -> Self {
37
- if let Some ( ( before, after) ) = s. split_once ( "{NONCE}" ) {
38
- Self {
39
- before_nonce : Arc :: from ( before) ,
40
- after_nonce : Some ( Arc :: from ( after) ) ,
41
- }
42
- } else {
43
- Self {
44
- before_nonce : Arc :: from ( s) ,
45
- after_nonce : None ,
46
- }
52
+ let nonce_position = s. find ( NONCE_PLACEHOLDER ) ;
53
+ Self {
54
+ template : s. to_owned ( ) ,
55
+ nonce_position,
47
56
}
48
57
}
49
58
}
@@ -60,44 +69,19 @@ impl<'de> Deserialize<'de> for ContentSecurityPolicyTemplate {
60
69
61
70
impl ContentSecurityPolicy {
62
71
#[ must_use]
63
- pub fn new ( template : ContentSecurityPolicyTemplate ) -> Self {
64
- Self {
65
- nonce : random ( ) ,
66
- template,
67
- }
72
+ pub fn with_random_nonce ( ) -> Self {
73
+ Self { nonce : random ( ) }
68
74
}
69
75
70
- pub fn apply_to_response ( & self , response : & mut HttpResponseBuilder ) {
71
- if self . is_enabled ( ) {
72
- response. insert_header ( self ) ;
76
+ pub fn apply_to_response (
77
+ & self ,
78
+ template : & ContentSecurityPolicyTemplate ,
79
+ response : & mut HttpResponseBuilder ,
80
+ ) {
81
+ if template. is_enabled ( ) {
82
+ response. insert_header ( ( CONTENT_SECURITY_POLICY , template. format_nonce ( self . nonce ) ) ) ;
73
83
}
74
84
}
75
-
76
- fn is_enabled ( & self ) -> bool {
77
- !self . template . before_nonce . is_empty ( ) || self . template . after_nonce . is_some ( )
78
- }
79
- }
80
-
81
- impl Display for ContentSecurityPolicy {
82
- fn fmt ( & self , f : & mut Formatter < ' _ > ) -> std:: fmt:: Result {
83
- let before = self . template . before_nonce . as_ref ( ) ;
84
- if let Some ( after) = & self . template . after_nonce {
85
- let nonce = self . nonce ;
86
- write ! ( f, "{before}{nonce}{after}" )
87
- } else {
88
- write ! ( f, "{before}" )
89
- }
90
- }
91
- }
92
- impl TryIntoHeaderPair for & ContentSecurityPolicy {
93
- type Error = InvalidHeaderValue ;
94
-
95
- fn try_into_pair ( self ) -> Result < ( HeaderName , HeaderValue ) , Self :: Error > {
96
- Ok ( (
97
- CONTENT_SECURITY_POLICY ,
98
- HeaderValue :: from_maybe_shared ( self . to_string ( ) ) ?,
99
- ) )
100
- }
101
85
}
102
86
103
87
#[ cfg( test) ]
@@ -109,12 +93,12 @@ mod tests {
109
93
let template = ContentSecurityPolicyTemplate :: from (
110
94
"script-src 'self' 'nonce-{NONCE}' 'unsafe-inline'" ,
111
95
) ;
112
- let csp = ContentSecurityPolicy :: new ( template . clone ( ) ) ;
113
- let csp_str = csp . to_string ( ) ;
96
+ let csp = ContentSecurityPolicy :: with_random_nonce ( ) ;
97
+ let csp_str = template . format_nonce ( csp . nonce ) ;
114
98
assert ! ( csp_str. starts_with( "script-src 'self' 'nonce-" ) ) ;
115
99
assert ! ( csp_str. ends_with( "' 'unsafe-inline'" ) ) ;
116
- let second_csp = ContentSecurityPolicy :: new ( template ) ;
117
- let second_csp_str = second_csp . to_string ( ) ;
100
+ let second_csp = ContentSecurityPolicy :: with_random_nonce ( ) ;
101
+ let second_csp_str = template . format_nonce ( second_csp . nonce ) ;
118
102
assert_ne ! (
119
103
csp_str, second_csp_str,
120
104
"We should not generate the same nonce twice"
0 commit comments