Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Conversation

ncake
Copy link

@ncake ncake commented Jan 15, 2023

This adds an option called functionClass, which purpose is to allow to bring your own execution runtime.

Here's a basic example of how one could integrate quickjs-emscripten module to execute code in a WASM-powered virtual machine.

// index.js
import LoadSafeEJS from "./safe-ejs.js";

const safeEJS = await LoadSafeEJS();
const res = safeEJS.render("<%- 'hello ' + world %>", {world: 'world'});
console.log(res);
safeEJS.dispose();
// safe-ejs.js
import ejs from 'ejs/ejs.js';
import { getQuickJS } from "quickjs-emscripten"

/** @param {import('quickjs-emscripten/dist').QuickJSWASMModule} QuickJS */
function SafeEJS(QuickJS){
	const vm = QuickJS.newContext();

	this.dispose = function(){
		vm.dispose();
	};

	this.render = function(source, data, ejsOptions){
		return ejs.render(source, data, {...ejsOptions, functionClass})
	};

	const functionClass = function(argNames, funcBody){
		// not implemented here:
		// - caching the function between calls (thus useless for ejs.compile)
		// - async, include(), probably more
		return (locals, escapeFn, include, rethrow) => {
			// create function from source
			const ctor = vm.getProp(vm.global, 'Function');
			const argNamesHandle = vm.newString(argNames);
			const funcBodyHandle = vm.newString(funcBody);
			const newFuncRet = vm.callFunction(ctor, vm.undefined, argNamesHandle, funcBodyHandle);
			ctor.dispose();
			argNamesHandle.dispose();
			funcBodyHandle.dispose();
			const newFunc = vm.unwrapResult(newFuncRet);
			// wrap user-passed data into quickjs values
			const handleList = [];
			const makeDisposable = handle => (handleList.unshift(handle), handle);
			const localsHandle = wrapValue(locals, makeDisposable);
			// wrap the rest of the arguments
			const escapeFnHandle = vm.newFunction('escapeFn', strHandle => {
				const str = vm.getString(strHandle);
				const res = escapeFn(str);
				return vm.newString(res);
			});
			const includeHandle = vm.undefined;
			const rethrowHandle = vm.newFunction('rethrow',
				(errHandle, strHandle, flnmHandle, linenoHandle) => {
					const str = vm.getString(strHandle);
					const flnm = vm.getString(flnmHandle);
					const lineno = vm.getNumber(linenoHandle);
					const errMsg = vm.getProp(errHandle, 'message');
					const errName = vm.getProp(errHandle, 'name');
					const errStack = vm.getProp(errHandle, 'stack');
					const err = new Error();
					if(errMsg !== vm.undefined) err.message = vm.getString(errMsg);
					if(errName !== vm.undefined) err.name = vm.getString(errName);
					if(errStack !== vm.undefined) err.stack = vm.getString(errStack);
					rethrow(err, str, flnm, lineno, escapeFn);
				}
			);
			// execute our function
			const ret = vm.callFunction(
				newFunc, vm.undefined, localsHandle,
				escapeFnHandle, includeHandle, rethrowHandle
			);
			// dispose of everything to prevent memory leaks
			escapeFnHandle.dispose();
			rethrowHandle.dispose();
			for(const handle of handleList){
				handle.dispose();
			}
			newFunc.dispose();
			// return or throw an error
			const res = vm.unwrapResult(ret);
			const str = vm.getString(res);
			res.dispose();
			return str;
		};
	};

	const wrapValue = function(value, makeDisposable){
		if(value === undefined) return vm.undefined;
		if(value === null) return vm.null;
		if(typeof value === "boolean") return value ? vm.true : vm.false;
		if(typeof value === "number") return makeDisposable( vm.newNumber(value) );
		if(Array.isArray(value)){
			const arr = makeDisposable( vm.newArray() );
			for(let i = (value.length - 1); i >= 0; i--)
				vm.setProp(arr, i, wrapValue(value[i], makeDisposable));
			return arr;
		}
		if(typeof value === "object"){
			const obj = makeDisposable( vm.newObject() );
			for(const key in value)
				vm.setProp(obj, key, wrapValue(value[key], makeDisposable));
			return obj;
		}
		if(value.toString) return makeDisposable( vm.newString(value.toString()) );
		return vm.undefined;
	};
}

async function LoadSafeEJS(){
	return new SafeEJS(await getQuickJS());
}

export default LoadSafeEJS;

@ralyodio
Copy link

ralyodio commented Sep 3, 2023

can we get this merged?

Copy link

@ralyodio ralyodio left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@teoboley
Copy link

Can this get merged? I would love to use quickjs-sandboxed templates in my app

@teoboley
Copy link

@mde pls

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants