@doodad-js/safeeval
Version:
doodad-js SafeEval (beta)
2 lines • 9.97 kB
JavaScript
// Copyright 2015-2018 Claude Petit, licensed under Apache License version 2.0
;exports.add=function add(modules){modules=(modules||{});modules['Doodad.Tools.SafeEval']={version:'4.1.9b',create:function create(root,_options,_shared){const doodad=root.Doodad,types=doodad.Types,tools=doodad.Tools,locale=tools.Locale,unicode=tools.Unicode,safeEval=tools.SafeEval;const __Internal__={deniedTokensAlways:['constructor','__proto__'],deniedTokensGlobal:['Function','eval','setTimeout','setInterval','arguments','this','var','const','let'],constants:['true','false','null','undefined','NaN','Infinity'],allDigitsRegEx:/^([0-9]+[.]?[0-9]*([e][-+]?[0-9]+)?|0[xX]([0-9a-fA-F])+|0[bB]([01])+|0[oO]([0-7])+)$/,newLineChars:['\n','\r','\u2028','\u2029'],symbolCachedSafeEvalFn:types.getSymbol('__SAFE_EVAL_FN__'),symbolCachedSafeEvalOptions:types.getSymbol('__SAFE_EVAL_OPTIONS__'),};__Internal__.validateExpression=function(expression,locals,globals,options){const preventAssignment=types.get(options,'preventAssignment',true),allowFunctions=types.get(options,'allowFunctions',false),allowNew=types.get(options,'allowNew',false),allowRegExp=types.get(options,'allowRegExp',false);if(root.DD_ASSERT){root.DD_ASSERT(types.isString(expression),"Invalid expression.");root.DD_ASSERT(types.isNothing(locals)||types.isJsObject(locals),"Invalid locals.");root.DD_ASSERT(types.isNothing(globals)||types.isArray(globals),"Invalid globals.")};let prevChr='',isString=false,isEscape=false,stringChar=null,isAssignment=false,isComment=false,isCommentBlock=false,isRegExp=false,isRegExpFlags=false,isGlobal=true,isDot=false,lastTokens=[],isFunction=false,isFunctionArgs=false,functionArgs=null,level={name:''},prevLevel=level,isShift=false,noPrevChr=false,maybeObject=false;const levels=[level];const pushLevel=function(name){level={name};levels.push(level)};const popLevel=function(name){prevLevel=level;level=levels.pop();if(level.name!==name){throw new types.AccessDenied("Invalid expression.")}};const validateTokens=function validateTokens(){let tokenName;while(tokenName=lastTokens.shift()){let deniedToken='';if(tools.indexOf(__Internal__.deniedTokensAlways,tokenName)>=0){deniedToken=tokenName}else if(isGlobal){if(tools.indexOf(__Internal__.deniedTokensGlobal,tokenName)>=0){deniedToken=tokenName}else if(__Internal__.allDigitsRegEx.test(tokenName)){}else if(allowNew&&(tokenName==='new')){}else if(tools.indexOf(__Internal__.constants,tokenName)>=0){}else if(types.has(locals,tokenName)){}else if(tools.indexOf(globals,tokenName)>=0){}else if(isFunction&&(tools.indexOf(functionArgs,tokenName)>=0)){}else{deniedToken=tokenName}};if(deniedToken){throw new types.AccessDenied("Access to '~0~' is denied.",[deniedToken])}};if(tokenName){isGlobal=false}};const curLocale=locale.getCurrent();let chr=unicode.nextChar(expression);loopChars:while(chr){if(isString){if(isEscape){isEscape=false}else if(chr.chr==='\\'){isEscape=true}else if(chr.chr===stringChar){isString=false;noPrevChr=true;prevChr='';isGlobal=false}}else if(isRegExpFlags){if((chr.codePoint<97)||(chr.codePoint>122)){isRegExpFlags=false}}else if(isRegExp){if(isEscape){isEscape=false}else if(chr.chr==='\\'){isEscape=true}else if(chr.chr==='/'){isRegExp=false;isRegExpFlags=true;noPrevChr=true;prevChr=''}}else if(isComment){if(tools.indexOf(__Internal__.newLineChars,chr.chr)>=0){isComment=false;validateTokens()}}else if(isCommentBlock){if((prevChr==='*')&&(chr.chr==='/')){isCommentBlock=false;noPrevChr=true;prevChr=''}}else if(isAssignment&&(chr.chr==='>')){if(!allowFunctions){throw new types.AccessDenied("Functions are denied.")};if(isFunction){throw new types.AccessDenied("Function in function is denied.")};isAssignment=false;isFunction=true;if(prevLevel.name==='('){functionArgs=lastTokens;lastTokens=[]}else if(lastTokens.length>0){functionArgs=[lastTokens.pop()]}else{functionArgs=[]}}else if((isAssignment&&(chr.chr!=='='))||(isShift&&(chr.chr==='='))){if(preventAssignment){throw new types.AccessDenied("Assignment is not allowed.")};validateTokens()}else if((prevChr==='/')&&(chr.chr==='/')){isComment=true;noPrevChr=true;prevChr=''}else if((prevChr==='/')&&(chr.chr==='*')){isCommentBlock=true;noPrevChr=true;prevChr=''}else if(isGlobal&&(lastTokens.length<=0)&&(prevChr==='/')&&(chr.chr!=='/')){if(!allowRegExp){throw new types.AccessDenied("Regular expressions are not allowed.")};isRegExp=true;if(chr.chr==='\\'){isEscape=true};noPrevChr=true;prevChr=''}else if((chr.chr===';')||(tools.indexOf(__Internal__.newLineChars,chr.chr)>=0)){validateTokens();let hasSemi=false;do{if(chr.chr===';'){hasSemi=true};chr=chr.nextChar()}while(chr&&((chr.chr===';')||(tools.indexOf(__Internal__.newLineChars,chr.chr)>=0)));if(!isDot||hasSemi){isGlobal=true};if(hasSemi){prevChr=''};continue loopChars}else if(unicode.isSpace(chr.chr,curLocale)){do{chr=chr.nextChar()}while(chr&&unicode.isSpace(chr.chr,curLocale));continue loopChars}else if((chr.chr==='$')||(chr.chr==='_')||unicode.isAlnum(chr.chr,curLocale)){let tokenName='';do{tokenName+=chr.chr;chr=chr.nextChar()}while(chr&&((chr.chr==='$')||(chr.chr==='_')||unicode.isAlnum(chr.chr,curLocale)));if(isGlobal&&(tokenName==='class')){throw new types.AccessDenied("Classes are denied.")}else if(isGlobal&&(tokenName==='function')){if(!allowFunctions){throw new types.AccessDenied("Functions are denied.")};if(isFunction||isFunctionArgs){throw new types.AccessDenied("Function in function is denied.")};isFunctionArgs=true}else if(isFunction&&isGlobal&&(tokenName==='return')){}else{lastTokens.push(tokenName)};prevChr='';continue loopChars}else if(['][','+[','-[','![','~[','|[','&[','*[','/['].indexOf(prevChr+chr.chr)>=0){throw new types.AccessDenied("Invalid property accessor.")}else if(chr.chr==='\\'){throw new types.AccessDenied("Escape sequences not allowed.")}else if(chr.codePoint>0x7F){throw new types.AccessDenied("Invalid character.")}else if((level.name==='{')&&(chr.chr===':')){lastTokens=[]}else if((chr.chr==='"')||(chr.chr==="'")){validateTokens();isString=true;stringChar=chr.chr}else if(chr.chr==='`'){validateTokens();throw new types.AccessDenied("Template strings are denied.")}else if(chr.chr===']'){popLevel('[');isGlobal=false}else if(chr.chr==='['){if(!isGlobal||(lastTokens.length>0)){throw new types.AccessDenied("Invalid property accessor.")};pushLevel('[');validateTokens()}else if(chr.chr==='{'){pushLevel('{');maybeObject=isGlobal;isGlobal=true;isDot=false}else if(chr.chr==='}'){popLevel('{');isGlobal=false;maybeObject=false}else if(chr.chr==='('){if(maybeObject){if(!allowFunctions){throw new types.AccessDenied("Functions are denied.")};maybeObject=false;isFunctionArgs=true}else{validateTokens()};pushLevel('(');noPrevChr=true}else if(chr.chr===')'){popLevel('(');if(isFunctionArgs){isFunction=true;functionArgs=lastTokens;lastTokens=[]};noPrevChr=true;prevChr=''}else if(((chr.chr==='+')||(chr.chr==='-'))&&(prevChr===chr.chr)){validateTokens();if(preventAssignment){throw new types.AccessDenied("Increment operators are not allowed.")};noPrevChr=true;prevChr=''}else if(((chr.chr==='<')||(chr.chr==='>'))&&(prevChr===chr.chr)){isShift=true}else if((chr.chr==='=')&&((prevChr!=='>')&&(prevChr!=='<')&&(prevChr!=='=')&&(prevChr!=='!'))){isAssignment=true}else{if(chr.chr!==','){validateTokens()};isAssignment=false;isShift=false;if(chr.chr==='.'){isDot=true;isGlobal=false}else{isDot=false;isGlobal=true}};if(noPrevChr){noPrevChr=false}else{prevChr=chr.chr};chr=chr.nextChar()};validateTokens()};__Internal__.createEvalFn=function createEvalFn(locals,globals){root.DD_ASSERT&&root.DD_ASSERT(types.isNothing(locals)||types.isObject(locals),"Invalid locals object.");if(types.isNothing(globals)){globals=[]}else{root.DD_ASSERT&&root.DD_ASSERT(types.isArray(globals),"Invalid global names array.")};globals=tools.reduce(globals,function(locals,name){if(name in global){locals[name]=global[name]};return locals},{});locals=tools.nullObject(globals,locals);if(types.isEmpty(locals)){return tools.eval}else{return tools.createEval(types.keys(locals)).apply(null,types.values(locals))}};safeEval.ADD('get',root.DD_DOC(null,function get(obj,key){if(!types.isString(key)&&!types.isSymbol(key)){key=types.toString(key)};if(__Internal__.deniedTokensAlways.indexOf(key)>=0){throw new types.AccessDenied("Access to '~0~' is denied.",[key])};return obj[key]}));safeEval.ADD('eval',root.DD_DOC(null,function _eval(expression,locals,globals,options){__Internal__.validateExpression(expression,locals,globals,options);const evalFn=__Internal__.createEvalFn(locals,globals);return evalFn(expression)}));safeEval.ADD('evalCached',root.DD_DOC(null,function evalCached(evalCacheObject,expression,options){if(root.DD_ASSERT){root.DD_ASSERT(types.isJsObject(evalCacheObject),"Invalid cache object.");root.DD_ASSERT(types.isString(expression),"Invalid expression.")};expression=tools.trim(types.toString(expression));let evalFn=evalCacheObject[__Internal__.symbolCachedSafeEvalFn];let locals,globals;if(evalFn){options=evalCacheObject[__Internal__.symbolCachedSafeEvalOptions];locals=types.get(options,'locals');globals=types.get(options,'globals')}else{locals=types.freezeObject(types.clone(types.get(options,'locals')));globals=types.freezeObject(types.clone(types.get(options,'globals')));options=types.freezeObject(tools.extend({},options,{locals,globals}));evalFn=__Internal__.createEvalFn(locals,globals);types.setAttribute(evalCacheObject,__Internal__.symbolCachedSafeEvalFn,evalFn,{});types.setAttribute(evalCacheObject,__Internal__.symbolCachedSafeEvalOptions,options,{})};const notDenied=(__Internal__.deniedTokensAlways.indexOf(expression)<0);if(notDenied&&types.has(evalCacheObject,expression)){return evalCacheObject[expression]};__Internal__.validateExpression(expression,locals,globals,options);root.DD_ASSERT&&root.DD_ASSERT(notDenied,"Should have been denied by '__Internal__.validateExpression'.");const result=evalFn(expression);if(notDenied){evalCacheObject[expression]=result};return result}))},};return modules};