sandbox.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353
  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.sandbox"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
  7. dojo._hasResource["dojox.secure.sandbox"] = true;
  8. dojo.provide("dojox.secure.sandbox");
  9. dojo.require("dojox.secure.DOM");
  10. dojo.require("dojox.secure.capability");
  11. dojo.require("dojo.NodeList-fx");
  12. (function() {
  13. var oldTimeout = setTimeout;
  14. var oldInterval = setInterval;
  15. if({}.__proto__){
  16. // mozilla has unsafe methods on array
  17. var fixMozArrayFunction = function (name) {
  18. var method = Array.prototype[name];
  19. if(method && !method.fixed){
  20. (Array.prototype[name] = function () {
  21. if (this == window) {
  22. throw new TypeError("Called with wrong this");
  23. }
  24. return method.apply(this, arguments);
  25. }).fixed = true;
  26. }
  27. };
  28. // these are not safe in mozilla
  29. fixMozArrayFunction('concat');
  30. fixMozArrayFunction('reverse');
  31. fixMozArrayFunction('sort');
  32. fixMozArrayFunction("slice");
  33. fixMozArrayFunction("forEach");
  34. fixMozArrayFunction("filter");
  35. fixMozArrayFunction("reduce");
  36. fixMozArrayFunction("reduceRight");
  37. fixMozArrayFunction("every");
  38. fixMozArrayFunction("map");
  39. fixMozArrayFunction("some");
  40. }
  41. var xhrGet = function(){
  42. return dojo.xhrGet.apply(dojo,arguments);
  43. };
  44. dojox.secure.sandbox = function(element) {
  45. // summary:
  46. // Creates a secure sandbox from which scripts and HTML can be loaded that
  47. // will only be able to access the provided element and it's descendants, the
  48. // rest of the DOM and JS environment will not be accessible to the sandboxed
  49. // scripts and HTML.
  50. //
  51. // element:
  52. // The DOM element to use as the container for the sandbox
  53. //
  54. // description:
  55. // This function will create and return a sandbox object (see dojox.secure.__Sandbox)
  56. // for the provided element.
  57. var wrap = dojox.secure.DOM(element);
  58. element = wrap(element);
  59. var document = element.ownerDocument;
  60. var mixin, dojo = dojox.secure._safeDojoFunctions(element,wrap);
  61. var imports= [];
  62. var safeCalls = ["isNaN","isFinite","parseInt","parseFloat","escape","unescape",
  63. "encodeURI","encodeURIComponent","decodeURI","decodeURIComponent",
  64. "alert","confirm","prompt", // some people may not want to allow these to be called, but they don't break capability-limiting
  65. "Error","EvalError","RangeError","ReferenceError","SyntaxError","TypeError",
  66. "Date","RegExp","Number","Object","Array","String","Math",
  67. //"ADSAFE", // not using ADSAFE runtime for the time being
  68. "setTimeout","setInterval","clearTimeout","clearInterval", // we make these safe below
  69. "dojo","get","set","forEach","load","evaluate"];
  70. for(var i in dojo){
  71. safeCalls.push(i); // add the safe dojo functions to as available global top level functions
  72. imports.push("var " + i + "=dojo." + i); // add to the list of imports
  73. }
  74. // open the dojo namespace (namespaces are pretty silly in an environment where you can't set globals)
  75. eval(imports.join(";"));
  76. function get(obj,prop) {
  77. // basic access by index function
  78. prop = '' + prop;
  79. if(dojox.secure.badProps.test(prop)) {
  80. throw new Error("bad property access");
  81. }
  82. if(obj.__get__) {
  83. return obj.__get__(prop);
  84. }
  85. return obj[prop];
  86. }
  87. function set(obj,prop,value) {
  88. // basic set by index function
  89. prop = '' + prop;
  90. get(obj,prop); // test it
  91. if(obj.__set) {
  92. return obj.__set(prop);
  93. }
  94. obj[prop] = value;
  95. return value;
  96. }
  97. function forEach(obj,fun) {
  98. // short syntax iterator function
  99. if(typeof fun != "function"){
  100. throw new TypeError();
  101. }
  102. if("length" in obj) {
  103. // do arrays the fast way
  104. if(obj.__get__) {
  105. // use the catch getter
  106. var len = obj.__get__('length');
  107. for (var i = 0; i < len; i++) {
  108. if(i in obj) {
  109. fun.call(obj, obj.__get__(i), i, obj);
  110. }
  111. }
  112. }
  113. else {
  114. // fast
  115. len = obj.length;
  116. for (i = 0; i < len; i++) {
  117. if(i in obj) {
  118. fun.call(obj, obj[i], i, obj);
  119. }
  120. }
  121. }
  122. }
  123. else {
  124. // for each an object
  125. for (i in obj) {
  126. fun.call(obj, get(obj,i), i, obj);
  127. }
  128. }
  129. }
  130. function Class(/*Function*/superclass, /*Object*/properties, /*Object*/classProperties) {
  131. // summary:
  132. // A safe class constructor
  133. //
  134. // superclass:
  135. // There may be zero or more superclass arguments. The constructed class
  136. // will inherit from any provided superclasses, protypically from the first,
  137. // via mixin for the subsequent. Later arguments
  138. // will override properties/methods from earlier arguments
  139. //
  140. // properties:
  141. // The constructed
  142. // "class" will also have the methods/properties defined in this argument.
  143. // These methods may utilize the <em>this</em> operator, and they
  144. // are only the code that has access to <em>this</em>. Inner functions
  145. // are also prohibited from using <em>this</em>.
  146. //
  147. // If no superclasses are provided, this object will be the prototype of the
  148. // constructed class (no copying
  149. // will be done). Consequently you can "beget" by calling new (Class(obj)).
  150. // All methods are "bound", each call results in |this| safety checking call.
  151. //
  152. // classProperties:
  153. // This properties will be copied to the new class function.
  154. //
  155. // Note that neither dojo.declare nor dojo.extend are acceptable class constructors as
  156. // they are completely unsecure. This class constructor is conceptually based on declare
  157. // but also somewhat influenced by base2, prototype, YUI, resig's patterns, etc.
  158. //
  159. // example:
  160. // | var Car = Class({drive:function(speed) { ... } ); // create a Car class with a "drive" method
  161. // | var FastCar = Class(Car,{driveFast: function(speed) { return this.drive(2 * speed); } }); // create a FastCar that extends Car
  162. // | var fastCar = new FastCar; // instantiate
  163. // | fastCar.driveFast(50); // call a method
  164. // | var driveFast = fastCar.driveFast;
  165. // | var driveFast(50); // this will throw an error, the method can be used with an object that is not an instance of FastCar
  166. var proto,superConstructor,ourConstructor;
  167. var arg;
  168. for (var i = 0, l = arguments.length; typeof (arg = arguments[i]) == 'function' && i < l; i++) {
  169. // go through each superclass argument
  170. if(proto) { // we have a prototype now, we must mixin now
  171. mixin(proto,arg.prototype);
  172. }
  173. else {
  174. // this is the first argument, so we can define the prototype ourselves
  175. // link up the prototype chain to the superclass's prototype, so we are a subtype
  176. superConstructor = arg;
  177. var F = function() {};
  178. F.prototype = arg.prototype;
  179. proto = new F;
  180. }
  181. }
  182. if(arg) { // the next object should be the properties
  183. // apply binding checking on all the functions
  184. for (var j in arg) {
  185. // TODO: check on non-enumerables?
  186. var value = arg[j];
  187. if(typeof value == 'function') {
  188. arg[j] = function() {
  189. if(this instanceof Class){
  190. return arguments.callee.__rawMethod__.apply(this,arguments);
  191. }
  192. throw new Error("Method called on wrong object");
  193. };
  194. arg[j].__rawMethod__ = value; // may want to use this for reconstruction and toString,valueOf
  195. }
  196. }
  197. if(arg.hasOwnProperty('constructor')) {
  198. ourConstructor = arg.constructor;
  199. }
  200. }
  201. proto = proto ? mixin(proto,arg) : arg; // if there is no proto yet, we can use the provided object
  202. function Class() {
  203. // the super class may not have been constructed using the same technique, we will just call the constructor
  204. if(superConstructor){
  205. superConstructor.apply(this,arguments);
  206. }
  207. if(ourConstructor){
  208. ourConstructor.apply(this,arguments);
  209. }
  210. }
  211. mixin(Class,arguments[i]); // the optional second object adds properties to the class
  212. proto.constructor = Class;
  213. Class.prototype = proto;
  214. return Class;
  215. }
  216. function checkString(func){
  217. if(typeof func != 'function') {
  218. throw new Error("String is not allowed in setTimeout/setInterval");
  219. }
  220. }
  221. function setTimeout(func,time) {
  222. // sandboxed setTimeout
  223. checkString(func);
  224. return oldTimeout(func,time);
  225. }
  226. function setInterval(func,time) {
  227. // sandboxed setInterval
  228. checkString(func);
  229. return oldInterval(func,time);
  230. }
  231. function evaluate(script){
  232. // sandboxed eval
  233. return wrap.evaluate(script);
  234. }
  235. var load = wrap.load = function(url){
  236. // provides a loader function for the sandbox
  237. if (url.match(/^[\w\s]*:/)){
  238. throw new Error("Access denied to cross-site requests");
  239. }
  240. return xhrGet({url:(new dojo._Url(wrap.rootUrl,url))+'',secure:true});
  241. }
  242. wrap.evaluate = function(script){
  243. //if(!alreadyValidated) {
  244. dojox.secure.capability.validate(script,safeCalls, // the safe dojo library and standard operators
  245. {document:1,element:1}); // these are secured DOM starting points
  246. //}
  247. if(script.match(/^\s*[\[\{]/)) {
  248. var result = eval('(' + script + ')');
  249. // TODO: call render on result?
  250. }
  251. else {
  252. eval(script);
  253. }
  254. //eval('wrap.evaluate=('+arguments.callee.toString()+')'); // yeah, recursive scoping;
  255. };
  256. return /*===== dojo.declare("dojox.secure.__Sandbox", null, =====*/ { // dojox.secure.__Sandbox
  257. loadJS : function(url){
  258. // summary:
  259. // Loads the script from the given URL using XHR (assuming
  260. // a plugin system is in place for cross-site requests) within the sandbox
  261. //
  262. // url:
  263. // The url of the script to load
  264. wrap.rootUrl = url;
  265. return xhrGet({url:url,secure:true}).addCallback(function(result) {
  266. evaluate(result,element /*If we get the results with a secure proxy, we would call put true here */);
  267. });
  268. },
  269. loadHTML : function(url){
  270. // summary:
  271. // Loads the web page from the provided URL using XHR (assuming the
  272. // plugin system is in place) within the sandbox. All scripts within the web
  273. // page will also be sandboxed.
  274. //
  275. // url:
  276. // The url of the web page to load
  277. wrap.rootUrl = url;
  278. return xhrGet({url:url,secure:true}).addCallback(function(result){
  279. element.innerHTML = result;
  280. });
  281. },
  282. evaluate : function(script){
  283. // summary:
  284. // Evaluates the given script within the sandbox
  285. //
  286. // script:
  287. // The JavaScript text to evaluate
  288. return wrap.evaluate(script);
  289. }
  290. // TODO: could add something for pre-validated scripts
  291. }/*===== ) =====*/;
  292. };
  293. })();
  294. dojox.secure._safeDojoFunctions = function(element,wrap) {
  295. // Creates a safe subset of Dojo core library
  296. var safeFunctions = ["mixin","require","isString","isArray","isFunction","isObject","isArrayLike","isAlien",
  297. "hitch","delegate","partial","trim","disconnect","subscribe","unsubscribe","Deferred","toJson","style","attr"];
  298. //var domFunctions = ["clone","byId"];
  299. var doc = element.ownerDocument;
  300. var unwrap = dojox.secure.unwrap;
  301. dojo.NodeList.prototype.addContent.safetyCheck = function(content){
  302. wrap.safeHTML(content);
  303. };
  304. dojo.NodeList.prototype.style.safetyCheck = function(name,value){
  305. if(name=='behavior'){
  306. throw new Error("Can not set behavior");
  307. }
  308. wrap.safeCSS(value);
  309. };
  310. dojo.NodeList.prototype.attr.safetyCheck = function(name,value){
  311. if (value && (name == 'src' || name == 'href' || name=='style')){
  312. throw new Error("Illegal to set " + name);
  313. }
  314. };
  315. var safe = {
  316. query : function(query,root) {
  317. return wrap(dojo.query(query,unwrap(root || element))); // wrap the NodeList
  318. },
  319. connect: function(el,event) {
  320. var obj = el;
  321. arguments[0] = unwrap(el);
  322. if(obj!=arguments[0] && event.substring(0,2) != 'on'){
  323. // it is probably an element, and it doesn't look like an event handler, probably not safe
  324. throw new Error("Invalid event name for element");
  325. }
  326. return dojo.connect.apply(dojo,arguments);
  327. },
  328. body : function() {
  329. return element;
  330. },
  331. byId : function(id) {
  332. return element.ownerDocument.getElementById(id); // use the safe document
  333. },
  334. fromJson : function(str) {
  335. // make sure it is safe before passing it to the unsafe dojo.fromJson
  336. dojox.secure.capability.validate(str,[],{});
  337. return dojo.fromJson(str);
  338. }
  339. };
  340. for (var i = 0; i < safeFunctions.length; i++) {
  341. safe[safeFunctions[i]] = dojo[safeFunctions[i]];
  342. }
  343. return safe;
  344. };
  345. }