projecte_ionic/node_modules/webpack-subresource-integrity/index.js
2022-02-09 18:30:03 +01:00

464 lines
16 KiB
JavaScript
Executable file

/**
* 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 crypto = require('crypto');
var path = require('path');
var webpack = require('webpack');
// eslint-disable-next-line global-require
var ReplaceSource = (webpack.sources || require('webpack-sources')).ReplaceSource;
var util = require('./util');
var WebIntegrityJsonpMainTemplatePlugin = require('./jmtp');
var HtmlWebpackPlugin;
// https://www.w3.org/TR/2016/REC-SRI-20160623/#cryptographic-hash-functions
var standardHashFuncNames = ['sha256', 'sha384', 'sha512'];
try {
// eslint-disable-next-line global-require
HtmlWebpackPlugin = require('html-webpack-plugin');
} catch (e) {
if (!(e instanceof Error) || e.code !== 'MODULE_NOT_FOUND') {
throw e;
}
}
function SubresourceIntegrityPlugin(options) {
var useOptions;
if (options === null || typeof options === 'undefined') {
useOptions = {};
} else if (typeof options === 'object') {
useOptions = options;
} else {
throw new Error('webpack-subresource-integrity: argument must be an object');
}
this.options = {
enabled: true
};
Object.assign(this.options, useOptions);
this.emittedMessages = {};
this.assetIntegrity = new Map();
}
SubresourceIntegrityPlugin.prototype.emitMessage = function emitMessage(messages, message) {
messages.push(new Error('webpack-subresource-integrity: ' + message));
};
SubresourceIntegrityPlugin.prototype.emitMessageOnce = function emitMessageOnce(messages, message) {
if (!this.emittedMessages[message]) {
this.emittedMessages[message] = true;
this.emitMessage(messages, message);
}
};
SubresourceIntegrityPlugin.prototype.warnOnce = function warn(compilation, message) {
this.emitMessageOnce(compilation.warnings, message);
};
SubresourceIntegrityPlugin.prototype.error = function error(compilation, message) {
this.emitMessage(compilation.errors, message);
};
SubresourceIntegrityPlugin.prototype.errorOnce = function error(compilation, message) {
this.emitMessageOnce(compilation.errors, message);
};
SubresourceIntegrityPlugin.prototype.validateOptions = function validateOptions(compilation) {
if (this.optionsValidated) {
return;
}
this.optionsValidated = true;
if (this.options.enabled && !compilation.compiler.options.output.crossOriginLoading) {
this.warnOnce(
compilation,
'SRI requires a cross-origin policy, defaulting to "anonymous". ' +
'Set webpack option output.crossOriginLoading to a value other than false ' +
'to make this warning go away. ' +
'See https://w3c.github.io/webappsec-subresource-integrity/#cross-origin-data-leakage'
);
}
this.validateHashFuncNames(compilation);
};
SubresourceIntegrityPlugin.prototype.validateHashFuncNames =
function validateHashFuncNames(compilation) {
if (!Array.isArray(this.options.hashFuncNames)) {
this.error(
compilation,
'options.hashFuncNames must be an array of hash function names, ' +
'instead got \'' + this.options.hashFuncNames + '\'.');
this.options.enabled = false;
} else if (
!this.options.hashFuncNames.every(this.validateHashFuncName.bind(this, compilation))
) {
this.options.enabled = false;
} else {
this.warnStandardHashFunc(compilation);
}
};
SubresourceIntegrityPlugin.prototype.warnStandardHashFunc =
function warnStandardHashFunc(compilation) {
var foundStandardHashFunc = false;
var i;
for (i = 0; i < this.options.hashFuncNames.length; i += 1) {
if (standardHashFuncNames.indexOf(this.options.hashFuncNames[i]) >= 0) {
foundStandardHashFunc = true;
}
}
if (!foundStandardHashFunc) {
this.warnOnce(
compilation,
'It is recommended that at least one hash function is part of the set ' +
'for which support is mandated by the specification. ' +
'These are: ' + standardHashFuncNames.join(', ') + '. ' +
'See http://www.w3.org/TR/SRI/#cryptographic-hash-functions for more information.');
}
};
SubresourceIntegrityPlugin.prototype.validateHashFuncName =
function validateHashFuncName(compilation, hashFuncName) {
if (typeof hashFuncName !== 'string' &&
!(hashFuncName instanceof String)) {
this.error(
compilation,
'options.hashFuncNames must be an array of hash function names, ' +
'but contained ' + hashFuncName + '.');
return false;
}
try {
crypto.createHash(hashFuncName);
} catch (error) {
this.error(
compilation,
'Cannot use hash function \'' + hashFuncName + '\': ' +
error.message);
return false;
}
return true;
};
/* Given a public URL path to an asset, as generated by
* HtmlWebpackPlugin for use as a `<script src>` or `<link href`> URL
* in `index.html`, return the path to the asset, suitable as a key
* into `compilation.assets`.
*/
SubresourceIntegrityPlugin.prototype.hwpAssetPath = function hwpAssetPath(src) {
return path.relative(this.hwpPublicPath, src);
};
SubresourceIntegrityPlugin.prototype.warnIfHotUpdate = function warnIfHotUpdate(
compilation, source
) {
if (source.indexOf('webpackHotUpdate') >= 0) {
this.warnOnce(
compilation,
'webpack-subresource-integrity may interfere with hot reloading. ' +
'Consider disabling this plugin in development mode.'
);
}
};
SubresourceIntegrityPlugin.prototype.replaceAsset = function replaceAsset(
assets,
hashByChunkId,
chunkFile
) {
var oldSource = assets[chunkFile].source();
var newAsset;
var magicMarker;
var magicMarkerPos;
var hashFuncNames = this.options.hashFuncNames;
newAsset = new ReplaceSource(assets[chunkFile]);
Array.from(hashByChunkId.entries()).forEach(function replaceMagicMarkers(idAndHash) {
magicMarker = util.makePlaceholder(hashFuncNames, idAndHash[0]);
magicMarkerPos = oldSource.indexOf(magicMarker);
if (magicMarkerPos >= 0) {
newAsset.replace(
magicMarkerPos,
(magicMarkerPos + magicMarker.length) - 1,
idAndHash[1]);
}
});
// eslint-disable-next-line no-param-reassign
assets[chunkFile] = newAsset;
newAsset.integrity = util.computeIntegrity(hashFuncNames, newAsset.source());
return newAsset;
};
SubresourceIntegrityPlugin.prototype.processChunk = function processChunk(
chunk, compilation, assets
) {
var self = this;
var newAsset;
var hashByChunkId = new Map();
Array.from(util.findChunks(chunk)).reverse().forEach(childChunk => {
var sourcePath;
var files = Array.isArray(childChunk.files) ? new Set(childChunk.files) : childChunk.files;
// This can happen with invalid Webpack configurations
if (files.size === 0) return;
sourcePath = compilation.sriChunkAssets[childChunk.id];
if (!files.has(sourcePath)) {
self.warnOnce(
compilation,
'Cannot determine asset for chunk ' + childChunk.id + ', computed="' + sourcePath +
'", available=' + Array.from(childChunk.files)[0] + '. Please report this full error message ' +
'along with your Webpack configuration at ' +
'https://github.com/waysact/webpack-subresource-integrity/issues/new'
);
sourcePath = Array.from(files)[0];
}
self.warnIfHotUpdate(compilation, assets[sourcePath].source());
newAsset = self.replaceAsset(assets, hashByChunkId, sourcePath);
hashByChunkId.set(childChunk.id, newAsset.integrity);
this.assetIntegrity.set(sourcePath, newAsset.integrity);
});
};
SubresourceIntegrityPlugin.prototype.chunkAsset =
function chunkAsset(compilation, chunk, asset) {
if (compilation.assets[asset]) {
// eslint-disable-next-line no-param-reassign
compilation.sriChunkAssets[chunk.id] = asset;
}
};
SubresourceIntegrityPlugin.prototype.statsFactory =
function stats(compilation, statsFactory) {
statsFactory.hooks.extract.for("asset").tap('SriPlugin', (object, asset) => {
if (this.assetIntegrity.has(asset.name)) {
// eslint-disable-next-line no-param-reassign
object.integrity = String(this.assetIntegrity.get(asset.name));
}
});
};
SubresourceIntegrityPlugin.prototype.addMissingIntegrityHashes =
function addMissingIntegrityHashes(assets) {
var self = this;
Object.keys(assets).forEach(function loop(assetKey) {
var asset = assets[assetKey];
if (!asset.integrity) {
asset.integrity = util.computeIntegrity(self.options.hashFuncNames, asset.source());
}
});
};
/*
* Calculate SRI values for each chunk and replace the magic
* placeholders by the actual values.
*/
SubresourceIntegrityPlugin.prototype.afterOptimizeAssets =
function afterOptimizeAssets(compilation, assets) {
var self = this;
Array.from(compilation.chunks).filter(util.isRuntimeChunk).forEach(function forEachChunk(chunk) {
self.processChunk(chunk, compilation, assets);
});
this.addMissingIntegrityHashes(assets);
};
SubresourceIntegrityPlugin.prototype.processTag =
function processTag(compilation, tag) {
var src = this.hwpAssetPath(util.getTagSrc(tag));
/* eslint-disable no-param-reassign */
var integrity = util.getIntegrityChecksumForAsset(compilation.assets, src);
if (!Object.prototype.hasOwnProperty.call(tag.attributes, "integrity")) {
tag.attributes.integrity = integrity;
tag.attributes.crossorigin = compilation.compiler.options.output.crossOriginLoading || 'anonymous';
}
/* eslint-enable no-param-reassign */
};
SubresourceIntegrityPlugin.prototype.alterAssetTags =
function alterAssetTags(compilation, pluginArgs, callback) {
/* html-webpack-plugin has added an event so we can pre-process the html tags before they
inject them. This does the work.
*/
var processTag = this.processTag.bind(this, compilation);
pluginArgs.head.filter(util.filterTag).forEach(processTag);
pluginArgs.body.filter(util.filterTag).forEach(processTag);
callback(null, pluginArgs);
};
/* Add jsIntegrity and cssIntegrity properties to pluginArgs, to
* go along with js and css properties. These are later
* accessible on `htmlWebpackPlugin.files`.
*/
SubresourceIntegrityPlugin.prototype.beforeHtmlGeneration =
function beforeHtmlGeneration(compilation, pluginArgs, callback) {
var self = this;
this.hwpPublicPath = pluginArgs.assets.publicPath;
this.addMissingIntegrityHashes(compilation.assets);
['js', 'css'].forEach(function addIntegrity(fileType) {
// eslint-disable-next-line no-param-reassign
pluginArgs.assets[fileType + 'Integrity'] =
pluginArgs.assets[fileType].map(function assetIntegrity(filePath) {
return util.getIntegrityChecksumForAsset(compilation.assets, self.hwpAssetPath(filePath));
});
});
callback(null, pluginArgs);
};
SubresourceIntegrityPlugin.prototype.registerJMTP = function registerJMTP(compilation) {
var plugin = new WebIntegrityJsonpMainTemplatePlugin(this, compilation);
if (plugin.apply) {
plugin.apply(compilation.mainTemplate);
} else {
compilation.mainTemplate.apply(plugin);
}
};
SubresourceIntegrityPlugin.prototype.registerHwpHooks =
function registerHwpHooks(alterAssetTags, beforeHtmlGeneration, hwpCompilation) {
var self = this;
if (HtmlWebpackPlugin && HtmlWebpackPlugin.getHooks) {
// HtmlWebpackPlugin >= 4
HtmlWebpackPlugin.getHooks(hwpCompilation).beforeAssetTagGeneration.tapAsync(
'sri',
this.beforeHtmlGeneration.bind(this, hwpCompilation)
);
HtmlWebpackPlugin.getHooks(hwpCompilation).alterAssetTags.tapAsync(
'sri',
function cb(data, callback) {
var processTag = self.processTag.bind(self, hwpCompilation);
data.assetTags.scripts.filter(util.filterTag).forEach(processTag);
data.assetTags.styles.filter(util.filterTag).forEach(processTag);
callback(null, data);
}
);
} else if (hwpCompilation.hooks.htmlWebpackPluginAlterAssetTags &&
hwpCompilation.hooks.htmlWebpackPluginBeforeHtmlGeneration) {
// HtmlWebpackPlugin 3
hwpCompilation.hooks.htmlWebpackPluginAlterAssetTags.tapAsync('SriPlugin', alterAssetTags);
hwpCompilation.hooks.htmlWebpackPluginBeforeHtmlGeneration.tapAsync('SriPlugin', beforeHtmlGeneration);
}
};
SubresourceIntegrityPlugin.prototype.thisCompilation = function thisCompilation(
compiler,
compilation
) {
this.validateOptions(compilation);
if (!this.options.enabled) {
return;
}
this.registerJMTP(compilation);
// FIXME: refactor into separate per-compilation state
// eslint-disable-next-line no-param-reassign
compilation.sriChunkAssets = {};
/*
* html-webpack support:
* Modify the asset tags before webpack injects them for anything with an integrity value.
*/
if (compiler.hooks) {
this.setupHooks(compiler, compilation);
} else {
this.setupLegacyHooks(compiler, compilation);
}
};
SubresourceIntegrityPlugin.prototype.setupHooks = function setupHooks(
compiler,
compilation
) {
var afterOptimizeAssets = this.afterOptimizeAssets.bind(this, compilation);
var beforeChunkAssets = this.beforeChunkAssets.bind(this, compilation);
var alterAssetTags = this.alterAssetTags.bind(this, compilation);
var beforeHtmlGeneration = this.beforeHtmlGeneration.bind(this, compilation);
if (compilation.hooks.processAssets) {
compilation.hooks.processAssets.tap(
{
name: 'SriPlugin',
stage: webpack.Compilation.PROCESS_ASSETS_STAGE_OPTIMIZE_HASH
},
afterOptimizeAssets
);
} else {
compilation.hooks.afterOptimizeAssets.tap('SriPlugin', afterOptimizeAssets);
}
compilation.hooks.beforeChunkAssets.tap('SriPlugin', beforeChunkAssets);
compiler.hooks.compilation.tap(
'HtmlWebpackPluginHooks',
this.registerHwpHooks.bind(this, alterAssetTags, beforeHtmlGeneration)
);
};
SubresourceIntegrityPlugin.prototype.setupLegacyHooks = function setupLegacyHooks(
compiler,
compilation
) {
var afterOptimizeAssets = this.afterOptimizeAssets.bind(this, compilation);
var beforeChunkAssets = this.beforeChunkAssets.bind(this, compilation);
var alterAssetTags = this.alterAssetTags.bind(this, compilation);
var beforeHtmlGeneration = this.beforeHtmlGeneration.bind(this, compilation);
compilation.plugin('after-optimize-assets', afterOptimizeAssets);
compilation.plugin('before-chunk-assets', beforeChunkAssets);
compilation.plugin('html-webpack-plugin-alter-asset-tags', alterAssetTags);
compilation.plugin(
'html-webpack-plugin-before-html-generation',
beforeHtmlGeneration
);
};
SubresourceIntegrityPlugin.prototype.beforeChunkAssets = function afterPlugins(compilation) {
var chunkAsset = this.chunkAsset.bind(this, compilation);
if (compilation.hooks) {
compilation.hooks.chunkAsset.tap('SriPlugin', chunkAsset);
} else {
compilation.plugin('chunk-asset', chunkAsset);
}
};
SubresourceIntegrityPlugin.prototype.afterPlugins = function afterPlugins(compiler) {
if (compiler.hooks) {
compiler.hooks.thisCompilation.tap('SriPlugin', this.thisCompilation.bind(this, compiler));
compiler.hooks.compilation.tap("DefaultStatsFactoryPlugin", compilation => {
if (compilation.hooks.statsFactory) {
compilation.hooks.statsFactory.tap('SriPlugin', this.statsFactory.bind(this, compilation));
}
});
} else {
compiler.plugin('this-compilation', this.thisCompilation.bind(this, compiler));
}
};
SubresourceIntegrityPlugin.prototype.apply = function apply(compiler) {
if (compiler.hooks) {
compiler.hooks.afterPlugins.tap('SriPlugin', this.afterPlugins.bind(this));
} else {
compiler.plugin('after-plugins', this.afterPlugins.bind(this));
}
};
module.exports = SubresourceIntegrityPlugin;