You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
343 lines
12 KiB
343 lines
12 KiB
dojo.provide("dojox.secure.sandbox"); |
|
dojo.require("dojox.secure.DOM"); |
|
dojo.require("dojox.secure.capability"); |
|
dojo.require("dojo.NodeList-fx"); |
|
dojo.require("dojo._base.url"); |
|
|
|
(function() { |
|
var oldTimeout = setTimeout; |
|
var oldInterval = setInterval; |
|
if({}.__proto__){ |
|
// mozilla has unsafe methods on array |
|
var fixMozArrayFunction = function (name) { |
|
var method = Array.prototype[name]; |
|
if(method && !method.fixed){ |
|
(Array.prototype[name] = function () { |
|
if (this == window) { |
|
throw new TypeError("Called with wrong this"); |
|
} |
|
return method.apply(this, arguments); |
|
}).fixed = true; |
|
} |
|
}; |
|
// these are not safe in mozilla |
|
fixMozArrayFunction('concat'); |
|
fixMozArrayFunction('reverse'); |
|
fixMozArrayFunction('sort'); |
|
fixMozArrayFunction("slice"); |
|
fixMozArrayFunction("forEach"); |
|
fixMozArrayFunction("filter"); |
|
fixMozArrayFunction("reduce"); |
|
fixMozArrayFunction("reduceRight"); |
|
fixMozArrayFunction("every"); |
|
fixMozArrayFunction("map"); |
|
fixMozArrayFunction("some"); |
|
} |
|
var xhrGet = function(){ |
|
return dojo.xhrGet.apply(dojo,arguments); |
|
}; |
|
dojox.secure.sandbox = function(element) { |
|
// summary: |
|
// Creates a secure sandbox from which scripts and HTML can be loaded that |
|
// will only be able to access the provided element and it's descendants, the |
|
// rest of the DOM and JS environment will not be accessible to the sandboxed |
|
// scripts and HTML. |
|
// |
|
// element: |
|
// The DOM element to use as the container for the sandbox |
|
// |
|
// description: |
|
// This function will create and return a sandbox object (see dojox.secure.__Sandbox) |
|
// for the provided element. |
|
var wrap = dojox.secure.DOM(element); |
|
element = wrap(element); |
|
var document = element.ownerDocument; |
|
var mixin, dojo = dojox.secure._safeDojoFunctions(element,wrap); |
|
var imports= []; |
|
var safeCalls = ["isNaN","isFinite","parseInt","parseFloat","escape","unescape", |
|
"encodeURI","encodeURIComponent","decodeURI","decodeURIComponent", |
|
"alert","confirm","prompt", // some people may not want to allow these to be called, but they don't break capability-limiting |
|
"Error","EvalError","RangeError","ReferenceError","SyntaxError","TypeError", |
|
"Date","RegExp","Number","Object","Array","String","Math", |
|
//"ADSAFE", // not using ADSAFE runtime for the time being |
|
"setTimeout","setInterval","clearTimeout","clearInterval", // we make these safe below |
|
"dojo","get","set","forEach","load","evaluate"]; |
|
for(var i in dojo){ |
|
safeCalls.push(i); // add the safe dojo functions to as available global top level functions |
|
imports.push("var " + i + "=dojo." + i); // add to the list of imports |
|
} |
|
// open the dojo namespace (namespaces are pretty silly in an environment where you can't set globals) |
|
eval(imports.join(";")); |
|
function get(obj,prop) { |
|
// basic access by index function |
|
prop = '' + prop; |
|
if(dojox.secure.badProps.test(prop)) { |
|
throw new Error("bad property access"); |
|
} |
|
if(obj.__get__) { |
|
return obj.__get__(prop); |
|
} |
|
return obj[prop]; |
|
} |
|
function set(obj,prop,value) { |
|
// basic set by index function |
|
prop = '' + prop; |
|
get(obj,prop); // test it |
|
if(obj.__set) { |
|
return obj.__set(prop); |
|
} |
|
obj[prop] = value; |
|
return value; |
|
} |
|
function forEach(obj,fun) { |
|
// short syntax iterator function |
|
if(typeof fun != "function"){ |
|
throw new TypeError(); |
|
} |
|
if("length" in obj) { |
|
// do arrays the fast way |
|
if(obj.__get__) { |
|
// use the catch getter |
|
var len = obj.__get__('length'); |
|
for (var i = 0; i < len; i++) { |
|
if(i in obj) { |
|
fun.call(obj, obj.__get__(i), i, obj); |
|
} |
|
} |
|
} |
|
else { |
|
// fast |
|
len = obj.length; |
|
for (i = 0; i < len; i++) { |
|
if(i in obj) { |
|
fun.call(obj, obj[i], i, obj); |
|
} |
|
} |
|
} |
|
} |
|
else { |
|
// for each an object |
|
for (i in obj) { |
|
fun.call(obj, get(obj,i), i, obj); |
|
} |
|
} |
|
} |
|
function Class(/*Function*/superclass, /*Object*/properties, /*Object*/classProperties) { |
|
// summary: |
|
// A safe class constructor |
|
// |
|
// superclass: |
|
// There may be zero or more superclass arguments. The constructed class |
|
// will inherit from any provided superclasses, protypically from the first, |
|
// via mixin for the subsequent. Later arguments |
|
// will override properties/methods from earlier arguments |
|
// |
|
// properties: |
|
// The constructed |
|
// "class" will also have the methods/properties defined in this argument. |
|
// These methods may utilize the <em>this</em> operator, and they |
|
// are only the code that has access to <em>this</em>. Inner functions |
|
// are also prohibited from using <em>this</em>. |
|
// |
|
// If no superclasses are provided, this object will be the prototype of the |
|
// constructed class (no copying |
|
// will be done). Consequently you can "beget" by calling new (Class(obj)). |
|
// All methods are "bound", each call results in |this| safety checking call. |
|
// |
|
// classProperties: |
|
// This properties will be copied to the new class function. |
|
// |
|
// Note that neither dojo.declare nor dojo.extend are acceptable class constructors as |
|
// they are completely unsecure. This class constructor is conceptually based on declare |
|
// but also somewhat influenced by base2, prototype, YUI, resig's patterns, etc. |
|
// |
|
// example: |
|
// | var Car = Class({drive:function(speed) { ... } ); // create a Car class with a "drive" method |
|
// | var FastCar = Class(Car,{driveFast: function(speed) { return this.drive(2 * speed); } }); // create a FastCar that extends Car |
|
// | var fastCar = new FastCar; // instantiate |
|
// | fastCar.driveFast(50); // call a method |
|
// | var driveFast = fastCar.driveFast; |
|
// | var driveFast(50); // this will throw an error, the method can be used with an object that is not an instance of FastCar |
|
var proto,superConstructor,ourConstructor; |
|
var arg; |
|
for (var i = 0, l = arguments.length; typeof (arg = arguments[i]) == 'function' && i < l; i++) { |
|
// go through each superclass argument |
|
if(proto) { // we have a prototype now, we must mixin now |
|
mixin(proto,arg.prototype); |
|
} |
|
else { |
|
// this is the first argument, so we can define the prototype ourselves |
|
// link up the prototype chain to the superclass's prototype, so we are a subtype |
|
superConstructor = arg; |
|
var F = function() {}; |
|
F.prototype = arg.prototype; |
|
proto = new F; |
|
} |
|
} |
|
|
|
if(arg) { // the next object should be the properties |
|
// apply binding checking on all the functions |
|
for (var j in arg) { |
|
// TODO: check on non-enumerables? |
|
var value = arg[j]; |
|
if(typeof value == 'function') { |
|
arg[j] = function() { |
|
if(this instanceof Class){ |
|
return arguments.callee.__rawMethod__.apply(this,arguments); |
|
} |
|
throw new Error("Method called on wrong object"); |
|
}; |
|
arg[j].__rawMethod__ = value; // may want to use this for reconstruction and toString,valueOf |
|
} |
|
} |
|
if(arg.hasOwnProperty('constructor')) { |
|
ourConstructor = arg.constructor; |
|
} |
|
} |
|
proto = proto ? mixin(proto,arg) : arg; // if there is no proto yet, we can use the provided object |
|
function Class() { |
|
// the super class may not have been constructed using the same technique, we will just call the constructor |
|
if(superConstructor){ |
|
superConstructor.apply(this,arguments); |
|
} |
|
if(ourConstructor){ |
|
ourConstructor.apply(this,arguments); |
|
} |
|
} |
|
mixin(Class,arguments[i]); // the optional second object adds properties to the class |
|
proto.constructor = Class; |
|
Class.prototype = proto; |
|
return Class; |
|
} |
|
function checkString(func){ |
|
if(typeof func != 'function') { |
|
throw new Error("String is not allowed in setTimeout/setInterval"); |
|
} |
|
} |
|
function setTimeout(func,time) { |
|
// sandboxed setTimeout |
|
checkString(func); |
|
return oldTimeout(func,time); |
|
} |
|
function setInterval(func,time) { |
|
// sandboxed setInterval |
|
checkString(func); |
|
return oldInterval(func,time); |
|
} |
|
function evaluate(script){ |
|
// sandboxed eval |
|
return wrap.evaluate(script); |
|
} |
|
var load = wrap.load = function(url){ |
|
// provides a loader function for the sandbox |
|
if (url.match(/^[\w\s]*:/)){ |
|
throw new Error("Access denied to cross-site requests"); |
|
} |
|
return xhrGet({url:(new dojo._Url(wrap.rootUrl,url))+'',secure:true}); |
|
} |
|
wrap.evaluate = function(script){ |
|
//if(!alreadyValidated) { |
|
dojox.secure.capability.validate(script,safeCalls, // the safe dojo library and standard operators |
|
{document:1,element:1}); // these are secured DOM starting points |
|
|
|
//} |
|
if(script.match(/^\s*[\[\{]/)) { |
|
var result = eval('(' + script + ')'); |
|
// TODO: call render on result? |
|
} |
|
else { |
|
eval(script); |
|
} |
|
//eval('wrap.evaluate=('+arguments.callee.toString()+')'); // yeah, recursive scoping; |
|
}; |
|
return /*===== dojo.declare("dojox.secure.__Sandbox", null, =====*/ { // dojox.secure.__Sandbox |
|
loadJS : function(url){ |
|
// summary: |
|
// Loads the script from the given URL using XHR (assuming |
|
// a plugin system is in place for cross-site requests) within the sandbox |
|
// |
|
// url: |
|
// The url of the script to load |
|
wrap.rootUrl = url; |
|
return xhrGet({url:url,secure:true}).addCallback(function(result) { |
|
evaluate(result,element /*If we get the results with a secure proxy, we would call put true here */); |
|
}); |
|
}, |
|
loadHTML : function(url){ |
|
// summary: |
|
// Loads the web page from the provided URL using XHR (assuming the |
|
// plugin system is in place) within the sandbox. All scripts within the web |
|
// page will also be sandboxed. |
|
// |
|
// url: |
|
// The url of the web page to load |
|
|
|
wrap.rootUrl = url; |
|
return xhrGet({url:url,secure:true}).addCallback(function(result){ |
|
element.innerHTML = result; |
|
}); |
|
}, |
|
evaluate : function(script){ |
|
// summary: |
|
// Evaluates the given script within the sandbox |
|
// |
|
// script: |
|
// The JavaScript text to evaluate |
|
return wrap.evaluate(script); |
|
} |
|
// TODO: could add something for pre-validated scripts |
|
}/*===== ) =====*/; |
|
}; |
|
})(); |
|
dojox.secure._safeDojoFunctions = function(element,wrap) { |
|
// Creates a safe subset of Dojo core library |
|
var safeFunctions = ["mixin","require","isString","isArray","isFunction","isObject","isArrayLike","isAlien", |
|
"hitch","delegate","partial","trim","disconnect","subscribe","unsubscribe","Deferred","toJson","style","attr"]; |
|
//var domFunctions = ["clone","byId"]; |
|
var doc = element.ownerDocument; |
|
var unwrap = dojox.secure.unwrap; |
|
dojo.NodeList.prototype.addContent.safetyCheck = function(content){ |
|
wrap.safeHTML(content); |
|
}; |
|
dojo.NodeList.prototype.style.safetyCheck = function(name,value){ |
|
if(name=='behavior'){ |
|
throw new Error("Can not set behavior"); |
|
} |
|
wrap.safeCSS(value); |
|
}; |
|
dojo.NodeList.prototype.attr.safetyCheck = function(name,value){ |
|
if (value && (name == 'src' || name == 'href' || name=='style')){ |
|
throw new Error("Illegal to set " + name); |
|
} |
|
}; |
|
var safe = { |
|
query : function(query,root) { |
|
return wrap(dojo.query(query,unwrap(root || element))); // wrap the NodeList |
|
}, |
|
connect: function(el,event) { |
|
var obj = el; |
|
arguments[0] = unwrap(el); |
|
if(obj!=arguments[0] && event.substring(0,2) != 'on'){ |
|
// it is probably an element, and it doesn't look like an event handler, probably not safe |
|
throw new Error("Invalid event name for element"); |
|
} |
|
return dojo.connect.apply(dojo,arguments); |
|
}, |
|
body : function() { |
|
return element; |
|
}, |
|
byId : function(id) { |
|
return element.ownerDocument.getElementById(id); // use the safe document |
|
}, |
|
fromJson : function(str) { |
|
// make sure it is safe before passing it to the unsafe dojo.fromJson |
|
dojox.secure.capability.validate(str,[],{}); |
|
return dojo.fromJson(str); |
|
} |
|
}; |
|
for (var i = 0; i < safeFunctions.length; i++) { |
|
safe[safeFunctions[i]] = dojo[safeFunctions[i]]; |
|
} |
|
return safe; |
|
}; |
|
|
|
|