webpack-util.js 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101
  1. const fs = require('fs');
  2. const babelCore = require('@babel/core');
  3. const WrapperWebpackPlugin = require('wrapper-webpack-plugin');
  4. const entryGlobals = {
  5. 'common': [],
  6. 'injected/content': [],
  7. 'injected/web': [],
  8. };
  9. const entryPathToFilename = path => path === '*'
  10. ? `./src/common/safe-globals-shared.js`
  11. : `./src/${path}/safe-globals.js`;
  12. Object.entries(entryGlobals).forEach(([name, val]) => {
  13. const parts = name.split('/');
  14. if (parts[1]) parts[1] = name;
  15. val.push('*', ...parts);
  16. });
  17. exports.restrictedSyntax = (
  18. // Hiding `code` so eslint doesn't complain about invalid schema
  19. rules => rules.map(r => (
  20. Object.defineProperty(r, 'code', { enumerable: false, value: r.code })
  21. ))
  22. )([{
  23. selector: 'ObjectExpression > SpreadElement',
  24. message: 'Object spread adds a polyfill in injected* even if unused by it',
  25. code: 'open({...{foo:1}})',
  26. }, {
  27. selector: 'ArrayPattern',
  28. message: 'Destructuring via Symbol.iterator may be spoofed/broken in an unsafe environment',
  29. code: '[window.foo]=[]',
  30. }, {
  31. selector: ':matches(ArrayExpression, CallExpression) > SpreadElement',
  32. message: 'Spreading via Symbol.iterator may be spoofed/broken in an unsafe environment',
  33. code: 'open([...[]])',
  34. }, {
  35. selector: '[callee.object.name="Object"], MemberExpression[object.name="Object"]',
  36. message: 'Using potentially spoofed methods in an unsafe environment',
  37. code: 'Object.assign()',
  38. // TODO: auto-generate the rule using GLOBALS
  39. }, {
  40. selector: `CallExpression[callee.name="defineProperty"]:not(${[
  41. '[arguments.2.properties.0.key.name="__proto__"]',
  42. ':has(CallExpression[callee.name="nullObjFrom"])'
  43. ].join(',')})`,
  44. message: 'Prototype of descriptor may be spoofed/broken in an unsafe environment',
  45. code: 'defineProperty(open, "foo", {foo:1})',
  46. }]);
  47. /**
  48. * Adds a watcher for files in entryGlobals to properly recompile the project on changes.
  49. */
  50. function addWrapperWithGlobals(name, config, defsObj, callback) {
  51. config.module.rules.push({
  52. test: new RegExp(`/${name}/.*?\\.js$`.replace(/\//g, /[/\\]/.source)),
  53. use: [{
  54. loader: './scripts/fake-dep-loader.js',
  55. options: { files: entryGlobals[name].map(entryPathToFilename) },
  56. }],
  57. });
  58. const defsRe = new RegExp(`\\b(${
  59. Object.keys(defsObj)
  60. .join('|')
  61. .replace(/\./g, '\\.')
  62. })\\b`, 'g');
  63. const reader = () => (
  64. entryGlobals[name]
  65. .map(path => readGlobalsFile(path))
  66. .join('\n')
  67. .replace(defsRe, s => defsObj[s])
  68. );
  69. config.plugins.push(new WrapperWebpackPlugin(callback(reader)));
  70. }
  71. function getCodeMirrorThemes() {
  72. const name = 'neo.css';
  73. return fs.readdirSync(
  74. require.resolve(`codemirror/theme/${name}`).slice(0, -name.length),
  75. { withFileTypes: true },
  76. ).map(e => e.isFile() && e.name.endsWith('.css') && e.name.slice(0, -4))
  77. .filter(Boolean);
  78. }
  79. function readGlobalsFile(path, babelOpts = {}) {
  80. const { ast, code = !ast } = babelOpts;
  81. const filename = entryPathToFilename(path);
  82. const src = fs.readFileSync(filename, { encoding: 'utf8' })
  83. .replace(/\bexport\s+(function\s+(\w+))/g, 'const $2 = $1')
  84. .replace(/\bexport\s+(?=(const|let)\s)/g, '');
  85. const res = babelCore.transformSync(src, {
  86. ...babelOpts,
  87. ast,
  88. code,
  89. filename,
  90. });
  91. return ast ? res : res.code;
  92. }
  93. exports.addWrapperWithGlobals = addWrapperWithGlobals;
  94. exports.getCodeMirrorThemes = getCodeMirrorThemes;
  95. exports.readGlobalsFile = readGlobalsFile;