diff options
Diffstat (limited to 'js/dojo/dojox/secure')
| -rw-r--r-- | js/dojo/dojox/secure/DOM.js | 262 | ||||
| -rw-r--r-- | js/dojo/dojox/secure/README | 50 | ||||
| -rw-r--r-- | js/dojo/dojox/secure/capability.js | 134 | ||||
| -rw-r--r-- | js/dojo/dojox/secure/fromJson.js | 245 | ||||
| -rw-r--r-- | js/dojo/dojox/secure/sandbox.js | 348 |
5 files changed, 1039 insertions, 0 deletions
diff --git a/js/dojo/dojox/secure/DOM.js b/js/dojo/dojox/secure/DOM.js new file mode 100644 index 0000000..9bff85b --- /dev/null +++ b/js/dojo/dojox/secure/DOM.js @@ -0,0 +1,262 @@ +//>>built +// wrapped by build app +define("dojox/secure/DOM", ["dijit","dojo","dojox","dojo/require!dojox/lang/observable"], function(dijit,dojo,dojox){ +dojo.provide("dojox.secure.DOM"); +dojo.require("dojox.lang.observable"); + +dojox.secure.DOM = function(element){ + function safeNode(node){ + if(!node){ + return node; + } + var parent = node; + do { + if(parent == element){ + return wrap(node); + } + } while((parent = parent.parentNode)); + return null; + } + function wrap(result){ + if(result){ + if(result.nodeType){ + // wrap the node + var wrapped = nodeObserver(result); + if(result.nodeType == 1 && typeof wrapped.style == 'function'){ // if it is a function, that means it is holding a slot for us, now we will override it + wrapped.style = styleObserver(result.style); + wrapped.ownerDocument = safeDoc; + wrapped.childNodes = {__get__:function(i){ + return wrap(result.childNodes[i]); + }, + length:0 + }; + //TODO: maybe add attributes + } + return wrapped; + } + if(result && typeof result == 'object'){ + if(result.__observable){ + // we have already wrapped it, this helps prevent circular/infinite loops + return result.__observable; + } + // wrap the node list + wrapped = result instanceof Array ? [] : {}; + result.__observable = wrapped; + for(var i in result){ + if (i != '__observable'){ + wrapped[i] = wrap(result[i]); + } + } + wrapped.data__ = result; + + return wrapped; + } + if(typeof result == 'function'){ + var unwrap = function(result){ + if(typeof result == 'function'){ + // if untrusted code passes a function to trusted code, we want the trusted code to be + // able to execute it and have the arguments automatically wrapped + return function(){ + for (var i = 0; i < arguments.length; i++){ + arguments[i] = wrap(arguments[i]); + } + return unwrap(result.apply(wrap(this),arguments)); + } + } + return dojox.secure.unwrap(result); + }; + // when we wrap a function we make it so that we can untrusted code can execute + // the function and the arguments will be unwrapped for the trusted code + return function(){ + if(result.safetyCheck){ + result.safetyCheck.apply(unwrap(this),arguments); + } + for (var i = 0; i < arguments.length; i++){ + arguments[i] = unwrap(arguments[i]); + } + return wrap(result.apply(unwrap(this),arguments)); + } + } + } + return result; + } + unwrap = dojox.secure.unwrap; + + function safeCSS(css){ + css += ''; // make sure it is a string + if(css.match(/behavior:|content:|javascript:|binding|expression|\@import/)){ + throw new Error("Illegal CSS"); + } + var id = element.id || (element.id = "safe" + ('' + Math.random()).substring(2)); + return css.replace(/(\}|^)\s*([^\{]*\{)/g,function(t,a,b){ // put all the styles in the context of the id of the sandbox + return a + ' #' + id + ' ' + b; // need to remove body and html references something like: .replace(/body/g,''); but that would break mybody... + }); + } + function safeURL(url){ + // test a url to see if it is safe + if(url.match(/:/) && !url.match(/^(http|ftp|mailto)/)){ + throw new Error("Unsafe URL " + url); + } + } + function safeElement(el){ + // test an element to see if it is safe + if(el && el.nodeType == 1){ + if(el.tagName.match(/script/i)){ + var src = el.src; + if (src && src != ""){ + // load the src and evaluate it safely + el.parentNode.removeChild(el); + dojo.xhrGet({url:src,secure:true}).addCallback(function(result){ + safeDoc.evaluate(result); + }); + } + else{ + //evaluate the script safely and remove it + var script = el.innerHTML; + el.parentNode.removeChild(el); + wrap.evaluate(script); + } + } + if(el.tagName.match(/link/i)){ + throw new Error("illegal tag"); + } + if(el.tagName.match(/style/i)){ + var setCSS = function(cssStr){ + if(el.styleSheet){// IE + el.styleSheet.cssText = cssStr; + } else {// w3c + var cssText = doc.createTextNode(cssStr); + if (el.childNodes[0]) + el.replaceChild(cssText,el.childNodes[0]) + else + el.appendChild(cssText); + } + + } + src = el.src; + if(src && src != ""){ + alert('src' + src); + // try to load it by url and safely load it + el.src = null; + dojo.xhrGet({url:src,secure:true}).addCallback(function(result){ + setCSS(safeCSS(result)); + }); + } + setCSS(safeCSS(el.innerHTML)); + } + if(el.style){ + safeCSS(el.style.cssText); + } + if(el.href){ + safeURL(el.href); + } + if(el.src){ + safeURL(el.src); + } + var attr,i = 0; + while ((attr=el.attributes[i++])){ + if(attr.name.substring(0,2)== "on" && attr.value != "null" && attr.value != ""){ // must remove all the event handlers + throw new Error("event handlers not allowed in the HTML, they must be set with element.addEventListener"); + } + } + var children = el.childNodes; + for (var i =0, l = children.length; i < l; i++){ + safeElement(children[i]); + } + } + } + function safeHTML(html){ + var div = document.createElement("div"); + if(html.match(/<object/i)) + throw new Error("The object tag is not allowed"); + div.innerHTML = html; // this is safe with an unattached node + safeElement(div); + return div; + } + var doc = element.ownerDocument; + var safeDoc = { + getElementById : function(id){ + return safeNode(doc.getElementById(id)); + }, + createElement : function(name){ + return wrap(doc.createElement(name)); + }, + createTextNode : function(name){ + return wrap(doc.createTextNode(name)); + }, + write : function(str){ + var div = safeHTML(str); + while (div.childNodes.length){ + // move all these children to the main node + element.appendChild(div.childNodes[0]); + } + } + }; + safeDoc.open = safeDoc.close = function(){}; // no-op functions + var setters = { + innerHTML : function(node,value){ + console.log('setting innerHTML'); + node.innerHTML = safeHTML(value).innerHTML; + } + }; + setters.outerHTML = function(node,value){ + throw new Error("Can not set this property"); + }; // blocked + function domChanger(name,newNodeArg){ + return function(node,args){ + safeElement(args[newNodeArg]); // check to make sure the new node is safe + return node[name](args[0]);// execute the method + }; + } + var invokers = { + appendChild : domChanger("appendChild",0), + insertBefore : domChanger("insertBefore",0), + replaceChild : domChanger("replaceChild",1), + cloneNode : function(node,args){ + return node.cloneNode(args[0]); + }, + addEventListener : function(node,args){ + dojo.connect(node,'on' + args[0],this,function(event){ + event = nodeObserver(event || window.event); + args[1].call(this,event); + }); + } + }; + invokers.childNodes = invokers.style = invokers.ownerDocument = function(){}; // this is a trick to get these property slots available, they will be overridden + function makeObserver(setter){ // we make two of these, but the setter for style nodes is different + return dojox.lang.makeObservable( + function(node, prop){ + var result; + return node[prop]; + },setter, + function(wrapper, node, methodName, args){ + for (var i = 0; i < args.length; i++){ + args[i] = unwrap(args[i]); + } + if(invokers[methodName]){ + return wrap(invokers[methodName].call(wrapper,node,args)); + } + return wrap(node[methodName].apply(node,args)); + },invokers); + } + var nodeObserver = makeObserver(function(node, prop, value){ + if(setters[prop]){ + setters[prop](node,value); + } + node[prop] = value; + }); + var blockedStyles = {behavior:1,MozBinding:1}; + var styleObserver = makeObserver(function(node, prop, value){ + if(!blockedStyles[prop]){ + node[prop] = safeCSS(value); + } + }); + wrap.safeHTML = safeHTML; + wrap.safeCSS = safeCSS; + return wrap; +}; +dojox.secure.unwrap = function unwrap(result){ + return (result && result.data__) || result; +}; + +}); diff --git a/js/dojo/dojox/secure/README b/js/dojo/dojox/secure/README new file mode 100644 index 0000000..283c829 --- /dev/null +++ b/js/dojo/dojox/secure/README @@ -0,0 +1,50 @@ +------------------------------------------------------------------------------- +DojoX Secure +------------------------------------------------------------------------------- +Version 0.1 +Release date: 07/04/2008 +------------------------------------------------------------------------------- +Project state: +alpha +------------------------------------------------------------------------------- +Project authors + Kris Zyp (kris@sitepen.com) +------------------------------------------------------------------------------- +Project description + +DojoX Secure is a collection of tools for security, in particular for working with +untrusted data and code. The following tools are a part of DojoX Secure: + +dojox.secure.capability - Object-capability JavaScript validation. This is +a validator to run before eval to ensure that a script can't access or modify +any objects outside of those specifically provided to it. + +dojox.secure.sandbox - Provides support for loading web pages, JSON, and scripts +from other domains using XHR (and XHR plugins) with a safe subset library and +sandboxed access to the DOM. + +dojox.secure.DOM - Provides a DOM facade that restricts access to +a specified subtree of the DOM. The DOM facade uses getters/setters +and lettables to emulate the DOM API. + +dojox.secure.OAuth - Future project to provide an implementation of OAuth. + + +------------------------------------------------------------------------------- +Dependencies: + +Dojo Core (package loader). +------------------------------------------------------------------------------- +Documentation + +------------------------------------------------------------------------------- +Installation instructions + +Grab the following from the Dojo SVN Repository: +http://svn.dojotoolkit.org/var/src/dojo/dojox/trunk/secure/* + +Install into the following directory structure: +/dojox/secure/ + +...which should be at the same level as your Dojo checkout. + diff --git a/js/dojo/dojox/secure/capability.js b/js/dojo/dojox/secure/capability.js new file mode 100644 index 0000000..d2a2629 --- /dev/null +++ b/js/dojo/dojox/secure/capability.js @@ -0,0 +1,134 @@ +//>>built +// wrapped by build app +define("dojox/secure/capability", ["dijit","dojo","dojox"], function(dijit,dojo,dojox){ +dojo.provide("dojox.secure.capability"); + +dojox.secure.badProps = /^__|^(apply|call|callee|caller|constructor|eval|prototype|this|unwatch|valueOf|watch)$|__$/; +dojox.secure.capability = { + keywords: ["break", "case", "catch", "const", "continue","debugger", "default", "delete", "do", + "else", "enum","false", "finally", "for", "function","if", "in", "instanceof", "new", + "null","yield","return", "switch", + "throw", "true", "try", "typeof", "var", "void", "while"], + validate : function(/*string*/script,/*Array*/safeLibraries,/*Object*/safeGlobals) { + // summary: + // pass in the text of a script. If it passes and it can be eval'ed, it should be safe. + // Note that this does not do full syntax checking, it relies on eval to reject invalid scripts. + // There are also known false rejections: + // Nesting vars inside blocks will not declare the variable for the outer block + // Named functions are not treated as declaration so they are generally not allowed unless the name is declared with a var. + // Var declaration that involve multiple comma delimited variable assignments are not accepted + // + // script: + // the script to execute + // + // safeLibraries: + // The safe libraries that can be called (the functions can not be access/modified by the untrusted code, only called) + // + // safeGlobals: + // These globals can be freely interacted with by the untrusted code + + + var keywords = this.keywords; + for (var i = 0; i < keywords.length; i++) { + safeGlobals[keywords[i]]=true; + } + var badThis = "|this| keyword in object literal without a Class call"; + var blocks = []; // keeps track of the outer references from each inner block + if(script.match(/[\u200c-\u200f\u202a-\u202e\u206a-\u206f\uff00-\uffff]/)){ + throw new Error("Illegal unicode characters detected"); + } + if(script.match(/\/\*@cc_on/)){ + throw new Error("Conditional compilation token is not allowed"); + } + script = script.replace(/\\["'\\\/bfnrtu]/g, '@'). // borrows some tricks from json.js + // now clear line comments, block comments, regular expressions, and strings. + // By doing it all at once, the regular expression uses left to right parsing, and the most + // left token is read first. It is also more compact. + replace(/\/\/.*|\/\*[\w\W]*?\*\/|("[^"]*")|('[^']*')/g,function(t) { + return t.match(/^\/\/|^\/\*/) ? ' ' : '0'; // comments are replaced with a space, strings and regex are replaced with a single safe token (0) + }). + replace(/\.\s*([a-z\$_A-Z][\w\$_]*)|([;,{])\s*([a-z\$_A-Z][\w\$_]*\s*):/g,function(t,prop,prefix,key) { + // find all the dot property references, all the object literal keys, and labels + prop = prop || key; + if(/^__|^(apply|call|callee|caller|constructor|eval|prototype|this|unwatch|valueOf|watch)$|__$/.test(prop)){ + throw new Error("Illegal property name " + prop); + } + return (prefix && (prefix + "0:")) || '~'; // replace literal keys with 0: and replace properties with the innocuous ~ + }); + script.replace(/([^\[][\]\}]\s*=)|((\Wreturn|\S)\s*\[\s*\+?)|([^=!][=!]=[^=])/g,function(oper) {// check for illegal operator usages + if(!oper.match(/((\Wreturn|[=\&\|\:\?\,])\s*\[)|\[\s*\+$/)){ // the whitelist for [ operator for array initializer context or [+num] syntax + throw new Error("Illegal operator " + oper.substring(1)); + } + }); + script = script.replace(new RegExp("(" + safeLibraries.join("|") + ")[\\s~]*\\(","g"),function(call) { // find library calls and make them look safe + return "new("; // turn into a known safe call + }); + function findOuterRefs(block,func) { + var outerRefs = {}; + block.replace(/#\d+/g,function(b) { // graft in the outer references from the inner scopes + var refs = blocks[b.substring(1)]; + for (var i in refs) { + if(i == badThis) { + throw i; + } + if(i == 'this' && refs[':method'] && refs['this'] == 1) { + // if we are in an object literal the function may be a bindable method, this must only be in the local scope + i = badThis; + } + if(i != ':method'){ + outerRefs[i] = 2; // the reference is more than just local + } + } + }); + block.replace(/(\W|^)([a-z_\$A-Z][\w_\$]*)/g,function(t,a,identifier) { // find all the identifiers + if(identifier.charAt(0)=='_'){ + throw new Error("Names may not start with _"); + } + outerRefs[identifier] = 1; + }); + return outerRefs; + } + var newScript,outerRefs; + function parseBlock(t,func,a,b,params,block) { + block.replace(/(^|,)0:\s*function#(\d+)/g,function(t,a,b) { // find functions in object literals + // note that if named functions are allowed, it could be possible to have label: function name() {} which is a security breach + var refs = blocks[b]; + refs[':method'] = 1;//mark it as a method + }); + block = block.replace(/(^|[^_\w\$])Class\s*\(\s*([_\w\$]+\s*,\s*)*#(\d+)/g,function(t,p,a,b) { // find Class calls + var refs = blocks[b]; + delete refs[badThis]; + return (p||'') + (a||'') + "#" + b; + }); + outerRefs = findOuterRefs(block,func); // find the variables in this block + function parseVars(t,a,b,decl) { // find var decls + decl.replace(/,?([a-z\$A-Z][_\w\$]*)/g,function(t,identifier) { + if(identifier == 'Class'){ + throw new Error("Class is reserved"); + } + delete outerRefs[identifier]; // outer reference is safely referenced here + }); + } + + if(func) { + parseVars(t,a,a,params); // the parameters are declare variables + } + block.replace(/(\W|^)(var) ([ \t,_\w\$]+)/g,parseVars); // and vars declare variables + // FIXME: Give named functions #name syntax so they can be detected as vars in outer scopes (but be careful of nesting) + return (a || '') + (b || '') + "#" + (blocks.push(outerRefs)-1); // return a block reference so the outer block can fetch it + } + do { + // get all the blocks, starting with inside and moving out, capturing the parameters of functions and catchs as variables along the way + newScript = script.replace(/((function|catch)(\s+[_\w\$]+)?\s*\(([^\)]*)\)\s*)?{([^{}]*)}/g, parseBlock); + } + while(newScript != script && (script = newScript)); // keep going until we can't find anymore blocks + parseBlock(0,0,0,0,0,script); //findOuterRefs(script); // find the references in the outside scope + for (i in outerRefs) { + if(!(i in safeGlobals)) { + throw new Error("Illegal reference to " + i); + } + } + + } +}; +}); diff --git a/js/dojo/dojox/secure/fromJson.js b/js/dojo/dojox/secure/fromJson.js new file mode 100644 index 0000000..263be3a --- /dev/null +++ b/js/dojo/dojox/secure/fromJson.js @@ -0,0 +1,245 @@ +//>>built +// wrapped by build app +define("dojox/secure/fromJson", ["dijit","dojo","dojox"], function(dijit,dojo,dojox){ +dojo.provide("dojox.secure.fromJson"); + +// Used with permission from Mike Samuel of Google (has CCLA), from the json-sans-eval project: +// http://code.google.com/p/json-sans-eval/ +// Mike Samuel <mikesamuel@gmail.com> + + + +dojox.secure.fromJson = typeof JSON != "undefined" ? JSON.parse : +// summary: +// Parses a string of well-formed JSON text. +// description: +// Parses a string of well-formed JSON text. If the input is not well-formed, +// then behavior is undefined, but it is +// deterministic and is guaranteed not to modify any object other than its +// return value. +// +// This does not use `eval` so is less likely to have obscure security bugs than +// json2.js. +// It is optimized for speed, so is much faster than json_parse.js. +// +// This library should be used whenever security is a concern (when JSON may +// come from an untrusted source), speed is a concern, and erroring on malformed +// JSON is *not* a concern. +// +// json2.js is very fast, but potentially insecure since it calls `eval` to +// parse JSON data, so an attacker might be able to supply strange JS that +// looks like JSON, but that executes arbitrary javascript. +// +// To configure dojox.secure.fromJson as the JSON parser for all Dojo +// JSON parsing, simply do: +// | dojo.require("dojox.secure.fromJson"); +// | dojo.fromJson = dojox.secure.fromJson; +// or alternately you could configure dojox.secure.fromJson to only handle +// XHR responses: +// | dojo._contentHandlers.json = function(xhr){ +// | return dojox.secure.fromJson.fromJson(xhr.responseText); +// | }; +// +// json: String +// per RFC 4627 +// optReviver: Function (this:Object, string, *) +// optional function +// that reworks JSON objects post-parse per Chapter 15.12 of EcmaScript3.1. +// If supplied, the function is called with a string key, and a value. +// The value is the property of 'this'. The reviver should return +// the value to use in its place. So if dates were serialized as +// {@code { "type": "Date", "time": 1234 }}, then a reviver might look like +// {@code +// function (key, value) { +// if (value && typeof value === 'object' && 'Date' === value.type) { +// return new Date(value.time); +// } else { +// return value; +// } +// }}. +// If the reviver returns {@code undefined} then the property named by key +// will be deleted from its container. +// {@code this} is bound to the object containing the specified property. +// returns: {Object|Array} +(function () { + var number + = '(?:-?\\b(?:0|[1-9][0-9]*)(?:\\.[0-9]+)?(?:[eE][+-]?[0-9]+)?\\b)'; + var oneChar = '(?:[^\\0-\\x08\\x0a-\\x1f\"\\\\]' + + '|\\\\(?:[\"/\\\\bfnrt]|u[0-9A-Fa-f]{4}))'; + var string = '(?:\"' + oneChar + '*\")'; + + // Will match a value in a well-formed JSON file. + // If the input is not well-formed, may match strangely, but not in an unsafe + // way. + // Since this only matches value tokens, it does not match whitespace, colons, + // or commas. + var jsonToken = new RegExp( + '(?:false|true|null|[\\{\\}\\[\\]]' + + '|' + number + + '|' + string + + ')', 'g'); + + // Matches escape sequences in a string literal + var escapeSequence = new RegExp('\\\\(?:([^u])|u(.{4}))', 'g'); + + // Decodes escape sequences in object literals + var escapes = { + '"': '"', + '/': '/', + '\\': '\\', + 'b': '\b', + 'f': '\f', + 'n': '\n', + 'r': '\r', + 't': '\t' + }; + function unescapeOne(_, ch, hex) { + return ch ? escapes[ch] : String.fromCharCode(parseInt(hex, 16)); + } + + // A non-falsy value that coerces to the empty string when used as a key. + var EMPTY_STRING = new String(''); + var SLASH = '\\'; + + // Constructor to use based on an open token. + var firstTokenCtors = { '{': Object, '[': Array }; + + var hop = Object.hasOwnProperty; + + return function (json, opt_reviver) { + // Split into tokens + var toks = json.match(jsonToken); + // Construct the object to return + var result; + var tok = toks[0]; + var topLevelPrimitive = false; + if ('{' === tok) { + result = {}; + } else if ('[' === tok) { + result = []; + } else { + // The RFC only allows arrays or objects at the top level, but the JSON.parse + // defined by the EcmaScript 5 draft does allow strings, booleans, numbers, and null + // at the top level. + result = []; + topLevelPrimitive = true; + } + + // If undefined, the key in an object key/value record to use for the next + // value parsed. + var key; + // Loop over remaining tokens maintaining a stack of uncompleted objects and + // arrays. + var stack = [result]; + for (var i = 1 - topLevelPrimitive, n = toks.length; i < n; ++i) { + tok = toks[i]; + + var cont; + switch (tok.charCodeAt(0)) { + default: // sign or digit + cont = stack[0]; + cont[key || cont.length] = +(tok); + key = void 0; + break; + case 0x22: // '"' + tok = tok.substring(1, tok.length - 1); + if (tok.indexOf(SLASH) !== -1) { + tok = tok.replace(escapeSequence, unescapeOne); + } + cont = stack[0]; + if (!key) { + if (cont instanceof Array) { + key = cont.length; + } else { + key = tok || EMPTY_STRING; // Use as key for next value seen. + break; + } + } + cont[key] = tok; + key = void 0; + break; + case 0x5b: // '[' + cont = stack[0]; + stack.unshift(cont[key || cont.length] = []); + key = void 0; + break; + case 0x5d: // ']' + stack.shift(); + break; + case 0x66: // 'f' + cont = stack[0]; + cont[key || cont.length] = false; + key = void 0; + break; + case 0x6e: // 'n' + cont = stack[0]; + cont[key || cont.length] = null; + key = void 0; + break; + case 0x74: // 't' + cont = stack[0]; + cont[key || cont.length] = true; + key = void 0; + break; + case 0x7b: // '{' + cont = stack[0]; + stack.unshift(cont[key || cont.length] = {}); + key = void 0; + break; + case 0x7d: // '}' + stack.shift(); + break; + } + } + // Fail if we've got an uncompleted object. + if (topLevelPrimitive) { + if (stack.length !== 1) { throw new Error(); } + result = result[0]; + } else { + if (stack.length) { throw new Error(); } + } + + if (opt_reviver) { + // Based on walk as implemented in http://www.json.org/json2.js + var walk = function (holder, key) { + var value = holder[key]; + if (value && typeof value === 'object') { + var toDelete = null; + for (var k in value) { + if (hop.call(value, k) && value !== holder) { + // Recurse to properties first. This has the effect of causing + // the reviver to be called on the object graph depth-first. + + // Since 'this' is bound to the holder of the property, the + // reviver can access sibling properties of k including ones + // that have not yet been revived. + + // The value returned by the reviver is used in place of the + // current value of property k. + // If it returns undefined then the property is deleted. + var v = walk(value, k); + if (v !== void 0) { + value[k] = v; + } else { + // Deleting properties inside the loop has vaguely defined + // semantics in ES3 and ES3.1. + if (!toDelete) { toDelete = []; } + toDelete.push(k); + } + } + } + if (toDelete) { + for (var i = toDelete.length; --i >= 0;) { + delete value[toDelete[i]]; + } + } + } + return opt_reviver.call(holder, key, value); + }; + result = walk({ '': result }, ''); + } + + return result; + }; +})(); +}); diff --git a/js/dojo/dojox/secure/sandbox.js b/js/dojo/dojox/secure/sandbox.js new file mode 100644 index 0000000..70cc782 --- /dev/null +++ b/js/dojo/dojox/secure/sandbox.js @@ -0,0 +1,348 @@ +//>>built +// wrapped by build app +define("dojox/secure/sandbox", ["dijit","dojo","dojox","dojo/require!dojox/secure/DOM,dojox/secure/capability,dojo/NodeList-fx,dojo/_base/url"], function(dijit,dojo,dojox){ +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; +}; + + +}); |
