capability.js 6.7 KB

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