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.
		
		
		
		
		
			
		
			
				
					458 lines
				
				12 KiB
			
		
		
			
		
	
	
					458 lines
				
				12 KiB
			| 
											3 years ago
										 | /* global host, bridge, data, context */ | ||
|  | 
 | ||
|  | 'use strict'; | ||
|  | 
 | ||
|  | const { | ||
|  | 	Object: localObject, | ||
|  | 	Array: localArray, | ||
|  | 	Error: LocalError, | ||
|  | 	Reflect: localReflect, | ||
|  | 	Proxy: LocalProxy, | ||
|  | 	WeakMap: LocalWeakMap, | ||
|  | 	Function: localFunction, | ||
|  | 	Promise: localPromise, | ||
|  | 	eval: localEval | ||
|  | } = global; | ||
|  | 
 | ||
|  | const { | ||
|  | 	freeze: localObjectFreeze | ||
|  | } = localObject; | ||
|  | 
 | ||
|  | const { | ||
|  | 	getPrototypeOf: localReflectGetPrototypeOf, | ||
|  | 	apply: localReflectApply, | ||
|  | 	deleteProperty: localReflectDeleteProperty, | ||
|  | 	has: localReflectHas, | ||
|  | 	defineProperty: localReflectDefineProperty, | ||
|  | 	setPrototypeOf: localReflectSetPrototypeOf, | ||
|  | 	getOwnPropertyDescriptor: localReflectGetOwnPropertyDescriptor | ||
|  | } = localReflect; | ||
|  | 
 | ||
|  | const { | ||
|  | 	isArray: localArrayIsArray | ||
|  | } = localArray; | ||
|  | 
 | ||
|  | const { | ||
|  | 	ensureThis, | ||
|  | 	ReadOnlyHandler, | ||
|  | 	from, | ||
|  | 	fromWithFactory, | ||
|  | 	readonlyFactory, | ||
|  | 	connect, | ||
|  | 	addProtoMapping, | ||
|  | 	VMError, | ||
|  | 	ReadOnlyMockHandler | ||
|  | } = bridge; | ||
|  | 
 | ||
|  | const { | ||
|  | 	allowAsync, | ||
|  | 	GeneratorFunction, | ||
|  | 	AsyncFunction, | ||
|  | 	AsyncGeneratorFunction | ||
|  | } = data; | ||
|  | 
 | ||
|  | const { | ||
|  | 	get: localWeakMapGet, | ||
|  | 	set: localWeakMapSet | ||
|  | } = LocalWeakMap.prototype; | ||
|  | 
 | ||
|  | function localUnexpected() { | ||
|  | 	return new VMError('Should not happen'); | ||
|  | } | ||
|  | 
 | ||
|  | // global is originally prototype of host.Object so it can be used to climb up from the sandbox.
 | ||
|  | if (!localReflectSetPrototypeOf(context, localObject.prototype)) throw localUnexpected(); | ||
|  | 
 | ||
|  | Object.defineProperties(global, { | ||
|  | 	global: {value: global, writable: true, configurable: true, enumerable: true}, | ||
|  | 	globalThis: {value: global, writable: true, configurable: true}, | ||
|  | 	GLOBAL: {value: global, writable: true, configurable: true}, | ||
|  | 	root: {value: global, writable: true, configurable: true}, | ||
|  | 	Error: {value: LocalError} | ||
|  | }); | ||
|  | 
 | ||
|  | if (!localReflectDefineProperty(global, 'VMError', { | ||
|  | 	__proto__: null, | ||
|  | 	value: VMError, | ||
|  | 	writable: true, | ||
|  | 	enumerable: false, | ||
|  | 	configurable: true | ||
|  | })) throw localUnexpected(); | ||
|  | 
 | ||
|  | // Fixes buffer unsafe allocation
 | ||
|  | /* eslint-disable no-use-before-define */ | ||
|  | class BufferHandler extends ReadOnlyHandler { | ||
|  | 
 | ||
|  | 	apply(target, thiz, args) { | ||
|  | 		if (args.length > 0 && typeof args[0] === 'number') { | ||
|  | 			return LocalBuffer.alloc(args[0]); | ||
|  | 		} | ||
|  | 		return localReflectApply(LocalBuffer.from, LocalBuffer, args); | ||
|  | 	} | ||
|  | 
 | ||
|  | 	construct(target, args, newTarget) { | ||
|  | 		if (args.length > 0 && typeof args[0] === 'number') { | ||
|  | 			return LocalBuffer.alloc(args[0]); | ||
|  | 		} | ||
|  | 		return localReflectApply(LocalBuffer.from, LocalBuffer, args); | ||
|  | 	} | ||
|  | 
 | ||
|  | } | ||
|  | /* eslint-enable no-use-before-define */ | ||
|  | 
 | ||
