/** * Invoke untrusted guest code in a sandbox. * The guest code can access objects of the standard library of ECMAScript. * * function chainchomp(script: string, scope?: any = {}): any; * * @param script guest code. * @param scope an object whose properties will be exposed to the guest code. * @return result of the process. */ function chainchomp(script, scope, options){ // First, you need to pile a picket to tie a Chain Chomp. // If the environment is changed, the picket will drop out. // You should remake a new picket each time as long as you are so busy. // ------------------------------------------------------------------ // If the global object is changed, you must remake a picket. var picket = chainchomp.pick(); // Next, get new Chain Chomp tied the picket. // Different Chain Chomps have different behavior. // -------------------------------------------------------------- // If you need a different function, you can get another one. var chomp = picket(script, scope); // Last, feed the chomp and let it rampage! // A chomp eats nothing but a kind of feed that the chomp ate at first. // ---------------------------------------------------------------------- // If only a value in the scope object is changed, you need not to remake the Chain Chomp and the picket. return chomp(options); } /** * create sandbox */ chainchomp.pick = (function(){ // Dynamic instantiation idiom // http://stackoverflow.com/questions/1606797/use-of-apply-with-new-operator-is-this-possible function construct(constructor, args) { function F() { return constructor.apply(this, args); } F.prototype = constructor.prototype; return new F(); } function getBannedVars(){ // correct banned object names. var banned = ['__proto__', 'prototype']; function ban(k){ if(k && banned.indexOf(k) < 0 && k !== 'eval' && k.match(/^[_$a-zA-Z][_$a-zA-Z0-9]*$/)){ banned.push(k); } } var global = new Function("return this")(); Object.getOwnPropertyNames(global).forEach(ban); for(var k in global){ ban(k); } // ban all ids of the elements function traverse(elem){ ban(elem.getAttribute && elem.getAttribute('id')); var childs = elem.childNodes; for(var i = 0; i < childs.length; i++){ traverse(childs[i]); } } traverse(document); return banned; } // table of exposed objects function getStdlibs(){ return { 'Object' : Object, 'String' : String, 'Number' : Number, 'Boolean' : Boolean, 'Array' : Array, 'Date' : Date, 'Math' : Math, 'RegExp' : RegExp, 'Error' : Error, 'EvalError' : EvalError, 'RangeError' : RangeError, 'ReferenceError' : ReferenceError, 'SyntaxError' : SyntaxError, 'TypeError' : TypeError, 'URIError' : URIError, 'JSON' : JSON, 'NaN' : NaN, 'Infinity' : Infinity, 'undefined' : undefined, 'parseInt' : parseInt, 'parseFloat' : parseFloat, 'isNaN' : isNaN, 'isFinite' : isFinite, 'decodeURI' : decodeURI, 'decodeURIComponent': decodeURIComponent, 'encodeURI' : encodeURI, 'encodeURIComponent': encodeURIComponent }; } var isFreezedStdLibObjs = false; /** * create sandbox. */ return function(){ if(isFreezedStdLibObjs == false){ var stdlibs = getStdlibs(); function freeze(v){ if(v && (typeof v === 'object' || typeof v === 'function') && ! Object.isFrozen(v)){ Object.freeze(v); Object.getOwnPropertyNames(v).forEach(function(k, i){ var value; try{ value = v[k]; }catch(e){ // do notiong } freeze(value); }); } } freeze(stdlibs); // freeze Function.prototype Object.defineProperty(Function.prototype, "constructor", { enumerable: false, get: function(){ throw new ReferenceError('Access to "Function.prototype.constructor" is not allowed.') }, set: function(){ throw new ReferenceError('Access to "Function.prototype.constructor" is not allowed.') } }); freeze(Function); isFreezedStdLibObjs = true; } var banned = getBannedVars(); /** * create sandboxed function. */ var createSandboxedFunction = function(script, scope){ // validate arguments if( ! (typeof script === 'string' || script instanceof String )){ throw new TypeError(); } // store default values of the parameter scope = scope || {}; Object.seal(scope); // Expose custom properties var guestGlobal = getStdlibs(); Object.keys(scope).forEach(function(k){ guestGlobal[k] = scope[k]; }); Object.seal(guestGlobal); // create sandboxed function var args = Object.keys(guestGlobal).concat(banned.filter(function(b){ return ! guestGlobal.hasOwnProperty(b); })); args.push('"use strict";\n' + script); var functionObject = construct(Function, args); var safeEval = function(s){ return createSandboxedFunction("return " + s, guestGlobal)(); }; Object.freeze(safeEval); /** * Invoke sandboxed function. */ var invokeSandboxedFunction = function(options){ options = options || {}; // replace eval with safe eval-like function var _eval = eval; if(options.debug !== true){ eval = safeEval; } // call the sandboxed function try{ // restore default values Object.keys(scope).forEach(function(k){ guestGlobal[k] = scope[k]; }); // call var params = Object.keys(guestGlobal).map(function(k){ return guestGlobal[k]; }); return functionObject.apply(undefined, params); }finally{ eval = _eval; } }; return invokeSandboxedFunction; }; return createSandboxedFunction; }; })(); // chainchomp.callback = function(callback, args, options){ options = options || {}; args = args || []; // replace eval with safe eval-like function var _eval = eval; if(options.debug !== true){ eval = undefined; } try{ return callback.apply(undefined, args); }finally{ eval = _eval; } };