source: Dev/branches/rest-dojo-ui/client/dojox/secure/capability.js @ 256

Last change on this file since 256 was 256, checked in by hendrikvanantwerpen, 13 years ago

Reworked project structure based on REST interaction and Dojo library. As
soon as this is stable, the old jQueryUI branch can be removed (it's
kept for reference).

File size: 6.3 KB
Line 
1dojo.provide("dojox.secure.capability");
2
3dojox.secure.badProps = /^__|^(apply|call|callee|caller|constructor|eval|prototype|this|unwatch|valueOf|watch)$|__$/;
4dojox.secure.capability = {
5        keywords: ["break", "case", "catch", "const", "continue","debugger", "default", "delete", "do",
6                 "else", "enum","false", "finally", "for", "function","if", "in", "instanceof", "new",
7                 "null","yield","return", "switch",
8                 "throw", "true", "try", "typeof", "var", "void", "while"],
9        validate : function(/*string*/script,/*Array*/safeLibraries,/*Object*/safeGlobals) {
10                // summary:
11                //              pass in the text of a script. If it passes and it can be eval'ed, it should be safe.
12                //              Note that this does not do full syntax checking, it relies on eval to reject invalid scripts.
13                //              There are also known false rejections:
14                //                      Nesting vars inside blocks will not declare the variable for the outer block
15                //                      Named functions are not treated as declaration so they are generally not allowed unless the name is declared with a var.
16                //                      Var declaration that involve multiple comma delimited variable assignments are not accepted
17                //
18                // script:
19                //               the script to execute
20                //
21                // safeLibraries:
22                //              The safe libraries that can be called (the functions can not be access/modified by the untrusted code, only called)
23                //
24                // safeGlobals:
25                //              These globals can be freely interacted with by the untrusted code
26               
27       
28                var keywords = this.keywords;
29                for (var i = 0; i < keywords.length; i++) {
30                        safeGlobals[keywords[i]]=true;
31                }
32                var badThis = "|this| keyword in object literal without a Class call";
33                var blocks = []; // keeps track of the outer references from each inner block
34                if(script.match(/[\u200c-\u200f\u202a-\u202e\u206a-\u206f\uff00-\uffff]/)){
35                        throw new Error("Illegal unicode characters detected");
36                }
37                if(script.match(/\/\*@cc_on/)){
38                        throw new Error("Conditional compilation token is not allowed");
39                }
40                script = script.replace(/\\["'\\\/bfnrtu]/g, '@'). // borrows some tricks from json.js
41                        // now clear line comments, block comments, regular expressions, and strings.
42                        // By doing it all at once, the regular expression uses left to right parsing, and the most
43                        // left token is read first. It is also more compact.
44                        replace(/\/\/.*|\/\*[\w\W]*?\*\/|("[^"]*")|('[^']*')/g,function(t) {
45                                return t.match(/^\/\/|^\/\*/) ? ' ' : '0'; // comments are replaced with a space, strings and regex are replaced with a single safe token (0)
46                        }).
47                        replace(/\.\s*([a-z\$_A-Z][\w\$_]*)|([;,{])\s*([a-z\$_A-Z][\w\$_]*\s*):/g,function(t,prop,prefix,key) {
48                                // find all the dot property references, all the object literal keys, and labels
49                                prop = prop || key;
50                                if(/^__|^(apply|call|callee|caller|constructor|eval|prototype|this|unwatch|valueOf|watch)$|__$/.test(prop)){
51                                        throw new Error("Illegal property name " + prop);
52                                }
53                                return (prefix && (prefix + "0:")) || '~'; // replace literal keys with 0: and replace properties with the innocuous ~
54                        });
55                script.replace(/([^\[][\]\}]\s*=)|((\Wreturn|\S)\s*\[\s*\+?)|([^=!][=!]=[^=])/g,function(oper) {// check for illegal operator usages
56                        if(!oper.match(/((\Wreturn|[=\&\|\:\?\,])\s*\[)|\[\s*\+$/)){ // the whitelist for [ operator for array initializer context or [+num] syntax
57                                throw new Error("Illegal operator " + oper.substring(1));
58                        }
59                });
60                script = script.replace(new RegExp("(" + safeLibraries.join("|") + ")[\\s~]*\\(","g"),function(call) { // find library calls and make them look safe
61                        return "new("; // turn into a known safe call
62                });
63                function findOuterRefs(block,func) {
64                        var outerRefs = {};
65                        block.replace(/#\d+/g,function(b) { // graft in the outer references from the inner scopes
66                                var refs = blocks[b.substring(1)];
67                                for (var i in refs) {
68                                        if(i == badThis) {
69                                                throw i;
70                                        }
71                                        if(i == 'this' && refs[':method'] && refs['this'] == 1) {
72                                                // if we are in an object literal the function may be a bindable method, this must only be in the local scope
73                                                i = badThis;
74                                        }
75                                        if(i != ':method'){
76                                                outerRefs[i] = 2; // the reference is more than just local
77                                        }
78                                }
79                        });
80                        block.replace(/(\W|^)([a-z_\$A-Z][\w_\$]*)/g,function(t,a,identifier) { // find all the identifiers
81                                if(identifier.charAt(0)=='_'){
82                                        throw new Error("Names may not start with _");
83                                }
84                                outerRefs[identifier] = 1;
85                        });
86                        return outerRefs;
87                }
88                var newScript,outerRefs;
89                function parseBlock(t,func,a,b,params,block) {
90                        block.replace(/(^|,)0:\s*function#(\d+)/g,function(t,a,b) { // find functions in object literals
91                        // note that if named functions are allowed, it could be possible to have label: function name() {} which is a security breach
92                                        var refs = blocks[b];
93                                        refs[':method'] = 1;//mark it as a method
94                        });
95                        block = block.replace(/(^|[^_\w\$])Class\s*\(\s*([_\w\$]+\s*,\s*)*#(\d+)/g,function(t,p,a,b) { // find Class calls
96                                        var refs = blocks[b];
97                                        delete refs[badThis];
98                                        return (p||'') + (a||'') + "#" + b;
99                        });
100                        outerRefs = findOuterRefs(block,func); // find the variables in this block
101                        function parseVars(t,a,b,decl) { // find var decls
102                                decl.replace(/,?([a-z\$A-Z][_\w\$]*)/g,function(t,identifier) {
103                                        if(identifier == 'Class'){
104                                                throw new Error("Class is reserved");
105                                        }
106                                        delete outerRefs[identifier]; // outer reference is safely referenced here
107                                });
108                        }
109                       
110                        if(func) {
111                                parseVars(t,a,a,params); // the parameters are declare variables
112                        }
113                        block.replace(/(\W|^)(var) ([ \t,_\w\$]+)/g,parseVars); // and vars declare variables
114                        // FIXME: Give named functions #name syntax so they can be detected as vars in outer scopes (but be careful of nesting)
115                        return (a || '') + (b || '') + "#" + (blocks.push(outerRefs)-1); // return a block reference so the outer block can fetch it
116                }
117                do {
118                        // get all the blocks, starting with inside and moving out, capturing the parameters of functions and catchs as variables along the way
119                        newScript = script.replace(/((function|catch)(\s+[_\w\$]+)?\s*\(([^\)]*)\)\s*)?{([^{}]*)}/g, parseBlock);
120                }
121                while(newScript != script && (script = newScript)); // keep going until we can't find anymore blocks
122                parseBlock(0,0,0,0,0,script); //findOuterRefs(script); // find the references in the outside scope
123                for (i in outerRefs) {
124                        if(!(i in safeGlobals)) {
125                                throw new Error("Illegal reference to " + i);
126                        }
127                }
128               
129        }
130};
Note: See TracBrowser for help on using the repository browser.