/* eslint-disable import/no-extraneous-dependencies -- this file is a devDependency*/ const cheerio = require("cheerio"); const crypto = require("crypto"); const HtmlWebpackPlugin = require("html-webpack-plugin"); const digest = (str) => { const hash = crypto.createHash("sha256").update(str, "utf8").digest("base64"); return `sha256-${hash}`; }; class CspHashPlugin { constructor(opts) { this.opts = { ...opts }; } apply(compiler) { compiler.hooks.compilation.tap("CspHashPlugin", (compilation) => { HtmlWebpackPlugin.getHooks(compilation).beforeEmit.tapAsync( "CspHashPlugin", (data, cb) => { const { filename } = this.opts; const $ = cheerio.load(data.html, { decodeEntities: false }); if (filename) { const results = {}; results["script-hashes"] = $("script:not([src]):not([no-csp-hash])") .map((i, elmt) => digest($(elmt).html())) .get(); const json = JSON.stringify(results); compilation.assets[filename] = { source: () => json, size: () => json.length, }; } // Remove no-csp-hash attributes. Cheerio does not parse Jinja templates // correctly, so we brute force this with a regular expression. data.html = data.html.replace( /(