You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					470 lines
				
				11 KiB
			
		
		
			
		
	
	
					470 lines
				
				11 KiB
			| 
											3 years ago
										 | /* global host, data, VMError */ | ||
|  | 
 | ||
|  | 'use strict'; | ||
|  | 
 | ||
|  | const LocalError = Error; | ||
|  | const LocalTypeError = TypeError; | ||
|  | const LocalWeakMap = WeakMap; | ||
|  | 
 | ||
|  | const { | ||
|  | 	apply: localReflectApply, | ||
|  | 	defineProperty: localReflectDefineProperty | ||
|  | } = Reflect; | ||
|  | 
 | ||
|  | const { | ||
|  | 	set: localWeakMapSet, | ||
|  | 	get: localWeakMapGet | ||
|  | } = LocalWeakMap.prototype; | ||
|  | 
 | ||
|  | const { | ||
|  | 	isArray: localArrayIsArray | ||
|  | } = Array; | ||
|  | 
 | ||
|  | function uncurryThis(func) { | ||
|  | 	return (thiz, ...args) => localReflectApply(func, thiz, args); | ||
|  | } | ||
|  | 
 | ||
|  | const localArrayPrototypeSlice = uncurryThis(Array.prototype.slice); | ||
|  | const localArrayPrototypeIncludes = uncurryThis(Array.prototype.includes); | ||
|  | const localArrayPrototypePush = uncurryThis(Array.prototype.push); | ||
|  | const localArrayPrototypeIndexOf = uncurryThis(Array.prototype.indexOf); | ||
|  | const localArrayPrototypeSplice = uncurryThis(Array.prototype.splice); | ||
|  | const localStringPrototypeStartsWith = uncurryThis(String.prototype.startsWith); | ||
|  | const localStringPrototypeSlice = uncurryThis(String.prototype.slice); | ||
|  | const localStringPrototypeIndexOf = uncurryThis(String.prototype.indexOf); | ||
|  | 
 | ||
|  | const { | ||
|  | 	argv: optionArgv, | ||
|  | 	env: optionEnv, | ||
|  | 	console: optionConsole, | ||
|  | 	vm, | ||
|  | 	resolver, | ||
|  | 	extensions | ||
|  | } = data; | ||
|  | 
 | ||
|  | function ensureSandboxArray(a) { | ||
|  | 	return localArrayPrototypeSlice(a); | ||
|  | } | ||
|  | 
 | ||
|  | const globalPaths = ensureSandboxArray(resolver.globalPaths); | ||
|  | 
 | ||
|  | class Module { | ||
|  | 
 | ||
|  | 	constructor(id, path, parent) { | ||
|  | 		this.id = id; | ||
|  | 		this.filename = id; | ||
|  | 		this.path = path; | ||
|  | 		this.parent = parent; | ||
|  | 		this.loaded = false; | ||
|  | 		this.paths = path ? ensureSandboxArray(resolver.genLookupPaths(path)) : []; | ||
|  | 		this.children = []; | ||
|  | 		this.exports = {}; | ||
|  | 	} | ||
|  | 
 | ||
|  | 	_updateChildren(child, isNew) { | ||
|  | 		const children = this.children; | ||
|  | 		if (children && (isNew || !localArrayPrototypeIncludes(children, child))) { | ||
|  | 			localArrayPrototypePush(children, child); | ||
|  | 		} | ||
|  | 	} | ||
|  | 
 | ||
|  | 	require(id) { | ||
|  | 		return requireImpl(this, id, false); | ||
|  | 	} | ||
|  | 
 | ||
|  | } | ||
|  | 
 | ||
|  | const originalRequire = Module.prototype.require; | ||
|  | const cacheBuiltins = {__proto__: null}; | ||
|  | 
 | ||
