source: Dev/trunk/src/client/dojox/secure/capability.js @ 529

Last change on this file since 529 was 483, checked in by hendrikvanantwerpen, 11 years ago

Added Dojo 1.9.3 release.

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