webpack-subresource-integrity
Version:
Webpack plugin for enabling Subresource Integrity
225 lines • 8.1 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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.hasOwnProperty = exports.usesAnyHash = exports.replaceInSource = exports.tryGetSource = exports.updateAsset = exports.allChunksInPrimaryChunkIterable = exports.allChunksInChunkIterable = exports.allChunksInGroupIterable = exports.flatMap = exports.map = exports.unionSet = exports.intersectSets = exports.intersect = exports.generateSriHashPlaceholders = exports.notNil = exports.findChunks = exports.addIfNotExist = exports.makePlaceholder = exports.computeIntegrity = exports.placeholderRegex = exports.placeholderPrefix = exports.normalizePath = exports.getTagSrc = exports.assert = exports.sriHashVariableReference = void 0;
const crypto_1 = require("crypto");
const path_1 = require("path");
exports.sriHashVariableReference = "__webpack_require__.sriHashes";
function assert(value, message) {
if (!value) {
throw new Error(message);
}
}
exports.assert = assert;
function getTagSrc(tag) {
if (!["script", "link"].includes(tag.tagName) || !tag.attributes) {
return undefined;
}
if (typeof tag.attributes["href"] === "string") {
return tag.attributes["href"];
}
if (typeof tag.attributes["src"] === "string") {
return tag.attributes["src"];
}
return undefined;
}
exports.getTagSrc = getTagSrc;
const normalizePath = (p) => p.replace(/\?.*$/, "").split(path_1.sep).join("/");
exports.normalizePath = normalizePath;
exports.placeholderPrefix = "*-*-*-CHUNK-SRI-HASH-";
exports.placeholderRegex = new RegExp(`${exports.placeholderPrefix.replace(/[-*/\\]/g, "\\$&")}[a-zA-Z0-9=/+]+(\\ssha\\d{3}-[a-zA-Z0-9=/+]+)*`, "g");
const computeIntegrity = (hashFuncNames, source) => {
const result = hashFuncNames
.map((hashFuncName) => hashFuncName +
"-" +
(0, crypto_1.createHash)(hashFuncName)
.update(typeof source === "string" ? Buffer.from(source, "utf-8") : source)
.digest("base64"))
.join(" ");
return result;
};
exports.computeIntegrity = computeIntegrity;
const placeholderCache = {};
const makePlaceholder = (hashFuncNames, id) => {
const cacheKey = hashFuncNames.join() + id;
const cachedPlaceholder = placeholderCache[cacheKey];
if (cachedPlaceholder)
return cachedPlaceholder;
const placeholderSource = `${exports.placeholderPrefix}${id}`;
const filler = (0, exports.computeIntegrity)(hashFuncNames, placeholderSource);
const placeholder = exports.placeholderPrefix + filler.substring(exports.placeholderPrefix.length);
placeholderCache[cacheKey] = placeholder;
return placeholder;
};
exports.makePlaceholder = makePlaceholder;
function addIfNotExist(set, item) {
if (set.has(item))
return true;
set.add(item);
return false;
}
exports.addIfNotExist = addIfNotExist;
function findChunks(chunk) {
const allChunks = new Set();
const groupsVisited = new Set();
(function recurseChunk(childChunk) {
function recurseGroup(group) {
if (addIfNotExist(groupsVisited, group.id))
return;
group.chunks.forEach(recurseChunk);
group.childrenIterable.forEach(recurseGroup);
}
if (addIfNotExist(allChunks, childChunk))
return;
Array.from(childChunk.groupsIterable).forEach(recurseGroup);
})(chunk);
return allChunks;
}
exports.findChunks = findChunks;
function notNil(value) {
return value !== null && value !== undefined;
}
exports.notNil = notNil;
function generateSriHashPlaceholders(chunks, hashFuncNames) {
return Array.from(chunks).reduce((sriHashes, depChunk) => {
if (depChunk.id) {
sriHashes[depChunk.id] = (0, exports.makePlaceholder)(hashFuncNames, depChunk.id);
}
return sriHashes;
}, {});
}
exports.generateSriHashPlaceholders = generateSriHashPlaceholders;
function allSetsHave(sets, item) {
for (const set of sets) {
if (!set.has(item)) {
return false;
}
}
return true;
}
function* intersect(sets) {
const { value: initialSet } = sets[Symbol.iterator]().next();
if (!initialSet) {
return;
}
initialSetLoop: for (const item of initialSet) {
if (!allSetsHave(sets, item)) {
continue initialSetLoop;
}
yield item;
}
}
exports.intersect = intersect;
function intersectSets(setsToIntersect) {
return new Set(intersect(setsToIntersect));
}
exports.intersectSets = intersectSets;
function unionSet(...sets) {
const result = new Set();
for (const set of sets) {
for (const item of set) {
result.add(item);
}
}
return result;
}
exports.unionSet = unionSet;
function* map(items, fn) {
for (const item of items) {
yield fn(item);
}
}
exports.map = map;
function* flatMap(collections, fn) {
for (const item of collections) {
for (const result of fn(item)) {
yield result;
}
}
}
exports.flatMap = flatMap;
function* allChunksInGroupIterable(chunkGroup) {
for (const childGroup of chunkGroup.childrenIterable) {
for (const childChunk of childGroup.chunks) {
yield childChunk;
}
}
}
exports.allChunksInGroupIterable = allChunksInGroupIterable;
function* allChunksInChunkIterable(chunk) {
for (const group of chunk.groupsIterable) {
for (const childChunk of allChunksInGroupIterable(group)) {
yield childChunk;
}
}
}
exports.allChunksInChunkIterable = allChunksInChunkIterable;
function* allChunksInPrimaryChunkIterable(chunk) {
for (const chunkGroup of chunk.groupsIterable) {
if (chunkGroup.chunks[chunkGroup.chunks.length - 1] !== chunk) {
// Only add sri hashes for one chunk per chunk group,
// where the last chunk in the group is the primary chunk
continue;
}
for (const childChunk of allChunksInGroupIterable(chunkGroup)) {
yield childChunk;
}
}
}
exports.allChunksInPrimaryChunkIterable = allChunksInPrimaryChunkIterable;
function updateAsset(compilation, assetPath, source, integrity, onUpdate) {
compilation.updateAsset(assetPath, source, (assetInfo) => {
if (!assetInfo) {
return undefined;
}
onUpdate(assetInfo);
return Object.assign(Object.assign({}, assetInfo), { contenthash: Array.isArray(assetInfo.contenthash)
? [...new Set([...assetInfo.contenthash, integrity])]
: assetInfo.contenthash
? [assetInfo.contenthash, integrity]
: integrity });
});
}
exports.updateAsset = updateAsset;
function tryGetSource(source) {
try {
return source.source();
}
catch (_) {
return undefined;
}
}
exports.tryGetSource = tryGetSource;
function replaceInSource(compiler, source, path, replacements) {
const oldSource = source.source();
if (typeof oldSource !== "string") {
return source;
}
const newAsset = new compiler.webpack.sources.ReplaceSource(source, path);
for (const match of oldSource.matchAll(exports.placeholderRegex)) {
const placeholder = match[0];
const position = match.index;
if (placeholder && position !== undefined) {
newAsset.replace(position, position + placeholder.length - 1, replacements.get(placeholder) || placeholder);
}
}
return newAsset;
}
exports.replaceInSource = replaceInSource;
function usesAnyHash(assetInfo) {
return !!(assetInfo.fullhash ||
assetInfo.chunkhash ||
assetInfo.modulehash ||
assetInfo.contenthash);
}
exports.usesAnyHash = usesAnyHash;
function hasOwnProperty(obj, prop) {
return Object.prototype.hasOwnProperty.call(obj, prop);
}
exports.hasOwnProperty = hasOwnProperty;
//# sourceMappingURL=util.js.map
;