|  | function requireImpl(mod, id, direct) { | ||
|  | 	if (direct && mod.require !== originalRequire) { | ||
|  | 		return mod.require(id); | ||
|  | 	} | ||
|  | 	const filename = resolver.resolve(mod, id, undefined, Module._extensions, direct); | ||
|  | 	if (localStringPrototypeStartsWith(filename, 'node:')) { | ||
|  | 		id = localStringPrototypeSlice(filename, 5); | ||
|  | 		let nmod = cacheBuiltins[id]; | ||
|  | 		if (!nmod) { | ||
|  | 			nmod = resolver.loadBuiltinModule(vm, id); | ||
|  | 			if (!nmod) throw new VMError(`Cannot find module '${filename}'`, 'ENOTFOUND'); | ||
|  | 			cacheBuiltins[id] = nmod; | ||
|  | 		} | ||
|  | 		return nmod; | ||
|  | 	} | ||
|  | 
 | ||
|  | 	const cachedModule = Module._cache[filename]; | ||
|  | 	if (cachedModule !== undefined) { | ||
|  | 		mod._updateChildren(cachedModule, false); | ||
|  | 		return cachedModule.exports; | ||
|  | 	} | ||
|  | 
 | ||
|  | 	let nmod = cacheBuiltins[id]; | ||
|  | 	if (nmod) return nmod; | ||
|  | 	nmod = resolver.loadBuiltinModule(vm, id); | ||
|  | 	if (nmod) { | ||
|  | 		cacheBuiltins[id] = nmod; | ||
|  | 		return nmod; | ||
|  | 	} | ||
|  | 
 | ||
|  | 	const path = resolver.fs.dirname(filename); | ||
|  | 	const module = new Module(filename, path, mod); | ||
|  | 	resolver.registerModule(module, filename, path, mod, direct); | ||
|  | 	mod._updateChildren(module, true); | ||
|  | 	try { | ||
|  | 		Module._cache[filename] = module; | ||
|  | 		const handler = findBestExtensionHandler(filename); | ||
|  | 		handler(module, filename); | ||
|  | 		module.loaded = true; | ||
|  | 	} catch (e) { | ||
|  | 		delete Module._cache[filename]; | ||
|  | 		const children = mod.children; | ||
|  | 		if (localArrayIsArray(children)) { | ||
|  | 			const index = localArrayPrototypeIndexOf(children, module); | ||
|  | 			if (index !== -1) { | ||
|  | 				localArrayPrototypeSplice(children, index, 1); | ||
|  | 			} | ||
|  | 		} | ||
|  | 		throw e; | ||
|  | 	} | ||
|  | 
 | ||
|  | 	return module.exports; | ||
|  | } | ||
|  | 
 | ||
|  | Module.builtinModules = ensureSandboxArray(resolver.getBuiltinModulesList()); | ||
|  | Module.globalPaths = globalPaths; | ||
|  | Module._extensions = {__proto__: null}; | ||
|  | Module._cache = {__proto__: null}; | ||
|  | 
 | ||
|  | { | ||
|  | 	const keys = Object.getOwnPropertyNames(extensions); | ||
|  | 	for (let i = 0; i < keys.length; i++) { | ||
|  | 		const key = keys[i]; | ||
|  | 		const handler = extensions[key]; | ||
|  | 		Module._extensions[key] = (mod, filename) => handler(mod, filename); | ||
|  | 	} | ||
|  | } | ||
|  | 
 | ||
|  | function findBestExtensionHandler(filename) { | ||
|  | 	const name = resolver.fs.basename(filename); | ||
|  | 	for (let i = 0; (i = localStringPrototypeIndexOf(name, '.', i + 1)) !== -1;) { | ||
|  | 		const ext = localStringPrototypeSlice(name, i); | ||
|  | 		const handler = Module._extensions[ext]; | ||
|  | 		if (handler) return handler; | ||
|  | 	} | ||
|  | 	const js = Module._extensions['.js']; | ||
|  | 	if (js) return js; | ||
|  | 	const keys = Object.getOwnPropertyNames(Module._extensions); | ||
|  | 	if (keys.length === 0) throw new VMError(`Failed to load '${filename}': Unknown type.`, 'ELOADFAIL'); | ||
|  | 	return Module._extensions[keys[0]]; | ||
|  | } | ||
|  | 
 | ||
