UNPKG

webpack-subresource-integrity

Version:
203 lines 7.66 kB
/** * Copyright (c) 2015-present, Waysact Pty Ltd * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ import { createHash } from "crypto"; import { Plugin } from "./plugin"; import { Reporter } from "./reporter"; import { findChunks, placeholderPrefix, generateSriHashPlaceholders, sriHashVariableReference, } from "./util"; import { install } from "./hooks"; import { AddLazySriRuntimeModule } from "./manifest"; import { thisPluginName, standardHashFuncNames } from "./globals"; /** * The webpack-subresource-integrity plugin. * * @public */ export class SubresourceIntegrityPlugin { options; /** * Create a new instance. * * @public */ constructor(options = {}) { if (typeof options !== "object") { throw new Error("webpack-subresource-integrity: argument must be an object"); } this.options = { hashFuncNames: ["sha384"], enabled: "auto", hashLoading: "eager", ...options, }; } /** * @internal */ isEnabled(compilation) { if (this.options.enabled === "auto") { return compilation.options.mode !== "development"; } return this.options.enabled; } /** * @internal */ setup = (compilation, hwpHooks) => { const reporter = new Reporter(compilation); if (!this.validateOptions(compilation, reporter) || !this.isEnabled(compilation)) { return; } const plugin = new Plugin(compilation, this.options, reporter); if (typeof compilation.outputOptions.chunkLoading === "string" && ["require", "async-node"].includes(compilation.outputOptions.chunkLoading)) { reporter.warnNonWeb(); return; } compilation.hooks.beforeRuntimeRequirements.tap(thisPluginName, () => { plugin.beforeRuntimeRequirements(); }); compilation.hooks.processAssets.tap({ name: thisPluginName, stage: compilation.compiler.webpack.Compilation .PROCESS_ASSETS_STAGE_OPTIMIZE_INLINE, }, (records) => { return plugin.processAssets(records); }); compilation.hooks.afterProcessAssets.tap(thisPluginName, (records) => { for (const chunk of compilation.chunks.values()) { for (const chunkFile of chunk.files) { const record = records[chunkFile]; if (record && record.source().includes(placeholderPrefix)) { reporter.errorUnresolvedIntegrity(chunkFile); } } } }); compilation.compiler.webpack.optimize.RealContentHashPlugin.getCompilationHooks(compilation).updateHash.tap(thisPluginName, (input, oldHash) => { // FIXME: remove type hack pending https://github.com/webpack/webpack/pull/12642#issuecomment-784744910 return plugin.updateHash(input, oldHash); }); if (hwpHooks) { hwpHooks.beforeAssetTagGeneration.tapPromise(thisPluginName, async (pluginArgs) => { plugin.handleHwpPluginArgs(pluginArgs); return pluginArgs; }); hwpHooks.alterAssetTagGroups.tapPromise({ name: thisPluginName, stage: 10000, }, async (data) => { plugin.handleHwpBodyTags(data); return data; }); } const { mainTemplate } = compilation; mainTemplate.hooks.jsonpScript.tap(thisPluginName, (source) => plugin.addAttribute("script", source)); mainTemplate.hooks.linkPreload.tap(thisPluginName, (source) => plugin.addAttribute("link", source)); mainTemplate.hooks.localVars.tap(thisPluginName, (source, chunk) => { const allChunks = this.options.hashLoading === "lazy" ? plugin.getChildChunksToAddToChunkManifest(chunk) : findChunks(chunk); const includedChunks = chunk.getChunkMaps(false).hash; if (Object.keys(includedChunks).length > 0) { return compilation.compiler.webpack.Template.asString([ source, `${sriHashVariableReference} = ` + JSON.stringify(generateSriHashPlaceholders(Array.from(allChunks).filter((depChunk) => depChunk.id !== null && includedChunks[depChunk.id.toString()]), this.options.hashFuncNames)) + ";", ]); } return source; }); if (this.options.hashLoading === "lazy") { compilation.hooks.additionalChunkRuntimeRequirements.tap(thisPluginName, (chunk) => { const childChunks = plugin.getChildChunksToAddToChunkManifest(chunk); if (childChunks.size > 0 && !chunk.hasRuntime()) { compilation.addRuntimeModule(chunk, new AddLazySriRuntimeModule(generateSriHashPlaceholders(childChunks, this.options.hashFuncNames), chunk.name ?? chunk.id)); } }); } }; /** * @internal */ validateOptions = (compilation, reporter) => { if (this.isEnabled(compilation) && !compilation.compiler.options.output.crossOriginLoading) { reporter.warnCrossOriginPolicy(); } return (this.validateHashFuncNames(reporter) && this.validateHashLoading(reporter)); }; /** * @internal */ validateHashFuncNames = (reporter) => { if (!Array.isArray(this.options.hashFuncNames)) { reporter.errorHashFuncsNonArray(this.options.hashFuncNames); return false; } else if (this.options.hashFuncNames.length === 0) { reporter.errorHashFuncsEmpty(); return false; } else if (!this.options.hashFuncNames.every(this.validateHashFuncName.bind(this, reporter))) { return false; } else { this.warnStandardHashFunc(reporter); return true; } }; /** * @internal */ validateHashLoading = (reporter) => { const supportedHashLoadingOptions = Object.freeze(["eager", "lazy"]); if (supportedHashLoadingOptions.includes(this.options.hashLoading)) { return true; } reporter.errorInvalidHashLoading(this.options.hashLoading, supportedHashLoadingOptions); return false; }; /** * @internal */ warnStandardHashFunc = (reporter) => { let foundStandardHashFunc = false; this.options.hashFuncNames.forEach((hashFuncName) => { if (standardHashFuncNames.indexOf(hashFuncName) >= 0) { foundStandardHashFunc = true; } }); if (!foundStandardHashFunc) { reporter.warnStandardHashFuncs(); } }; /** * @internal */ validateHashFuncName = (reporter, hashFuncName) => { if (typeof hashFuncName !== "string" && !(hashFuncName instanceof String)) { reporter.errorNonStringHashFunc(hashFuncName); return false; } try { createHash(hashFuncName); } catch (error) { reporter.errorUnusableHashFunc(hashFuncName, error); return false; } return true; }; apply(compiler) { install(compiler, this.setup); } } //# sourceMappingURL=index.js.map