resolver-compat.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342
  1. 'use strict';
  2. // Translate the old options to the new Resolver functionality.
  3. const fs = require('fs');
  4. const pa = require('path');
  5. const nmod = require('module');
  6. const {EventEmitter} = require('events');
  7. const util = require('util');
  8. const {
  9. Resolver,
  10. DefaultResolver
  11. } = require('./resolver');
  12. const {VMScript} = require('./script');
  13. const {VM} = require('./vm');
  14. const {VMError} = require('./bridge');
  15. /**
  16. * Require wrapper to be able to annotate require with webpackIgnore.
  17. *
  18. * @private
  19. * @param {string} moduleName - Name of module to load.
  20. * @return {*} Module exports.
  21. */
  22. function defaultRequire(moduleName) {
  23. // Set module.parser.javascript.commonjsMagicComments=true in your webpack config.
  24. // eslint-disable-next-line global-require
  25. return require(/* webpackIgnore: true */ moduleName);
  26. }
  27. // source: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#Escaping
  28. function escapeRegExp(string) {
  29. return string.replace(/[.*+\-?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string
  30. }
  31. function makeExternalMatcherRegex(obj) {
  32. return escapeRegExp(obj).replace(/\\\\|\//g, '[\\\\/]')
  33. .replace(/\\\*\\\*/g, '.*').replace(/\\\*/g, '[^\\\\/]*').replace(/\\\?/g, '[^\\\\/]');
  34. }
  35. function makeExternalMatcher(obj) {
  36. const regexString = makeExternalMatcherRegex(obj);
  37. return new RegExp(`[\\\\/]node_modules[\\\\/]${regexString}(?:[\\\\/](?!(?:.*[\\\\/])?node_modules[\\\\/]).*)?$`);
  38. }
  39. class LegacyResolver extends DefaultResolver {
  40. constructor(builtinModules, checkPath, globalPaths, pathContext, customResolver, hostRequire, compiler, externals, allowTransitive) {
  41. super(builtinModules, checkPath, globalPaths, pathContext, customResolver, hostRequire, compiler);
  42. this.externals = externals;
  43. this.currMod = undefined;
  44. this.trustedMods = new WeakMap();
  45. this.allowTransitive = allowTransitive;
  46. }
  47. isPathAllowed(path) {
  48. return this.isPathAllowedForModule(path, this.currMod);
  49. }
  50. isPathAllowedForModule(path, mod) {
  51. if (!super.isPathAllowed(path)) return false;
  52. if (mod) {
  53. if (mod.allowTransitive) return true;
  54. if (path.startsWith(mod.path)) {
  55. const rem = path.slice(mod.path.length);
  56. if (!/(?:^|[\\\\/])node_modules(?:$|[\\\\/])/.test(rem)) return true;
  57. }
  58. }
  59. return this.externals.some(regex => regex.test(path));
  60. }
  61. registerModule(mod, filename, path, parent, direct) {
  62. const trustedParent = this.trustedMods.get(parent);
  63. this.trustedMods.set(mod, {
  64. filename,
  65. path,
  66. paths: this.genLookupPaths(path),
  67. allowTransitive: this.allowTransitive &&
  68. ((direct && trustedParent && trustedParent.allowTransitive) || this.externals.some(regex => regex.test(filename)))
  69. });
  70. }
  71. resolveFull(mod, x, options, ext, direct) {
  72. this.currMod = undefined;
  73. if (!direct) return super.resolveFull(mod, x, options, ext, false);
  74. const trustedMod = this.trustedMods.get(mod);
  75. if (!trustedMod || mod.path !== trustedMod.path) return super.resolveFull(mod, x, options, ext, false);
  76. const paths = [...mod.paths];
  77. if (paths.length === trustedMod.length) {
  78. for (let i = 0; i < paths.length; i++) {
  79. if (paths[i] !== trustedMod.paths[i]) {
  80. return super.resolveFull(mod, x, options, ext, false);
  81. }
  82. }
  83. }
  84. const extCopy = Object.assign({__proto__: null}, ext);
  85. try {
  86. this.currMod = trustedMod;
  87. return super.resolveFull(trustedMod, x, undefined, extCopy, true);
  88. } finally {
  89. this.currMod = undefined;
  90. }
  91. }
  92. checkAccess(mod, filename) {
  93. const trustedMod = this.trustedMods.get(mod);
  94. if ((!trustedMod || trustedMod.filename !== filename) && !this.isPathAllowedForModule(filename, undefined)) {
  95. throw new VMError(`Module '${filename}' is not allowed to be required. The path is outside the border!`, 'EDENIED');
  96. }
  97. }
  98. loadJS(vm, mod, filename) {
  99. filename = this.pathResolve(filename);
  100. this.checkAccess(mod, filename);
  101. if (this.pathContext(filename, 'js') === 'sandbox') {
  102. const trustedMod = this.trustedMods.get(mod);
  103. const script = this.readScript(filename);
  104. vm.run(script, {filename, strict: true, module: mod, wrapper: 'none', dirname: trustedMod ? trustedMod.path : mod.path});
  105. } else {
  106. const m = this.hostRequire(filename);
  107. mod.exports = vm.readonly(m);
  108. }
  109. }
  110. }
  111. function defaultBuiltinLoader(resolver, vm, id) {
  112. const mod = resolver.hostRequire(id);
  113. return vm.readonly(mod);
  114. }
  115. const eventsModules = new WeakMap();
  116. function defaultBuiltinLoaderEvents(resolver, vm, id) {
  117. return eventsModules.get(vm);
  118. }
  119. let cacheBufferScript;
  120. function defaultBuiltinLoaderBuffer(resolver, vm, id) {
  121. if (!cacheBufferScript) {
  122. cacheBufferScript = new VMScript('return buffer=>({Buffer: buffer});', {__proto__: null, filename: 'buffer.js'});
  123. }
  124. const makeBuffer = vm.run(cacheBufferScript, {__proto__: null, strict: true, wrapper: 'none'});
  125. return makeBuffer(Buffer);
  126. }
  127. let cacheUtilScript;
  128. function defaultBuiltinLoaderUtil(resolver, vm, id) {
  129. if (!cacheUtilScript) {
  130. cacheUtilScript = new VMScript(`return function inherits(ctor, superCtor) {
  131. ctor.super_ = superCtor;
  132. Object.setPrototypeOf(ctor.prototype, superCtor.prototype);
  133. }`, {__proto__: null, filename: 'util.js'});
  134. }
  135. const inherits = vm.run(cacheUtilScript, {__proto__: null, strict: true, wrapper: 'none'});
  136. const copy = Object.assign({}, util);
  137. copy.inherits = inherits;
  138. return vm.readonly(copy);
  139. }
  140. const BUILTIN_MODULES = (nmod.builtinModules || Object.getOwnPropertyNames(process.binding('natives'))).filter(s=>!s.startsWith('internal/'));
  141. let EventEmitterReferencingAsyncResourceClass = null;
  142. if (EventEmitter.EventEmitterAsyncResource) {
  143. // eslint-disable-next-line global-require
  144. const {AsyncResource} = require('async_hooks');
  145. const kEventEmitter = Symbol('kEventEmitter');
  146. class EventEmitterReferencingAsyncResource extends AsyncResource {
  147. constructor(ee, type, options) {
  148. super(type, options);
  149. this[kEventEmitter] = ee;
  150. }
  151. get eventEmitter() {
  152. return this[kEventEmitter];
  153. }
  154. }
  155. EventEmitterReferencingAsyncResourceClass = EventEmitterReferencingAsyncResource;
  156. }
  157. let cacheEventsScript;
  158. const SPECIAL_MODULES = {
  159. events(vm) {
  160. if (!cacheEventsScript) {
  161. const eventsSource = fs.readFileSync(`${__dirname}/events.js`, 'utf8');
  162. cacheEventsScript = new VMScript(`(function (fromhost) { const module = {}; module.exports={};{ ${eventsSource}
  163. } return module.exports;})`, {filename: 'events.js'});
  164. }
  165. const closure = VM.prototype.run.call(vm, cacheEventsScript);
  166. const eventsInstance = closure(vm.readonly({
  167. kErrorMonitor: EventEmitter.errorMonitor,
  168. once: EventEmitter.once,
  169. on: EventEmitter.on,
  170. getEventListeners: EventEmitter.getEventListeners,
  171. EventEmitterReferencingAsyncResource: EventEmitterReferencingAsyncResourceClass
  172. }));
  173. eventsModules.set(vm, eventsInstance);
  174. vm._addProtoMapping(EventEmitter.prototype, eventsInstance.EventEmitter.prototype);
  175. return defaultBuiltinLoaderEvents;
  176. },
  177. buffer(vm) {
  178. return defaultBuiltinLoaderBuffer;
  179. },
  180. util(vm) {
  181. return defaultBuiltinLoaderUtil;
  182. }
  183. };
  184. function addDefaultBuiltin(builtins, key, vm) {
  185. if (builtins[key]) return;
  186. const special = SPECIAL_MODULES[key];
  187. builtins[key] = special ? special(vm) : defaultBuiltinLoader;
  188. }
  189. function genBuiltinsFromOptions(vm, builtinOpt, mockOpt, override) {
  190. const builtins = {__proto__: null};
  191. if (mockOpt) {
  192. const keys = Object.getOwnPropertyNames(mockOpt);
  193. for (let i = 0; i < keys.length; i++) {
  194. const key = keys[i];
  195. builtins[key] = (resolver, tvm, id) => tvm.readonly(mockOpt[key]);
  196. }
  197. }
  198. if (override) {
  199. const keys = Object.getOwnPropertyNames(override);
  200. for (let i = 0; i < keys.length; i++) {
  201. const key = keys[i];
  202. builtins[key] = override[key];
  203. }
  204. }
  205. if (Array.isArray(builtinOpt)) {
  206. const def = builtinOpt.indexOf('*') >= 0;
  207. if (def) {
  208. for (let i = 0; i < BUILTIN_MODULES.length; i++) {
  209. const name = BUILTIN_MODULES[i];
  210. if (builtinOpt.indexOf(`-${name}`) === -1) {
  211. addDefaultBuiltin(builtins, name, vm);
  212. }
  213. }
  214. } else {
  215. for (let i = 0; i < BUILTIN_MODULES.length; i++) {
  216. const name = BUILTIN_MODULES[i];
  217. if (builtinOpt.indexOf(name) !== -1) {
  218. addDefaultBuiltin(builtins, name, vm);
  219. }
  220. }
  221. }
  222. } else if (builtinOpt) {
  223. for (let i = 0; i < BUILTIN_MODULES.length; i++) {
  224. const name = BUILTIN_MODULES[i];
  225. if (builtinOpt[name]) {
  226. addDefaultBuiltin(builtins, name, vm);
  227. }
  228. }
  229. }
  230. return builtins;
  231. }
  232. function defaultCustomResolver() {
  233. return undefined;
  234. }
  235. const DENY_RESOLVER = new Resolver({__proto__: null}, [], id => {
  236. throw new VMError(`Access denied to require '${id}'`, 'EDENIED');
  237. });
  238. function resolverFromOptions(vm, options, override, compiler) {
  239. if (!options) {
  240. if (!override) return DENY_RESOLVER;
  241. const builtins = genBuiltinsFromOptions(vm, undefined, undefined, override);
  242. return new Resolver(builtins, [], defaultRequire);
  243. }
  244. const {
  245. builtin: builtinOpt,
  246. mock: mockOpt,
  247. external: externalOpt,
  248. root: rootPaths,
  249. resolve: customResolver,
  250. customRequire: hostRequire = defaultRequire,
  251. context = 'host'
  252. } = options;
  253. const builtins = genBuiltinsFromOptions(vm, builtinOpt, mockOpt, override);
  254. if (!externalOpt) return new Resolver(builtins, [], hostRequire);
  255. let checkPath;
  256. if (rootPaths) {
  257. const checkedRootPaths = (Array.isArray(rootPaths) ? rootPaths : [rootPaths]).map(f => pa.resolve(f));
  258. checkPath = (filename) => {
  259. return checkedRootPaths.some(path => {
  260. if (!filename.startsWith(path)) return false;
  261. const len = path.length;
  262. if (filename.length === len || (len > 0 && path[len-1] === pa.sep)) return true;
  263. const sep = filename[len];
  264. return sep === '/' || sep === pa.sep;
  265. });
  266. };
  267. } else {
  268. checkPath = () => true;
  269. }
  270. let newCustomResolver = defaultCustomResolver;
  271. let externals = undefined;
  272. let external = undefined;
  273. if (customResolver) {
  274. let externalCache;
  275. newCustomResolver = (resolver, x, path, extList) => {
  276. if (external && !(resolver.pathIsAbsolute(x) || resolver.pathIsRelative(x))) {
  277. if (!externalCache) {
  278. externalCache = external.map(ext => new RegExp(makeExternalMatcherRegex(ext)));
  279. }
  280. if (!externalCache.some(regex => regex.test(x))) return undefined;
  281. }
  282. const resolved = customResolver(x, path);
  283. if (!resolved) return undefined;
  284. if (externals) externals.push(new RegExp('^' + escapeRegExp(resolved)));
  285. return resolver.loadAsFileOrDirecotry(resolved, extList);
  286. };
  287. }
  288. if (typeof externalOpt !== 'object') {
  289. return new DefaultResolver(builtins, checkPath, [], () => context, newCustomResolver, hostRequire, compiler);
  290. }
  291. let transitive = false;
  292. if (Array.isArray(externalOpt)) {
  293. external = externalOpt;
  294. } else {
  295. external = externalOpt.modules;
  296. transitive = context === 'sandbox' && externalOpt.transitive;
  297. }
  298. externals = external.map(makeExternalMatcher);
  299. return new LegacyResolver(builtins, checkPath, [], () => context, newCustomResolver, hostRequire, compiler, externals, transitive);
  300. }
  301. exports.resolverFromOptions = resolverFromOptions;