|  | function createRequireForModule(mod) { | ||
|  | 	// eslint-disable-next-line no-shadow
 | ||
|  | 	function require(id) { | ||
|  | 		return requireImpl(mod, id, true); | ||
|  | 	} | ||
|  | 	function resolve(id, options) { | ||
|  | 		return resolver.resolve(mod, id, options, Module._extensions, true); | ||
|  | 	} | ||
|  | 	require.resolve = resolve; | ||
|  | 	function paths(id) { | ||
|  | 		return ensureSandboxArray(resolver.lookupPaths(mod, id)); | ||
|  | 	} | ||
|  | 	resolve.paths = paths; | ||
|  | 
 | ||
|  | 	require.extensions = Module._extensions; | ||
|  | 
 | ||
|  | 	require.cache = Module._cache; | ||
|  | 
 | ||
|  | 	return require; | ||
|  | } | ||
|  | 
 | ||
|  | /** | ||
|  |  * Prepare sandbox. | ||
|  |  */ | ||
|  | 
 | ||
|  | const TIMERS = new LocalWeakMap(); | ||
|  | 
 | ||
|  | class Timeout { | ||
|  | } | ||
|  | 
 | ||
|  | class Interval { | ||
|  | } | ||
|  | 
 | ||
|  | class Immediate { | ||
|  | } | ||
|  | 
 | ||
|  | function clearTimer(timer) { | ||
|  | 	const obj = localReflectApply(localWeakMapGet, TIMERS, [timer]); | ||
|  | 	if (obj) { | ||
|  | 		obj.clear(obj.value); | ||
|  | 	} | ||
|  | } | ||
|  | 
 | ||
|  | // This is a function and not an arrow function, since the original is also a function
 | ||
|  | // eslint-disable-next-line no-shadow
 | ||
|  | global.setTimeout = function setTimeout(callback, delay, ...args) { | ||
|  | 	if (typeof callback !== 'function') throw new LocalTypeError('"callback" argument must be a function'); | ||
|  | 	const obj = new Timeout(callback, args); | ||
|  | 	const cb = () => { | ||
|  | 		localReflectApply(callback, null, args); | ||
|  | 	}; | ||
|  | 	const tmr = host.setTimeout(cb, delay); | ||
|  | 
 | ||
|  | 	const ref = { | ||
|  | 		__proto__: null, | ||
|  | 		clear: host.clearTimeout, | ||
|  | 		value: tmr | ||
|  | 	}; | ||
|  | 
 | ||
|  | 	localReflectApply(localWeakMapSet, TIMERS, [obj, ref]); | ||
|  | 	return obj; | ||
|  | }; | ||
|  | 
 | ||
|  | // eslint-disable-next-line no-shadow
 | ||
|  | global.setInterval = function setInterval(callback, interval, ...args) { | ||
|  | 	if (typeof callback !== 'function') throw new LocalTypeError('"callback" argument must be a function'); | ||
|  | 	const obj = new Interval(); | ||
|  | 	const cb = () => { | ||
|  | 		localReflectApply(callback, null, args); | ||
|  | 	}; | ||
|  | 	const tmr = host.setInterval(cb, interval); | ||
|  | 
 | ||
|  | 	const ref = { | ||
|  | 		__proto__: null, | ||
|  | 		clear: host.clearInterval, | ||
|  | 		value: tmr | ||
|  | 	}; | ||
|  | 
 | ||
|  | 	localReflectApply(localWeakMapSet, TIMERS, [obj, ref]); | ||
|  | 	return obj; | ||
|  | }; | ||
|  | 
 | ||
|  | // eslint-disable-next-line no-shadow
 | ||
|  | global.setImmediate = function setImmediate(callback, ...args) { | ||
|  | 	if (typeof callback !== 'function') throw new LocalTypeError('"callback" argument must be a function'); | ||
|  | 	const obj = new Immediate(); | ||
|  | 	const cb = () => { | ||
|  | 		localReflectApply(callback, null, args); | ||
|  | 	}; | ||
|  | 	const tmr = host.setImmediate(cb); | ||
|  | 
 | ||
|  | 	const ref = { | ||
|  | 		__proto__: null, | ||
|  | 		clear: host.clearImmediate, | ||
|  | 		value: tmr | ||
|  | 	}; | ||
|  | 
 | ||
|  | 	localReflectApply(localWeakMapSet, TIMERS, [obj, ref]); | ||
|  | 	return obj; | ||
|  | }; | ||
|  | 
 | ||
|  | // eslint-disable-next-line no-shadow
 | ||
|  | global.clearTimeout = function clearTimeout(timeout) { | ||
|  | 	clearTimer(timeout); | ||
|  | }; | ||
|  | 
 | ||
