6
6
* found in the LICENSE file at https://angular.dev/license
7
7
*/
8
8
9
- import { autoCsp , hashScriptText } from './auto-csp' ;
9
+ import { autoCsp , hashTextContent } from './auto-csp' ;
10
+
11
+ // Utility function to grab the meta tag CSPs from the HTML response.
12
+ const getCsps = ( html : string ) => {
13
+ return Array . from (
14
+ html . matchAll ( / < m e t a h t t p - e q u i v = " C o n t e n t - S e c u r i t y - P o l i c y " c o n t e n t = " ( [ ^ " ] * ) " > / g) ,
15
+ ) . map ( ( m ) => m [ 1 ] ) ; // Only capture group.
16
+ } ;
17
+
18
+ const ONE_HASH_CSP =
19
+ / s c r i p t - s r c ' s t r i c t - d y n a m i c ' ' s h a 2 5 6 - [ ^ ' ] + ' h t t p s : ' u n s a f e - i n l i n e ' ; o b j e c t - s r c ' n o n e ' ; b a s e - u r i ' s e l f ' ; / ;
20
+
21
+ const FOUR_HASH_CSP =
22
+ / s c r i p t - s r c ' s t r i c t - d y n a m i c ' (?: ' s h a 2 5 6 - [ ^ ' ] + ' ) { 4 } h t t p s : ' u n s a f e - i n l i n e ' ; o b j e c t - s r c ' n o n e ' ; b a s e - u r i ' s e l f ' ; / ;
10
23
11
24
describe ( 'auto-csp' , ( ) => {
12
25
it ( 'should rewrite a single inline script' , async ( ) => {
@@ -21,9 +34,10 @@ describe('auto-csp', () => {
21
34
</html>
22
35
` ) ;
23
36
24
- expect ( result ) . toContain (
25
- `<meta http-equiv="Content-Security-Policy" content="script-src 'strict-dynamic' ${ hashScriptText ( "console.log('foo');" ) } https: 'unsafe-inline';object-src 'none';base-uri 'self';">` ,
26
- ) ;
37
+ const csps = getCsps ( result ) ;
38
+ expect ( csps . length ) . toBe ( 1 ) ;
39
+ expect ( csps [ 0 ] ) . toMatch ( ONE_HASH_CSP ) ;
40
+ expect ( csps [ 0 ] ) . toContain ( hashTextContent ( "console.log('foo');" ) ) ;
27
41
} ) ;
28
42
29
43
it ( 'should rewrite a single source script' , async ( ) => {
@@ -38,9 +52,9 @@ describe('auto-csp', () => {
38
52
</html>
39
53
` ) ;
40
54
41
- expect ( result ) . toContain (
42
- `<meta http-equiv="Content-Security-Policy" content="script-src 'strict-dynamic' 'sha256-cfa69N/DhgtxzDzIHo+IFj9KPigQLDJgb6ZGZa3g5Cs=' https: 'unsafe-inline';object-src 'none';base-uri 'self';">` ,
43
- ) ;
55
+ const csps = getCsps ( result ) ;
56
+ expect ( csps . length ) . toBe ( 1 ) ;
57
+ expect ( csps [ 0 ] ) . toMatch ( ONE_HASH_CSP ) ;
44
58
expect ( result ) . toContain ( `var scripts = [['./main.js', undefined, false, false]];` ) ;
45
59
} ) ;
46
60
@@ -56,9 +70,9 @@ describe('auto-csp', () => {
56
70
</html>
57
71
` ) ;
58
72
59
- expect ( result ) . toContain (
60
- `<meta http-equiv="Content-Security-Policy" content="script-src 'strict-dynamic' 'sha256-cfa69N/DhgtxzDzIHo+IFj9KPigQLDJgb6ZGZa3g5Cs=' https: 'unsafe-inline';object-src 'none';base-uri 'self';">` ,
61
- ) ;
73
+ const csps = getCsps ( result ) ;
74
+ expect ( csps . length ) . toBe ( 1 ) ;
75
+ expect ( csps [ 0 ] ) . toMatch ( ONE_HASH_CSP ) ;
62
76
// Our loader script appears after the HTML text content.
63
77
expect ( result ) . toMatch (
64
78
/ S o m e t e x t < \/ d i v > \s * < s c r i p t > \s * v a r s c r i p t s = \[ \[ ' .\/ m a i n .j s ' , u n d e f i n e d , f a l s e , f a l s e \] \] ; / ,
@@ -80,9 +94,9 @@ describe('auto-csp', () => {
80
94
</html>
81
95
` ) ;
82
96
83
- expect ( result ) . toContain (
84
- `<meta http-equiv="Content-Security-Policy" content="script-src 'strict-dynamic' 'sha256-oK8+CQgKHPljcYJpTNKJt/y0A0oiBIm3LRke3EhzHVQ=' https: 'unsafe-inline';object-src 'none';base-uri 'self';">` ,
85
- ) ;
97
+ const csps = getCsps ( result ) ;
98
+ expect ( csps . length ) . toBe ( 1 ) ;
99
+ expect ( csps [ 0 ] ) . toMatch ( ONE_HASH_CSP ) ;
86
100
expect ( result ) . toContain (
87
101
`var scripts = [['./main1.js', undefined, false, false],['./main2.js', undefined, true, false],['./main3.js', 'module', true, true]];` ,
88
102
) ;
@@ -107,9 +121,12 @@ describe('auto-csp', () => {
107
121
</html>
108
122
` ) ;
109
123
110
- expect ( result ) . toContain (
111
- `<meta http-equiv="Content-Security-Policy" content="script-src 'strict-dynamic' ${ hashScriptText ( "console.log('foo');" ) } 'sha256-6q4qOp9MMB///5kaRda2I++J9l0mJiqWRxQ9/8hoSyw=' ${ hashScriptText ( "console.log('bar');" ) } 'sha256-AUmEDzNdja438OLB3B8Opyxy9B3Tr1Tib+aaGZdhhWQ=' https: 'unsafe-inline';object-src 'none';base-uri 'self';">` ,
112
- ) ;
124
+ const csps = getCsps ( result ) ;
125
+ expect ( csps . length ) . toBe ( 1 ) ;
126
+ // Exactly four hashes for the four scripts that remain (inline, loader, inline, loader).
127
+ expect ( csps [ 0 ] ) . toMatch ( FOUR_HASH_CSP ) ;
128
+ expect ( csps [ 0 ] ) . toContain ( hashTextContent ( "console.log('foo');" ) ) ;
129
+ expect ( csps [ 0 ] ) . toContain ( hashTextContent ( "console.log('bar');" ) ) ;
113
130
// Loader script for main.js and main2.js appear after 'foo' and before 'bar'.
114
131
expect ( result ) . toMatch (
115
132
/ c o n s o l e .l o g \( ' f o o ' \) ; < \/ s c r i p t > \s * < s c r i p t > \s * v a r s c r i p t s = \[ \[ ' .\/ m a i n .j s ' , u n d e f i n e d , f a l s e , f a l s e \] , \[ ' .\/ m a i n 2 .j s ' , u n d e f i n e d , f a l s e , f a l s e \] \] ; [ \s \S ] * c o n s o l e .l o g \( ' b a r ' \) ; / ,
0 commit comments