import { CspConfigType } from './config' ; export type CspDirectiveName = 'script-src' | 'worker-src' | 'style-src' | 'frame-ancestors' | 'connect-src' | 'default-src' | 'font-src' | 'frame-src' | 'img-src' | 'report-uri' | 'report-to' ; /**
 * The default directives rules that are always applied
 */ export const defaultRules = { 'script-src' : [ `'unsafe-eval'` , `'self'` ] , 'worker-src' : [ `blob:` , `'self'` ] , 'style-src' : [ `'unsafe-inline'` , `'self'` ] , } ; /**
 * Per-directive rules that will be added when the configuration contains at least one value
 * Main purpose is to add `self` value to some directives when the configuration specifies other values
 */ export const additionalRules = { 'connect-src' : [ `'self'` ] , 'default-src' : [ `'self'` ] , 'font-src' : [ `'self'` ] , 'img-src' : [ `'self'` ] , 'frame-ancestors' : [ `'self'` ] , 'frame-src' : [ `'self'` ] , } ; export class CspDirectives { private readonly directives = new Map < CspDirectiveName , Set < string > > ( ) ; addDirectiveValue ( directiveName , directiveValue ) { if ( ! this . directives . has ( directiveName ) ) { this . directives . set ( directiveName , new Set ( ) ) ; } this . directives . get ( directiveName ) ! . add ( normalizeDirectiveValue ( directiveValue ) ) ; } clearDirectiveValues ( directiveName ) { this . directives . delete ( directiveName ) ; } getCspHeader ( ) { return [ ... this . directives . entries ( ) ] . map ( ( [ name , values ] ) => { return [ name , ... values ] . join ( ' ' ) ; } ) . join ( '; ' ) ; } static fromConfig ( config ) { const cspDirectives = new CspDirectives ( ) ; Object . entries ( defaultRules ) . forEach ( ( [ key , values ] ) => { values ?. forEach ( ( value ) => { cspDirectives . addDirectiveValue ( key as CspDirectiveName , value ) ; } ) ; } ) ; const additiveConfig = parseConfigDirectives ( config ) ; [ ... additiveConfig . entries ( ) ] . forEach ( ( [ directiveName , directiveValues ] ) => { const additionalValues = additionalRules [ directiveName ] ?? [ ] ; [ ... additionalValues , ... directiveValues ] . forEach ( ( value ) => { cspDirectives . addDirectiveValue ( directiveName , value ) ; } ) ; } ) ; return cspDirectives ; } } const parseConfigDirectives = ( cspConfig ) : Map < CspDirectiveName , string [ ] > => { const map = new Map < CspDirectiveName , string [ ] > ( ) ; if ( cspConfig . script_src ?. length ) { map . set ( 'script-src' , cspConfig . script_src ) ; } if ( cspConfig . worker_src ?. length ) { map . set ( 'worker-src' , cspConfig . worker_src ) ; } if ( cspConfig . style_src ?. length ) { map . set ( 'style-src' , cspConfig . style_src ) ; } if ( cspConfig . connect_src ?. length ) { map . set ( 'connect-src' , cspConfig . connect_src ) ; } if ( cspConfig . default_src ?. length ) { map . set ( 'default-src' , cspConfig . default_src ) ; } if ( cspConfig . font_src ?. length ) { map . set ( 'font-src' , cspConfig . font_src ) ; } if ( cspConfig . frame_src ?. length ) { map . set ( 'frame-src' , cspConfig . frame_src ) ; } if ( cspConfig . img_src ?. length ) { map . set ( 'img-src' , cspConfig . img_src ) ; } if ( cspConfig . frame_ancestors ?. length ) { map . set ( 'frame-ancestors' , cspConfig . frame_ancestors ) ; } if ( cspConfig . report_uri ?. length ) { map . set ( 'report-uri' , cspConfig . report_uri ) ; } if ( cspConfig . report_to ?. length ) { map . set ( 'report-to' , cspConfig . report_to ) ; } return map ; } ; const keywordTokens = [ 'none' , 'self' , 'strict-dynamic' , 'report-sample' , 'unsafe-inline' , 'unsafe-eval' , 'unsafe-hashes' , 'unsafe-allow-redirects' , ] ; function normalizeDirectiveValue ( value ) { if ( keywordTokens . includes ( value ) ) { return `'${ value }'` ; } return value ; }