|  | // eslint-disable-next-line no-shadow
 | ||
|  | global.clearInterval = function clearInterval(interval) { | ||
|  | 	clearTimer(interval); | ||
|  | }; | ||
|  | 
 | ||
|  | // eslint-disable-next-line no-shadow
 | ||
|  | global.clearImmediate = function clearImmediate(immediate) { | ||
|  | 	clearTimer(immediate); | ||
|  | }; | ||
|  | 
 | ||
|  | const localProcess = host.process; | ||
|  | 
 | ||
|  | function vmEmitArgs(event, args) { | ||
|  | 	const allargs = [event]; | ||
|  | 	for (let i = 0; i < args.length; i++) { | ||
|  | 		if (!localReflectDefineProperty(allargs, i + 1, { | ||
|  | 			__proto__: null, | ||
|  | 			value: args[i], | ||
|  | 			writable: true, | ||
|  | 			enumerable: true, | ||
|  | 			configurable: true | ||
|  | 		})) throw new LocalError('Unexpected'); | ||
|  | 	} | ||
|  | 	return localReflectApply(vm.emit, vm, allargs); | ||
|  | } | ||
|  | 
 | ||
|  | const LISTENERS = new LocalWeakMap(); | ||
|  | const LISTENER_HANDLER = new LocalWeakMap(); | ||
|  | 
 | ||
|  | /** | ||
|  |  * | ||
|  |  * @param {*} name | ||
|  |  * @param {*} handler | ||
|  |  * @this process | ||
|  |  * @return {this} | ||
|  |  */ | ||
|  | function addListener(name, handler) { | ||
|  | 	if (name !== 'beforeExit' && name !== 'exit') { | ||
|  | 		throw new LocalError(`Access denied to listen for '${name}' event.`); | ||
|  | 	} | ||
|  | 
 | ||
|  | 	let cb = localReflectApply(localWeakMapGet, LISTENERS, [handler]); | ||
|  | 	if (!cb) { | ||
|  | 		cb = () => { | ||
|  | 			handler(); | ||
|  | 		}; | ||
|  | 		localReflectApply(localWeakMapSet, LISTENER_HANDLER, [cb, handler]); | ||
|  | 		localReflectApply(localWeakMapSet, LISTENERS, [handler, cb]); | ||
|  | 	} | ||
|  | 
 | ||
|  | 	localProcess.on(name, cb); | ||
|  | 
 | ||
|  | 	return this; | ||
|  | } | ||
|  | 
 | ||
|  | /** | ||
|  |  * | ||
|  |  * @this process | ||
|  |  * @return {this} | ||
|  |  */ | ||
|  | // eslint-disable-next-line no-shadow
 | ||
|  | function process() { | ||
|  | 	return this; | ||
|  | } | ||
|  | 
 | ||
|  | const baseUptime = localProcess.uptime(); | ||
|  | 
 | ||
|  | // FIXME wrong class structure
 | ||
