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