DOM.js 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268
  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.DOM"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
  7. dojo._hasResource["dojox.secure.DOM"] = true;
  8. dojo.provide("dojox.secure.DOM");
  9. dojo.require("dojox.lang.observable");
  10. dojox.secure.DOM = function(element){
  11. function safeNode(node){
  12. if(!node){
  13. return node;
  14. }
  15. var parent = node;
  16. do {
  17. if(parent == element){
  18. return wrap(node);
  19. }
  20. } while((parent = parent.parentNode));
  21. return null;
  22. }
  23. function wrap(result){
  24. if(result){
  25. if(result.nodeType){
  26. // wrap the node
  27. var wrapped = nodeObserver(result);
  28. 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
  29. wrapped.style = styleObserver(result.style);
  30. wrapped.ownerDocument = safeDoc;
  31. wrapped.childNodes = {__get__:function(i){
  32. return wrap(result.childNodes[i]);
  33. },
  34. length:0
  35. };
  36. //TODO: maybe add attributes
  37. }
  38. return wrapped;
  39. }
  40. if(result && typeof result == 'object'){
  41. if(result.__observable){
  42. // we have already wrapped it, this helps prevent circular/infinite loops
  43. return result.__observable;
  44. }
  45. // wrap the node list
  46. wrapped = result instanceof Array ? [] : {};
  47. result.__observable = wrapped;
  48. for(var i in result){
  49. if (i != '__observable'){
  50. wrapped[i] = wrap(result[i]);
  51. }
  52. }
  53. wrapped.data__ = result;
  54. return wrapped;
  55. }
  56. if(typeof result == 'function'){
  57. var unwrap = function(result){
  58. if(typeof result == 'function'){
  59. // if untrusted code passes a function to trusted code, we want the trusted code to be
  60. // able to execute it and have the arguments automatically wrapped
  61. return function(){
  62. for (var i = 0; i < arguments.length; i++){
  63. arguments[i] = wrap(arguments[i]);
  64. }
  65. return unwrap(result.apply(wrap(this),arguments));
  66. }
  67. }
  68. return dojox.secure.unwrap(result);
  69. };
  70. // when we wrap a function we make it so that we can untrusted code can execute
  71. // the function and the arguments will be unwrapped for the trusted code
  72. return function(){
  73. if(result.safetyCheck){
  74. result.safetyCheck.apply(unwrap(this),arguments);
  75. }
  76. for (var i = 0; i < arguments.length; i++){
  77. arguments[i] = unwrap(arguments[i]);
  78. }
  79. return wrap(result.apply(unwrap(this),arguments));
  80. }
  81. }
  82. }
  83. return result;
  84. }
  85. unwrap = dojox.secure.unwrap;
  86. function safeCSS(css){
  87. css += ''; // make sure it is a string
  88. if(css.match(/behavior:|content:|javascript:|binding|expression|\@import/)){
  89. throw new Error("Illegal CSS");
  90. }
  91. var id = element.id || (element.id = "safe" + ('' + Math.random()).substring(2));
  92. return css.replace(/(\}|^)\s*([^\{]*\{)/g,function(t,a,b){ // put all the styles in the context of the id of the sandbox
  93. return a + ' #' + id + ' ' + b; // need to remove body and html references something like: .replace(/body/g,''); but that would break mybody...
  94. });
  95. }
  96. function safeURL(url){
  97. // test a url to see if it is safe
  98. if(url.match(/:/) && !url.match(/^(http|ftp|mailto)/)){
  99. throw new Error("Unsafe URL " + url);
  100. }
  101. }
  102. function safeElement(el){
  103. // test an element to see if it is safe
  104. if(el && el.nodeType == 1){
  105. if(el.tagName.match(/script/i)){
  106. var src = el.src;
  107. if (src && src != ""){
  108. // load the src and evaluate it safely
  109. el.parentNode.removeChild(el);
  110. dojo.xhrGet({url:src,secure:true}).addCallback(function(result){
  111. safeDoc.evaluate(result);
  112. });
  113. }
  114. else{
  115. //evaluate the script safely and remove it
  116. var script = el.innerHTML;
  117. el.parentNode.removeChild(el);
  118. wrap.evaluate(script);
  119. }
  120. }
  121. if(el.tagName.match(/link/i)){
  122. throw new Error("illegal tag");
  123. }
  124. if(el.tagName.match(/style/i)){
  125. var setCSS = function(cssStr){
  126. if(el.styleSheet){// IE
  127. el.styleSheet.cssText = cssStr;
  128. } else {// w3c
  129. var cssText = doc.createTextNode(cssStr);
  130. if (el.childNodes[0])
  131. el.replaceChild(cssText,el.childNodes[0])
  132. else
  133. el.appendChild(cssText);
  134. }
  135. }
  136. src = el.src;
  137. if(src && src != ""){
  138. alert('src' + src);
  139. // try to load it by url and safely load it
  140. el.src = null;
  141. dojo.xhrGet({url:src,secure:true}).addCallback(function(result){
  142. setCSS(safeCSS(result));
  143. });
  144. }
  145. setCSS(safeCSS(el.innerHTML));
  146. }
  147. if(el.style){
  148. safeCSS(el.style.cssText);
  149. }
  150. if(el.href){
  151. safeURL(el.href);
  152. }
  153. if(el.src){
  154. safeURL(el.src);
  155. }
  156. var attr,i = 0;
  157. while ((attr=el.attributes[i++])){
  158. if(attr.name.substring(0,2)== "on" && attr.value != "null" && attr.value != ""){ // must remove all the event handlers
  159. throw new Error("event handlers not allowed in the HTML, they must be set with element.addEventListener");
  160. }
  161. }
  162. var children = el.childNodes;
  163. for (var i =0, l = children.length; i < l; i++){
  164. safeElement(children[i]);
  165. }
  166. }
  167. }
  168. function safeHTML(html){
  169. var div = document.createElement("div");
  170. if(html.match(/<object/i))
  171. throw new Error("The object tag is not allowed");
  172. div.innerHTML = html; // this is safe with an unattached node
  173. safeElement(div);
  174. return div;
  175. }
  176. var doc = element.ownerDocument;
  177. var safeDoc = {
  178. getElementById : function(id){
  179. return safeNode(doc.getElementById(id));
  180. },
  181. createElement : function(name){
  182. return wrap(doc.createElement(name));
  183. },
  184. createTextNode : function(name){
  185. return wrap(doc.createTextNode(name));
  186. },
  187. write : function(str){
  188. var div = safeHTML(str);
  189. while (div.childNodes.length){
  190. // move all these children to the main node
  191. element.appendChild(div.childNodes[0]);
  192. }
  193. }
  194. };
  195. safeDoc.open = safeDoc.close = function(){}; // no-op functions
  196. var setters = {
  197. innerHTML : function(node,value){
  198. console.log('setting innerHTML');
  199. node.innerHTML = safeHTML(value).innerHTML;
  200. }
  201. };
  202. setters.outerHTML = function(node,value){
  203. throw new Error("Can not set this property");
  204. }; // blocked
  205. function domChanger(name,newNodeArg){
  206. return function(node,args){
  207. safeElement(args[newNodeArg]); // check to make sure the new node is safe
  208. return node[name](args[0]);// execute the method
  209. };
  210. }
  211. var invokers = {
  212. appendChild : domChanger("appendChild",0),
  213. insertBefore : domChanger("insertBefore",0),
  214. replaceChild : domChanger("replaceChild",1),
  215. cloneNode : function(node,args){
  216. return node.cloneNode(args[0]);
  217. },
  218. addEventListener : function(node,args){
  219. dojo.connect(node,'on' + args[0],this,function(event){
  220. event = nodeObserver(event || window.event);
  221. args[1].call(this,event);
  222. });
  223. }
  224. };
  225. invokers.childNodes = invokers.style = invokers.ownerDocument = function(){}; // this is a trick to get these property slots available, they will be overridden
  226. function makeObserver(setter){ // we make two of these, but the setter for style nodes is different
  227. return dojox.lang.makeObservable(
  228. function(node, prop){
  229. var result;
  230. return node[prop];
  231. },setter,
  232. function(wrapper, node, methodName, args){
  233. for (var i = 0; i < args.length; i++){
  234. args[i] = unwrap(args[i]);
  235. }
  236. if(invokers[methodName]){
  237. return wrap(invokers[methodName].call(wrapper,node,args));
  238. }
  239. return wrap(node[methodName].apply(node,args));
  240. },invokers);
  241. }
  242. var nodeObserver = makeObserver(function(node, prop, value){
  243. if(setters[prop]){
  244. setters[prop](node,value);
  245. }
  246. node[prop] = value;
  247. });
  248. var blockedStyles = {behavior:1,MozBinding:1};
  249. var styleObserver = makeObserver(function(node, prop, value){
  250. if(!blockedStyles[prop]){
  251. node[prop] = safeCSS(value);
  252. }
  253. });
  254. wrap.safeHTML = safeHTML;
  255. wrap.safeCSS = safeCSS;
  256. return wrap;
  257. };
  258. dojox.secure.unwrap = function unwrap(result){
  259. return (result && result.data__) || result;
  260. };
  261. }