|  | global.process = { | ||
|  | 	__proto__: process.prototype, | ||
|  | 	argv: optionArgv !== undefined ? optionArgv : [], | ||
|  | 	title: localProcess.title, | ||
|  | 	version: localProcess.version, | ||
|  | 	versions: localProcess.versions, | ||
|  | 	arch: localProcess.arch, | ||
|  | 	platform: localProcess.platform, | ||
|  | 	env: optionEnv !== undefined ? optionEnv : {}, | ||
|  | 	pid: localProcess.pid, | ||
|  | 	features: localProcess.features, | ||
|  | 	nextTick: function nextTick(callback, ...args) { | ||
|  | 		if (typeof callback !== 'function') { | ||
|  | 			throw new LocalError('Callback must be a function.'); | ||
|  | 		} | ||
|  | 
 | ||
|  | 		localProcess.nextTick(()=>{ | ||
|  | 			localReflectApply(callback, null, args); | ||
|  | 		}); | ||
|  | 	}, | ||
|  | 	hrtime: function hrtime(time) { | ||
|  | 		return localProcess.hrtime(time); | ||
|  | 	}, | ||
|  | 	uptime: function uptime() { | ||
|  | 		return localProcess.uptime() - baseUptime; | ||
|  | 	}, | ||
|  | 	cwd: function cwd() { | ||
|  | 		return localProcess.cwd(); | ||
|  | 	}, | ||
|  | 	addListener, | ||
|  | 	on: addListener, | ||
|  | 
 | ||
|  | 	once: function once(name, handler) { | ||
|  | 		if (name !== 'beforeExit' && name !== 'exit') { | ||
|  | 			throw new LocalError(`Access denied to listen for '${name}' event.`); | ||
|  | 		} | ||
|  | 
 | ||
|  | 		let triggered = false; | ||
|  | 		const cb = () => { | ||
|  | 			if (triggered) return; | ||
|  | 			triggered = true; | ||
|  | 			localProcess.removeListener(name, cb); | ||
|  | 			handler(); | ||
|  | 		}; | ||
|  | 		localReflectApply(localWeakMapSet, LISTENER_HANDLER, [cb, handler]); | ||
|  | 
 | ||
|  | 		localProcess.on(name, cb); | ||
|  | 
 | ||
|  | 		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
 | ||
|  | 		const all = localProcess.listeners(name); | ||
|  | 		const filtered = []; | ||
|  | 		let j = 0; | ||
|  | 		for (let i = 0; i < all.length; i++) { | ||
|  | 			const h = localReflectApply(localWeakMapGet, LISTENER_HANDLER, [all[i]]); | ||
|  | 			if (h) { | ||
|  | 				if (!localReflectDefineProperty(filtered, j, { | ||
|  | 					__proto__: null, | ||
|  | 					value: h, | ||
|  | 					writable: true, | ||
|  | 					enumerable: true, | ||
|  | 					configurable: true | ||
|  | 				})) throw new LocalError('Unexpected'); | ||
|  | 				j++; | ||
|  | 			} | ||
|  | 		} | ||
|  | 		return filtered; | ||
|  | 	}, | ||
|  | 
 | ||
|  | 	removeListener: function removeListener(name, handler) { | ||
|  | 		if (name !== 'beforeExit' && name !== 'exit') { | ||
|  | 			return this; | ||
|  | 		} | ||
|  | 
 | ||
|  | 		const cb = localReflectApply(localWeakMapGet, LISTENERS, [handler]); | ||
|  | 		if (cb) localProcess.removeListener(name, cb); | ||
|  | 
 | ||
|  | 		return this; | ||
|  | 	}, | ||
|  | 
 | ||
|  | 	umask: function umask() { | ||
|  | 		if (arguments.length) { | ||
|  | 			throw new LocalError('Access denied to set umask.'); | ||
|  | 		} | ||
|  | 
 | ||
|  | 		return localProcess.umask(); | ||
|  | 	} | ||
|  | }; | ||
|  | 
 | ||
|  | if (optionConsole === 'inherit') { | ||
|  | 	global.console = host.console; | ||
|  | } else if (optionConsole === 'redirect') { | ||
|  | 	global.console = { | ||
|  | 		debug(...args) { | ||
|  | 			vmEmitArgs('console.debug', args); | ||
|  | 		}, | ||
|  | 		log(...args) { | ||
|  | 			vmEmitArgs('console.log', args); | ||
|  | 		}, | ||
|  | 		info(...args) { | ||
|  | 			vmEmitArgs('console.info', args); | ||
|  | 		}, | ||
|  | 		warn(...args) { | ||
|  | 			vmEmitArgs('console.warn', args); | ||
|  | 		}, | ||
|  | 		error(...args) { | ||
|  | 			vmEmitArgs('console.error', args); | ||
|  | 		}, | ||
|  | 		dir(...args) { | ||
|  | 			vmEmitArgs('console.dir', args); | ||
|  | 		}, | ||
|  | 		time() {}, | ||
|  | 		timeEnd() {}, | ||
|  | 		trace(...args) { | ||
|  | 			vmEmitArgs('console.trace', args); | ||
|  | 		} | ||
|  | 	}; | ||
|  | } | ||
|  | 
 | ||
|  | return { | ||
|  | 	__proto__: null, | ||
|  | 	Module, | ||
|  | 	jsonParse: JSON.parse, | ||
|  | 	createRequireForModule, | ||
|  | 	requireImpl | ||
|  | }; |