diff --git a/dist/contextify.js b/dist/contextify.js new file mode 100644 index 0000000..5695e0e --- /dev/null +++ b/dist/contextify.js @@ -0,0 +1,990 @@ +/* global host */ +/* eslint-disable block-spacing, no-multi-spaces, brace-style, no-array-constructor, new-cap, no-use-before-define */ + +'use strict'; + +// eslint-disable-next-line no-invalid-this, no-shadow +const global = this; + +const local = host.Object.create(null); +local.Object = Object; +local.Array = Array; +local.Reflect = host.Object.create(null); +local.Reflect.ownKeys = Reflect.ownKeys; +local.Reflect.enumerate = Reflect.enumerate; +local.Reflect.getPrototypeOf = Reflect.getPrototypeOf; +local.Reflect.construct = Reflect.construct; +local.Reflect.apply = Reflect.apply; +local.Reflect.set = Reflect.set; +local.Reflect.deleteProperty = Reflect.deleteProperty; +local.Reflect.has = Reflect.has; +local.Reflect.defineProperty = Reflect.defineProperty; +local.Reflect.setPrototypeOf = Reflect.setPrototypeOf; +local.Reflect.isExtensible = Reflect.isExtensible; +local.Reflect.preventExtensions = Reflect.preventExtensions; +local.Reflect.getOwnPropertyDescriptor = Reflect.getOwnPropertyDescriptor; + +// global is originally prototype of host.Object so it can be used to climb up from the sandbox. +Object.setPrototypeOf(global, Object.prototype); + +Object.defineProperties(global, { + global: {value: global}, + GLOBAL: {value: global}, + root: {value: global}, + isVM: {value: true} +}); + +const DEBUG = false; +const OPNA = 'Operation not allowed on contextified object.'; +const captureStackTrace = Error.captureStackTrace; + +const FROZEN_TRAPS = host.Object.create(null); +FROZEN_TRAPS.set = (target, key) => false; +FROZEN_TRAPS.setPrototypeOf = (target, key) => false; +FROZEN_TRAPS.defineProperty = (target, key) => false; +FROZEN_TRAPS.deleteProperty = (target, key) => false; +FROZEN_TRAPS.isExtensible = (target, key) => false; +FROZEN_TRAPS.preventExtensions = (target) => false; + +// Map of contextified objects to original objects +const Contextified = new host.WeakMap(); +const Decontextified = new host.WeakMap(); + +// We can't use host's hasInstance method +const hasInstance = local.Object[Symbol.hasInstance]; +function instanceOf(value, construct) { + try { + return host.Reflect.apply(hasInstance, construct, [value]); + } catch (ex) { + // Never pass the handled exception through! + throw new VMError('Unable to perform instanceOf check.'); + // This exception actually never get to the user. It only instructs the caller to return null because we wasn't able to perform instanceOf check. + } +} + +const SHARED_OBJECT = {__proto__: null}; + +function createBaseObject(obj) { + let base; + if (typeof obj === 'function') { + try { + // eslint-disable-next-line no-new + new new host.Proxy(obj, { + __proto__: null, + construct() { + return this; + } + })(); + // eslint-disable-next-line func-names + base = function() {}; + base.prototype = null; + } catch (e) { + base = () => {}; + } + } else if (host.Array.isArray(obj)) { + base = []; + } else { + return {__proto__: null}; + } + if (!local.Reflect.setPrototypeOf(base, null)) { + // Should not happen + return null; + } + return base; +} + +/** + * VMError definition. + */ + +class VMError extends Error { + constructor(message, code) { + super(message); + + this.name = 'VMError'; + this.code = code; + + captureStackTrace(this, this.constructor); + } +} + +global.VMError = VMError; + +/* + * This function will throw a TypeError for accessing properties + * on a strict mode function + */ +function throwCallerCalleeArgumentsAccess(key) { + 'use strict'; + throwCallerCalleeArgumentsAccess[key]; + return new VMError('Unreachable'); +} + +function unexpected() { + throw new VMError('Should not happen'); +} + +function doPreventExtensions(target, object, doProxy) { + const keys = local.Reflect.ownKeys(object); + for (let i = 0; i < keys.length; i++) { + const key = keys[i]; + let desc = local.Reflect.getOwnPropertyDescriptor(object, key); + if (!desc) continue; + if (!local.Reflect.setPrototypeOf(desc, null)) unexpected(); + if (!desc.configurable) { + const current = local.Reflect.getOwnPropertyDescriptor(target, key); + if (current && !current.configurable) continue; + if (desc.get || desc.set) { + desc.get = doProxy(desc.get); + desc.set = doProxy(desc.set); + } else { + desc.value = doProxy(desc.value); + } + } else { + if (desc.get || desc.set) { + desc = { + __proto__: null, + configurable: true, + enumerable: desc.enumerable, + writable: true, + value: null + }; + } else { + desc.value = null; + } + } + if (!local.Reflect.defineProperty(target, key, desc)) unexpected(); + } + if (!local.Reflect.preventExtensions(target)) unexpected(); +} + +/** + * Decontextify. + */ + +const Decontextify = host.Object.create(null); +Decontextify.proxies = new host.WeakMap(); + +Decontextify.arguments = args => { + if (!host.Array.isArray(args)) return new host.Array(); + + try { + const arr = new host.Array(); + for (let i = 0, l = args.length; i < l; i++) arr[i] = Decontextify.value(args[i]); + return arr; + } catch (e) { + // Never pass the handled exception through! + return new host.Array(); + } +}; +Decontextify.instance = (instance, klass, deepTraps, flags, toStringTag) => { + if (typeof instance === 'function') return Decontextify.function(instance); + + // We must not use normal object because there's a chance object already contains malicious code in the prototype + const base = host.Object.create(null); + + base.get = (target, key, receiver) => { + try { + if (key === 'vmProxyTarget' && DEBUG) return instance; + if (key === 'isVMProxy') return true; + if (key === 'constructor') return klass; + if (key === '__proto__') return klass.prototype; + } catch (e) { + // Never pass the handled exception through! This block can't throw an exception under normal conditions. + return null; + } + + if (key === '__defineGetter__') return host.Object.prototype.__defineGetter__; + if (key === '__defineSetter__') return host.Object.prototype.__defineSetter__; + if (key === '__lookupGetter__') return host.Object.prototype.__lookupGetter__; + if (key === '__lookupSetter__') return host.Object.prototype.__lookupSetter__; + if (key === host.Symbol.toStringTag && toStringTag) return toStringTag; + + try { + return Decontextify.value(instance[key], null, deepTraps, flags); + } catch (e) { + throw Decontextify.value(e); + } + }; + base.getPrototypeOf = (target) => { + return klass && klass.prototype; + }; + + return Decontextify.object(instance, base, deepTraps, flags); +}; +Decontextify.function = (fnc, traps, deepTraps, flags, mock) => { + // We must not use normal object because there's a chance object already contains malicious code in the prototype + const base = host.Object.create(null); + // eslint-disable-next-line prefer-const + let proxy; + + base.apply = (target, context, args) => { + context = Contextify.value(context); + + // Set context of all arguments to vm's context. + args = Contextify.arguments(args); + + try { + return Decontextify.value(fnc.apply(context, args)); + } catch (e) { + throw Decontextify.value(e); + } + }; + base.construct = (target, args, newTarget) => { + args = Contextify.arguments(args); + + try { + return Decontextify.instance(new fnc(...args), proxy, deepTraps, flags); + } catch (e) { + throw Decontextify.value(e); + } + }; + base.get = (target, key, receiver) => { + try { + if (key === 'vmProxyTarget' && DEBUG) return fnc; + if (key === 'isVMProxy') return true; + if (mock && host.Object.prototype.hasOwnProperty.call(mock, key)) return mock[key]; + if (key === 'constructor') return host.Function; + if (key === '__proto__') return host.Function.prototype; + } catch (e) { + // Never pass the handled exception through! This block can't throw an exception under normal conditions. + return null; + } + + if (key === '__defineGetter__') return host.Object.prototype.__defineGetter__; + if (key === '__defineSetter__') return host.Object.prototype.__defineSetter__; + if (key === '__lookupGetter__') return host.Object.prototype.__lookupGetter__; + if (key === '__lookupSetter__') return host.Object.prototype.__lookupSetter__; + + try { + return Decontextify.value(fnc[key], null, deepTraps, flags); + } catch (e) { + throw Decontextify.value(e); + } + }; + base.getPrototypeOf = (target) => { + return host.Function.prototype; + }; + + proxy = Decontextify.object(fnc, host.Object.assign(base, traps), deepTraps); + return proxy; +}; +Decontextify.object = (object, traps, deepTraps, flags, mock) => { + // We must not use normal object because there's a chance object already contains malicious code in the prototype + const base = host.Object.create(null); + + base.get = (target, key, receiver) => { + try { + if (key === 'vmProxyTarget' && DEBUG) return object; + if (key === 'isVMProxy') return true; + if (mock && host.Object.prototype.hasOwnProperty.call(mock, key)) return mock[key]; + if (key === 'constructor') return host.Object; + if (key === '__proto__') return host.Object.prototype; + } catch (e) { + // Never pass the handled exception through! This block can't throw an exception under normal conditions. + return null; + } + + if (key === '__defineGetter__') return host.Object.prototype.__defineGetter__; + if (key === '__defineSetter__') return host.Object.prototype.__defineSetter__; + if (key === '__lookupGetter__') return host.Object.prototype.__lookupGetter__; + if (key === '__lookupSetter__') return host.Object.prototype.__lookupSetter__; + + try { + return Decontextify.value(object[key], null, deepTraps, flags); + } catch (e) { + throw Decontextify.value(e); + } + }; + base.set = (target, key, value, receiver) => { + value = Contextify.value(value); + + try { + return local.Reflect.set(object, key, value); + } catch (e) { + throw Decontextify.value(e); + } + }; + base.getOwnPropertyDescriptor = (target, prop) => { + let def; + + try { + def = host.Object.getOwnPropertyDescriptor(object, prop); + } catch (e) { + throw Decontextify.value(e); + } + + // Following code prevents V8 to throw + // TypeError: 'getOwnPropertyDescriptor' on proxy: trap reported non-configurability for property '' + // which is either non-existant or configurable in the proxy target + + let desc; + if (!def) { + return undefined; + } else if (def.get || def.set) { + desc = { + __proto__: null, + get: Decontextify.value(def.get) || undefined, + set: Decontextify.value(def.set) || undefined, + enumerable: def.enumerable === true, + configurable: def.configurable === true + }; + } else { + desc = { + __proto__: null, + value: Decontextify.value(def.value), + writable: def.writable === true, + enumerable: def.enumerable === true, + configurable: def.configurable === true + }; + } + if (!desc.configurable) { + try { + def = host.Object.getOwnPropertyDescriptor(target, prop); + if (!def || def.configurable || def.writable !== desc.writable) { + local.Reflect.defineProperty(target, prop, desc); + } + } catch (e) { + // Should not happen. + } + } + return desc; + }; + base.defineProperty = (target, key, descriptor) => { + let success = false; + try { + success = local.Reflect.setPrototypeOf(descriptor, null); + } catch (e) { + // Should not happen + } + if (!success) return false; + // There's a chance accessing a property throws an error so we must not access them + // in try catch to prevent contextifying local objects. + + const propertyDescriptor = host.Object.create(null); + if (descriptor.get || descriptor.set) { + propertyDescriptor.get = Contextify.value(descriptor.get, null, deepTraps, flags) || undefined; + propertyDescriptor.set = Contextify.value(descriptor.set, null, deepTraps, flags) || undefined; + propertyDescriptor.enumerable = descriptor.enumerable === true; + propertyDescriptor.configurable = descriptor.configurable === true; + } else { + propertyDescriptor.value = Contextify.value(descriptor.value, null, deepTraps, flags); + propertyDescriptor.writable = descriptor.writable === true; + propertyDescriptor.enumerable = descriptor.enumerable === true; + propertyDescriptor.configurable = descriptor.configurable === true; + } + + try { + success = local.Reflect.defineProperty(object, key, propertyDescriptor); + } catch (e) { + throw Decontextify.value(e); + } + if (success && !descriptor.configurable) { + try { + local.Reflect.defineProperty(target, key, descriptor); + } catch (e) { + // This should not happen. + return false; + } + } + return success; + }; + base.deleteProperty = (target, prop) => { + try { + return Decontextify.value(local.Reflect.deleteProperty(object, prop)); + } catch (e) { + throw Decontextify.value(e); + } + }; + base.getPrototypeOf = (target) => { + return host.Object.prototype; + }; + base.setPrototypeOf = (target) => { + throw new host.Error(OPNA); + }; + base.has = (target, key) => { + try { + return Decontextify.value(local.Reflect.has(object, key)); + } catch (e) { + throw Decontextify.value(e); + } + }; + base.isExtensible = target => { + let result; + try { + result = local.Reflect.isExtensible(object); + } catch (e) { + throw Decontextify.value(e); + } + if (!result) { + try { + if (local.Reflect.isExtensible(target)) { + doPreventExtensions(target, object, obj => Contextify.value(obj, null, deepTraps, flags)); + } + } catch (e) { + // Should not happen + } + } + return result; + }; + base.ownKeys = target => { + try { + return Decontextify.value(local.Reflect.ownKeys(object)); + } catch (e) { + throw Decontextify.value(e); + } + }; + base.preventExtensions = target => { + let success; + try { + success = local.Reflect.preventExtensions(object); + } catch (e) { + throw Decontextify.value(e); + } + if (success) { + try { + if (local.Reflect.isExtensible(target)) { + doPreventExtensions(target, object, obj => Contextify.value(obj, null, deepTraps, flags)); + } + } catch (e) { + // Should not happen + } + } + return success; + }; + base.enumerate = target => { + try { + return Decontextify.value(local.Reflect.enumerate(object)); + } catch (e) { + throw Decontextify.value(e); + } + }; + + host.Object.assign(base, traps, deepTraps); + + let shallow; + if (host.Array.isArray(object)) { + const origGet = base.get; + shallow = { + __proto__: null, + ownKeys: base.ownKeys, + // TODO this get will call getOwnPropertyDescriptor of target all the time. + get: origGet + }; + base.ownKeys = target => { + try { + const keys = local.Reflect.ownKeys(object); + // Do this hack so that console.log(decontextify([1,2,3])) doesn't write the properties twice + // a la [1,2,3,'0':1,'1':2,'2':3] + return Decontextify.value(keys.filter(key=>typeof key!=='string' || !key.match(/^\d+$/))); + } catch (e) { + throw Decontextify.value(e); + } + }; + base.get = (target, key, receiver) => { + if (key === host.Symbol.toStringTag) return; + return origGet(target, key, receiver); + }; + } else { + shallow = SHARED_OBJECT; + } + + const proxy = new host.Proxy(createBaseObject(object), base); + Decontextified.set(proxy, object); + // We need two proxies since nodes inspect just removes one. + const proxy2 = new host.Proxy(proxy, shallow); + Decontextify.proxies.set(object, proxy2); + Decontextified.set(proxy2, object); + return proxy2; +}; +Decontextify.value = (value, traps, deepTraps, flags, mock) => { + try { + if (Contextified.has(value)) { + // Contextified object has returned back from vm + return Contextified.get(value); + } else if (Decontextify.proxies.has(value)) { + // Decontextified proxy already exists, reuse + return Decontextify.proxies.get(value); + } + + switch (typeof value) { + case 'object': + if (value === null) { + return null; + } else if (instanceOf(value, Number)) { return Decontextify.instance(value, host.Number, deepTraps, flags, 'Number'); + } else if (instanceOf(value, String)) { return Decontextify.instance(value, host.String, deepTraps, flags, 'String'); + } else if (instanceOf(value, Boolean)) { return Decontextify.instance(value, host.Boolean, deepTraps, flags, 'Boolean'); + } else if (instanceOf(value, Date)) { return Decontextify.instance(value, host.Date, deepTraps, flags, 'Date'); + } else if (instanceOf(value, RangeError)) { return Decontextify.instance(value, host.RangeError, deepTraps, flags, 'Error'); + } else if (instanceOf(value, ReferenceError)) { return Decontextify.instance(value, host.ReferenceError, deepTraps, flags, 'Error'); + } else if (instanceOf(value, SyntaxError)) { return Decontextify.instance(value, host.SyntaxError, deepTraps, flags, 'Error'); + } else if (instanceOf(value, TypeError)) { return Decontextify.instance(value, host.TypeError, deepTraps, flags, 'Error'); + } else if (instanceOf(value, VMError)) { return Decontextify.instance(value, host.VMError, deepTraps, flags, 'Error'); + } else if (instanceOf(value, EvalError)) { return Decontextify.instance(value, host.EvalError, deepTraps, flags, 'Error'); + } else if (instanceOf(value, URIError)) { return Decontextify.instance(value, host.URIError, deepTraps, flags, 'Error'); + } else if (instanceOf(value, Error)) { return Decontextify.instance(value, host.Error, deepTraps, flags, 'Error'); + } else if (instanceOf(value, Array)) { return Decontextify.instance(value, host.Array, deepTraps, flags, 'Array'); + } else if (instanceOf(value, RegExp)) { return Decontextify.instance(value, host.RegExp, deepTraps, flags, 'RegExp'); + } else if (instanceOf(value, Map)) { return Decontextify.instance(value, host.Map, deepTraps, flags, 'Map'); + } else if (instanceOf(value, WeakMap)) { return Decontextify.instance(value, host.WeakMap, deepTraps, flags, 'WeakMap'); + } else if (instanceOf(value, Set)) { return Decontextify.instance(value, host.Set, deepTraps, flags, 'Set'); + } else if (instanceOf(value, WeakSet)) { return Decontextify.instance(value, host.WeakSet, deepTraps, flags, 'WeakSet'); + } else if (typeof Promise === 'function' && instanceOf(value, Promise)) { + return Decontextify.instance(value, host.Promise, deepTraps, flags, 'Promise'); + } else if (local.Reflect.getPrototypeOf(value) === null) { + return Decontextify.instance(value, null, deepTraps, flags); + } else { + return Decontextify.object(value, traps, deepTraps, flags, mock); + } + case 'function': + return Decontextify.function(value, traps, deepTraps, flags, mock); + + case 'undefined': + return undefined; + + default: // string, number, boolean, symbol + return value; + } + } catch (ex) { + // Never pass the handled exception through! This block can't throw an exception under normal conditions. + return null; + } +}; + +/** + * Contextify. + */ + +const Contextify = host.Object.create(null); +Contextify.proxies = new host.WeakMap(); + +Contextify.arguments = args => { + if (!host.Array.isArray(args)) return new local.Array(); + + try { + const arr = new local.Array(); + for (let i = 0, l = args.length; i < l; i++) arr[i] = Contextify.value(args[i]); + return arr; + } catch (e) { + // Never pass the handled exception through! + return new local.Array(); + } +}; +Contextify.instance = (instance, klass, deepTraps, flags, toStringTag) => { + if (typeof instance === 'function') return Contextify.function(instance); + + // We must not use normal object because there's a chance object already contains malicious code in the prototype + const base = host.Object.create(null); + + base.get = (target, key, receiver) => { + try { + if (key === 'vmProxyTarget' && DEBUG) return instance; + if (key === 'isVMProxy') return true; + if (key === 'constructor') return klass; + if (key === '__proto__') return klass.prototype; + } catch (e) { + // Never pass the handled exception through! This block can't throw an exception under normal conditions. + return null; + } + + if (key === '__defineGetter__') return local.Object.prototype.__defineGetter__; + if (key === '__defineSetter__') return local.Object.prototype.__defineSetter__; + if (key === '__lookupGetter__') return local.Object.prototype.__lookupGetter__; + if (key === '__lookupSetter__') return local.Object.prototype.__lookupSetter__; + if (key === host.Symbol.toStringTag && toStringTag) return toStringTag; + + try { + return Contextify.value(host.Reflect.get(instance, key), null, deepTraps, flags); + } catch (e) { + throw Contextify.value(e); + } + }; + base.getPrototypeOf = (target) => { + return klass && klass.prototype; + }; + + return Contextify.object(instance, base, deepTraps, flags); +}; +Contextify.function = (fnc, traps, deepTraps, flags, mock) => { + // We must not use normal object because there's a chance object already contains malicious code in the prototype + const base = host.Object.create(null); + // eslint-disable-next-line prefer-const + let proxy; + + base.apply = (target, context, args) => { + context = Decontextify.value(context); + + // Set context of all arguments to host's context. + args = Decontextify.arguments(args); + + try { + return Contextify.value(fnc.apply(context, args)); + } catch (e) { + throw Contextify.value(e); + } + }; + base.construct = (target, args, newTarget) => { + // Fixes buffer unsafe allocation for node v6/7 + if (host.version < 8 && fnc === host.Buffer && 'number' === typeof args[0]) { + args[0] = new Array(args[0]).fill(0); + } + + args = Decontextify.arguments(args); + + try { + return Contextify.instance(new fnc(...args), proxy, deepTraps, flags); + } catch (e) { + throw Contextify.value(e); + } + }; + base.get = (target, key, receiver) => { + try { + if (key === 'vmProxyTarget' && DEBUG) return fnc; + if (key === 'isVMProxy') return true; + if (mock && host.Object.prototype.hasOwnProperty.call(mock, key)) return mock[key]; + if (key === 'constructor') return Function; + if (key === '__proto__') return Function.prototype; + } catch (e) { + // Never pass the handled exception through! This block can't throw an exception under normal conditions. + return null; + } + + if (key === '__defineGetter__') return local.Object.prototype.__defineGetter__; + if (key === '__defineSetter__') return local.Object.prototype.__defineSetter__; + if (key === '__lookupGetter__') return local.Object.prototype.__lookupGetter__; + if (key === '__lookupSetter__') return local.Object.prototype.__lookupSetter__; + + if (key === 'caller' || key === 'callee' || key === 'arguments') throw throwCallerCalleeArgumentsAccess(key); + + try { + return Contextify.value(host.Reflect.get(fnc, key), null, deepTraps, flags); + } catch (e) { + throw Contextify.value(e); + } + }; + base.getPrototypeOf = (target) => { + return Function.prototype; + }; + + proxy = Contextify.object(fnc, host.Object.assign(base, traps), deepTraps); + return proxy; +}; +Contextify.object = (object, traps, deepTraps, flags, mock) => { + // We must not use normal object because there's a chance object already contains malicious code in the prototype + const base = host.Object.create(null); + + base.get = (target, key, receiver) => { + try { + if (key === 'vmProxyTarget' && DEBUG) return object; + if (key === 'isVMProxy') return true; + if (mock && host.Object.prototype.hasOwnProperty.call(mock, key)) return mock[key]; + if (key === 'constructor') return Object; + if (key === '__proto__') return Object.prototype; + } catch (e) { + // Never pass the handled exception through! This block can't throw an exception under normal conditions. + return null; + } + + if (key === '__defineGetter__') return local.Object.prototype.__defineGetter__; + if (key === '__defineSetter__') return local.Object.prototype.__defineSetter__; + if (key === '__lookupGetter__') return local.Object.prototype.__lookupGetter__; + if (key === '__lookupSetter__') return local.Object.prototype.__lookupSetter__; + + try { + return Contextify.value(host.Reflect.get(object, key), null, deepTraps, flags); + } catch (e) { + throw Contextify.value(e); + } + }; + base.set = (target, key, value, receiver) => { + if (key === '__proto__') return false; + if (flags && flags.protected && typeof value === 'function') return false; + + value = Decontextify.value(value); + + try { + return host.Reflect.set(object, key, value); + } catch (e) { + throw Contextify.value(e); + } + }; + base.getOwnPropertyDescriptor = (target, prop) => { + let def; + + try { + def = host.Object.getOwnPropertyDescriptor(object, prop); + } catch (e) { + throw Contextify.value(e); + } + + // Following code prevents V8 to throw + // TypeError: 'getOwnPropertyDescriptor' on proxy: trap reported non-configurability for property '' + // which is either non-existant or configurable in the proxy target + + let desc; + if (!def) { + return undefined; + } else if (def.get || def.set) { + desc = { + __proto__: null, + get: Contextify.value(def.get, null, deepTraps, flags) || undefined, + set: Contextify.value(def.set, null, deepTraps, flags) || undefined, + enumerable: def.enumerable === true, + configurable: def.configurable === true + }; + } else { + desc = { + __proto__: null, + value: Contextify.value(def.value, null, deepTraps, flags), + writable: def.writable === true, + enumerable: def.enumerable === true, + configurable: def.configurable === true + }; + } + if (!desc.configurable) { + try { + def = host.Object.getOwnPropertyDescriptor(target, prop); + if (!def || def.configurable || def.writable !== desc.writable) { + local.Reflect.defineProperty(target, prop, desc); + } + } catch (e) { + // Should not happen. + } + } + return desc; + }; + base.defineProperty = (target, key, descriptor) => { + let success = false; + try { + success = local.Reflect.setPrototypeOf(descriptor, null); + } catch (e) { + // Should not happen + } + if (!success) return false; + // There's a chance accessing a property throws an error so we must not access them + // in try catch to prevent contextifying local objects. + + const descGet = descriptor.get; + const descSet = descriptor.set; + const descValue = descriptor.value; + + if (flags && flags.protected) { + if (descGet || descSet || typeof descValue === 'function') return false; + } + + const propertyDescriptor = host.Object.create(null); + if (descGet || descSet) { + propertyDescriptor.get = Decontextify.value(descGet, null, deepTraps, flags) || undefined; + propertyDescriptor.set = Decontextify.value(descSet, null, deepTraps, flags) || undefined; + propertyDescriptor.enumerable = descriptor.enumerable === true; + propertyDescriptor.configurable = descriptor.configurable === true; + } else { + propertyDescriptor.value = Decontextify.value(descValue, null, deepTraps, flags); + propertyDescriptor.writable = descriptor.writable === true; + propertyDescriptor.enumerable = descriptor.enumerable === true; + propertyDescriptor.configurable = descriptor.configurable === true; + } + + try { + success = host.Reflect.defineProperty(object, key, propertyDescriptor); + } catch (e) { + throw Contextify.value(e); + } + if (success && !descriptor.configurable) { + try { + local.Reflect.defineProperty(target, key, descriptor); + } catch (e) { + // This should not happen. + return false; + } + } + return success; + }; + base.deleteProperty = (target, prop) => { + try { + return Contextify.value(host.Reflect.deleteProperty(object, prop)); + } catch (e) { + throw Contextify.value(e); + } + }; + base.getPrototypeOf = (target) => { + return local.Object.prototype; + }; + base.setPrototypeOf = (target) => { + throw new VMError(OPNA); + }; + base.has = (target, key) => { + try { + return Contextify.value(host.Reflect.has(object, key)); + } catch (e) { + throw Contextify.value(e); + } + }; + base.isExtensible = target => { + let result; + try { + result = host.Reflect.isExtensible(object); + } catch (e) { + throw Contextify.value(e); + } + if (!result) { + try { + if (local.Reflect.isExtensible(target)) { + doPreventExtensions(target, object, obj => Decontextify.value(obj, null, deepTraps, flags)); + } + } catch (e) { + // Should not happen + } + } + return result; + }; + base.ownKeys = target => { + try { + return Contextify.value(host.Reflect.ownKeys(object)); + } catch (e) { + throw Contextify.value(e); + } + }; + base.preventExtensions = target => { + let success; + try { + success = local.Reflect.preventExtensions(object); + } catch (e) { + throw Contextify.value(e); + } + if (success) { + try { + if (local.Reflect.isExtensible(target)) { + doPreventExtensions(target, object, obj => Decontextify.value(obj, null, deepTraps, flags)); + } + } catch (e) { + // Should not happen + } + } + return success; + }; + base.enumerate = target => { + try { + return Contextify.value(host.Reflect.enumerate(object)); + } catch (e) { + throw Contextify.value(e); + } + }; + + const proxy = new host.Proxy(createBaseObject(object), host.Object.assign(base, traps, deepTraps)); + Contextify.proxies.set(object, proxy); + Contextified.set(proxy, object); + return proxy; +}; +Contextify.value = (value, traps, deepTraps, flags, mock) => { + try { + if (Decontextified.has(value)) { + // Decontextified object has returned back to vm + return Decontextified.get(value); + } else if (Contextify.proxies.has(value)) { + // Contextified proxy already exists, reuse + return Contextify.proxies.get(value); + } + + switch (typeof value) { + case 'object': + if (value === null) { + return null; + } else if (instanceOf(value, host.Number)) { return Contextify.instance(value, Number, deepTraps, flags, 'Number'); + } else if (instanceOf(value, host.String)) { return Contextify.instance(value, String, deepTraps, flags, 'String'); + } else if (instanceOf(value, host.Boolean)) { return Contextify.instance(value, Boolean, deepTraps, flags, 'Boolean'); + } else if (instanceOf(value, host.Date)) { return Contextify.instance(value, Date, deepTraps, flags, 'Date'); + } else if (instanceOf(value, host.RangeError)) { return Contextify.instance(value, RangeError, deepTraps, flags, 'Error'); + } else if (instanceOf(value, host.ReferenceError)) { return Contextify.instance(value, ReferenceError, deepTraps, flags, 'Error'); + } else if (instanceOf(value, host.SyntaxError)) { return Contextify.instance(value, SyntaxError, deepTraps, flags, 'Error'); + } else if (instanceOf(value, host.TypeError)) { return Contextify.instance(value, TypeError, deepTraps, flags, 'Error'); + } else if (instanceOf(value, host.VMError)) { return Contextify.instance(value, VMError, deepTraps, flags, 'Error'); + } else if (instanceOf(value, host.EvalError)) { return Contextify.instance(value, EvalError, deepTraps, flags, 'Error'); + } else if (instanceOf(value, host.URIError)) { return Contextify.instance(value, URIError, deepTraps, flags, 'Error'); + } else if (instanceOf(value, host.Error)) { return Contextify.instance(value, Error, deepTraps, flags, 'Error'); + } else if (instanceOf(value, host.Array)) { return Contextify.instance(value, Array, deepTraps, flags, 'Array'); + } else if (instanceOf(value, host.RegExp)) { return Contextify.instance(value, RegExp, deepTraps, flags, 'RegExp'); + } else if (instanceOf(value, host.Map)) { return Contextify.instance(value, Map, deepTraps, flags, 'Map'); + } else if (instanceOf(value, host.WeakMap)) { return Contextify.instance(value, WeakMap, deepTraps, flags, 'WeakMap'); + } else if (instanceOf(value, host.Set)) { return Contextify.instance(value, Set, deepTraps, flags, 'Set'); + } else if (instanceOf(value, host.WeakSet)) { return Contextify.instance(value, WeakSet, deepTraps, flags, 'WeakSet'); + } else if (typeof Promise === 'function' && instanceOf(value, host.Promise)) { + return Contextify.instance(value, Promise, deepTraps, flags, 'Promise'); + } else if (instanceOf(value, host.Buffer)) { return Contextify.instance(value, LocalBuffer, deepTraps, flags, 'Uint8Array'); + } else if (host.Reflect.getPrototypeOf(value) === null) { + return Contextify.instance(value, null, deepTraps, flags); + } else { + return Contextify.object(value, traps, deepTraps, flags, mock); + } + case 'function': + return Contextify.function(value, traps, deepTraps, flags, mock); + + case 'undefined': + return undefined; + + default: // string, number, boolean, symbol + return value; + } + } catch (ex) { + // Never pass the handled exception through! This block can't throw an exception under normal conditions. + return null; + } +}; +Contextify.setGlobal = (name, value) => { + const prop = Contextify.value(name); + try { + global[prop] = Contextify.value(value); + } catch (e) { + throw Decontextify.value(e); + } +}; +Contextify.getGlobal = (name) => { + const prop = Contextify.value(name); + try { + return Decontextify.value(global[prop]); + } catch (e) { + throw Decontextify.value(e); + } +}; +Contextify.readonly = (value, mock) => { + return Contextify.value(value, null, FROZEN_TRAPS, null, mock); +}; +Contextify.protected = (value, mock) => { + return Contextify.value(value, null, null, {protected: true}, mock); +}; +Contextify.connect = (outer, inner) => { + Decontextified.set(outer, inner); + Contextified.set(inner, outer); +}; +Contextify.makeModule = ()=>({exports: {}}); +Contextify.isVMProxy = (obj) => Decontextified.has(obj); + +const BufferMock = host.Object.create(null); +BufferMock.allocUnsafe = function allocUnsafe(size) { + return this.alloc(size); +}; +BufferMock.allocUnsafeSlow = function allocUnsafeSlow(size) { + return this.alloc(size); +}; +const BufferOverride = host.Object.create(null); +BufferOverride.inspect = function inspect(recurseTimes, ctx) { + // Mimic old behavior, could throw but didn't pass a test. + const max = host.INSPECT_MAX_BYTES; + const actualMax = Math.min(max, this.length); + const remaining = this.length - max; + let str = this.hexSlice(0, actualMax).replace(/(.{2})/g, '$1 ').trim(); + if (remaining > 0) str += ` ... ${remaining} more byte${remaining > 1 ? 's' : ''}`; + return `<${this.constructor.name} ${str}>`; +}; +const LocalBuffer = global.Buffer = Contextify.readonly(host.Buffer, BufferMock); +Contextify.connect(host.Buffer.prototype.inspect, BufferOverride.inspect); + + +const exportsMap = host.Object.create(null); +exportsMap.Contextify = Contextify; +exportsMap.Decontextify = Decontextify; +exportsMap.Buffer = LocalBuffer; +exportsMap.sandbox = Decontextify.value(global); +exportsMap.Function = Function; + +return exportsMap; diff --git a/dist/fixasync.js b/dist/fixasync.js new file mode 100644 index 0000000..e0f409d --- /dev/null +++ b/dist/fixasync.js @@ -0,0 +1,80 @@ +'use strict'; + +// eslint-disable-next-line no-invalid-this, no-shadow +const {GeneratorFunction, AsyncFunction, AsyncGeneratorFunction, global, internal, host, hook} = this; +const {Contextify, Decontextify} = internal; +// eslint-disable-next-line no-shadow +const {Function, eval: eval_, Promise, Object, Reflect} = global; +const {getOwnPropertyDescriptor, defineProperty, assign} = Object; +const {apply: rApply, construct: rConstruct} = Reflect; + +const FunctionHandler = { + __proto__: null, + apply(target, thiz, args) { + const type = this.type; + args = Decontextify.arguments(args); + try { + args = Contextify.value(hook(type, args)); + } catch (e) { + throw Contextify.value(e); + } + return rApply(target, thiz, args); + }, + construct(target, args, newTarget) { + const type = this.type; + args = Decontextify.arguments(args); + try { + args = Contextify.value(hook(type, args)); + } catch (e) { + throw Contextify.value(e); + } + return rConstruct(target, args, newTarget); + } +}; + +function makeCheckFunction(type) { + return assign({ + __proto__: null, + type + }, FunctionHandler); +} + +function override(obj, prop, value) { + const desc = getOwnPropertyDescriptor(obj, prop); + desc.value = value; + defineProperty(obj, prop, desc); +} + +const proxiedFunction = new host.Proxy(Function, makeCheckFunction('function')); +override(Function.prototype, 'constructor', proxiedFunction); +if (GeneratorFunction) { + Object.setPrototypeOf(GeneratorFunction, proxiedFunction); + override(GeneratorFunction.prototype, 'constructor', new host.Proxy(GeneratorFunction, makeCheckFunction('generator_function'))); +} +if (AsyncFunction) { + Object.setPrototypeOf(AsyncFunction, proxiedFunction); + override(AsyncFunction.prototype, 'constructor', new host.Proxy(AsyncFunction, makeCheckFunction('async_function'))); +} +if (AsyncGeneratorFunction) { + Object.setPrototypeOf(AsyncGeneratorFunction, proxiedFunction); + override(AsyncGeneratorFunction.prototype, 'constructor', new host.Proxy(AsyncGeneratorFunction, makeCheckFunction('async_generator_function'))); +} + +global.Function = proxiedFunction; +global.eval = new host.Proxy(eval_, makeCheckFunction('eval')); + +if (Promise) { + + Promise.prototype.then = new host.Proxy(Promise.prototype.then, makeCheckFunction('promise_then')); + Contextify.connect(host.Promise.prototype.then, Promise.prototype.then); + + if (Promise.prototype.finally) { + Promise.prototype.finally = new host.Proxy(Promise.prototype.finally, makeCheckFunction('promise_finally')); + Contextify.connect(host.Promise.prototype.finally, Promise.prototype.finally); + } + if (Promise.prototype.catch) { + Promise.prototype.catch = new host.Proxy(Promise.prototype.catch, makeCheckFunction('promise_catch')); + Contextify.connect(host.Promise.prototype.catch, Promise.prototype.catch); + } + +} diff --git a/dist/sandbox.js b/dist/sandbox.js new file mode 100644 index 0000000..fac738d --- /dev/null +++ b/dist/sandbox.js @@ -0,0 +1,680 @@ +/* eslint-disable no-shadow, no-invalid-this */ +/* global vm, host, Contextify, Decontextify, VMError, options */ + +'use strict'; + +const {Script} = host.require('vm'); +const fs = host.require('fs'); +const pa = host.require('path'); + +const BUILTIN_MODULES = host.process.binding('natives'); +const parseJSON = JSON.parse; +const importModuleDynamically = () => { + // We can't throw an error object here because since vm.Script doesn't store a context, we can't properly contextify that error object. + // eslint-disable-next-line no-throw-literal + throw 'Dynamic imports are not allowed.'; +}; + +/** + * @param {Object} host Hosts's internal objects. + */ + +return ((vm, host) => { + 'use strict'; + + const global = this; + + const TIMERS = new host.WeakMap(); // Contains map of timers created inside sandbox + const BUILTINS = {__proto__: null}; + const CACHE = {__proto__: null}; + const EXTENSIONS = { + __proto__: null, + ['.json'](module, filename) { + try { + const code = fs.readFileSync(filename, 'utf8'); + module.exports = parseJSON(code); + } catch (e) { + throw Contextify.value(e); + } + }, + ['.node'](module, filename) { + if (vm.options.require.context === 'sandbox') throw new VMError('Native modules can be required only with context set to \'host\'.'); + + try { + module.exports = Contextify.readonly(host.require(filename)); + } catch (e) { + throw Contextify.value(e); + } + } + }; + + for (let i = 0; i < vm.options.sourceExtensions.length; i++) { + const ext = vm.options.sourceExtensions[i]; + + EXTENSIONS['.' + ext] = (module, filename, dirname) => { + if (vm.options.require.context !== 'sandbox') { + try { + module.exports = Contextify.readonly(host.require(filename)); + } catch (e) { + throw Contextify.value(e); + } + } else { + let script; + + try { + // Load module + let contents = fs.readFileSync(filename, 'utf8'); + contents = vm._compiler(contents, filename); + + const code = `(function (exports, require, module, __filename, __dirname) { 'use strict'; ${contents} \n});`; + + // Precompile script + script = new Script(code, { + __proto__: null, + filename: filename || 'vm.js', + displayErrors: false, + importModuleDynamically + }); + + } catch (ex) { + throw Contextify.value(ex); + } + + const closure = script.runInContext(global, { + __proto__: null, + filename: filename || 'vm.js', + displayErrors: false, + importModuleDynamically + }); + + // run the script + closure(module.exports, module.require, module, filename, dirname); + } + }; + } + + const _parseExternalOptions = (options) => { + if (host.Array.isArray(options)) { + return { + __proto__: null, + external: options, + transitive: false + }; + } + + return { + __proto__: null, + external: options.modules, + transitive: options.transitive + }; + }; + + /** + * Resolve filename. + */ + + const _resolveFilename = (path) => { + if (!path) return null; + let hasPackageJson; + try { + path = pa.resolve(path); + + const exists = fs.existsSync(path); + const isdir = exists ? fs.statSync(path).isDirectory() : false; + + // direct file match + if (exists && !isdir) return path; + + // load as file + + for (let i = 0; i < vm.options.sourceExtensions.length; i++) { + const ext = vm.options.sourceExtensions[i]; + if (fs.existsSync(`${path}.${ext}`)) return `${path}.${ext}`; + } + if (fs.existsSync(`${path}.json`)) return `${path}.json`; + if (fs.existsSync(`${path}.node`)) return `${path}.node`; + + // load as module + + hasPackageJson = fs.existsSync(`${path}/package.json`); + } catch (e) { + throw Contextify.value(e); + } + + if (hasPackageJson) { + let pkg; + try { + pkg = fs.readFileSync(`${path}/package.json`, 'utf8'); + } catch (e) { + throw Contextify.value(e); + } + try { + pkg = parseJSON(pkg); + } catch (ex) { + throw new VMError(`Module '${path}' has invalid package.json`, 'EMODULEINVALID'); + } + + let main; + if (pkg && pkg.main) { + main = _resolveFilename(`${path}/${pkg.main}`); + if (!main) main = _resolveFilename(`${path}/index`); + } else { + main = _resolveFilename(`${path}/index`); + } + + return main; + } + + // load as directory + + try { + for (let i = 0; i < vm.options.sourceExtensions.length; i++) { + const ext = vm.options.sourceExtensions[i]; + if (fs.existsSync(`${path}/index.${ext}`)) return `${path}/index.${ext}`; + } + + if (fs.existsSync(`${path}/index.json`)) return `${path}/index.json`; + if (fs.existsSync(`${path}/index.node`)) return `${path}/index.node`; + } catch (e) { + throw Contextify.value(e); + } + + return null; + }; + + /** + * Builtin require. + */ + + const _requireBuiltin = (moduleName) => { + if (moduleName === 'buffer') return ({Buffer}); + if (BUILTINS[moduleName]) return BUILTINS[moduleName].exports; // Only compiled builtins are stored here + + if (moduleName === 'util') { + return Contextify.readonly(host.require(moduleName), { + // Allows VM context to use util.inherits + __proto__: null, + inherits: (ctor, superCtor) => { + ctor.super_ = superCtor; + Object.setPrototypeOf(ctor.prototype, superCtor.prototype); + } + }); + } + + if (moduleName === 'events' || moduleName === 'internal/errors') { + let script; + try { + script = new Script(`(function (exports, require, module, process, internalBinding) { + 'use strict'; + const primordials = global; + ${BUILTIN_MODULES[moduleName]} + \n + });`, { + filename: `${moduleName}.vm.js` + }); + + } catch (e) { + throw Contextify.value(e); + } + + // setup module scope + const module = BUILTINS[moduleName] = { + exports: {}, + require: _requireBuiltin + }; + + // run script + try { + // FIXME binding should be contextified + script.runInContext(global)(module.exports, module.require, module, host.process, host.process.binding); + } catch (e) { + // e could be from inside or outside of sandbox + throw new VMError(`Error loading '${moduleName}'`); + } + return module.exports; + } + + return Contextify.readonly(host.require(moduleName)); + }; + + /** + * Prepare require. + */ + + const _prepareRequire = (currentDirname, parentAllowsTransitive = false) => { + const _require = moduleName => { + let requireObj; + try { + const optionsObj = vm.options; + if (optionsObj.nesting && moduleName === 'vm2') return {VM: Contextify.readonly(host.VM), NodeVM: Contextify.readonly(host.NodeVM)}; + requireObj = optionsObj.require; + } catch (e) { + throw Contextify.value(e); + } + + if (!requireObj) throw new VMError(`Access denied to require '${moduleName}'`, 'EDENIED'); + if (moduleName == null) throw new VMError("Module '' not found.", 'ENOTFOUND'); + if (typeof moduleName !== 'string') throw new VMError(`Invalid module name '${moduleName}'`, 'EINVALIDNAME'); + + let filename; + let allowRequireTransitive = false; + + // Mock? + + try { + const {mock} = requireObj; + if (mock) { + const mockModule = mock[moduleName]; + if (mockModule) { + return Contextify.readonly(mockModule); + } + } + } catch (e) { + throw Contextify.value(e); + } + + // Builtin? + + if (BUILTIN_MODULES[moduleName]) { + let allowed; + try { + const builtinObj = requireObj.builtin; + if (host.Array.isArray(builtinObj)) { + if (builtinObj.indexOf('*') >= 0) { + allowed = builtinObj.indexOf(`-${moduleName}`) === -1; + } else { + allowed = builtinObj.indexOf(moduleName) >= 0; + } + } else if (builtinObj) { + allowed = builtinObj[moduleName]; + } else { + allowed = false; + } + } catch (e) { + throw Contextify.value(e); + } + if (!allowed) throw new VMError(`Access denied to require '${moduleName}'`, 'EDENIED'); + + return _requireBuiltin(moduleName); + } + + // External? + + let externalObj; + try { + externalObj = requireObj.external; + } catch (e) { + throw Contextify.value(e); + } + + if (!externalObj) throw new VMError(`Access denied to require '${moduleName}'`, 'EDENIED'); + + if (/^(\.|\.\/|\.\.\/)/.exec(moduleName)) { + // Module is relative file, e.g. ./script.js or ../script.js + + if (!currentDirname) throw new VMError('You must specify script path to load relative modules.', 'ENOPATH'); + + filename = _resolveFilename(`${currentDirname}/${moduleName}`); + } else if (/^(\/|\\|[a-zA-Z]:\\)/.exec(moduleName)) { + // Module is absolute file, e.g. /script.js or //server/script.js or C:\script.js + + filename = _resolveFilename(moduleName); + } else { + // Check node_modules in path + + if (!currentDirname) throw new VMError('You must specify script path to load relative modules.', 'ENOPATH'); + + if (typeof externalObj === 'object') { + let isWhitelisted; + try { + const { external, transitive } = _parseExternalOptions(externalObj); + + isWhitelisted = external.some(ext => host.helpers.match(ext, moduleName)) || (transitive && parentAllowsTransitive); + } catch (e) { + throw Contextify.value(e); + } + if (!isWhitelisted) { + throw new VMError(`The module '${moduleName}' is not whitelisted in VM.`, 'EDENIED'); + } + + allowRequireTransitive = true; + } + + // FIXME the paths array has side effects + const paths = currentDirname.split(pa.sep); + + while (paths.length) { + const path = paths.join(pa.sep); + + // console.log moduleName, "#{path}#{pa.sep}node_modules#{pa.sep}#{moduleName}" + + filename = _resolveFilename(`${path}${pa.sep}node_modules${pa.sep}${moduleName}`); + if (filename) break; + + paths.pop(); + } + } + + if (!filename) { + let resolveFunc; + try { + resolveFunc = requireObj.resolve; + } catch (e) { + throw Contextify.value(e); + } + if (resolveFunc) { + let resolved; + try { + resolved = requireObj.resolve(moduleName, currentDirname); + } catch (e) { + throw Contextify.value(e); + } + filename = _resolveFilename(resolved); + } + } + if (!filename) throw new VMError(`Cannot find module '${moduleName}'`, 'ENOTFOUND'); + + // return cache whenever possible + if (CACHE[filename]) return CACHE[filename].exports; + + const dirname = pa.dirname(filename); + const extname = pa.extname(filename); + + let allowedModule = true; + try { + const rootObj = requireObj.root; + if (rootObj) { + const rootPaths = host.Array.isArray(rootObj) ? rootObj : host.Array.of(rootObj); + allowedModule = rootPaths.some(path => host.String.prototype.startsWith.call(dirname, pa.resolve(path))); + } + } catch (e) { + throw Contextify.value(e); + } + + if (!allowedModule) { + throw new VMError(`Module '${moduleName}' is not allowed to be required. The path is outside the border!`, 'EDENIED'); + } + + const module = CACHE[filename] = { + filename, + exports: {}, + require: _prepareRequire(dirname, allowRequireTransitive) + }; + + // lookup extensions + if (EXTENSIONS[extname]) { + EXTENSIONS[extname](module, filename, dirname); + return module.exports; + } + + throw new VMError(`Failed to load '${moduleName}': Unknown type.`, 'ELOADFAIL'); + }; + + return _require; + }; + + /** + * Prepare sandbox. + */ + + // This is a function and not an arrow function, since the original is also a function + global.setTimeout = function setTimeout(callback, delay, ...args) { + if (typeof callback !== 'function') throw new TypeError('"callback" argument must be a function'); + let tmr; + try { + tmr = host.setTimeout(Decontextify.value(() => { + // FIXME ...args has side effects + callback(...args); + }), Decontextify.value(delay)); + } catch (e) { + throw Contextify.value(e); + } + const local = Contextify.value(tmr); + + TIMERS.set(local, tmr); + return local; + }; + + global.setInterval = function setInterval(callback, interval, ...args) { + if (typeof callback !== 'function') throw new TypeError('"callback" argument must be a function'); + let tmr; + try { + tmr = host.setInterval(Decontextify.value(() => { + // FIXME ...args has side effects + callback(...args); + }), Decontextify.value(interval)); + } catch (e) { + throw Contextify.value(e); + } + + const local = Contextify.value(tmr); + + TIMERS.set(local, tmr); + return local; + }; + + global.setImmediate = function setImmediate(callback, ...args) { + if (typeof callback !== 'function') throw new TypeError('"callback" argument must be a function'); + let tmr; + try { + tmr = host.setImmediate(Decontextify.value(() => { + // FIXME ...args has side effects + callback(...args); + })); + } catch (e) { + throw Contextify.value(e); + } + + const local = Contextify.value(tmr); + + TIMERS.set(local, tmr); + return local; + }; + + global.clearTimeout = function clearTimeout(local) { + try { + host.clearTimeout(TIMERS.get(local)); + } catch (e) { + throw Contextify.value(e); + } + }; + + global.clearInterval = function clearInterval(local) { + try { + host.clearInterval(TIMERS.get(local)); + } catch (e) { + throw Contextify.value(e); + } + }; + + global.clearImmediate = function clearImmediate(local) { + try { + host.clearImmediate(TIMERS.get(local)); + } catch (e) { + throw Contextify.value(e); + } + }; + + function addListener(name, handler) { + if (name !== 'beforeExit' && name !== 'exit') { + throw new Error(`Access denied to listen for '${name}' event.`); + } + + try { + host.process.on(name, Decontextify.value(handler)); + } catch (e) { + throw Contextify.value(e); + } + + return this; + } + + const {argv: optionArgv, env: optionsEnv} = options; + + // FIXME wrong class structure + global.process = { + argv: optionArgv !== undefined ? Contextify.value(optionArgv) : [], + title: host.process.title, + version: host.process.version, + versions: Contextify.readonly(host.process.versions), + arch: host.process.arch, + platform: host.process.platform, + env: optionsEnv !== undefined ? Contextify.value(optionsEnv) : {}, + pid: host.process.pid, + features: Contextify.readonly(host.process.features), + nextTick: function nextTick(callback, ...args) { + if (typeof callback !== 'function') { + throw new Error('Callback must be a function.'); + } + + try { + host.process.nextTick(Decontextify.value(() => { + // FIXME ...args has side effects + callback(...args); + })); + } catch (e) { + throw Contextify.value(e); + } + }, + hrtime: function hrtime(time) { + try { + return Contextify.value(host.process.hrtime(Decontextify.value(time))); + } catch (e) { + throw Contextify.value(e); + } + }, + cwd: function cwd() { + try { + return Contextify.value(host.process.cwd()); + } catch (e) { + throw Contextify.value(e); + } + }, + addListener, + on: addListener, + + once: function once(name, handler) { + if (name !== 'beforeExit' && name !== 'exit') { + throw new Error(`Access denied to listen for '${name}' event.`); + } + + try { + host.process.once(name, Decontextify.value(handler)); + } catch (e) { + throw Contextify.value(e); + } + + return this; + }, + + listeners: function listeners(name) { + if (name !== 'beforeExit' && name !== 'exit') { + // Maybe add ({__proto__:null})[name] to throw when name fails in https://tc39.es/ecma262/#sec-topropertykey. + return []; + } + + // Filter out listeners, which were not created in this sandbox + try { + return Contextify.value(host.process.listeners(name).filter(listener => Contextify.isVMProxy(listener))); + } catch (e) { + throw Contextify.value(e); + } + }, + + removeListener: function removeListener(name, handler) { + if (name !== 'beforeExit' && name !== 'exit') { + return this; + } + + try { + host.process.removeListener(name, Decontextify.value(handler)); + } catch (e) { + throw Contextify.value(e); + } + + return this; + }, + + umask: function umask() { + if (arguments.length) { + throw new Error('Access denied to set umask.'); + } + + try { + return Contextify.value(host.process.umask()); + } catch (e) { + throw Contextify.value(e); + } + } + }; + + if (vm.options.console === 'inherit') { + global.console = Contextify.readonly(host.console); + } else if (vm.options.console === 'redirect') { + global.console = { + debug(...args) { + try { + // FIXME ...args has side effects + vm.emit('console.debug', ...Decontextify.arguments(args)); + } catch (e) { + throw Contextify.value(e); + } + }, + log(...args) { + try { + // FIXME ...args has side effects + vm.emit('console.log', ...Decontextify.arguments(args)); + } catch (e) { + throw Contextify.value(e); + } + }, + info(...args) { + try { + // FIXME ...args has side effects + vm.emit('console.info', ...Decontextify.arguments(args)); + } catch (e) { + throw Contextify.value(e); + } + }, + warn(...args) { + try { + // FIXME ...args has side effects + vm.emit('console.warn', ...Decontextify.arguments(args)); + } catch (e) { + throw Contextify.value(e); + } + }, + error(...args) { + try { + // FIXME ...args has side effects + vm.emit('console.error', ...Decontextify.arguments(args)); + } catch (e) { + throw Contextify.value(e); + } + }, + dir(...args) { + try { + vm.emit('console.dir', ...Decontextify.arguments(args)); + } catch (e) { + throw Contextify.value(e); + } + }, + time() {}, + timeEnd() {}, + trace(...args) { + try { + // FIXME ...args has side effects + vm.emit('console.trace', ...Decontextify.arguments(args)); + } catch (e) { + throw Contextify.value(e); + } + } + }; + } + + /* + Return contextified require. + */ + + return _prepareRequire; +})(vm, host);