UNPKG

webpack-userscript

Version:
206 lines 8.8 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.ProcessSSRI = void 0; const tslib_1 = require("tslib"); const node_path_1 = tslib_1.__importDefault(require("node:path")); const node_fetch_1 = tslib_1.__importDefault(require("node-fetch")); const p_limit_1 = tslib_1.__importDefault(require("p-limit")); const ssri_1 = require("ssri"); const url_1 = require("url"); const fs_1 = require("../fs"); const feature_1 = require("./feature"); class ProcessSSRI extends feature_1.Feature { constructor() { var _a; super(...arguments); this.name = 'ProcessSSRI'; this.allowedProtocols = new Set(['http:', 'https:']); this.ssriLockDirty = false; this.ssriLock = {}; this.limit = (0, p_limit_1.default)((_a = (typeof this.options.ssri === 'object' ? this.options.ssri.concurrency : undefined)) !== null && _a !== void 0 ? _a : 6); } apply({ hooks }) { const { ssri, root } = this.options; if (!ssri) { return; } // read lock hooks.init.tapPromise(this.name, (compiler) => tslib_1.__awaiter(this, void 0, void 0, function* () { const lockfile = this.getSSRILockFile(ssri); if (!lockfile) { return; } try { this.ssriLock = this.parseSSRILock(yield (0, fs_1.readJSON)(node_path_1.default.resolve(root !== null && root !== void 0 ? root : compiler.context, lockfile), compiler.inputFileSystem)); } catch (_a) { } this.ssriLockDirty = false; })); // write lock hooks.close.tapPromise(this.name, (compiler) => tslib_1.__awaiter(this, void 0, void 0, function* () { const lock = this.getSSRILockFile(ssri); if (!lock) { return; } const lockfile = node_path_1.default.resolve(root !== null && root !== void 0 ? root : compiler.context, lock); if (this.ssriLockDirty) { const { intermediateFileSystem } = compiler; const dir = node_path_1.default.dirname(lockfile); const isNotRoot = node_path_1.default.dirname(dir) !== dir; if (isNotRoot) { yield (0, fs_1.mkdirp)(dir, intermediateFileSystem); } yield (0, fs_1.writeJSON)(lockfile, this.toRawSSRILock(this.ssriLock), intermediateFileSystem); this.ssriLockDirty = false; } })); hooks.headers.tapPromise(this.name, (headers) => tslib_1.__awaiter(this, void 0, void 0, function* () { const targetURLs = this.getTargetURLs(headers, ssri).reduce((map, url) => map.set(url, this.normalizeURL(url)), new Map()); if (targetURLs.size === 0) { return headers; } // merge integrities from ssri-lock // and those provided within headers option (in respective tags) for (const [url, normalizedURL] of targetURLs) { const integrity = (0, ssri_1.parse)(this.parseSSRILike(url), { strict: true, }); if (integrity) { if (this.ssriLock[normalizedURL]) { integrity.merge(this.ssriLock[normalizedURL], { strict: true, }); } this.ssriLockDirty = true; this.ssriLock[normalizedURL] = integrity; } } // compute and merge missing hashes based on specified algorithms option yield Promise.all(Array.from(targetURLs.values()).map((normalizedURL) => tslib_1.__awaiter(this, void 0, void 0, function* () { var _b; const expectedAlgorithms = (_b = ssri.algorithms) !== null && _b !== void 0 ? _b : ['sha512']; const missingAlgorithms = expectedAlgorithms.filter((alg) => { var _a; return !((_a = this.ssriLock[normalizedURL]) === null || _a === void 0 ? void 0 : _a[alg]); }); const newIntegrity = missingAlgorithms.length > 0 ? yield this.limit(() => this.computeSSRI(normalizedURL, missingAlgorithms, { strict: ssri.strict, })) : null; if (newIntegrity) { if (this.ssriLock[normalizedURL]) { newIntegrity.merge(this.ssriLock[normalizedURL]); } this.ssriLock[normalizedURL] = newIntegrity; this.ssriLockDirty = true; } }))); return Object.assign(Object.assign({}, headers), this.patchHeaders(headers, this.ssriLock)); })); } getSSRILockFile({ lock = true } = {}) { return typeof lock === 'string' ? lock : lock ? 'ssri-lock.json' : undefined; } getTargetURLs(headers, options) { const urls = []; if (headers.require !== undefined) { const requireURLs = (Array.isArray(headers.require) ? headers.require : [headers.require]).filter((url) => typeof url === 'string'); for (const urlStr of requireURLs) { const url = this.parseURL(urlStr); if (this.filterURL(url, 'require', options)) { urls.push(this.stringifyURL(url)); } } } if (headers.resource !== undefined) { for (const urlStr of Object.values(headers.resource).filter((url) => typeof url === 'string')) { const url = this.parseURL(urlStr); if (this.filterURL(url, 'resource', options)) { urls.push(this.stringifyURL(url)); } } } return urls; } normalizeURL(url) { const u = new url_1.URL('', this.parseURL(url)); u.hash = ''; return u.toString(); } filterURL(url, tag, { include, exclude } = {}) { if (!this.allowedProtocols.has(url.protocol)) { return false; } if (include && !include(tag, url)) { return false; } if (exclude && exclude(tag, url)) { return false; } return true; } computeSSRI(url, algorithms, { strict }) { return tslib_1.__awaiter(this, void 0, void 0, function* () { const response = yield (0, node_fetch_1.default)(url); if (response.status !== 200 || response.body === null) { throw new Error(`Failed to fetch SSRI sources. ` + `[${response.status} ${response.statusText}] ${url}`); } return yield (0, ssri_1.fromStream)(response.body, { algorithms, strict, }); }); } parseSSRILike(url) { return this.parseURL(url).hash.slice(1).replace(/[,;]/g, ' '); } parseSSRILock(rawSSRILock) { return Object.fromEntries(Object.entries(rawSSRILock).map(([url, integrityLike]) => [ url, (0, ssri_1.parse)(integrityLike, { strict: true }), ])); } toRawSSRILock(ssriLock) { return Object.fromEntries(Object.entries(ssriLock).map(([url, integrity]) => [url, (0, ssri_1.stringify)(integrity, { strict: true })])); } parseURL(url) { return new url_1.URL(url); } stringifyURL(url) { return url.toString(); } updateURL(url, ssriLock) { const integrity = ssriLock[url]; if (!integrity) return url; const urlObj = this.parseURL(url); urlObj.hash = '#' + (0, ssri_1.stringify)(integrity, { sep: ',', strict: true }); return urlObj.toString(); } patchHeaders(headers, ssriLock) { const headersProps = {}; if (headers.require !== undefined) { if (Array.isArray(headers.require)) { headersProps.require = headers.require .filter((url) => typeof url === 'string') .map((url) => this.updateURL(url, ssriLock)); } else { headersProps.require = this.updateURL(headers.require, ssriLock); } } if (headers.resource !== undefined) { headersProps.resource = Object.fromEntries(Object.entries(headers.resource) .filter((entry) => typeof entry[1] === 'string') .map(([name, url]) => [name, this.updateURL(url, ssriLock)])); } return headersProps; } } exports.ProcessSSRI = ProcessSSRI; //# sourceMappingURL=ssri.js.map