Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Running JS code in limited context

Tags:

javascript

I'm trying to run trusted JS code in an "isolated" context. Basically came up with this method:

function limitedEval(src, context) {
    return (function() {
        with(this) {
            return eval(src) 
        }
    }).call(context)
}

This works great, however when the script is using the var keyword it is stored in the execution context as opposed to the provided context in the with statement (which I understand is by design). So for example, the following code doesn't work:

var ctx = {};
limitedEval('var foo = "hello"', ctx);
limitedEval('alert(foo)', ctx); // error: foo is undefined

I'd like to be able to call limitedEval() multiple times and reuse the context. Is that possible?

like image 416
Dandan Avatar asked May 15 '17 22:05

Dandan


1 Answers

This seems like a very interesting problem. The problem with your code is that you generate new function every time you execute limitedEval. This means that whatever variables you create using var keyword, will only exist within the context of that function. What you really need is to have 1 function per context and reuse that function's context. The most obvious way to do that is by using generators. Generators are essentially functions that can be paused and then restarted.

// IIFE to store gen_name
var limitedEval = function() {
    // Symbol for generator property so we don't pollute `ctx` namespace
    var gen_name = Symbol();

    return function limitedEval(src, context) {
        if(!(gen_name in context)) {
            // Generator that will run eval and pause til getting the next source code
            var generator = function * () {
                with(this) {
                    while(true) {
                        yield eval( yield );
                    }
                }
            };

            context[gen_name] = generator.call(context);

            // Initially, we need to execute code until reaching the first `yield`
            context[gen_name].next();
        }

        // First, send the source to the `eval`
        context[gen_name].next( src );

        // Second, get the `eval` result
        return context[gen_name].next().value;
    };
}();

And now you can call

var ctx = {};
limitedEval('var foo = "hello"', ctx);
limitedEval('alert(foo);', ctx);

Every limitedEval call will now reuse whatever generator function it will find on the provided ctx object. If the function doesn't exist, it will create it and put it on the ctx object. Because you now have only one function per ctx, it will reuse the function's context when creating variables with var keyword. Note: you will not be able to look up those variables via the ctx object, because they will only exist within the function's context.

I'm not entirely sure if you can achieve the same result without using generators.

Edit: others made great suggestions so I replaced randomly generated property with Symbol and did with(this) instead of with(context)

like image 60
guitarino Avatar answered Sep 30 '22 06:09

guitarino