webpack-subresource-integrity
Version:
Webpack plugin for enabling Subresource Integrity
141 lines • 5.75 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 { addIfNotExist, map, flatMap, intersect, intersectSets, unionSet, allChunksInChunkIterable, allChunksInPrimaryChunkIterable, sriHashVariableReference, } from "./util";
import { createDAGfromGraph } from "./scc";
import { RuntimeModule, Template } from "webpack";
// This implementation assumes a directed acyclic graph (such as one produced by createDAGfromGraph),
// and does not attempt to detect cycles
function topologicalSort({ vertices, edges }) {
const sortedItems = [];
const seenNodes = new Set();
function visit(node) {
if (addIfNotExist(seenNodes, node)) {
return;
}
(edges.get(node) ?? []).forEach(visit);
sortedItems.push(node);
}
vertices.forEach(visit);
return sortedItems;
}
function buildTopologicallySortedChunkGraph(chunks) {
const vertices = new Set();
const edges = new Map();
// Chunks should have *all* chunks, not simply entry chunks
for (const vertex of chunks) {
if (addIfNotExist(vertices, vertex)) {
continue;
}
edges.set(vertex, new Set());
for (const childChunk of allChunksInChunkIterable(vertex)) {
edges.get(vertex)?.add(childChunk);
}
}
const dag = createDAGfromGraph({ vertices, edges });
const sortedVertices = topologicalSort(dag);
const chunkToSccMap = new Map(flatMap(dag.vertices, (scc) => map(scc.nodes, (chunk) => [chunk, scc])));
return [sortedVertices, dag, chunkToSccMap];
}
class ChunkToManifestMapBuilder {
sortedVertices;
chunkToSccMap;
manifest;
// This map tracks which hashes a chunk group has in its manifest and the intersection
// of all its parents (and intersection of all their parents, etc.)
// This is meant as a guarantee that the hash for a given chunk is handled by a chunk group
// or its parents regardless of the tree traversal used from the roots
hashesByChunkGroupAndParents = new Map();
constructor(chunks) {
const [sortedVertices, , chunkToSccMap] = buildTopologicallySortedChunkGraph(chunks);
this.sortedVertices = sortedVertices;
this.chunkToSccMap = chunkToSccMap;
this.manifest = this.createManifest();
}
build() {
return [this.sortedVertices, this.manifest];
}
createManifest() {
// A map of what child chunks a given chunk should contain hashes for
// We want to walk from the root nodes down to the leaves
return this.sortedVertices.reduceRight((manifest, vertex) => {
for (const chunk of vertex.nodes) {
manifest.set(chunk, this.createChunkManifest(chunk));
}
return manifest;
}, new Map());
}
createChunkManifest(chunk) {
const manifest = this.getChildChunksToAddToChunkManifest(chunk);
for (const manifestEntry of this.findIntersectionOfParentSets(chunk)) {
manifest.delete(manifestEntry);
}
const combinedParentManifest = this.findIntersectionOfParentSets(chunk);
for (const chunk of manifest) {
if (combinedParentManifest.has(chunk)) {
manifest.delete(chunk);
}
else {
combinedParentManifest.add(chunk);
}
}
this.addGroupCombinedManifest(chunk, manifest);
return manifest;
}
addGroupCombinedManifest(chunk, manifest) {
for (const group of chunk.groupsIterable) {
this.hashesByChunkGroupAndParents.set(group, unionSet(
// Intersection of all parent manifests
intersect(map(group.parentsIterable, (parent) => this.hashesByChunkGroupAndParents.get(parent) ??
new Set())),
// Add this chunk's manifest
manifest,
// Add any existing manifests part of the group
this.hashesByChunkGroupAndParents.get(group) ?? new Set()));
}
}
findIntersectionOfParentSets(chunk) {
const setsToIntersect = [];
for (const group of chunk.groupsIterable) {
for (const parent of group.parentsIterable) {
setsToIntersect.push(this.hashesByChunkGroupAndParents.get(parent) ?? new Set());
}
}
return intersectSets(setsToIntersect);
}
getChildChunksToAddToChunkManifest(chunk) {
const childChunks = new Set();
const chunkSCC = this.chunkToSccMap.get(chunk);
for (const childChunk of allChunksInPrimaryChunkIterable(chunk)) {
const childChunkSCC = this.chunkToSccMap.get(childChunk);
if (childChunkSCC === chunkSCC) {
// Don't include your own SCC.
// Your parent will have the hashes for your SCC siblings
continue;
}
for (const childChunkSccNode of childChunkSCC?.nodes ?? []) {
childChunks.add(childChunkSccNode);
}
}
return childChunks;
}
}
export function getChunkToManifestMap(chunks) {
return new ChunkToManifestMapBuilder(chunks).build();
}
export class AddLazySriRuntimeModule extends RuntimeModule {
sriHashes;
constructor(sriHashes, chunkName) {
super(`webpack-subresource-integrity lazy hashes for direct children of chunk ${chunkName}`);
this.sriHashes = sriHashes;
}
generate() {
return Template.asString([
`Object.assign(${sriHashVariableReference}, ${JSON.stringify(this.sriHashes)});`,
]);
}
}
//# sourceMappingURL=manifest.js.map