source: trip-planner-front/node_modules/webpack-subresource-integrity/index.js@ 188ee53

Last change on this file since 188ee53 was 6a3a178, checked in by Ema <ema_spirova@…>, 3 years ago

initial commit

  • Property mode set to 100644
File size: 15.6 KB
Line 
1/**
2 * Copyright (c) 2015-present, Waysact Pty Ltd
3 *
4 * This source code is licensed under the MIT license found in the
5 * LICENSE file in the root directory of this source tree.
6 */
7
8var crypto = require('crypto');
9var path = require('path');
10var webpack = require('webpack');
11// eslint-disable-next-line global-require
12var ReplaceSource = (webpack.sources || require('webpack-sources')).ReplaceSource;
13var util = require('./util');
14var WebIntegrityJsonpMainTemplatePlugin = require('./jmtp');
15var HtmlWebpackPlugin;
16
17// https://www.w3.org/TR/2016/REC-SRI-20160623/#cryptographic-hash-functions
18var standardHashFuncNames = ['sha256', 'sha384', 'sha512'];
19
20try {
21 // eslint-disable-next-line global-require
22 HtmlWebpackPlugin = require('html-webpack-plugin');
23} catch (e) {
24 if (!(e instanceof Error) || e.code !== 'MODULE_NOT_FOUND') {
25 throw e;
26 }
27}
28
29function SubresourceIntegrityPlugin(options) {
30 var useOptions;
31 if (options === null || typeof options === 'undefined') {
32 useOptions = {};
33 } else if (typeof options === 'object') {
34 useOptions = options;
35 } else {
36 throw new Error('webpack-subresource-integrity: argument must be an object');
37 }
38
39 this.options = {
40 enabled: true
41 };
42
43 Object.assign(this.options, useOptions);
44
45 this.emittedMessages = {};
46
47 this.assetIntegrity = new Map();
48}
49
50SubresourceIntegrityPlugin.prototype.emitMessage = function emitMessage(messages, message) {
51 messages.push(new Error('webpack-subresource-integrity: ' + message));
52};
53
54SubresourceIntegrityPlugin.prototype.emitMessageOnce = function emitMessageOnce(messages, message) {
55 if (!this.emittedMessages[message]) {
56 this.emittedMessages[message] = true;
57 this.emitMessage(messages, message);
58 }
59};
60
61SubresourceIntegrityPlugin.prototype.warnOnce = function warn(compilation, message) {
62 this.emitMessageOnce(compilation.warnings, message);
63};
64
65SubresourceIntegrityPlugin.prototype.error = function error(compilation, message) {
66 this.emitMessage(compilation.errors, message);
67};
68
69SubresourceIntegrityPlugin.prototype.errorOnce = function error(compilation, message) {
70 this.emitMessageOnce(compilation.errors, message);
71};
72
73SubresourceIntegrityPlugin.prototype.validateOptions = function validateOptions(compilation) {
74 if (this.optionsValidated) {
75 return;
76 }
77 this.optionsValidated = true;
78
79 if (this.options.enabled && !compilation.compiler.options.output.crossOriginLoading) {
80 this.warnOnce(
81 compilation,
82 'SRI requires a cross-origin policy, defaulting to "anonymous". ' +
83 'Set webpack option output.crossOriginLoading to a value other than false ' +
84 'to make this warning go away. ' +
85 'See https://w3c.github.io/webappsec-subresource-integrity/#cross-origin-data-leakage'
86 );
87 }
88 this.validateHashFuncNames(compilation);
89};
90
91SubresourceIntegrityPlugin.prototype.validateHashFuncNames =
92 function validateHashFuncNames(compilation) {
93 if (!Array.isArray(this.options.hashFuncNames)) {
94 this.error(
95 compilation,
96 'options.hashFuncNames must be an array of hash function names, ' +
97 'instead got \'' + this.options.hashFuncNames + '\'.');
98 this.options.enabled = false;
99 } else if (
100 !this.options.hashFuncNames.every(this.validateHashFuncName.bind(this, compilation))
101 ) {
102 this.options.enabled = false;
103 } else {
104 this.warnStandardHashFunc(compilation);
105 }
106 };
107
108SubresourceIntegrityPlugin.prototype.warnStandardHashFunc =
109 function warnStandardHashFunc(compilation) {
110 var foundStandardHashFunc = false;
111 var i;
112 for (i = 0; i < this.options.hashFuncNames.length; i += 1) {
113 if (standardHashFuncNames.indexOf(this.options.hashFuncNames[i]) >= 0) {
114 foundStandardHashFunc = true;
115 }
116 }
117 if (!foundStandardHashFunc) {
118 this.warnOnce(
119 compilation,
120 'It is recommended that at least one hash function is part of the set ' +
121 'for which support is mandated by the specification. ' +
122 'These are: ' + standardHashFuncNames.join(', ') + '. ' +
123 'See http://www.w3.org/TR/SRI/#cryptographic-hash-functions for more information.');
124 }
125 };
126
127SubresourceIntegrityPlugin.prototype.validateHashFuncName =
128 function validateHashFuncName(compilation, hashFuncName) {
129 if (typeof hashFuncName !== 'string' &&
130 !(hashFuncName instanceof String)) {
131 this.error(
132 compilation,
133 'options.hashFuncNames must be an array of hash function names, ' +
134 'but contained ' + hashFuncName + '.');
135 return false;
136 }
137 try {
138 crypto.createHash(hashFuncName);
139 } catch (error) {
140 this.error(
141 compilation,
142 'Cannot use hash function \'' + hashFuncName + '\': ' +
143 error.message);
144 return false;
145 }
146 return true;
147 };
148
149/* Given a public URL path to an asset, as generated by
150 * HtmlWebpackPlugin for use as a `<script src>` or `<link href`> URL
151 * in `index.html`, return the path to the asset, suitable as a key
152 * into `compilation.assets`.
153 */
154SubresourceIntegrityPlugin.prototype.hwpAssetPath = function hwpAssetPath(src) {
155 return path.relative(this.hwpPublicPath, src);
156};
157
158SubresourceIntegrityPlugin.prototype.warnIfHotUpdate = function warnIfHotUpdate(
159 compilation, source
160) {
161 if (source.indexOf('webpackHotUpdate') >= 0) {
162 this.warnOnce(
163 compilation,
164 'webpack-subresource-integrity may interfere with hot reloading. ' +
165 'Consider disabling this plugin in development mode.'
166 );
167 }
168};
169
170SubresourceIntegrityPlugin.prototype.replaceAsset = function replaceAsset(
171 assets,
172 hashByChunkId,
173 chunkFile
174) {
175 var oldSource = assets[chunkFile].source();
176 var newAsset;
177 var magicMarker;
178 var magicMarkerPos;
179 var hashFuncNames = this.options.hashFuncNames;
180
181 newAsset = new ReplaceSource(assets[chunkFile]);
182
183 Array.from(hashByChunkId.entries()).forEach(function replaceMagicMarkers(idAndHash) {
184 magicMarker = util.makePlaceholder(hashFuncNames, idAndHash[0]);
185 magicMarkerPos = oldSource.indexOf(magicMarker);
186 if (magicMarkerPos >= 0) {
187 newAsset.replace(
188 magicMarkerPos,
189 (magicMarkerPos + magicMarker.length) - 1,
190 idAndHash[1]);
191 }
192 });
193
194 // eslint-disable-next-line no-param-reassign
195 assets[chunkFile] = newAsset;
196
197 newAsset.integrity = util.computeIntegrity(hashFuncNames, newAsset.source());
198 return newAsset;
199};
200
201SubresourceIntegrityPlugin.prototype.processChunk = function processChunk(
202 chunk, compilation, assets
203) {
204 var self = this;
205 var newAsset;
206 var hashByChunkId = new Map();
207
208 Array.from(util.findChunks(chunk)).reverse().forEach(childChunk => {
209 var sourcePath;
210 var files = Array.isArray(childChunk.files) ? new Set(childChunk.files) : childChunk.files;
211
212 // This can happen with invalid Webpack configurations
213 if (files.size === 0) return;
214
215 sourcePath = compilation.sriChunkAssets[childChunk.id];
216
217 if (!files.has(sourcePath)) {
218 self.warnOnce(
219 compilation,
220 'Cannot determine asset for chunk ' + childChunk.id + ', computed="' + sourcePath +
221 '", available=' + Array.from(childChunk.files)[0] + '. Please report this full error message ' +
222 'along with your Webpack configuration at ' +
223 'https://github.com/waysact/webpack-subresource-integrity/issues/new'
224 );
225 sourcePath = Array.from(files)[0];
226 }
227
228 self.warnIfHotUpdate(compilation, assets[sourcePath].source());
229 newAsset = self.replaceAsset(assets, hashByChunkId, sourcePath);
230 hashByChunkId.set(childChunk.id, newAsset.integrity);
231 this.assetIntegrity.set(sourcePath, newAsset.integrity);
232 });
233};
234
235SubresourceIntegrityPlugin.prototype.chunkAsset =
236 function chunkAsset(compilation, chunk, asset) {
237 if (compilation.assets[asset]) {
238 // eslint-disable-next-line no-param-reassign
239 compilation.sriChunkAssets[chunk.id] = asset;
240 }
241 };
242
243SubresourceIntegrityPlugin.prototype.statsFactory =
244 function stats(compilation, statsFactory) {
245 statsFactory.hooks.extract.for("asset").tap('SriPlugin', (object, asset) => {
246 if (this.assetIntegrity.has(asset.name)) {
247 // eslint-disable-next-line no-param-reassign
248 object.integrity = String(this.assetIntegrity.get(asset.name));
249 }
250 });
251 };
252
253SubresourceIntegrityPlugin.prototype.addMissingIntegrityHashes =
254 function addMissingIntegrityHashes(assets) {
255 var self = this;
256 Object.keys(assets).forEach(function loop(assetKey) {
257 var asset = assets[assetKey];
258 if (!asset.integrity) {
259 asset.integrity = util.computeIntegrity(self.options.hashFuncNames, asset.source());
260 }
261 });
262 };
263
264/*
265 * Calculate SRI values for each chunk and replace the magic
266 * placeholders by the actual values.
267 */
268SubresourceIntegrityPlugin.prototype.afterOptimizeAssets =
269 function afterOptimizeAssets(compilation, assets) {
270 var self = this;
271
272 Array.from(compilation.chunks).filter(util.isRuntimeChunk).forEach(function forEachChunk(chunk) {
273 self.processChunk(chunk, compilation, assets);
274 });
275
276 this.addMissingIntegrityHashes(assets);
277 };
278
279SubresourceIntegrityPlugin.prototype.processTag =
280 function processTag(compilation, tag) {
281 var src = this.hwpAssetPath(util.getTagSrc(tag));
282 /* eslint-disable no-param-reassign */
283 var integrity = util.getIntegrityChecksumForAsset(compilation.assets, src);
284 if (!Object.prototype.hasOwnProperty.call(tag.attributes, "integrity")) {
285 tag.attributes.integrity = integrity;
286 tag.attributes.crossorigin = compilation.compiler.options.output.crossOriginLoading || 'anonymous';
287 }
288 /* eslint-enable no-param-reassign */
289 };
290
291SubresourceIntegrityPlugin.prototype.alterAssetTags =
292 function alterAssetTags(compilation, pluginArgs, callback) {
293 /* html-webpack-plugin has added an event so we can pre-process the html tags before they
294 inject them. This does the work.
295 */
296 var processTag = this.processTag.bind(this, compilation);
297 pluginArgs.head.filter(util.filterTag).forEach(processTag);
298 pluginArgs.body.filter(util.filterTag).forEach(processTag);
299 callback(null, pluginArgs);
300 };
301
302
303/* Add jsIntegrity and cssIntegrity properties to pluginArgs, to
304 * go along with js and css properties. These are later
305 * accessible on `htmlWebpackPlugin.files`.
306 */
307SubresourceIntegrityPlugin.prototype.beforeHtmlGeneration =
308 function beforeHtmlGeneration(compilation, pluginArgs, callback) {
309 var self = this;
310 this.hwpPublicPath = pluginArgs.assets.publicPath;
311 this.addMissingIntegrityHashes(compilation.assets);
312
313 ['js', 'css'].forEach(function addIntegrity(fileType) {
314 // eslint-disable-next-line no-param-reassign
315 pluginArgs.assets[fileType + 'Integrity'] =
316 pluginArgs.assets[fileType].map(function assetIntegrity(filePath) {
317 return util.getIntegrityChecksumForAsset(compilation.assets, self.hwpAssetPath(filePath));
318 });
319 });
320 callback(null, pluginArgs);
321 };
322
323SubresourceIntegrityPlugin.prototype.registerJMTP = function registerJMTP(compilation) {
324 var plugin = new WebIntegrityJsonpMainTemplatePlugin(this, compilation);
325 if (plugin.apply) {
326 plugin.apply(compilation.mainTemplate);
327 } else {
328 compilation.mainTemplate.apply(plugin);
329 }
330};
331
332SubresourceIntegrityPlugin.prototype.registerHwpHooks =
333 function registerHwpHooks(alterAssetTags, beforeHtmlGeneration, hwpCompilation) {
334 var self = this;
335 if (HtmlWebpackPlugin && HtmlWebpackPlugin.getHooks) {
336 // HtmlWebpackPlugin >= 4
337 HtmlWebpackPlugin.getHooks(hwpCompilation).beforeAssetTagGeneration.tapAsync(
338 'sri',
339 this.beforeHtmlGeneration.bind(this, hwpCompilation)
340 );
341
342 HtmlWebpackPlugin.getHooks(hwpCompilation).alterAssetTags.tapAsync(
343 'sri',
344 function cb(data, callback) {
345 var processTag = self.processTag.bind(self, hwpCompilation);
346 data.assetTags.scripts.filter(util.filterTag).forEach(processTag);
347 data.assetTags.styles.filter(util.filterTag).forEach(processTag);
348 callback(null, data);
349 }
350 );
351 } else if (hwpCompilation.hooks.htmlWebpackPluginAlterAssetTags &&
352 hwpCompilation.hooks.htmlWebpackPluginBeforeHtmlGeneration) {
353 // HtmlWebpackPlugin 3
354 hwpCompilation.hooks.htmlWebpackPluginAlterAssetTags.tapAsync('SriPlugin', alterAssetTags);
355 hwpCompilation.hooks.htmlWebpackPluginBeforeHtmlGeneration.tapAsync('SriPlugin', beforeHtmlGeneration);
356 }
357 };
358
359SubresourceIntegrityPlugin.prototype.thisCompilation = function thisCompilation(
360 compiler,
361 compilation
362) {
363 this.validateOptions(compilation);
364
365 if (!this.options.enabled) {
366 return;
367 }
368
369 this.registerJMTP(compilation);
370
371 // FIXME: refactor into separate per-compilation state
372 // eslint-disable-next-line no-param-reassign
373 compilation.sriChunkAssets = {};
374
375 /*
376 * html-webpack support:
377 * Modify the asset tags before webpack injects them for anything with an integrity value.
378 */
379 if (compiler.hooks) {
380 this.setupHooks(compiler, compilation);
381 } else {
382 this.setupLegacyHooks(compiler, compilation);
383 }
384};
385
386SubresourceIntegrityPlugin.prototype.setupHooks = function setupHooks(
387 compiler,
388 compilation
389) {
390 var afterOptimizeAssets = this.afterOptimizeAssets.bind(this, compilation);
391 var beforeChunkAssets = this.beforeChunkAssets.bind(this, compilation);
392 var alterAssetTags = this.alterAssetTags.bind(this, compilation);
393 var beforeHtmlGeneration = this.beforeHtmlGeneration.bind(this, compilation);
394
395 if (compilation.hooks.processAssets) {
396 compilation.hooks.processAssets.tap(
397 {
398 name: 'SriPlugin',
399 stage: webpack.Compilation.PROCESS_ASSETS_STAGE_OPTIMIZE_HASH
400 },
401 afterOptimizeAssets
402 );
403 } else {
404 compilation.hooks.afterOptimizeAssets.tap('SriPlugin', afterOptimizeAssets);
405 }
406 compilation.hooks.beforeChunkAssets.tap('SriPlugin', beforeChunkAssets);
407 compiler.hooks.compilation.tap(
408 'HtmlWebpackPluginHooks',
409 this.registerHwpHooks.bind(this, alterAssetTags, beforeHtmlGeneration)
410 );
411};
412
413SubresourceIntegrityPlugin.prototype.setupLegacyHooks = function setupLegacyHooks(
414 compiler,
415 compilation
416) {
417 var afterOptimizeAssets = this.afterOptimizeAssets.bind(this, compilation);
418 var beforeChunkAssets = this.beforeChunkAssets.bind(this, compilation);
419 var alterAssetTags = this.alterAssetTags.bind(this, compilation);
420 var beforeHtmlGeneration = this.beforeHtmlGeneration.bind(this, compilation);
421
422 compilation.plugin('after-optimize-assets', afterOptimizeAssets);
423 compilation.plugin('before-chunk-assets', beforeChunkAssets);
424 compilation.plugin('html-webpack-plugin-alter-asset-tags', alterAssetTags);
425 compilation.plugin(
426 'html-webpack-plugin-before-html-generation',
427 beforeHtmlGeneration
428 );
429};
430
431SubresourceIntegrityPlugin.prototype.beforeChunkAssets = function afterPlugins(compilation) {
432 var chunkAsset = this.chunkAsset.bind(this, compilation);
433 if (compilation.hooks) {
434 compilation.hooks.chunkAsset.tap('SriPlugin', chunkAsset);
435 } else {
436 compilation.plugin('chunk-asset', chunkAsset);
437 }
438};
439
440SubresourceIntegrityPlugin.prototype.afterPlugins = function afterPlugins(compiler) {
441 if (compiler.hooks) {
442 compiler.hooks.thisCompilation.tap('SriPlugin', this.thisCompilation.bind(this, compiler));
443
444 compiler.hooks.compilation.tap("DefaultStatsFactoryPlugin", compilation => {
445 if (compilation.hooks.statsFactory) {
446 compilation.hooks.statsFactory.tap('SriPlugin', this.statsFactory.bind(this, compilation));
447 }
448 });
449
450 } else {
451 compiler.plugin('this-compilation', this.thisCompilation.bind(this, compiler));
452 }
453};
454
455SubresourceIntegrityPlugin.prototype.apply = function apply(compiler) {
456 if (compiler.hooks) {
457 compiler.hooks.afterPlugins.tap('SriPlugin', this.afterPlugins.bind(this));
458 } else {
459 compiler.plugin('after-plugins', this.afterPlugins.bind(this));
460 }
461};
462
463module.exports = SubresourceIntegrityPlugin;
Note: See TracBrowser for help on using the repository browser.