|  | const LocalBuffer = fromWithFactory(obj => new BufferHandler(obj), host.Buffer); | ||
|  | 
 | ||
|  | 
 | ||
|  | if (!localReflectDefineProperty(global, 'Buffer', { | ||
|  | 	__proto__: null, | ||
|  | 	value: LocalBuffer, | ||
|  | 	writable: true, | ||
|  | 	enumerable: false, | ||
|  | 	configurable: true | ||
|  | })) throw localUnexpected(); | ||
|  | 
 | ||
|  | addProtoMapping(LocalBuffer.prototype, host.Buffer.prototype, 'Uint8Array'); | ||
|  | 
 | ||
|  | /** | ||
|  |  * | ||
|  |  * @param {*} size Size of new buffer | ||
|  |  * @this LocalBuffer | ||
|  |  * @return {LocalBuffer} | ||
|  |  */ | ||
|  | function allocUnsafe(size) { | ||
|  | 	return LocalBuffer.alloc(size); | ||
|  | } | ||
|  | 
 | ||
|  | connect(allocUnsafe, host.Buffer.allocUnsafe); | ||
|  | 
 | ||
|  | /** | ||
|  |  * | ||
|  |  * @param {*} size Size of new buffer | ||
|  |  * @this LocalBuffer | ||
|  |  * @return {LocalBuffer} | ||
|  |  */ | ||
|  | function allocUnsafeSlow(size) { | ||
|  | 	return LocalBuffer.alloc(size); | ||
|  | } | ||
|  | 
 | ||
|  | connect(allocUnsafeSlow, host.Buffer.allocUnsafeSlow); | ||
|  | 
 | ||
