setup-node-sandbox.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464
  1. /* global host, data, VMError */
  2. 'use strict';
  3. const LocalError = Error;
  4. const LocalTypeError = TypeError;
  5. const LocalWeakMap = WeakMap;
  6. const {
  7. apply: localReflectApply,
  8. defineProperty: localReflectDefineProperty
  9. } = Reflect;
  10. const {
  11. set: localWeakMapSet,
  12. get: localWeakMapGet
  13. } = LocalWeakMap.prototype;
  14. const {
  15. isArray: localArrayIsArray
  16. } = Array;
  17. function uncurryThis(func) {
  18. return (thiz, ...args) => localReflectApply(func, thiz, args);
  19. }
  20. const localArrayPrototypeSlice = uncurryThis(Array.prototype.slice);
  21. const localArrayPrototypeIncludes = uncurryThis(Array.prototype.includes);
  22. const localArrayPrototypePush = uncurryThis(Array.prototype.push);
  23. const localArrayPrototypeIndexOf = uncurryThis(Array.prototype.indexOf);
  24. const localArrayPrototypeSplice = uncurryThis(Array.prototype.splice);
  25. const localStringPrototypeStartsWith = uncurryThis(String.prototype.startsWith);
  26. const localStringPrototypeSlice = uncurryThis(String.prototype.slice);
  27. const localStringPrototypeIndexOf = uncurryThis(String.prototype.indexOf);
  28. const {
  29. argv: optionArgv,
  30. env: optionEnv,
  31. console: optionConsole,
  32. vm,
  33. resolver,
  34. extensions
  35. } = data;
  36. function ensureSandboxArray(a) {
  37. return localArrayPrototypeSlice(a);
  38. }
  39. const globalPaths = ensureSandboxArray(resolver.globalPaths);
  40. class Module {
  41. constructor(id, path, parent) {
  42. this.id = id;
  43. this.filename = id;
  44. this.path = path;
  45. this.parent = parent;
  46. this.loaded = false;
  47. this.paths = path ? ensureSandboxArray(resolver.genLookupPaths(path)) : [];
  48. this.children = [];
  49. this.exports = {};
  50. }
  51. _updateChildren(child, isNew) {
  52. const children = this.children;
  53. if (children && (isNew || !localArrayPrototypeIncludes(children, child))) {
  54. localArrayPrototypePush(children, child);
  55. }
  56. }
  57. require(id) {
  58. return requireImpl(this, id, false);
  59. }
  60. }
  61. const originalRequire = Module.prototype.require;
  62. const cacheBuiltins = {__proto__: null};
  63. function requireImpl(mod, id, direct) {
  64. if (direct && mod.require !== originalRequire) {
  65. return mod.require(id);
  66. }
  67. const filename = resolver.resolve(mod, id, undefined, Module._extensions, direct);
  68. if (localStringPrototypeStartsWith(filename, 'node:')) {
  69. id = localStringPrototypeSlice(filename, 5);
  70. let nmod = cacheBuiltins[id];
  71. if (!nmod) {
  72. nmod = resolver.loadBuiltinModule(vm, id);
  73. if (!nmod) throw new VMError(`Cannot find module '${filename}'`, 'ENOTFOUND');
  74. cacheBuiltins[id] = nmod;
  75. }
  76. return nmod;
  77. }
  78. const cachedModule = Module._cache[filename];
  79. if (cachedModule !== undefined) {
  80. mod._updateChildren(cachedModule, false);
  81. return cachedModule.exports;
  82. }
  83. let nmod = cacheBuiltins[id];
  84. if (nmod) return nmod;
  85. nmod = resolver.loadBuiltinModule(vm, id);
  86. if (nmod) {
  87. cacheBuiltins[id] = nmod;
  88. return nmod;
  89. }
  90. const path = resolver.pathDirname(filename);
  91. const module = new Module(filename, path, mod);
  92. resolver.registerModule(module, filename, path, mod, direct);
  93. mod._updateChildren(module, true);
  94. try {
  95. Module._cache[filename] = module;
  96. const handler = findBestExtensionHandler(filename);
  97. handler(module, filename);
  98. module.loaded = true;
  99. } catch (e) {
  100. delete Module._cache[filename];
  101. const children = mod.children;
  102. if (localArrayIsArray(children)) {
  103. const index = localArrayPrototypeIndexOf(children, module);
  104. if (index !== -1) {
  105. localArrayPrototypeSplice(children, index, 1);
  106. }
  107. }
  108. throw e;
  109. }
  110. return module.exports;
  111. }
  112. Module.builtinModules = ensureSandboxArray(resolver.getBuiltinModulesList());
  113. Module.globalPaths = globalPaths;
  114. Module._extensions = {__proto__: null};
  115. Module._cache = {__proto__: null};
  116. {
  117. const keys = Object.getOwnPropertyNames(extensions);
  118. for (let i = 0; i < keys.length; i++) {
  119. const key = keys[i];
  120. const handler = extensions[key];
  121. Module._extensions[key] = (mod, filename) => handler(mod, filename);
  122. }
  123. }
  124. function findBestExtensionHandler(filename) {
  125. const name = resolver.pathBasename(filename);
  126. for (let i = 0; (i = localStringPrototypeIndexOf(name, '.', i + 1)) !== -1;) {
  127. const ext = localStringPrototypeSlice(name, i);
  128. const handler = Module._extensions[ext];
  129. if (handler) return handler;
  130. }
  131. const js = Module._extensions['.js'];
  132. if (js) return js;
  133. const keys = Object.getOwnPropertyNames(Module._extensions);
  134. if (keys.length === 0) throw new VMError(`Failed to load '${filename}': Unknown type.`, 'ELOADFAIL');
  135. return Module._extensions[keys[0]];
  136. }
  137. function createRequireForModule(mod) {
  138. // eslint-disable-next-line no-shadow
  139. function require(id) {
  140. return requireImpl(mod, id, true);
  141. }
  142. function resolve(id, options) {
  143. return resolver.resolve(mod, id, options, Module._extensions, true);
  144. }
  145. require.resolve = resolve;
  146. function paths(id) {
  147. return ensureSandboxArray(resolver.lookupPaths(mod, id));
  148. }
  149. resolve.paths = paths;
  150. require.extensions = Module._extensions;
  151. require.cache = Module._cache;
  152. return require;
  153. }
  154. /**
  155. * Prepare sandbox.
  156. */
  157. const TIMERS = new LocalWeakMap();
  158. class Timeout {
  159. }
  160. class Interval {
  161. }
  162. class Immediate {
  163. }
  164. function clearTimer(timer) {
  165. const obj = localReflectApply(localWeakMapGet, TIMERS, [timer]);
  166. if (obj) {
  167. obj.clear(obj.value);
  168. }
  169. }
  170. // This is a function and not an arrow function, since the original is also a function
  171. // eslint-disable-next-line no-shadow
  172. global.setTimeout = function setTimeout(callback, delay, ...args) {
  173. if (typeof callback !== 'function') throw new LocalTypeError('"callback" argument must be a function');
  174. const obj = new Timeout(callback, args);
  175. const cb = () => {
  176. localReflectApply(callback, null, args);
  177. };
  178. const tmr = host.setTimeout(cb, delay);
  179. const ref = {
  180. __proto__: null,
  181. clear: host.clearTimeout,
  182. value: tmr
  183. };
  184. localReflectApply(localWeakMapSet, TIMERS, [obj, ref]);
  185. return obj;
  186. };
  187. // eslint-disable-next-line no-shadow
  188. global.setInterval = function setInterval(callback, interval, ...args) {
  189. if (typeof callback !== 'function') throw new LocalTypeError('"callback" argument must be a function');
  190. const obj = new Interval();
  191. const cb = () => {
  192. localReflectApply(callback, null, args);
  193. };
  194. const tmr = host.setInterval(cb, interval);
  195. const ref = {
  196. __proto__: null,
  197. clear: host.clearInterval,
  198. value: tmr
  199. };
  200. localReflectApply(localWeakMapSet, TIMERS, [obj, ref]);
  201. return obj;
  202. };
  203. // eslint-disable-next-line no-shadow
  204. global.setImmediate = function setImmediate(callback, ...args) {
  205. if (typeof callback !== 'function') throw new LocalTypeError('"callback" argument must be a function');
  206. const obj = new Immediate();
  207. const cb = () => {
  208. localReflectApply(callback, null, args);
  209. };
  210. const tmr = host.setImmediate(cb);
  211. const ref = {
  212. __proto__: null,
  213. clear: host.clearImmediate,
  214. value: tmr
  215. };
  216. localReflectApply(localWeakMapSet, TIMERS, [obj, ref]);
  217. return obj;
  218. };
  219. // eslint-disable-next-line no-shadow
  220. global.clearTimeout = function clearTimeout(timeout) {
  221. clearTimer(timeout);
  222. };
  223. // eslint-disable-next-line no-shadow
  224. global.clearInterval = function clearInterval(interval) {
  225. clearTimer(interval);
  226. };
  227. // eslint-disable-next-line no-shadow
  228. global.clearImmediate = function clearImmediate(immediate) {
  229. clearTimer(immediate);
  230. };
  231. const localProcess = host.process;
  232. function vmEmitArgs(event, args) {
  233. const allargs = [event];
  234. for (let i = 0; i < args.length; i++) {
  235. if (!localReflectDefineProperty(allargs, i + 1, {
  236. __proto__: null,
  237. value: args[i],
  238. writable: true,
  239. enumerable: true,
  240. configurable: true
  241. })) throw new LocalError('Unexpected');
  242. }
  243. return localReflectApply(vm.emit, vm, allargs);
  244. }
  245. const LISTENERS = new LocalWeakMap();
  246. const LISTENER_HANDLER = new LocalWeakMap();
  247. /**
  248. *
  249. * @param {*} name
  250. * @param {*} handler
  251. * @this process
  252. * @return {this}
  253. */
  254. function addListener(name, handler) {
  255. if (name !== 'beforeExit' && name !== 'exit') {
  256. throw new LocalError(`Access denied to listen for '${name}' event.`);
  257. }
  258. let cb = localReflectApply(localWeakMapGet, LISTENERS, [handler]);
  259. if (!cb) {
  260. cb = () => {
  261. handler();
  262. };
  263. localReflectApply(localWeakMapSet, LISTENER_HANDLER, [cb, handler]);
  264. localReflectApply(localWeakMapSet, LISTENERS, [handler, cb]);
  265. }
  266. localProcess.on(name, cb);
  267. return this;
  268. }
  269. /**
  270. *
  271. * @this process
  272. * @return {this}
  273. */
  274. // eslint-disable-next-line no-shadow
  275. function process() {
  276. return this;
  277. }
  278. // FIXME wrong class structure
  279. global.process = {
  280. __proto__: process.prototype,
  281. argv: optionArgv !== undefined ? optionArgv : [],
  282. title: localProcess.title,
  283. version: localProcess.version,
  284. versions: localProcess.versions,
  285. arch: localProcess.arch,
  286. platform: localProcess.platform,
  287. env: optionEnv !== undefined ? optionEnv : {},
  288. pid: localProcess.pid,
  289. features: localProcess.features,
  290. nextTick: function nextTick(callback, ...args) {
  291. if (typeof callback !== 'function') {
  292. throw new LocalError('Callback must be a function.');
  293. }
  294. localProcess.nextTick(()=>{
  295. localReflectApply(callback, null, args);
  296. });
  297. },
  298. hrtime: function hrtime(time) {
  299. return localProcess.hrtime(time);
  300. },
  301. cwd: function cwd() {
  302. return localProcess.cwd();
  303. },
  304. addListener,
  305. on: addListener,
  306. once: function once(name, handler) {
  307. if (name !== 'beforeExit' && name !== 'exit') {
  308. throw new LocalError(`Access denied to listen for '${name}' event.`);
  309. }
  310. let triggered = false;
  311. const cb = () => {
  312. if (triggered) return;
  313. triggered = true;
  314. localProcess.removeListener(name, cb);
  315. handler();
  316. };
  317. localReflectApply(localWeakMapSet, LISTENER_HANDLER, [cb, handler]);
  318. localProcess.on(name, cb);
  319. return this;
  320. },
  321. listeners: function listeners(name) {
  322. if (name !== 'beforeExit' && name !== 'exit') {
  323. // Maybe add ({__proto__:null})[name] to throw when name fails in https://tc39.es/ecma262/#sec-topropertykey.
  324. return [];
  325. }
  326. // Filter out listeners, which were not created in this sandbox
  327. const all = localProcess.listeners(name);
  328. const filtered = [];
  329. let j = 0;
  330. for (let i = 0; i < all.length; i++) {
  331. const h = localReflectApply(localWeakMapGet, LISTENER_HANDLER, [all[i]]);
  332. if (h) {
  333. if (!localReflectDefineProperty(filtered, j, {
  334. __proto__: null,
  335. value: h,
  336. writable: true,
  337. enumerable: true,
  338. configurable: true
  339. })) throw new LocalError('Unexpected');
  340. j++;
  341. }
  342. }
  343. return filtered;
  344. },
  345. removeListener: function removeListener(name, handler) {
  346. if (name !== 'beforeExit' && name !== 'exit') {
  347. return this;
  348. }
  349. const cb = localReflectApply(localWeakMapGet, LISTENERS, [handler]);
  350. if (cb) localProcess.removeListener(name, cb);
  351. return this;
  352. },
  353. umask: function umask() {
  354. if (arguments.length) {
  355. throw new LocalError('Access denied to set umask.');
  356. }
  357. return localProcess.umask();
  358. }
  359. };
  360. if (optionConsole === 'inherit') {
  361. global.console = host.console;
  362. } else if (optionConsole === 'redirect') {
  363. global.console = {
  364. debug(...args) {
  365. vmEmitArgs('console.debug', args);
  366. },
  367. log(...args) {
  368. vmEmitArgs('console.log', args);
  369. },
  370. info(...args) {
  371. vmEmitArgs('console.info', args);
  372. },
  373. warn(...args) {
  374. vmEmitArgs('console.warn', args);
  375. },
  376. error(...args) {
  377. vmEmitArgs('console.error', args);
  378. },
  379. dir(...args) {
  380. vmEmitArgs('console.dir', args);
  381. },
  382. time() {},
  383. timeEnd() {},
  384. trace(...args) {
  385. vmEmitArgs('console.trace', args);
  386. }
  387. };
  388. }
  389. return {
  390. __proto__: null,
  391. Module,
  392. jsonParse: JSON.parse,
  393. createRequireForModule,
  394. requireImpl
  395. };