/*
Copyright (c) 2004-2011, The Dojo Foundation All Rights Reserved.
Available via Academic Free License >= 2.1 OR the modified BSD license.
see: http://dojotoolkit.org/license for details
*/
if(!dojo._hasResource["dojox.secure.sandbox"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
dojo._hasResource["dojox.secure.sandbox"] = true;
dojo.provide("dojox.secure.sandbox");
dojo.require("dojox.secure.DOM");
dojo.require("dojox.secure.capability");
dojo.require("dojo.NodeList-fx");
(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;
};
}