webpack-subresource-integrity
Version:
Webpack plugin for enabling Subresource Integrity
211 lines • 9.35 kB
JavaScript
/**
* 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.
*/
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.SubresourceIntegrityPlugin = void 0;
const crypto_1 = require("crypto");
const plugin_1 = require("./plugin");
const reporter_1 = require("./reporter");
const util_1 = require("./util");
const hooks_1 = require("./hooks");
const manifest_1 = require("./manifest");
const globals_1 = require("./globals");
/**
* The webpack-subresource-integrity plugin.
*
* @public
*/
class SubresourceIntegrityPlugin {
/**
* Create a new instance.
*
* @public
*/
constructor(options = {}) {
/**
* @internal
*/
this.setup = (compilation, hwpHooks) => {
const reporter = new reporter_1.Reporter(compilation);
if (!this.validateOptions(compilation, reporter) ||
!this.isEnabled(compilation)) {
return;
}
const plugin = new plugin_1.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(globals_1.thisPluginName, () => {
plugin.beforeRuntimeRequirements();
});
compilation.hooks.processAssets.tap({
name: globals_1.thisPluginName,
stage: compilation.compiler.webpack.Compilation
.PROCESS_ASSETS_STAGE_OPTIMIZE_INLINE,
}, (records) => {
return plugin.processAssets(records);
});
compilation.hooks.afterProcessAssets.tap(globals_1.thisPluginName, (records) => {
for (const chunk of compilation.chunks.values()) {
for (const chunkFile of chunk.files) {
const record = records[chunkFile];
if (record && record.source().includes(util_1.placeholderPrefix)) {
reporter.errorUnresolvedIntegrity(chunkFile);
}
}
}
});
compilation.compiler.webpack.optimize.RealContentHashPlugin.getCompilationHooks(compilation).updateHash.tap(globals_1.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(globals_1.thisPluginName, (pluginArgs) => __awaiter(this, void 0, void 0, function* () {
plugin.handleHwpPluginArgs(pluginArgs);
return pluginArgs;
}));
hwpHooks.alterAssetTagGroups.tapPromise({
name: globals_1.thisPluginName,
stage: 10000,
}, (data) => __awaiter(this, void 0, void 0, function* () {
plugin.handleHwpBodyTags(data);
return data;
}));
}
const { mainTemplate } = compilation;
mainTemplate.hooks.jsonpScript.tap(globals_1.thisPluginName, (source) => plugin.addAttribute("script", source));
mainTemplate.hooks.linkPreload.tap(globals_1.thisPluginName, (source) => plugin.addAttribute("link", source));
mainTemplate.hooks.localVars.tap(globals_1.thisPluginName, (source, chunk) => {
const allChunks = this.options.hashLoading === "lazy"
? plugin.getChildChunksToAddToChunkManifest(chunk)
: (0, util_1.findChunks)(chunk);
const includedChunks = chunk.getChunkMaps(false).hash;
if (Object.keys(includedChunks).length > 0) {
return compilation.compiler.webpack.Template.asString([
source,
`${util_1.sriHashVariableReference} = ` +
JSON.stringify((0, util_1.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(globals_1.thisPluginName, (chunk) => {
var _a;
const childChunks = plugin.getChildChunksToAddToChunkManifest(chunk);
if (childChunks.size > 0 && !chunk.hasRuntime()) {
compilation.addRuntimeModule(chunk, new manifest_1.AddLazySriRuntimeModule((0, util_1.generateSriHashPlaceholders)(childChunks, this.options.hashFuncNames), (_a = chunk.name) !== null && _a !== void 0 ? _a : chunk.id));
}
});
}
};
/**
* @internal
*/
this.validateOptions = (compilation, reporter) => {
if (this.isEnabled(compilation) &&
!compilation.compiler.options.output.crossOriginLoading) {
reporter.warnCrossOriginPolicy();
}
return (this.validateHashFuncNames(reporter) && this.validateHashLoading(reporter));
};
/**
* @internal
*/
this.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
*/
this.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
*/
this.warnStandardHashFunc = (reporter) => {
let foundStandardHashFunc = false;
this.options.hashFuncNames.forEach((hashFuncName) => {
if (globals_1.standardHashFuncNames.indexOf(hashFuncName) >= 0) {
foundStandardHashFunc = true;
}
});
if (!foundStandardHashFunc) {
reporter.warnStandardHashFuncs();
}
};
/**
* @internal
*/
this.validateHashFuncName = (reporter, hashFuncName) => {
if (typeof hashFuncName !== "string" &&
!(hashFuncName instanceof String)) {
reporter.errorNonStringHashFunc(hashFuncName);
return false;
}
try {
(0, crypto_1.createHash)(hashFuncName);
}
catch (error) {
reporter.errorUnusableHashFunc(hashFuncName, error);
return false;
}
return true;
};
if (typeof options !== "object") {
throw new Error("webpack-subresource-integrity: argument must be an object");
}
this.options = Object.assign({ hashFuncNames: ["sha384"], enabled: "auto", hashLoading: "eager" }, options);
}
/**
* @internal
*/
isEnabled(compilation) {
if (this.options.enabled === "auto") {
return compilation.options.mode !== "development";
}
return this.options.enabled;
}
apply(compiler) {
(0, hooks_1.install)(compiler, this.setup);
}
}
exports.SubresourceIntegrityPlugin = SubresourceIntegrityPlugin;
//# sourceMappingURL=index.js.map
;