浏览代码

fix: WebpackProtectBootstrapPlugin

tophf 3 年之前
父节点
当前提交
b63f755dd6
共有 3 个文件被更改,包括 76 次插入45 次删除
  1. 1 1
      scripts/manifest-helper.js
  2. 72 41
      scripts/webpack-protect-bootstrap-plugin.js
  3. 3 3
      scripts/webpack.conf.js

+ 1 - 1
scripts/manifest-helper.js

@@ -52,7 +52,7 @@ class ListBackgroundScriptsPlugin {
       const manifest = await buildManifest();
       const bgId = 'background/index';
       const bgEntry = compilation.entrypoints.get(bgId);
-      const scripts = bgEntry.chunks.map(c => c.files[0]);
+      const scripts = bgEntry.chunks.flatMap(c => [...c.files]);
       if (`${manifest.background.scripts}` !== `${scripts}`) {
         manifest.background.scripts = scripts;
         await fs.writeFile(path,

+ 72 - 41
scripts/webpack-protect-bootstrap-plugin.js

@@ -1,4 +1,52 @@
 const escapeStringRegexp = require('escape-string-regexp');
+const webpack = require('webpack');
+
+const G = webpack.RuntimeGlobals;
+const OPTIONAL = false;
+const OBJ_RULE = [
+  /([[(,=:]\s*{)(?!__proto__:)\s*(.)/g,
+  (_, str, next) => `${str}__proto__: null${next === '}' ? '' : ','}${next}`
+];
+const BOOTSTRAP_RULES = [
+  OBJ_RULE,
+  ["typeof Symbol !== 'undefined' && Symbol.toStringTag",
+    'true'],
+  ['Symbol.toStringTag',
+    'toStringTagSym'],
+  ['Object.defineProperty(',
+    'defineProperty('],
+  ['Object.prototype.hasOwnProperty.call(',
+    'safeCall(hasOwnProperty, '],
+  [`${G.hasOwnProperty}(definition, key) && !${G.hasOwnProperty}(exports, key)`,
+    '!(key in exports)'], // these objects have null proto
+];
+const MAIN_RULES = [
+  [
+    /(__webpack_modules__\[moduleId])\.call\(/g,
+    'safeCall($1, ',
+    OPTIONAL,
+  ], [
+    new RegExp(`var (__webpack_module_cache__|${G.require}) = {};.*?var ${G.exports} =`, 's'),
+    function (src, group1) {
+      let guard = '';
+      if (group1 !== G.require) {
+        // webpack didn't concatenate all modules into one, let's patch the machinery
+        const props = src.match(new RegExp(`(?<=\\b${G.require}\\.)(\\w+)`, 'g'));
+        const uniq = [...new Set(props)].join('');
+        if (uniq) guard = `for (let i = 0, props=${JSON.stringify(uniq)}; i < props.length; i++)
+          defineProperty(${G.require}, props[i], {__proto__: null, value: 0, writable: 1});\n`;
+      }
+      return guard + replace(guard ? BOOTSTRAP_RULES : [OBJ_RULE], src, this);
+    },
+  ], [
+    new RegExp(`(${[
+      `${G.definePropertyGetters}\\(${G.exports}, {`,
+      `var ${G.exports} = {`,
+      `var __webpack_modules__ = \\({`,
+    ].join('|')})(?!__proto__:)\\s*(.)`, 'g'),
+    OBJ_RULE[1],
+  ],
+];
 
 /**
  * WARNING! The following globals must be correctly assigned using wrapper-webpack-plugin.
@@ -9,54 +57,37 @@ const escapeStringRegexp = require('escape-string-regexp');
  */
 class WebpackProtectBootstrapPlugin {
   apply(compiler) {
-    const NAME = this.constructor.name;
-    const NULL_PROTO = '__proto__: null';
-    const NULL_OBJ = `{ ${NULL_PROTO} }`;
+    const NAME = WebpackProtectBootstrapPlugin.name;
     compiler.hooks.compilation.tap(NAME, (compilation) => {
-      const { hooks, requireFn } = compilation.mainTemplate;
-      hooks.localVars.tap(NAME, src => replace(src, [[
-        'installedModules = {};',
-        `installedModules = ${NULL_OBJ}; \
-         for (let i = 0, c, str = "cdmnoprst"; i < str.length && (c = str[i++]);) \
-           defineProperty(${requireFn}, c, { ${NULL_PROTO}, value: undefined, writable: true });`,
-      ]]));
-      hooks.moduleObj.tap(NAME, src => replace(src, [[
-        'exports: {}',
-        `exports: ${NULL_OBJ}, ${NULL_PROTO}`,
-      ]]));
-      hooks.require.tap(NAME, src => replace(src, [[
-        'modules[moduleId].call(',
-        'safeCall(modules[moduleId], ',
-      ]]));
-      hooks.requireExtensions.tap(NAME, src => replace(src, [
-        ["(typeof Symbol !== 'undefined' && Symbol.toStringTag)", '(true)'],
-        ['Symbol.toStringTag', 'toStringTagSym'],
-        [/Object\.(defineProperty\([^){\n]+{)/g, `$1${NULL_PROTO},`],
-        ['Object.create(null)', NULL_OBJ],
-        ['for(var key in value)', 'for(const key in value)'],
-        ['function(key) { return value[key]; }.bind(null, key)',
-          '() => value[key]'],
-        [/function[^{]+{[^}]+?hasOwnProperty\.call[^}]+}/g,
-          '(obj, key) => safeCall(hasOwnProperty, obj, key)'],
-      ]));
+      const hooks = webpack.javascript.JavascriptModulesPlugin.getCompilationHooks(compilation);
+      hooks.renderMain.tap(NAME, replace.bind(null, MAIN_RULES));
     });
   }
 }
 
-function replace(src, fromTo) {
-  const origSrc = src;
-  for (const [from, to] of fromTo) {
-    const fromRe = typeof from === 'string'
-      ? new RegExp(escapeStringRegexp(from), 'g')
-      : from;
-    const dst = src.replace(fromRe, to);
-    if (dst === src) {
-      throw new Error(`${WebpackProtectBootstrapPlugin.constructor.name}: `
-        + `"${from}" not found in "${origSrc}"`);
+function replace(rules, src, info) {
+  if (!src) return src;
+  if (src.source) src = src.source();
+  let res = src;
+  for (const rule of rules) {
+    if (typeof rule === 'function') {
+      res = rule(res);
+    } else {
+      const [from, to, mandatory = true] = rule;
+      const fromRe = typeof from === 'string'
+        ? new RegExp(escapeStringRegexp(from), 'g')
+        : from;
+      const dst = res.replace(fromRe, typeof to === 'function' ? to.bind(info) : to);
+      if (dst === res && mandatory) {
+        const err = `[${WebpackProtectBootstrapPlugin.name}] `
+          + `"${from}" not found in ${info?.chunk.name || 'bootstrap'}`;
+        console.log(`${err}:\n${src}`);
+        throw new Error(err);
+      }
+      res = dst;
     }
-    src = dst;
   }
-  return src;
+  return res;
 }
 
 module.exports = WebpackProtectBootstrapPlugin;

+ 3 - 3
scripts/webpack.conf.js

@@ -5,7 +5,7 @@ const TerserPlugin = isProd && require('terser-webpack-plugin');
 const deepmerge = isProd && require('deepmerge');
 const { ListBackgroundScriptsPlugin } = require('./manifest-helper');
 const { addWrapperWithGlobals, getCodeMirrorThemes, getUniqIdB64 } = require('./webpack-util');
-// const ProtectWebpackBootstrapPlugin = require('./webpack-protect-bootstrap-plugin');
+const ProtectWebpackBootstrapPlugin = require('./webpack-protect-bootstrap-plugin');
 const projectConfig = require('./plaid.conf');
 const { getVersion } = require('./version-helper');
 const mergedConfig = shallowMerge(defaultOptions, projectConfig);
@@ -125,7 +125,7 @@ module.exports = Promise.all([
   }),
 
   modify('injected', './src/injected', (config) => {
-    // config.plugins.push(new ProtectWebpackBootstrapPlugin());
+    config.plugins.push(new ProtectWebpackBootstrapPlugin());
     addWrapperWithGlobals('injected/content', config, defsObj, getGlobals => ({
       header: () => `${skipReinjectionHeader} { ${getGlobals()}`,
       footer: '}}',
@@ -135,7 +135,7 @@ module.exports = Promise.all([
   modify('injected-web', './src/injected/web', (config) => {
     // TODO: replace WebPack's Object.*, .call(), .apply() with safe calls
     config.output.libraryTarget = 'commonjs2';
-    // config.plugins.push(new ProtectWebpackBootstrapPlugin());
+    config.plugins.push(new ProtectWebpackBootstrapPlugin());
     addWrapperWithGlobals('injected/web', config, defsObj, getGlobals => ({
       header: () => `${skipReinjectionHeader}
         window[INIT_FUNC_NAME] = function (IS_FIREFOX,${HANDSHAKE_ID},${VAULT_ID}) {