1- use actix_web:: http:: header:: {
2- HeaderName , HeaderValue , TryIntoHeaderPair , CONTENT_SECURITY_POLICY ,
3- } ;
1+ use actix_web:: http:: header:: CONTENT_SECURITY_POLICY ;
42use actix_web:: HttpResponseBuilder ;
5- use awc:: http:: header:: InvalidHeaderValue ;
63use rand:: random;
74use serde:: Deserialize ;
8- use std:: fmt:: { Display , Formatter } ;
9- use std:: sync:: Arc ;
105
116pub const DEFAULT_CONTENT_SECURITY_POLICY : & str = "script-src 'self' 'nonce-{NONCE}'" ;
7+ pub const NONCE_PLACEHOLDER : & str = "{NONCE}" ;
128
139#[ derive( Debug , Clone ) ]
1410pub struct ContentSecurityPolicy {
1511 pub nonce : u64 ,
16- template : ContentSecurityPolicyTemplate ,
1712}
1813
1914/// A template for the Content Security Policy header.
@@ -22,8 +17,28 @@ pub struct ContentSecurityPolicy {
2217/// This struct is cheap to clone.
2318#[ derive( Debug , Clone , PartialEq , Eq ) ]
2419pub 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+ }
2742}
2843
2944impl Default for ContentSecurityPolicyTemplate {
@@ -34,16 +49,10 @@ impl Default for ContentSecurityPolicyTemplate {
3449
3550impl From < & str > for ContentSecurityPolicyTemplate {
3651 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,
4756 }
4857 }
4958}
@@ -60,44 +69,19 @@ impl<'de> Deserialize<'de> for ContentSecurityPolicyTemplate {
6069
6170impl ContentSecurityPolicy {
6271 #[ 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 ( ) }
6874 }
6975
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 ) ) ) ;
7383 }
7484 }
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- }
10185}
10286
10387#[ cfg( test) ]
@@ -109,12 +93,12 @@ mod tests {
10993 let template = ContentSecurityPolicyTemplate :: from (
11094 "script-src 'self' 'nonce-{NONCE}' 'unsafe-inline'" ,
11195 ) ;
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 ) ;
11498 assert ! ( csp_str. starts_with( "script-src 'self' 'nonce-" ) ) ;
11599 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 ) ;
118102 assert_ne ! (
119103 csp_str, second_csp_str,
120104 "We should not generate the same nonce twice"
0 commit comments