@@ -81,6 +81,9 @@ class CspHtmlWebpackPlugin {
81
81
// the additional options that this plugin allows
82
82
this . opts = Object . freeze ( { ...defaultAdditionalOpts , ...additionalOpts } ) ;
83
83
84
+ // the calculated hashes for each file, indexed by filename
85
+ this . hashes = { } ;
86
+
84
87
// valid hashes from https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/script-src#Sources
85
88
if ( ! [ 'sha256' , 'sha384' , 'sha512' ] . includes ( this . opts . hashingMethod ) ) {
86
89
throw new Error (
@@ -262,6 +265,19 @@ class CspHtmlWebpackPlugin {
262
265
return `'${ this . opts . hashingMethod } -${ hashed } '` ;
263
266
}
264
267
268
+ /**
269
+ * Gets the hash of a file that is a webpack asset, storing the hash in a cache.
270
+ * @param assets
271
+ * @param {string } filename
272
+ * @returns {string }
273
+ */
274
+ hashFile ( assets , filename ) {
275
+ if ( ! Object . prototype . hasOwnProperty . call ( this . hashes , filename ) ) {
276
+ this . hashes [ filename ] = this . hash ( assets [ filename ] . source ( ) ) ;
277
+ }
278
+ return this . hashes [ filename ] ;
279
+ }
280
+
265
281
/**
266
282
* Calculates shas of the policy / selector we define
267
283
* @param {object } $ - the Cheerio instance
@@ -345,12 +361,12 @@ class CspHtmlWebpackPlugin {
345
361
. filter ( ( filename ) =>
346
362
includedScripts . includes ( path . join ( this . publicPath , filename ) )
347
363
)
348
- . map ( ( filename ) => this . hash ( compilation . assets [ filename ] . source ( ) ) ) ;
364
+ . map ( ( filename ) => this . hashFile ( compilation . assets , filename ) ) ;
349
365
const linkedStyleShas = this . styleFilesToHash
350
366
. filter ( ( filename ) =>
351
367
includedStyles . includes ( path . join ( this . publicPath , filename ) )
352
368
)
353
- . map ( ( filename ) => this . hash ( compilation . assets [ filename ] . source ( ) ) ) ;
369
+ . map ( ( filename ) => this . hashFile ( compilation . assets , filename ) ) ;
354
370
355
371
const builtPolicy = this . buildPolicy ( {
356
372
...this . policy ,
@@ -395,6 +411,45 @@ class CspHtmlWebpackPlugin {
395
411
return compileCb ( null , htmlPluginData ) ;
396
412
}
397
413
414
+ /**
415
+ * Remove the public path from a URL, if present
416
+ * @param publicPath
417
+ * @param {string } path
418
+ * @returns {string }
419
+ */
420
+ getFilename ( publicPath , path ) {
421
+ if ( ! publicPath || ! path . startsWith ( publicPath ) ) {
422
+ return path ;
423
+ }
424
+ return path . substr ( publicPath . length ) ;
425
+ }
426
+
427
+ /**
428
+ * Add integrity attributes to asset tags
429
+ * @param compilation
430
+ * @param htmlPluginData
431
+ * @param compileCb
432
+ */
433
+ addIntegrityAttributes ( compilation , htmlPluginData , compileCb ) {
434
+ if ( this . hashEnabled [ 'script-src' ] !== false ) {
435
+ htmlPluginData . assetTags . scripts . filter ( tag => tag . attributes . src ) . forEach ( tag => {
436
+ const filename = this . getFilename ( compilation . options . output . publicPath , tag . attributes . src ) ;
437
+ if ( filename in compilation . assets ) {
438
+ tag . attributes . integrity = this . hashFile ( compilation . assets , filename ) . slice ( 1 , - 1 ) ;
439
+ }
440
+ } ) ;
441
+ }
442
+ if ( this . hashEnabled [ 'style-src' ] !== false ) {
443
+ htmlPluginData . assetTags . styles . filter ( tag => tag . attributes . href ) . forEach ( tag => {
444
+ const filename = this . getFilename ( compilation . options . output . publicPath , tag . attributes . href ) ;
445
+ if ( filename in compilation . assets ) {
446
+ tag . attributes . integrity = this . hashFile ( compilation . assets , filename ) . slice ( 1 , - 1 ) ;
447
+ }
448
+ } ) ;
449
+ }
450
+ return compileCb ( null , htmlPluginData ) ;
451
+ }
452
+
398
453
/**
399
454
* Hooks into webpack to collect assets and hash them, build the policy, and add it into our HTML template
400
455
* @param compiler
@@ -413,6 +468,10 @@ class CspHtmlWebpackPlugin {
413
468
'CspHtmlWebpackPlugin' ,
414
469
this . getFilesToHash . bind ( this )
415
470
) ;
471
+ HtmlWebpackPlugin . getHooks ( compilation ) . alterAssetTags . tapAsync (
472
+ 'CspHtmlWebpackPlugin' ,
473
+ this . addIntegrityAttributes . bind ( this , compilation )
474
+ )
416
475
} ) ;
417
476
}
418
477
}
0 commit comments