webpack-subresource-integrity
Version:
Webpack plugin for enabling Subresource Integrity
203 lines • 10.8 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());
});
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const path_1 = require("path");
const webpack_1 = __importDefault(require("webpack"));
const html_webpack_plugin_1 = __importDefault(require("html-webpack-plugin"));
const __1 = require("..");
const util_1 = require("../util");
jest.unmock("html-webpack-plugin");
process.on("unhandledRejection", (error) => {
console.log(error); // eslint-disable-line no-console
process.exit(1);
});
test("throws an error when options is not an object", () => __awaiter(void 0, void 0, void 0, function* () {
expect(() => {
new __1.SubresourceIntegrityPlugin(function dummy() {
// dummy function, never called
}); // eslint-disable-line no-new
}).toThrow(/argument must be an object/);
}));
const runCompilation = (compiler) => new Promise((resolve, reject) => {
compiler.run((err, stats) => {
if (err) {
reject(err);
}
else if (!stats) {
reject(new Error("Missing stats"));
}
else {
resolve(stats.compilation);
}
});
});
const disableOutputPlugin = {
apply(compiler) {
compiler.hooks.compilation.tap("DisableOutputWebpackPlugin", (compilation) => {
compilation.hooks.afterProcessAssets.tap({
name: "DisableOutputWebpackPlugin",
stage: 10000,
}, (compilationAssets) => {
Object.keys(compilation.assets).forEach((asset) => {
delete compilation.assets[asset];
});
Object.keys(compilationAssets).forEach((asset) => {
delete compilationAssets[asset];
});
});
});
},
};
const defaultOptions = {
mode: "none",
entry: (0, path_1.resolve)(__dirname, "./__fixtures__/simple-project/src/."),
output: {
crossOriginLoading: "anonymous",
},
};
test("warns when no standard hash function name is specified", () => __awaiter(void 0, void 0, void 0, function* () {
var _a;
const plugin = new __1.SubresourceIntegrityPlugin({
hashFuncNames: ["md5"],
});
const compilation = yield runCompilation((0, webpack_1.default)(Object.assign(Object.assign({}, defaultOptions), { plugins: [plugin] })));
expect(compilation.errors).toEqual([]);
expect((_a = compilation.warnings[0]) === null || _a === void 0 ? void 0 : _a.message).toMatch(new RegExp("It is recommended that at least one hash function is part of " +
"the set for which support is mandated by the specification"));
expect(compilation.warnings[1]).toBeUndefined();
}));
test("supports new constructor with array of hash function names", () => __awaiter(void 0, void 0, void 0, function* () {
const plugin = new __1.SubresourceIntegrityPlugin({
hashFuncNames: ["sha256", "sha384"],
});
const compilation = yield runCompilation((0, webpack_1.default)(Object.assign(Object.assign({}, defaultOptions), { plugins: [plugin, disableOutputPlugin] })));
expect(compilation.errors.length).toBe(0);
expect(compilation.warnings.length).toBe(0);
}));
test("errors if hash function names is not an array", () => __awaiter(void 0, void 0, void 0, function* () {
var _b;
const plugin = new __1.SubresourceIntegrityPlugin({
hashFuncNames: "sha256",
});
const compilation = yield runCompilation((0, webpack_1.default)(Object.assign(Object.assign({}, defaultOptions), { plugins: [plugin, disableOutputPlugin] })));
expect(compilation.errors.length).toBe(1);
expect(compilation.warnings.length).toBe(0);
expect((_b = compilation.errors[0]) === null || _b === void 0 ? void 0 : _b.message).toMatch(/options.hashFuncNames must be an array of hash function names, instead got 'sha256'/);
}));
test("errors if hash function names contains non-string", () => __awaiter(void 0, void 0, void 0, function* () {
var _c;
const plugin = new __1.SubresourceIntegrityPlugin({
hashFuncNames: [1234],
});
const compilation = yield runCompilation((0, webpack_1.default)(Object.assign(Object.assign({}, defaultOptions), { plugins: [plugin, disableOutputPlugin] })));
expect(compilation.errors.length).toBe(1);
expect(compilation.warnings.length).toBe(0);
expect((_c = compilation.errors[0]) === null || _c === void 0 ? void 0 : _c.message).toMatch(/options.hashFuncNames must be an array of hash function names, but contained 1234/);
}));
test("errors if hash function names are empty", () => __awaiter(void 0, void 0, void 0, function* () {
var _d;
const plugin = new __1.SubresourceIntegrityPlugin({
hashFuncNames: [],
});
const compilation = yield runCompilation((0, webpack_1.default)(Object.assign(Object.assign({}, defaultOptions), { plugins: [plugin, disableOutputPlugin] })));
expect(compilation.errors.length).toBe(1);
expect(compilation.warnings.length).toBe(0);
expect((_d = compilation.errors[0]) === null || _d === void 0 ? void 0 : _d.message).toMatch(/Must specify at least one hash function name/);
}));
test("errors if hash function names contains unsupported digest", () => __awaiter(void 0, void 0, void 0, function* () {
var _e;
const plugin = new __1.SubresourceIntegrityPlugin({
hashFuncNames: ["frobnicate"],
});
const compilation = yield runCompilation((0, webpack_1.default)(Object.assign(Object.assign({}, defaultOptions), { plugins: [plugin, disableOutputPlugin] })));
expect(compilation.errors.length).toBe(1);
expect(compilation.warnings.length).toBe(0);
expect((_e = compilation.errors[0]) === null || _e === void 0 ? void 0 : _e.message).toMatch(/Cannot use hash function 'frobnicate': Digest method not supported/);
}));
test("errors if hashLoading option uses unknown value", () => __awaiter(void 0, void 0, void 0, function* () {
var _f;
const plugin = new __1.SubresourceIntegrityPlugin({
hashLoading: "invalid",
});
const compilation = yield runCompilation((0, webpack_1.default)(Object.assign(Object.assign({}, defaultOptions), { plugins: [plugin, disableOutputPlugin] })));
expect(compilation.errors.length).toBe(1);
expect(compilation.warnings.length).toBe(0);
expect((_f = compilation.errors[0]) === null || _f === void 0 ? void 0 : _f.message).toMatch(/options.hashLoading must be one of 'eager', 'lazy', instead got 'invalid'/);
}));
test("uses default options", () => __awaiter(void 0, void 0, void 0, function* () {
const plugin = new __1.SubresourceIntegrityPlugin({
hashFuncNames: ["sha256"],
});
const compilation = yield runCompilation((0, webpack_1.default)(Object.assign(Object.assign({}, defaultOptions), { plugins: [plugin, disableOutputPlugin] })));
expect(plugin["options"].hashFuncNames).toEqual(["sha256"]);
expect(plugin["options"].enabled).toBeTruthy();
expect(compilation.errors.length).toBe(0);
expect(compilation.warnings.length).toBe(0);
}));
test("should warn when output.crossOriginLoading is not set", () => __awaiter(void 0, void 0, void 0, function* () {
var _g, _h;
const plugin = new __1.SubresourceIntegrityPlugin({ hashFuncNames: ["sha256"] });
const compilation = yield runCompilation((0, webpack_1.default)(Object.assign(Object.assign({}, defaultOptions), { output: { crossOriginLoading: false }, plugins: [plugin, disableOutputPlugin] })));
compilation.mainTemplate.hooks.jsonpScript.call("", {});
compilation.mainTemplate.hooks.linkPreload.call("", {});
expect(compilation.errors.length).toBe(1);
expect(compilation.warnings.length).toBe(1);
expect((_g = compilation.warnings[0]) === null || _g === void 0 ? void 0 : _g.message).toMatch(/Set webpack option output\.crossOriginLoading/);
expect((_h = compilation.errors[0]) === null || _h === void 0 ? void 0 : _h.message).toMatch(/webpack option output\.crossOriginLoading not set, code splitting will not work!/);
}));
test("should ignore tags without attributes", () => __awaiter(void 0, void 0, void 0, function* () {
const plugin = new __1.SubresourceIntegrityPlugin({ hashFuncNames: ["sha256"] });
const compilation = yield runCompilation((0, webpack_1.default)(Object.assign(Object.assign({}, defaultOptions), { plugins: [plugin, disableOutputPlugin] })));
const tag = {
tagName: "script",
voidTag: false,
attributes: {},
meta: {},
};
html_webpack_plugin_1.default.getHooks(compilation).alterAssetTagGroups.promise({
headTags: [],
bodyTags: [tag],
outputName: "foo",
publicPath: "public",
plugin: new html_webpack_plugin_1.default(),
});
expect(Object.keys(tag.attributes)).not.toContain(["integrity"]);
expect(compilation.errors).toEqual([]);
expect(compilation.warnings).toEqual([]);
}));
test("positive assertion", () => {
(0, util_1.assert)(true, "Pass");
});
test("negative assertion", () => {
expect(() => {
(0, util_1.assert)(false, "Fail");
}).toThrow(new Error("Fail"));
});
test("errors with unresolved integrity", () => __awaiter(void 0, void 0, void 0, function* () {
var _j;
const plugin = new __1.SubresourceIntegrityPlugin({
hashFuncNames: ["sha256", "sha384"],
});
const compilation = yield runCompilation((0, webpack_1.default)(Object.assign(Object.assign({}, defaultOptions), { entry: (0, path_1.resolve)(__dirname, "./__fixtures__/unresolved/src/."), plugins: [plugin, disableOutputPlugin] })));
expect(compilation.errors.length).toBe(1);
expect(compilation.warnings.length).toBe(0);
expect((_j = compilation.errors[0]) === null || _j === void 0 ? void 0 : _j.message).toMatch(new RegExp("contains unresolved integrity placeholders"));
}));
//# sourceMappingURL=unit.test.js.map
;