|  | /** | ||
|  |  * Replacement for Buffer inspect | ||
|  |  * | ||
|  |  * @param {*} recurseTimes | ||
|  |  * @param {*} ctx | ||
|  |  * @this LocalBuffer | ||
|  |  * @return {string} | ||
|  |  */ | ||
|  | 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}>`; | ||
|  | } | ||
|  | 
 | ||
|  | connect(inspect, host.Buffer.prototype.inspect); | ||
|  | 
 | ||
|  | connect(localFunction.prototype.bind, host.Function.prototype.bind); | ||
|  | 
 | ||
|  | connect(localObject.prototype.__defineGetter__, host.Object.prototype.__defineGetter__); | ||
|  | connect(localObject.prototype.__defineSetter__, host.Object.prototype.__defineSetter__); | ||
|  | connect(localObject.prototype.__lookupGetter__, host.Object.prototype.__lookupGetter__); | ||
|  | connect(localObject.prototype.__lookupSetter__, host.Object.prototype.__lookupSetter__); | ||
|  | 
 | ||
|  | /* | ||
|  |  * PrepareStackTrace sanitization | ||
|  |  */ | ||
|  | 
 | ||
|  | const oldPrepareStackTraceDesc = localReflectGetOwnPropertyDescriptor(LocalError, 'prepareStackTrace'); | ||
|  | 
 | ||
|  | let currentPrepareStackTrace = LocalError.prepareStackTrace; | ||
|  | const wrappedPrepareStackTrace = new LocalWeakMap(); | ||
|  | if (typeof currentPrepareStackTrace === 'function') { | ||
|  | 	wrappedPrepareStackTrace.set(currentPrepareStackTrace, currentPrepareStackTrace); | ||
|  | } | ||
|  | 
 | ||
|  | let OriginalCallSite; | ||
|  | LocalError.prepareStackTrace = (e, sst) => { | ||
|  | 	OriginalCallSite = sst[0].constructor; | ||
|  | }; | ||
|  | new LocalError().stack; | ||
|  | if (typeof OriginalCallSite === 'function') { | ||
|  | 	LocalError.prepareStackTrace = undefined; | ||
|  | 
 | ||
|  | 	function makeCallSiteGetters(list) { | ||
|  | 		const callSiteGetters = []; | ||
|  | 		for (let i=0; i<list.length; i++) { | ||
|  | 			const name = list[i]; | ||
|  | 			const func = OriginalCallSite.prototype[name]; | ||
|  | 			callSiteGetters[i] = {__proto__: null, | ||
|  | 				name, | ||
|  | 				propName: '_' + name, | ||
|  | 				func: (thiz) => { | ||
|  | 					return localReflectApply(func, thiz, []); | ||
|  | 				} | ||
|  | 			}; | ||
|  | 		} | ||
|  | 		return callSiteGetters; | ||
|  | 	} | ||
|  | 
 | ||
|  | 	function applyCallSiteGetters(thiz, callSite, getters) { | ||
|  | 		for (let i=0; i<getters.length; i++) { | ||
|  | 			const getter = getters[i]; | ||
|  | 			localReflectDefineProperty(thiz, getter.propName, { | ||
|  | 				__proto__: null, | ||
|  | 				value: getter.func(callSite) | ||
|  | 			}); | ||
|  | 		} | ||
|  | 	} | ||
|  | 
 | ||
|  | 	const callSiteGetters = makeCallSiteGetters([ | ||
|  | 		'getTypeName', | ||
|  | 		'getFunctionName', | ||
|  | 		'getMethodName', | ||
|  | 		'getFileName', | ||
|  | 		'getLineNumber', | ||
|  | 		'getColumnNumber', | ||
|  | 		'getEvalOrigin', | ||
|  | 		'isToplevel', | ||
|  | 		'isEval', | ||
|  | 		'isNative', | ||
|  | 		'isConstructor', | ||
|  | 		'isAsync', | ||
|  | 		'isPromiseAll', | ||
|  | 		'getPromiseIndex' | ||
|  | 	]); | ||
|  | 
 | ||
|  | 	class CallSite { | ||
|  | 		constructor(callSite) { | ||
|  | 			applyCallSiteGetters(this, callSite, callSiteGetters); | ||
|  | 		} | ||
|  | 		getThis() { | ||
|  | 			return undefined; | ||
|  | 		} | ||
|  | 		getFunction() { | ||
|  | 			return undefined; | ||
|  | 		} | ||
|  | 		toString() { | ||
|  | 			return 'CallSite {}'; | ||
|  | 		} | ||
|  | 	} | ||
|  | 
 | ||
|  | 
 | ||
|  | 	for (let i=0; i<callSiteGetters.length; i++) { | ||
|  | 		const name = callSiteGetters[i].name; | ||
|  | 		const funcProp = localReflectGetOwnPropertyDescriptor(OriginalCallSite.prototype, name); | ||
|  | 		if (!funcProp) continue; | ||
|  | 		const propertyName = callSiteGetters[i].propName; | ||
|  | 		const func = {func() { | ||
|  | 			return this[propertyName]; | ||
|  | 		}}.func; | ||
|  | 		const nameProp = localReflectGetOwnPropertyDescriptor(func, 'name'); | ||
|  | 		if (!nameProp) throw localUnexpected(); | ||
|  | 		nameProp.value = name; | ||
|  | 		if (!localReflectDefineProperty(func, 'name', nameProp)) throw localUnexpected(); | ||
|  | 		funcProp.value = func; | ||
|  | 		if (!localReflectDefineProperty(CallSite.prototype, name, funcProp)) throw localUnexpected(); | ||
|  | 	} | ||
|  | 
 | ||
|  | 	if (!localReflectDefineProperty(LocalError, 'prepareStackTrace', { | ||
|  | 		configurable: false, | ||
|  | 		enumerable: false, | ||
|  | 		get() { | ||
|  | 			return currentPrepareStackTrace; | ||
|  | 		}, | ||
|  | 		set(value) { | ||
|  | 			if (typeof(value) !== 'function') { | ||
|  | 				currentPrepareStackTrace = value; | ||
|  | 				return; | ||
|  | 			} | ||
|  | 			const wrapped = localReflectApply(localWeakMapGet, wrappedPrepareStackTrace, [value]); | ||
|  | 			if (wrapped) { | ||
|  | 				currentPrepareStackTrace = wrapped; | ||
|  | 				return; | ||
|  | 			} | ||
|  | 			const newWrapped = (error, sst) => { | ||
|  | 				if (localArrayIsArray(sst)) { | ||
|  | 					for (let i=0; i < sst.length; i++) { | ||
|  | 						const cs = sst[i]; | ||
|  | 						if (typeof cs === 'object' && localReflectGetPrototypeOf(cs) === OriginalCallSite.prototype) { | ||
|  | 							sst[i] = new CallSite(cs); | ||
|  | 						} | ||
|  | 					} | ||
|  | 				} | ||
|  | 				return value(error, sst); | ||
|  | 			}; | ||
|  | 			localReflectApply(localWeakMapSet, wrappedPrepareStackTrace, [value, newWrapped]); | ||
|  | 			localReflectApply(localWeakMapSet, wrappedPrepareStackTrace, [newWrapped, newWrapped]); | ||
|  | 			currentPrepareStackTrace = newWrapped; | ||
|  | 		} | ||
|  | 	})) throw localUnexpected(); | ||
|  | } else if (oldPrepareStackTraceDesc) { | ||
|  | 	localReflectDefineProperty(LocalError, 'prepareStackTrace', oldPrepareStackTraceDesc); | ||
|  | } else { | ||
|  | 	localReflectDeleteProperty(LocalError, 'prepareStackTrace'); | ||
|  | } | ||
|  | 
 | ||
|  | /* | ||
|  |  * Exception sanitization | ||
|  |  */ | ||
|  | 
 | ||
|  | const withProxy = localObjectFreeze({ | ||
|  | 	__proto__: null, | ||
|  | 	has(target, key) { | ||
|  | 		if (key === host.INTERNAL_STATE_NAME) return false; | ||
|  | 		return localReflectHas(target, key); | ||
|  | 	} | ||
|  | }); | ||
|  | 
 | ||
|  | const interanState = localObjectFreeze({ | ||
|  | 	__proto__: null, | ||
|  | 	wrapWith(x) { | ||
|  | 		if (x === null || x === undefined) return x; | ||
|  | 		return new LocalProxy(localObject(x), withProxy); | ||
|  | 	}, | ||
|  | 	handleException: ensureThis, | ||
|  | 	import(what) { | ||
|  | 		throw new VMError('Dynamic Import not supported'); | ||
|  | 	} | ||
|  | }); | ||
|  | 
 | ||
|  | if (!localReflectDefineProperty(global, host.INTERNAL_STATE_NAME, { | ||
|  | 	__proto__: null, | ||
|  | 	configurable: false, | ||
|  | 	enumerable: false, | ||
|  | 	writable: false, | ||
|  | 	value: interanState | ||
|  | })) throw localUnexpected(); | ||
|  | 
 | ||
|  | /* | ||
|  |  * Eval sanitization | ||
|  |  */ | ||
|  | 
 | ||
|  | function throwAsync() { | ||
|  | 	return new VMError('Async not available'); | ||
|  | } | ||
|  | 
 | ||
|  | function makeFunction(inputArgs, isAsync, isGenerator) { | ||
|  | 	const lastArgs = inputArgs.length - 1; | ||
|  | 	let code = lastArgs >= 0 ? `${inputArgs[lastArgs]}` : ''; | ||
|  | 	let args = lastArgs > 0 ? `${inputArgs[0]}` : ''; | ||
|  | 	for (let i = 1; i < lastArgs; i++) { | ||
|  | 		args += `,${inputArgs[i]}`; | ||
|  | 	} | ||
|  | 	try { | ||
|  | 		code = host.transformAndCheck(args, code, isAsync, isGenerator, allowAsync); | ||
|  | 	} catch (e) { | ||
|  | 		throw bridge.from(e); | ||
|  | 	} | ||
|  | 	return localEval(code); | ||
|  | } | ||
|  | 
 | ||
|  | const FunctionHandler = { | ||
|  | 	__proto__: null, | ||
|  | 	apply(target, thiz, args) { | ||
|  | 		return makeFunction(args, this.isAsync, this.isGenerator); | ||
|  | 	}, | ||
|  | 	construct(target, args, newTarget) { | ||
|  | 		return makeFunction(args, this.isAsync, this.isGenerator); | ||
|  | 	} | ||
|  | }; | ||
|  | 
 | ||
|  | const EvalHandler = { | ||
|  | 	__proto__: null, | ||
|  | 	apply(target, thiz, args) { | ||
|  | 		if (args.length === 0) return undefined; | ||
|  | 		let code = `${args[0]}`; | ||
|  | 		try { | ||
|  | 			code = host.transformAndCheck(null, code, false, false, allowAsync); | ||
|  | 		} catch (e) { | ||
|  | 			throw bridge.from(e); | ||
|  | 		} | ||
|  | 		return localEval(code); | ||
|  | 	} | ||
|  | }; | ||
|  | 
 | ||
|  | const AsyncErrorHandler = { | ||
|  | 	__proto__: null, | ||
|  | 	apply(target, thiz, args) { | ||
|  | 		throw throwAsync(); | ||
|  | 	}, | ||
|  | 	construct(target, args, newTarget) { | ||
|  | 		throw throwAsync(); | ||
|  | 	} | ||
|  | }; | ||
|  | 
 | ||
|  | function makeCheckFunction(isAsync, isGenerator) { | ||
|  | 	if (isAsync && !allowAsync) return AsyncErrorHandler; | ||
|  | 	return { | ||
|  | 		__proto__: FunctionHandler, | ||
|  | 		isAsync, | ||
|  | 		isGenerator | ||
|  | 	}; | ||
|  | } | ||
|  | 
 | ||
|  | function overrideWithProxy(obj, prop, value, handler) { | ||
|  | 	const proxy = new LocalProxy(value, handler); | ||
|  | 	if (!localReflectDefineProperty(obj, prop, {__proto__: null, value: proxy})) throw localUnexpected(); | ||
|  | 	return proxy; | ||
|  | } | ||
|  | 
 | ||
|  | const proxiedFunction = overrideWithProxy(localFunction.prototype, 'constructor', localFunction, makeCheckFunction(false, false)); | ||
|  | if (GeneratorFunction) { | ||
|  | 	if (!localReflectSetPrototypeOf(GeneratorFunction, proxiedFunction)) throw localUnexpected(); | ||
|  | 	overrideWithProxy(GeneratorFunction.prototype, 'constructor', GeneratorFunction, makeCheckFunction(false, true)); | ||
|  | } | ||
|  | if (AsyncFunction) { | ||
|  | 	if (!localReflectSetPrototypeOf(AsyncFunction, proxiedFunction)) throw localUnexpected(); | ||
|  | 	overrideWithProxy(AsyncFunction.prototype, 'constructor', AsyncFunction, makeCheckFunction(true, false)); | ||
|  | } | ||
|  | if (AsyncGeneratorFunction) { | ||
|  | 	if (!localReflectSetPrototypeOf(AsyncGeneratorFunction, proxiedFunction)) throw localUnexpected(); | ||
|  | 	overrideWithProxy(AsyncGeneratorFunction.prototype, 'constructor', AsyncGeneratorFunction, makeCheckFunction(true, true)); | ||
|  | } | ||
|  | 
 | ||
|  | global.Function = proxiedFunction; | ||
|  | global.eval = new LocalProxy(localEval, EvalHandler); | ||
|  | 
 | ||
|  | /* | ||
|  |  * Promise sanitization | ||
|  |  */ | ||
|  | 
 | ||
|  | if (localPromise && !allowAsync) { | ||
|  | 
 | ||
|  | 	const PromisePrototype = localPromise.prototype; | ||
|  | 
 | ||
|  | 	overrideWithProxy(PromisePrototype, 'then', PromisePrototype.then, AsyncErrorHandler); | ||
|  | 	// This seems not to work, and will produce
 | ||
|  | 	// UnhandledPromiseRejectionWarning: TypeError: Method Promise.prototype.then called on incompatible receiver [object Object].
 | ||
|  | 	// This is likely caused since the host.Promise.prototype.then cannot use the VM Proxy object.
 | ||
|  | 	// Contextify.connect(host.Promise.prototype.then, Promise.prototype.then);
 | ||
|  | 
 | ||
|  | 	if (PromisePrototype.finally) { | ||
|  | 		overrideWithProxy(PromisePrototype, 'finally', PromisePrototype.finally, AsyncErrorHandler); | ||
|  | 		// Contextify.connect(host.Promise.prototype.finally, Promise.prototype.finally);
 | ||
|  | 	} | ||
|  | 	if (Promise.prototype.catch) { | ||
|  | 		overrideWithProxy(PromisePrototype, 'catch', PromisePrototype.catch, AsyncErrorHandler); | ||
|  | 		// Contextify.connect(host.Promise.prototype.catch, Promise.prototype.catch);
 | ||
|  | 	} | ||
|  | 
 | ||
|  | } | ||
|  | 
 | ||
|  | function readonly(other, mock) { | ||
|  | 	// Note: other@other(unsafe) mock@other(unsafe) returns@this(unsafe) throws@this(unsafe)
 | ||
|  | 	if (!mock) return fromWithFactory(readonlyFactory, other); | ||
|  | 	const tmock = from(mock); | ||
|  | 	return fromWithFactory(obj=>new ReadOnlyMockHandler(obj, tmock), other); | ||
|  | } | ||
|  | 
 | ||
|  | return { | ||
|  | 	__proto__: null, | ||
|  | 	readonly, | ||
|  | 	global | ||
|  | }; |