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
						
					
					
				| /* 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
 | |
| };
 |