webpack-subresource-integrity
Version:
Webpack plugin for enabling Subresource Integrity
203 lines • 6.59 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.
*/
import { createHash } from "crypto";
import { sep } from "path";
export const sriHashVariableReference = "__webpack_require__.sriHashes";
export function assert(value, message) {
if (!value) {
throw new Error(message);
}
}
export 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;
}
export const normalizePath = (p) => p.replace(/\?.*$/, "").split(sep).join("/");
export const placeholderPrefix = "*-*-*-CHUNK-SRI-HASH-";
export const placeholderRegex = new RegExp(`${placeholderPrefix.replace(/[-*/\\]/g, "\\$&")}[a-zA-Z0-9=/+]+(\\ssha\\d{3}-[a-zA-Z0-9=/+]+)*`, "g");
export const computeIntegrity = (hashFuncNames, source) => {
const result = hashFuncNames
.map((hashFuncName) => hashFuncName +
"-" +
createHash(hashFuncName)
.update(typeof source === "string" ? Buffer.from(source, "utf-8") : source)
.digest("base64"))
.join(" ");
return result;
};
const placeholderCache = {};
export const makePlaceholder = (hashFuncNames, id) => {
const cacheKey = hashFuncNames.join() + id;
const cachedPlaceholder = placeholderCache[cacheKey];
if (cachedPlaceholder)
return cachedPlaceholder;
const placeholderSource = `${placeholderPrefix}${id}`;
const filler = computeIntegrity(hashFuncNames, placeholderSource);
const placeholder = placeholderPrefix + filler.substring(placeholderPrefix.length);
placeholderCache[cacheKey] = placeholder;
return placeholder;
};
export function addIfNotExist(set, item) {
if (set.has(item))
return true;
set.add(item);
return false;
}
export 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;
}
export function notNil(value) {
return value !== null && value !== undefined;
}
export function generateSriHashPlaceholders(chunks, hashFuncNames) {
return Array.from(chunks).reduce((sriHashes, depChunk) => {
if (depChunk.id) {
sriHashes[depChunk.id] = makePlaceholder(hashFuncNames, depChunk.id);
}
return sriHashes;
}, {});
}
function allSetsHave(sets, item) {
for (const set of sets) {
if (!set.has(item)) {
return false;
}
}
return true;
}
export 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;
}
}
export function intersectSets(setsToIntersect) {
return new Set(intersect(setsToIntersect));
}
export function unionSet(...sets) {
const result = new Set();
for (const set of sets) {
for (const item of set) {
result.add(item);
}
}
return result;
}
export function* map(items, fn) {
for (const item of items) {
yield fn(item);
}
}
export function* flatMap(collections, fn) {
for (const item of collections) {
for (const result of fn(item)) {
yield result;
}
}
}
export function* allChunksInGroupIterable(chunkGroup) {
for (const childGroup of chunkGroup.childrenIterable) {
for (const childChunk of childGroup.chunks) {
yield childChunk;
}
}
}
export function* allChunksInChunkIterable(chunk) {
for (const group of chunk.groupsIterable) {
for (const childChunk of allChunksInGroupIterable(group)) {
yield childChunk;
}
}
}
export 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;
}
}
}
export function updateAsset(compilation, assetPath, source, integrity, onUpdate) {
compilation.updateAsset(assetPath, source, (assetInfo) => {
if (!assetInfo) {
return undefined;
}
onUpdate(assetInfo);
return {
...assetInfo,
contenthash: Array.isArray(assetInfo.contenthash)
? [...new Set([...assetInfo.contenthash, integrity])]
: assetInfo.contenthash
? [assetInfo.contenthash, integrity]
: integrity,
};
});
}
export function tryGetSource(source) {
try {
return source.source();
}
catch (_) {
return undefined;
}
}
export 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(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;
}
export function usesAnyHash(assetInfo) {
return !!(assetInfo.fullhash ||
assetInfo.chunkhash ||
assetInfo.modulehash ||
assetInfo.contenthash);
}
export function hasOwnProperty(obj, prop) {
return Object.prototype.hasOwnProperty.call(obj, prop);
}
//# sourceMappingURL=util.js.map