You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@shindig.apache.org by do...@apache.org on 2008/03/11 15:21:19 UTC
svn commit: r635936 [1/2] - in /incubator/shindig/trunk/features:
caja/caja.js caja/domita.js caja/feature.xml caja/html-sanitizer.js
caja/html4-defs.js caja/unicode.js opensocial-reference/container.js
Author: doll
Date: Tue Mar 11 07:21:16 2008
New Revision: 635936
URL: http://svn.apache.org/viewvc?rev=635936&view=rev
Log:
patch from Mike Samuel
Updates the caja files in the features/caja dir and adds basic domita usage to container.js.
Added:
incubator/shindig/trunk/features/caja/domita.js
incubator/shindig/trunk/features/caja/html4-defs.js
incubator/shindig/trunk/features/caja/unicode.js
Modified:
incubator/shindig/trunk/features/caja/caja.js
incubator/shindig/trunk/features/caja/feature.xml
incubator/shindig/trunk/features/caja/html-sanitizer.js
incubator/shindig/trunk/features/opensocial-reference/container.js
Modified: incubator/shindig/trunk/features/caja/caja.js
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/features/caja/caja.js?rev=635936&r1=635935&r2=635936&view=diff
==============================================================================
--- incubator/shindig/trunk/features/caja/caja.js (original)
+++ incubator/shindig/trunk/features/caja/caja.js Tue Mar 11 07:21:16 2008
@@ -1,25 +1,21 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
+// Copyright (C) 2007 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// .............................................................................
// This module is the Caja runtime library. It is written in
// Javascript, not Caja, and would be rejected by the Caja
-// translator. This module exports two globals:
+// translator. This module exports two globals:
// * "___" for use by the output of the Caja translator and by some
// other untranslated Javascript code.
// * "caja" providing some common services to the Caja programmer.
@@ -36,9 +32,9 @@
////////////////////////////////////////////////////////////////////////
if (Array.prototype.indexOf === undefined) {
- /**
+ /**
* Returns the first index at which the specimen is found (by
- * "===") or -1 if none.
+ * "===") or -1 if none.
*/
Array.prototype.indexOf = function(specimen) {
var len = this.length;
@@ -52,9 +48,9 @@
}
if (Array.prototype.lastIndexOf === undefined) {
- /**
+ /**
* Returns the last index at which the specimen is found (by
- * "===") or -1 if none.
+ * "===") or -1 if none.
*/
Array.prototype.lastIndexOf = function(specimen) {
for (var i = this.length; --i >= 0; ) {
@@ -105,16 +101,16 @@
// like HTMLDivElement.
(function(global) {
-
+
////////////////////////////////////////////////////////////////////////
// Diagnostics and condition enforcement
////////////////////////////////////////////////////////////////////////
-
+
/**
- * The initial default logging function does nothing.
+ * The initial default logging function does nothing.
* <p>
* Note: JavaScript has no macros, so even in the "does nothing"
- * case, remember that the arguments are still evaluated.
+ * case, remember that the arguments are still evaluated.
*/
var myLogFunc_ = function(str, opt_stop) {};
@@ -125,9 +121,9 @@
/**
* Register newLogFunc as the current logging function, to be called
- * by <tt>___.log(str)</tt> and <tt>___.fail(...)</tt>.
+ * by <tt>___.log(str)</tt> and <tt>___.fail(...)</tt>.
* <p>
- * A logging function is assumed to have the signature
+ * A logging function is assumed to have the signature
* <tt>(str, opt_stop)</tt>, where<ul>
* <li><tt>str</tt> is the diagnostic string to be logged, and
* <li><tt>opt_stop</tt>, if present and <tt>true</tt>, indicates
@@ -135,7 +131,7 @@
* throw. This provides the logging function the opportunity to
* terminate normal control flow in its own way, such as by
* invoking an undefined method, in order to trigger a Firebug
- * stacktrace.
+ * stacktrace.
* </ul>
*/
function setLogFunc(newLogFunc) { myLogFunc_ = newLogFunc; }
@@ -146,7 +142,7 @@
function log(str) { myLogFunc_(String(str)); }
- /**
+ /**
* Throw, and optionally log, an error whose message is the
* concatentation of the arguments.
* <p>
@@ -155,12 +151,13 @@
* the message of the Error that's thrown.
*/
function fail(var_args) {
+ (typeof console !== 'undefined') && console.trace();
var message = Array.prototype.slice.call(arguments, 0).join('');
myLogFunc_(message, true);
throw new Error(message);
}
-
- /**
+
+ /**
* Like an assert that can't be turned off.
* <p>
* Either returns true (on success) or throws (on failure). The
@@ -175,10 +172,10 @@
* </pre>
*/
function enforce(test, var_args) {
- return test || fail.apply({},
+ return test || fail.apply({},
Array.prototype.slice.call(arguments, 1));
}
-
+
/**
* Enforces <tt>typeof specimen === typename</tt>, in which case
* specimen is returned.
@@ -195,7 +192,7 @@
}
return specimen;
}
-
+
/**
* Enforces that specimen is a non-negative integer within the range
* of exactly representable consecutive integers, in which case
@@ -221,13 +218,13 @@
}
return specimen;
}
-
+
////////////////////////////////////////////////////////////////////////
// Privileged fault handlers
////////////////////////////////////////////////////////////////////////
-
+
/**
- *
+ *
*/
var myKeeper_ = {
@@ -237,29 +234,29 @@
toString: function() { return '<Logging Keeper>'; },
/**
- *
+ *
*/
handleRead: function(obj, name) {
log('Not readable: (' + obj + ').' + name);
- return undefined;
+ return undefined;
},
/**
- *
+ *
*/
handleCall: function(obj, name, args) {
fail('Not callable: (', obj, ').', name);
},
/**
- *
+ *
*/
handleSet: function(obj, name, val) {
fail('Not settable: (', obj, ').', name);
},
/**
- *
+ *
*/
handleDelete: function(obj, name) {
fail('Not deletable: (', obj, ').', name);
@@ -267,17 +264,17 @@
};
/**
- *
+ *
*/
function getKeeper() { return myKeeper_; }
/**
- *
+ *
*/
function setKeeper(newKeeper) { myKeeper_ = newKeeper; }
/**
- *
+ *
*/
Object.prototype.handleRead___ = function(name) {
var handlerName = name + '_getter___';
@@ -288,7 +285,7 @@
};
/**
- *
+ *
*/
Object.prototype.handleCall___ = function(name, args) {
var handlerName = name + '_handler___';
@@ -299,7 +296,7 @@
};
/**
- *
+ *
*/
Object.prototype.handleSet___ = function(name, val) {
var handlerName = name + '_setter___';
@@ -310,7 +307,7 @@
};
/**
- *
+ *
*/
Object.prototype.handleDelete___ = function(name) {
var handlerName = name + '_deleter___';
@@ -319,15 +316,15 @@
}
return myKeeper_.handleDelete(this, name);
};
-
+
////////////////////////////////////////////////////////////////////////
// Overriding some very basic primordial methods
////////////////////////////////////////////////////////////////////////
-
+
/**
* Returns true only if we can call
* Object.prototype.hasOwnProperty on this object without
- * exploding.
+ * exploding.
* <p>
* On Firefox, it seems that calling hasOwnProperty on an
* HTMLDivElement sometimes causes an
@@ -336,7 +333,7 @@
* SECURITY BUG STOPGAP TODO(erights)
*/
var canCallHasOwnProperty = function(obj) { return true; };
-
+
// When we're in a non-browser environment, such that there isn't
// a global HTMLDivElement, then we don't need to worry about
// this bug.
@@ -345,45 +342,45 @@
return !(obj instanceof global.HTMLDivElement);
};
}
-
+
var originalHOP_ = Object.prototype.hasOwnProperty;
-
+
/**
* <tt>hasOwnProp(obj.prop)</tt> means what
* <tt>obj.hasOwnProperty(prop)</tt> would normally mean in an
* unmodified Javascript system.
*/
- function hasOwnProp(obj, name) {
+ function hasOwnProp(obj, name) {
var t = typeof obj;
- if (t !== 'object' && t !== 'function') {
- return false;
+ if (t !== 'object' && t !== 'function') {
+ return false;
}
if (canCallHasOwnProperty(obj)) {
- // Fails in Firefox for some DOM objects intermittently(?!)
+ // Fails in Firefox for some DOM objects intermittently(?!)
// with "Illegal operation on WrappedNative prototype object".
// For these, canCallHasOwnProperty must say false.
- return originalHOP_.call(obj, name);
+ return originalHOP_.call(obj, name);
} else {
return false;
}
}
-
+
////////////////////////////////////////////////////////////////////////
// walking prototype chain, checking JSON containers
////////////////////////////////////////////////////////////////////////
-
+
/**
- * Does str end with suffix?
+ * Does str end with suffix?
*/
function endsWith(str, suffix) {
enforceType(str, 'string');
enforceType(suffix, 'string');
var strLen = str.length;
var sufLen = suffix.length;
- return strLen >= sufLen &&
+ return strLen >= sufLen &&
(str.substring(strLen-sufLen, strLen) === suffix);
}
-
+
/**
* Returns the 'constructor' property of obj's prototype.
* <p>
@@ -398,33 +395,37 @@
*/
function directConstructor(obj) {
if (obj === null) { return undefined; }
- if (typeof obj !== 'object') {
- // Note that functions thereby return undefined,
- // so directConstructor() doesn't provide access to the
- // forbidden Function constructor.
- return undefined;
- }
- // The following test will initially return false in IE
- if (hasOwnProp(obj, '__proto__')) {
- if (obj.__proto__ === null) { return undefined; }
- return obj.__proto__.constructor;
- }
- var result;
- if (!hasOwnProp(obj, 'constructor')) {
- result = obj.constructor;
- } else {
- var oldConstr = obj.constructor;
- if (!(delete obj.constructor)) { return undefined; }
- result = obj.constructor;
- obj.constructor = oldConstr;
- }
- if (result.prototype.constructor === result) {
- // Memoize, so it'll be faster next time.
- obj.__proto__ = result.prototype;
+ try {
+ if (typeof obj !== 'object') {
+ // Note that functions thereby return undefined,
+ // so directConstructor() doesn't provide access to the
+ // forbidden Function constructor.
+ return undefined;
+ }
+ // The following test will initially return false in IE
+ if (hasOwnProp(obj, '__proto__')) {
+ if (obj.__proto__ === null) { return undefined; }
+ return obj.__proto__.constructor;
+ }
+ var result;
+ if (!hasOwnProp(obj, 'constructor')) {
+ result = obj.constructor;
+ } else {
+ var oldConstr = obj.constructor;
+ if (!(delete obj.constructor)) { return undefined; }
+ result = obj.constructor;
+ obj.constructor = oldConstr;
+ }
+ if (result.prototype.constructor === result) {
+ // Memoize, so it'll be faster next time.
+ obj.__proto__ = result.prototype;
+ }
+ return result;
+ } catch (ex) {
+ return null;
}
- return result;
}
-
+
/**
* A JSON container is an object whose direct constructor is
* Object or Array.
@@ -436,7 +437,7 @@
var constr = directConstructor(obj);
return constr === Object || constr === Array;
}
-
+
/**
* If obj is frozen, Caja code cannot directly assign to
* properties of obj, nor directly add or delete properties to
@@ -449,14 +450,14 @@
* If typeof <tt>obj</tt> is neither 'object' nor 'function', then
* it's currently considered frozen.
*/
- function isFrozen(obj) {
+ function isFrozen(obj) {
var t = typeof obj;
- if (t !== 'object' && t !== 'function') {
- return true;
+ if (t !== 'object' && t !== 'function') {
+ return true;
}
- return hasOwnProp(obj, '___FROZEN___');
+ return hasOwnProp(obj, '___FROZEN___');
}
-
+
/**
* Mark obj as frozen so that Caja code cannot directly assign to its
* properties.
@@ -475,9 +476,9 @@
// badFlags are names of properties we need to turn off.
// We accumulate these first, so that we're not in the midst of a
// for/in loop on obj while we're deleting properties from obj.
- var badFlags = [];
+ var badFlags = [];
for (var k in obj) {
- if (endsWith(k, '_canSet___') || endsWith(k, '_canDelete___')) {
+ if (endsWith(k, '_canSet___') || endsWith(k, '_canDelete___')) {
if (obj[k]) {
badFlags.push(k);
}
@@ -499,7 +500,7 @@
// for a future optimization, where the
// prototype can record as canSet those
// properties that appear in instances that
- // inherit from this prototype.
+ // inherit from this prototype.
obj[flag] = false;
}
}
@@ -510,7 +511,7 @@
}
return obj;
}
-
+
/**
* Like primFreeze(obj), but applicable only to JSON containers.
*/
@@ -520,7 +521,7 @@
}
return primFreeze(obj);
}
-
+
/**
* Makes a mutable copy of a JSON container.
* <p>
@@ -536,19 +537,19 @@
}));
return result;
}
-
+
/**
* A snapshot of a JSON container is a frozen copy of that
- * container.
+ * container.
*/
function snapshot(obj) {
return primFreeze(copy(obj));
}
-
+
////////////////////////////////////////////////////////////////////////
// Accessing property attributes
////////////////////////////////////////////////////////////////////////
-
+
/** Tests whether the fast-path canRead flag is set. */
function canRead(obj, name) { return !!obj[name + '_canRead___']; }
/** Tests whether the fast-path canEnum flag is set. */
@@ -559,33 +560,33 @@
function canSet(obj, name) { return !!obj[name + '_canSet___']; }
/** Tests whether the fast-path canDelete flag is set. */
function canDelete(obj, name) { return !!obj[name + '_canDelete___']; }
-
- /**
+
+ /**
* Sets the fast-path canRead flag.
* <p>
* The various <tt>allow*</tt> functions are called externally by
* Javascript code to express whitelisting taming decisions. And
* they are called internally to memoize decisions arrived at by
- * other means.
+ * other means.
*/
- function allowRead(obj, name) {
- obj[name + '_canRead___'] = true;
+ function allowRead(obj, name) {
+ obj[name + '_canRead___'] = true;
}
-
+
/** allowEnum implies allowRead */
- function allowEnum(obj, name) {
+ function allowEnum(obj, name) {
allowRead(obj, name);
obj[name + '_canEnum___'] = true;
}
-
- /**
+
+ /**
* Simple functions should callable and readable, but methods
* should only be callable.
*/
- function allowCall(obj, name) {
- obj[name + '_canCall___'] = true;
+ function allowCall(obj, name) {
+ obj[name + '_canCall___'] = true;
}
-
+
/**
* allowSet implies allowEnum and allowRead.
*/
@@ -596,10 +597,10 @@
allowEnum(obj, name);
obj[name + '_canSet___'] = true;
}
-
+
/**
* BUG TODO(erights): allowDelete is not yet specified or
- * implemented.
+ * implemented.
*/
function allowDelete(obj, name) {
if (isFrozen(obj)) {
@@ -608,16 +609,16 @@
fail('TODO(erights): allowDelete() not yet implemented');
obj[name + '_canDelete___'] = true;
}
-
+
////////////////////////////////////////////////////////////////////////
// Classifying functions
////////////////////////////////////////////////////////////////////////
-
+
function isCtor(constr) { return !!constr.___CONSTRUCTOR___; }
function isMethod(meth) { return '___METHOD_OF___' in meth; }
function isSimpleFunc(fun) { return !!fun.___SIMPLE_FUNC___; }
-
- /**
+
+ /**
* Mark constr as a constructor.
* <p>
* If opt_Sup is provided, set constr.Super = opt_Sup.
@@ -626,7 +627,7 @@
* method(), or simpleFunc(). Each of these checks that the
* function hasn't already been classified by any of the others. A
* function which has not been so classified is an <i>untamed
- * function</i>.
+ * function</i>.
* <p>
* opt_name, if provided, should be the name of the constructor
* function. Currently, this is used only to generate friendlier
@@ -656,9 +657,9 @@
}
return constr; // translator freezes constructor later
}
-
- /**
- * Mark meth as a method of instances of constr.
+
+ /**
+ * Mark meth as a method of instances of constr.
* <p>
* opt_name, if provided, should be the message name associated
* with the method. Currently, this is used only to generate
@@ -675,11 +676,11 @@
meth.___METHOD_OF___ = asCtorOnly(constr);
return primFreeze(meth);
}
-
- /**
+
+ /**
* Mark fun as a simple function.
* <p>
- * opt_name, if provided, should be the name of the
+ * opt_name, if provided, should be the name of the
* function. Currently, this is used only to generate friendlier
* error messages.
*/
@@ -696,47 +697,47 @@
fun.call_canCall___ = true;
return fun; // translator freezes fun later
}
-
+
/** This "Only" form doesn't freeze */
function asCtorOnly(constr) {
- if (isCtor(constr) || isSimpleFunc(constr)) {
- return constr;
+ if (isCtor(constr) || isSimpleFunc(constr)) {
+ return constr;
}
-
+
enforceType(constr, 'function');
if (isMethod(constr)) {
fail("Methods can't be called as constructors: ", constr);
}
fail("Untamed functions can't be called as constructors: ", constr);
}
-
+
/** Only constructors and simple functions can be called as constructors */
function asCtor(constr) {
- return primFreeze(asCtorOnly(constr));
+ return primFreeze(asCtorOnly(constr));
}
-
+
/** Only methods and simple functions can be called as methods */
function asMethod(meth) {
- if (isSimpleFunc(meth) || isMethod(meth)) {
+ if (isSimpleFunc(meth) || isMethod(meth)) {
if (!isFrozen(meth)) {
fail('internal: non-frozen func stored as method: ', meth);
}
- return meth;
+ return meth;
}
-
+
enforceType(meth, 'function');
if (isCtor(meth)) {
fail("Constructors can't be called as methods: ", meth);
}
fail("Untamed functions can't be called as methods: ", meth);
}
-
+
/** Only simple functions can be called as simple functions */
function asSimpleFunc(fun) {
- if (isSimpleFunc(fun)) {
- return primFreeze(fun);
+ if (isSimpleFunc(fun)) {
+ return primFreeze(fun);
}
-
+
enforceType(fun, 'function');
if (isCtor(fun)) {
if (fun === Number || fun === String || fun === Boolean) {
@@ -770,8 +771,8 @@
}
fail("Untamed functions can't be called as simple functions: ", fun);
}
-
- /**
+
+ /**
* Sets constr.prototype[name] = member.
* <p>
* If member is a method of constr, make it callable.
@@ -798,13 +799,13 @@
}
proto[name] = member;
}
-
+
////////////////////////////////////////////////////////////////////////
// Accessing properties
////////////////////////////////////////////////////////////////////////
-
- /**
- * Can a constructed Caja object read this property on itself?
+
+ /**
+ * Can a constructed Caja object read this property on itself?
* <p>
* Can a Caja method whose <tt>this</tt> is bound to <tt>that</tt>
* read its own <tt>name</tt> property? For properties added to
@@ -818,8 +819,8 @@
if (endsWith(name, '__')) { return false; }
return canRead(that, name);
}
-
- /**
+
+ /**
* A constructed Caja object's attempt to read this property on
* itself.
* <p>
@@ -829,9 +830,9 @@
name = String(name);
return canReadProp(that, name) ? that[name] : that.handleRead___(name);
}
-
- /**
- * Can a Caja client of <tt>obj</tt> read its <name> property?
+
+ /**
+ * Can a Caja client of <tt>obj</tt> read its <name> property?
* <p>
* If the property is Internal (i.e. ends in an '_'), then no.
* If the property was defined by Caja code, then yes. If it was
@@ -847,7 +848,7 @@
allowRead(obj, name); // memoize
return true;
}
-
+
/**
* Caja code attempting to read a property on something besides
* <tt>this</tt>.
@@ -858,7 +859,7 @@
name = String(name);
return canReadPub(obj, name) ? obj[name] : obj.handleRead___(name);
}
-
+
/**
* Can "innocent" code enumerate the named property on this object?
* <p>
@@ -884,9 +885,9 @@
if (endsWith(name, '___')) { return false; }
return true;
}
-
- /**
- * Would a Caja for/in loop on <tt>this</tt> see this name?
+
+ /**
+ * Would a Caja for/in loop on <tt>this</tt> see this name?
* <p>
* For properties defined in Caja, this is generally the same as
* canReadProp. Otherwise according to whitelisting.
@@ -896,9 +897,9 @@
if (endsWith(name, '__')) { return false; }
return canEnum(that, name);
}
-
- /**
- * Would a Caja for/in loop by a client of obj see this name?
+
+ /**
+ * Would a Caja for/in loop by a client of obj see this name?
* <p>
* For properties defined in Caja, this is generally the same as
* canReadProp. Otherwise according to whitelisting.
@@ -912,7 +913,7 @@
allowEnum(obj, name); // memoize
return true;
}
-
+
/**
* Like canEnumPub, but allows only non-inherited properties.
*/
@@ -920,14 +921,14 @@
name = String(name);
return hasOwnProp(obj, name) && canEnumPub(obj, name);
}
-
+
/**
* Inside a <tt>caja.each()</tt>, the body function can terminate
* early, as if with a conventional <tt>break;</tt>, by doing a
* <pre>return caja.BREAK;</pre>
*/
var BREAK = {};
-
+
/**
* For each sensible key/value pair in obj, call fn with that
* pair.
@@ -954,7 +955,7 @@
}
}
}
-
+
/**
* Can this be called as an internal method?
* <p>
@@ -964,7 +965,7 @@
* which we can memoize.
* <p>
* SECURITY HAZARD TODO(erights): If a settable property is
- * first set to a
+ * first set to a
* simple function, which is then called, memoizing canCall, and
* then set to some other kind of function which leaked (such as
* an untamed function), then that other function can be
@@ -983,7 +984,7 @@
* after a set will re-check the value to be called.
* <p>
* This plan will need to be thought through again when we
- * implement property deletion.
+ * implement property deletion.
*/
function canCallProp(that, name) {
name = String(name);
@@ -995,7 +996,7 @@
allowCall(that, name); // memoize
return true;
}
-
+
/**
* A Caja method tries to call one of its Internal methods.
*/
@@ -1008,7 +1009,7 @@
return that.handleCall___(name, args);
}
}
-
+
/**
* Like canCallProp(), with differences that parallel the
* differences between canReadProp vs canReadPub.
@@ -1023,7 +1024,7 @@
allowCall(obj, name); // memoize
return true;
}
-
+
/**
* A client of obj tries to call one of its methods.
*/
@@ -1032,12 +1033,14 @@
if (canCallPub(obj, name)) {
var meth = obj[name];
return meth.apply(obj, args);
- } else {
+ } else if (obj.handleCall___) {
return obj.handleCall___(name, args);
+ } else {
+ fail('not callable %o %s', obj, name);
}
}
-
- /**
+
+ /**
* Can a method of a Caja constructed object directly assign to
* this property of its object?
* <p>
@@ -1049,7 +1052,7 @@
if (canSet(that, name)) { return true; }
return !isFrozen(that);
}
-
+
/**
* A Caja method tries to assign to this property of its object.
*/
@@ -1063,12 +1066,12 @@
return that.handleSet___(name, val);
}
}
-
+
/**
* Can a client of obj directly assign to its name property?
* <p>
* If this property is Internal (i.e., ends with a '_') or if this
- * object is frozen, then no.
+ * object is frozen, then no.
* If this property is not Internal and was defined by Caja code,
* then yes. If the object is a JSON container, then
* yes. Otherwise according to whitelisting decisions.
@@ -1085,7 +1088,7 @@
if (canSet(obj, name)) { return true; }
return !isFrozen(obj) && isJSONContainer(obj);
}
-
+
/** A client of obj attempts to assign to one of its properties. */
function setPub(obj, name, val) {
name = String(name);
@@ -1097,22 +1100,22 @@
return obj.handleSet___(name, val);
}
}
-
+
/**
* Can a Caja constructed object delete the named property?
* <p>
* BUG TODO(erights): This is not yet supported. The precise
* enabling conditions are not yet determined, as is the implied
- * bookkeeping.
+ * bookkeeping.
*/
function canDeleteProp(that, name) {
fail('TODO(erights): deletion not yet supported');
return false;
}
-
+
/**
* A Caja constructed object attempts to delete one of its own
- * properties.
+ * properties.
* <p>
* BUG TODO(erights): This is not yet supported. The precise
* enabling conditions are not yet determined, as is the implied
@@ -1128,26 +1131,26 @@
return that.handleDelete___(name);
}
}
-
+
/**
* Can a client of obj delete the named property?
* <p>
* BUG TODO(erights): This is not yet supported. The precise
* enabling conditions are not yet determined, as is the implied
- * bookkeeping.
+ * bookkeeping.
*/
function canDeletePub(obj, name) {
fail('TODO(erights): deletion not yet supported');
return false;
}
-
+
/**
* A client of obj can only delete a property of obj if obj is a
* non-frozen JSON container.
* <p>
* BUG TODO(erights): This is not yet supported. The precise
* enabling conditions are not yet determined, as is the implied
- * bookkeeping.
+ * bookkeeping.
*/
function deletePub(obj, name) {
name = String(name);
@@ -1162,11 +1165,11 @@
return obj.handleDelete___(name);
}
}
-
+
////////////////////////////////////////////////////////////////////////
// Other
////////////////////////////////////////////////////////////////////////
-
+
/**
* This returns a frozen array copy of the original array or
* array-like object.
@@ -1182,7 +1185,7 @@
function args(original) {
return primFreeze(Array.prototype.slice.call(original, 0));
}
-
+
/**
*
*/
@@ -1191,7 +1194,7 @@
setMember(sub, mname, member);
}));
}
-
+
/**
* Provides a shorthand for a class-like declaration of a fresh
* Caja constructor.
@@ -1203,7 +1206,7 @@
* opt_statics added as members to sub.
* <p>
* TODO(erights): return a builder object that allows further
- * initialization.
+ * initialization.
*/
function def(sub, opt_Sup, opt_members, opt_statics) {
var sup = opt_Sup || Object;
@@ -1213,21 +1216,21 @@
fail('The static name "Super" is reserved ',
'for the super-constructor');
}
-
+
ctor(sub, sup);
function PseudoSuper() {}
PseudoSuper.prototype = sup.prototype;
sub.prototype = new PseudoSuper();
sub.prototype.constructor = sub;
-
+
setMemberMap(sub, members);
each(statics, simpleFunc(function(sname, staticMember) {
setPub(sub, sname, staticMember);
}));
-
+
// translator freezes sub and sub.prototype later.
}
-
+
////////////////////////////////////////////////////////////////////////
// Taming mechanism
////////////////////////////////////////////////////////////////////////
@@ -1235,7 +1238,7 @@
/**
* Arrange to handle read-faults on <tt>obj[name]</tt>
* by calling <tt>getHandler()</tt> as a method on the faulted
- * object.
+ * object.
* <p>
* In order for this fault-handler to get control, it's important
* that no one does a conflicting allowRead().
@@ -1247,7 +1250,7 @@
/**
* Arrange to handle call-faults on <tt>obj[name](args...)</tt> by
* calling <tt>applyHandler(args)</tt> as a method on the faulted
- * object.
+ * object.
* <p>
* Note that <tt>applyHandler</tt> is called with a single argument,
* which is the list of arguments in the original call.
@@ -1263,10 +1266,10 @@
/**
* Arrange to handle call-faults on <tt>obj[name](args...)</tt> by
* calling <tt>callHandler(args...)</tt> as a method on the faulted
- * object.
+ * object.
* <p>
* Note that <tt>callHandler</tt> is called with the same arguments
- * as the original call.
+ * as the original call.
* <p>
* In order for this fault-handler to get control, it's important
* that no one does a conflicting allowCall(), allowSimpleFunc(), or
@@ -1281,7 +1284,7 @@
/**
* Arrange to handle set-faults on <tt>obj[name] = newValue</tt> by
* calling <tt>setHandler(newValue)</tt> as a method on the faulted
- * object.
+ * object.
* <p>
* In order for this fault-handler to get control, it's important
* that no one does a conflicting allowSet().
@@ -1292,7 +1295,7 @@
/**
* Arrange to handle delete-faults on <tt>delete obj[name]</tt> by
- * calling <tt>deleteHandler()</tt> as a method on the faulted object.
+ * calling <tt>deleteHandler()</tt> as a method on the faulted object.
* <p>
* In order for this fault-handler to get control, it's important
* that no one does a conflicting allowDelete().
@@ -1310,7 +1313,7 @@
allowCall(obj, name);
allowRead(obj, name);
}
-
+
/**
* Whitelist constr.prototype[name] as a method that can be called
* on instances of constr.
@@ -1319,7 +1322,7 @@
method(constr, constr.prototype[name], name);
allowCall(constr.prototype, name);
}
-
+
/**
* Virtually replace constr.prototype[name] with a fault-handler
* wrapper that first verifies that <tt>this</tt> isn't frozen.
@@ -1329,7 +1332,7 @@
* mutation from violating Caja semantics. In order for this fault
* handler to get control, it's important that no one does an
* allowCall(), allowSimpleFunc(), or allowMethod() on the
- * original method.
+ * original method.
*/
function allowMutator(constr, name) {
var original = constr.prototype[name];
@@ -1340,7 +1343,7 @@
return original.apply(this, args);
});
}
-
+
/**
* Verifies that regexp is something that can appear as a
* parameter to a Javascript method that would use it in a match.
@@ -1355,7 +1358,7 @@
}
}
}
-
+
/**
* A shorthand that happens to be useful here.
* <p>
@@ -1367,7 +1370,7 @@
func2(arg1, arg2s[i]);
}
}
-
+
////////////////////////////////////////////////////////////////////////
// Taming decisions
////////////////////////////////////////////////////////////////////////
@@ -1380,8 +1383,8 @@
'abs', 'acos', 'asin', 'atan', 'atan2', 'ceil', 'cos', 'exp', 'floor',
'log', 'max', 'min', 'pow', 'random', 'round', 'sin', 'sqrt', 'tan'
]);
-
-
+
+
ctor(Object, undefined, 'Object');
all2(allowMethod, Object, [
'toString', 'toLocaleString', 'valueOf', 'isPrototypeOf'
@@ -1409,13 +1412,13 @@
useCallHandler(Object.prototype, 'freeze_', function() {
return primFreeze(this);
});
-
-
+
+
// SECURITY HAZARD TODO(erights): Seems dangerous, but doesn't add
- // risk. Or does it?
+ // risk. Or does it?
ctor(Function, Object, 'Function');
// SECURITY HAZARD TODO(erights): Seems dangerous, but doesn't add
- // risk. Or does it?
+ // risk. Or does it?
allowRead(Function.prototype, 'prototype');
useCallHandler(Function.prototype, 'apply', function(that, realArgs) {
@@ -1424,8 +1427,8 @@
useCallHandler(Function.prototype, 'call', function(that, realArgs) {
return asSimpleFunc(this).apply(that, realArgs);
});
-
-
+
+
ctor(Array, Object, 'Array');
all2(allowMethod, Array, [
'concat', 'join', 'slice', 'indexOf', 'lastIndexOf'
@@ -1433,8 +1436,8 @@
all2(allowMutator, Array, [
'pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'
]);
-
-
+
+
ctor(String, Object, 'String');
allowSimpleFunc(String, 'fromCharCode');
all2(allowMethod, String, [
@@ -1446,7 +1449,7 @@
enforceMatchable(regexp);
return this.match(regexp);
});
- useCallHandler(String.prototype, 'replace', function(searchValue,
+ useCallHandler(String.prototype, 'replace', function(searchValue,
replaceValue) {
enforceMatchable(searchValue);
return this.replace(searchValue, replaceValue);
@@ -1459,11 +1462,11 @@
enforceMatchable(separator);
return this.split(separator, limit);
});
-
-
+
+
ctor(Boolean, Object, 'Boolean');
-
-
+
+
ctor(Number, Object, 'Number');
all2(allowRead, Number, [
'MAX_VALUE', 'MIN_VALUE', 'NaN',
@@ -1472,18 +1475,18 @@
all2(allowMethod, Number, [
'toFixed', 'toExponential', 'toPrecision'
]);
-
-
+
+
ctor(Date, Object, 'Date');
allowSimpleFunc(Date, 'parse');
allowSimpleFunc(Date, 'UTC');
-
+
all2(allowMethod, Date, [
'toDateString', 'toTimeString', 'toUTCString',
'toLocaleString', 'toLocaleDateString', 'toLocaleTimeString',
'toISOString',
'getDay', 'getUTCDay', 'getTimezoneOffset',
-
+
'getTime', 'getFullYear', 'getUTCFullYear', 'getMonth', 'getUTCMonth',
'getDate', 'getUTCDate', 'getHours', 'getUTCHours',
'getMinutes', 'getUTCMinutes', 'getSeconds', 'getUTCSeconds',
@@ -1495,17 +1498,17 @@
'setMinutes', 'setUTCMinutes', 'setSeconds', 'setUTCSeconds',
'setMilliseconds', 'setUTCMilliseconds'
]);
-
-
+
+
ctor(RegExp, Object, 'RegExp');
allowMutator(RegExp, 'exec');
allowMutator(RegExp, 'test');
-
+
all2(allowRead, RegExp, [
'source', 'global', 'ignoreCase', 'multiline', 'lastIndex'
]);
-
-
+
+
ctor(Error, Object, 'Error');
allowRead(Error, 'name');
allowRead(Error, 'message');
@@ -1515,23 +1518,23 @@
ctor(SyntaxError, Error, 'SyntaxError');
ctor(TypeError, Error, 'TypeError');
ctor(URIError, Error, 'URIError');
-
-
+
+
var sharedOuters;
-
+
////////////////////////////////////////////////////////////////////////
// Module loading
////////////////////////////////////////////////////////////////////////
-
+
var myNewModuleHandler;
-
+
/**
* Gets the current module handler.
*/
function getNewModuleHandler() {
return myNewModuleHandler;
}
-
+
/**
* Registers a new-module-handler, to be called back when a new
* module is loaded.
@@ -1545,14 +1548,14 @@
function setNewModuleHandler(newModuleHandler) {
myNewModuleHandler = newModuleHandler;
}
-
+
/**
* A new-module-handler which does nothing.
*/
var ignoreNewModule = freeze({
handle: simpleFunc(function(newModule){})
});
-
+
/**
* Makes and returns a fresh "normal" module handler whose outers
* are initialized to a copy of the sharedOuters.
@@ -1572,13 +1575,13 @@
})
});
}
-
+
/**
* A module is a plugin-maker function.
* <p>
* loadModule(module) marks module as a simpleFunc, freezes it,
* asks the current new-module-handler to handle it (thereby
- * notifying the handler), and returns the new module.
+ * notifying the handler), and returns the new module.
*/
function loadModule(module) {
callPub(myNewModuleHandler, 'handle',
@@ -1590,7 +1593,7 @@
/**
* Gets or assigns the id associated with this (assumed to be)
- * outers object, registering it so that
+ * outers object, registering it so that
* <tt>getOuters(getId(outers)) ==== outers</tt>.
* <p>
* This system of registration and identification allows us to
@@ -1651,21 +1654,21 @@
* reregisters itself at its old id.
*/
function unregister(outers) {
- enforceType(outers, 'object', 'outers');
+ enforceType(outers, 'object', 'outers');
if ('id___' in outers) {
var id = enforceType(outers.id___, 'number', 'id');
registeredOuters[id] = undefined;
}
}
-
+
////////////////////////////////////////////////////////////////////////
// Exports
////////////////////////////////////////////////////////////////////////
-
+
caja = {
// Diagnostics and condition enforcement
- getLogFunc: getLogFunc,
+ getLogFunc: getLogFunc,
setLogFunc: setLogFunc,
log: log,
@@ -1673,29 +1676,29 @@
enforce: enforce,
enforceType: enforceType,
enforceNat: enforceNat,
-
+
// walking prototype chain, checking JSON containers
isJSONContainer: isJSONContainer,
freeze: freeze,
copy: copy,
snapshot: snapshot,
-
+
// Accessing properties
canReadPub: canReadPub, readPub: readPub,
canEnumPub: canEnumPub,
- canEnumOwn: canEnumOwn,
- BREAK: BREAK, each: each,
+ canEnumOwn: canEnumOwn,
+ BREAK: BREAK, each: each,
canCallPub: canCallPub, callPub: callPub,
canSetPub: canSetPub, setPub: setPub,
canDeletePub: canDeletePub, deletePub: deletePub,
-
+
// Other
def: def
};
-
+
sharedOuters = {
caja: caja,
-
+
'null': null,
'false': false,
'true': true,
@@ -1711,7 +1714,7 @@
encodeURI: encodeURI,
encodeURIComponent: encodeURIComponent,
Math: Math,
-
+
Object: Object,
Array: Array,
String: String,
@@ -1719,7 +1722,7 @@
Number: Number,
Date: Date,
RegExp: RegExp,
-
+
Error: Error,
EvalError: EvalError,
RangeError: RangeError,
@@ -1728,7 +1731,7 @@
TypeError: TypeError,
URIError: URIError
};
-
+
each(sharedOuters, simpleFunc(function(k, v) {
switch (typeof v) {
case 'object':
@@ -1740,7 +1743,7 @@
}
}));
primFreeze(sharedOuters);
-
+
___ = {
// Privileged fault handlers
@@ -1751,14 +1754,14 @@
directConstructor: directConstructor,
isFrozen: isFrozen,
primFreeze: primFreeze,
-
+
// Accessing property attributes
canRead: canRead, allowRead: allowRead,
canEnum: canEnum, allowEnum: allowEnum,
canCall: canCall, allowCall: allowCall,
canSet: canSet, allowSet: allowSet,
canDelete: canDelete, allowDelete: allowDelete,
-
+
// Classifying functions
isCtor: isCtor,
isMethod: isMethod,
@@ -1769,7 +1772,7 @@
simpleFunc: simpleFunc, asSimpleFunc: asSimpleFunc,
setMember: setMember,
setMemberMap: setMemberMap,
-
+
// Accessing properties
canReadProp: canReadProp, readProp: readProp,
canInnocentEnum: canInnocentEnum,
@@ -1777,13 +1780,13 @@
canCallProp: canCallProp, callProp: callProp,
canSetProp: canSetProp, setProp: setProp,
canDeleteProp: canDeleteProp, deleteProp: deleteProp,
-
+
// Other
hasOwnProp: hasOwnProp,
args: args,
-
+
// Taming mechanism
- useGetHandler: useGetHandler,
+ useGetHandler: useGetHandler,
useApplyHandler: useApplyHandler,
useCallHandler: useCallHandler,
useSetHandler: useSetHandler,
@@ -1794,10 +1797,10 @@
allowMutator: allowMutator,
enforceMatchable: enforceMatchable,
all2: all2,
-
+
// Taming decisions
sharedOuters: sharedOuters,
-
+
// Module loading
getNewModuleHandler: getNewModuleHandler,
setNewModuleHandler: setNewModuleHandler,
@@ -1809,7 +1812,7 @@
getOuters: getOuters,
unregister: unregister
};
-
+
each(caja, simpleFunc(function(k, v) {
if (k in ___) {
fail('internal: initialization conflict: ', k);
@@ -1821,7 +1824,7 @@
___[k] = v;
}));
primFreeze(caja);
-
+
setNewModuleHandler(makeNormalNewModuleHandler());
-
+
})(this);
Added: incubator/shindig/trunk/features/caja/domita.js
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/features/caja/domita.js?rev=635936&view=auto
==============================================================================
--- incubator/shindig/trunk/features/caja/domita.js (added)
+++ incubator/shindig/trunk/features/caja/domita.js Tue Mar 11 07:21:16 2008
@@ -0,0 +1,719 @@
+// Copyright (C) 2008 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+/**
+ * @fileoverview
+ * A partially tamed browser object model based on
+ * <a href="http://www.w3.org/TR/DOM-Level-2-HTML/Overview.html"
+ * >DOM-Level-2-HTML</a> and specifically, the
+ * <a href="http://www.w3.org/TR/DOM-Level-2-HTML/ecma-script-binding.html"
+ * >ECMAScript Language Bindings</a>.
+ *
+ * Requires caja.js, html-defs.js, html-sanitizer.js, unicode.js.
+ *
+ * Caveats:
+ * - This is not a full implementation.
+ * - Security Review is pending.
+ * - <code>===</code> and <code>!==</code> on tamed DOM nodes will not behave
+ * the same as with untamed nodes. Specifically, it is not always true that
+ * {@code document.getElementById('foo') === document.getElementById('foo')}.
+ *
+ * @author mikesamuel@gmail.com
+ */
+
+
+/**
+ * Add a tamed document implementation to a Gadget's global scope.
+ *
+ * @param {string} idPrefix a string prefix prepended to all node IDs.
+ * @param {Object} uriCallback an object like <pre>{
+ * rewrite: function (uri, mimeType) { return safeUri }
+ * }</pre>.
+ * The rewrite function should be idempotent to allow rewritten HTML
+ * to be reinjected.
+ * @param {Object} outers the gadget's global scope.
+ */
+attachDocumentStub = (function () {
+ var tameNodeSecret = {};
+
+ var XML_SPACE = '\t\n\r ';
+
+ var XML_NAME_PATTERN = new RegExp(
+ '^[' + unicode.LETTER + '_:][' + unicode.LETTER + unicode.DIGIT + '.\\-_:'
+ + unicode.COMBINING_CHAR + unicode.EXTENDER + ']*$');
+
+ var XML_NMTOKEN_PATTERN = new RegExp(
+ '^[' + unicode.LETTER + unicode.DIGIT + '.\\-_:'
+ + unicode.COMBINING_CHAR + unicode.EXTENDER + ']+$');
+
+ var XML_NMTOKENS_PATTERN = new RegExp(
+ '^(?:[' + XML_SPACE + ']*[' + unicode.LETTER + unicode.DIGIT + '.\\-_:'
+ + unicode.COMBINING_CHAR + unicode.EXTENDER + ']+)+[' + XML_SPACE + ']*$'
+ );
+
+ var JS_SPACE = '\t\n\r ';
+ // An identifier that does not end with __.
+ var JS_IDENT = '(?:[a-zA-Z_][a-zA-Z0-9$_]*[a-zA-Z0-9$]|[a-zA-Z])_?';
+ var SIMPLE_HANDLER_PATTERN = new RegExp(
+ '^[' + JS_SPACE + ']*'
+ + '(return[' + JS_SPACE + ']+)?' // Group 1 is present if it returns.
+ + '(' + JS_IDENT + ')[' + JS_SPACE + ']*' // Group 2 is a function name.
+ // Which can be passed optionally this node, and optionally the event.
+ + '\\((?:this'
+ + '(?:[' + JS_SPACE + ']*,[' + JS_SPACE + ']*event)?'
+ + '[' + JS_SPACE + ']*)?\\)'
+ // And it can end with a semicolon.
+ + '[' + JS_SPACE + ']*(?:;?[' + JS_SPACE + ']*)$');
+
+ /**
+ * Coerces the string to a valid XML Name.
+ * @see http://www.w3.org/TR/2000/REC-xml-20001006#NT-Name
+ */
+ function isXmlName(s) {
+ return XML_NAME_PATTERN.test(s);
+ }
+
+ /**
+ * Coerces the string to valid XML Nmtokens
+ * @see http://www.w3.org/TR/2000/REC-xml-20001006#NT-Nmtokens
+ */
+ function isXmlNmTokens(s) {
+ return XML_NMTOKENS_PATTERN.test(s);
+ }
+
+ function assert(cond) {
+ if (!cond) {
+ console && (console.log('domita assertion failed'), console.trace());
+ throw new Error();
+ }
+ }
+
+ /**
+ * Add setter and getter hooks so that the caja {@code node.innerHTML = '...'}
+ * works as expected.
+ */
+ function exportFields(ctor, fields) {
+ for (var i = fields.length; --i >= 0;) {
+ var field = fields[i];
+ var fieldUCamel = field.charAt(0).toUpperCase() + field.substring(1);
+ var getterName = 'get' + fieldUCamel;
+ var setterName = 'set' + fieldUCamel;
+ var count = 0;
+ if (ctor.prototype.hasOwnProperty(getterName)) {
+ ++count;
+ ___.useGetHandler(
+ ctor.prototype, field, ctor.prototype[getterName]);
+ }
+ if (ctor.prototype.hasOwnProperty(setterName)) {
+ ++count;
+ ___.useSetHandler(
+ ctor.prototype, field, ctor.prototype[setterName]);
+ }
+ if (!count) {
+ throw new Error('Failed to export field ' + field + ' on ' + ctor);
+ }
+ }
+ }
+
+ /**
+ * Makes the first a subclass of the second.
+ */
+ function extend(subClass, baseClass) {
+ var noop = function () {};
+ noop.prototype = baseClass.prototype;
+ subClass.prototype = new noop();
+ subClass.prototype.constructor = subClass;
+ }
+
+ // See above for a description of this function.
+ function attachDocumentStub(idPrefix, uriCallback, outers) {
+
+ var elementPolicies = {};
+ elementPolicies.FORM = function (attribs) {
+ // Forms must have a gated onsubmit handler or they must have an
+ // external target.
+ var sawHandler = false;
+ for (var i = 0, n = attribs.length; i < n; i += 2) {
+ if (attribs[i] === 'ONSUBMIT') {
+ sawHandler = true;
+ }
+ }
+ if (!sawHandler) {
+ attribs.push('ONSUBMIT', 'return false');
+ }
+ return attribs;
+ };
+ elementPolicies.A = elementPolicies.AREA = function (attribs) {
+ // Anchor tags must have a target.
+ attribs.push('TARGET', '_blank');
+ return attribs;
+ };
+
+
+ /** Sanitize HTML applying the appropriate transformations. */
+ function sanitizeHtml(htmlText) {
+ var out = [];
+ htmlSanitizer(htmlText, out);
+ return out.join('');
+ }
+ var htmlSanitizer = html.makeHtmlSanitizer(
+ function sanitizeAttributes(tagName, attribs) {
+ for (var i = 0; i < attribs.length; i += 2) {
+ var attribName = attribs[i];
+ var value = attribs[i + 1];
+ if (html4.ATTRIBS.hasOwnProperty(attribName)) {
+ var atype = html4.ATTRIBS[attribName];
+ value = rewriteAttribute(tagName, attribName, atype, value);
+ } else {
+ value = null;
+ }
+ if (value != null) { // intentionally matches undefined
+ attribs[i + 1] = value;
+ } else {
+ attribs.splice(i, 2);
+ i -= 2;
+ }
+ }
+ var policy = elementPolicies[tagName];
+ if (policy && elementPolicies.hasOwnProperty(tagName)) {
+ return policy(attribs);
+ }
+ return attribs;
+ });
+
+
+ /**
+ * Undoes some of the changes made by sanitizeHtml, e.g. stripping ID
+ * prefixes.
+ */
+ function tameInnerHtml(htmlText) {
+ var out = [];
+ innerHtmlTamer(htmlText, out);
+ return out.join('');
+ }
+ var innerHtmlTamer = html.makeSaxParser({
+ startTag: function (tagName, attribs, out) {
+ out.push('<', tagName);
+ for (var i = 0; i < attribs.length; i += 2) {
+ var attribName = attribs[i];
+ if (attribName === 'TARGET') { continue; }
+ var atype = html4.ATTRIBS[attribName];
+ var value = attribs[i + 1];
+ if (atype === html4.atype.IDREF) {
+ if (value.length <= idPrefix.length
+ || idPrefix !== value.substring(0, idPrefix.length)) {
+ continue;
+ }
+ value = value.substring(idPrefix.length);
+ }
+ if (value != null) {
+ out.push(' ', attribName, '="', html.escapeAttrib(value), '"');
+ }
+ }
+ out.push('>');
+ },
+ endTag: function (name, out) { out.push('</', name, '>'); },
+ pcdata: function (text, out) { out.push(text); },
+ rcdata: function (text, out) { out.push(text); },
+ cdata: function (text, out) { out.push(text); }
+ });
+
+ function rewriteAttribute(tagName, attribName, type, value) {
+ value = String(value);
+ switch (type) {
+ case html4.atype.IDREF:
+ if (!(value && isXmlName(value))) { return null; }
+ return idPrefix + value;
+ case html4.atype.NAME:
+ if (!(value && isXmlName(value))) { return null; }
+ return value;
+ case html4.atype.NMTOKENS:
+ if (!(value && isXmlNmTokens(value))) { return null; }
+ return value;
+ case html4.atype.SCRIPT:
+ // Translate a handler that calls a simple function like
+ // return foo(this, event)
+
+ // TODO(mikesamuel): integrate cajita compiler to allow arbitrary
+ // cajita in event handlers.
+ var match = value.match(SIMPLE_HANDLER_PATTERN);
+ if (!match) { return null; }
+ var doesReturn = match[1];
+ var fnName = match[2];
+ var pluginId = ___.getId(outers);
+ value = (doesReturn ? 'return ' : '') + 'plugin_dispatchEvent___('
+ + 'this, event || window.event, ' + pluginId + ', "'
+ + fnName + '");';
+ if (attribName === 'ONSUBMIT') {
+ value = 'try { ' + value + ' } finally { return false; }';
+ }
+ return value;
+ case html4.atype.URI:
+ if (!uriCallback) { return null; }
+ // TODO(mikesamuel): determine mime type properly.
+ return uriCallback.rewrite(value, '*/*') || null;
+ case html4.atype.STYLE:
+ // TODO(mikesamuel): sanitize css
+ return null;
+ case html4.atype.FRAME:
+ // Frames are ambient, so disallow reference.
+ return null;
+ default:
+ return value;
+ }
+ }
+
+ /**
+ * returns a tame DOM node.
+ * @param {Node} node
+ * @param {boolean} editable
+ * @see <a href="http://www.w3.org/TR/DOM-Level-2-HTML/html.html"
+ * >DOM Level 2</a>
+ */
+ function tameNode(node, editable) {
+ if (node == null) { return null; } // Returns null for undefined.
+ var tamed;
+ switch (node.nodeType) {
+ case 1: // Element
+ switch (node.tagName.toUpperCase()) {
+ case 'FORM':
+ tamed = new TameFormElement(node, editable);
+ break;
+ case 'INPUT':
+ tamed = new TameInputElement(node, editable);
+ break;
+ default:
+ tamed = new TameElement(node, editable);
+ break;
+ }
+ break;
+ case 3: // Text
+ tamed = new TameTextNode(node, editable);
+ break;
+ default:
+ return null;
+ }
+ return tamed;
+ }
+
+ /**
+ * Returns a NodeList like object.
+ */
+ function tameNodeList(nodeList, editable, opt_keyAttrib) {
+ var tamed = [];
+ for (var i = nodeList.length; --i >= 0;) {
+ var node = tameNode(nodeList.item(i), editable);
+ tamed[i] = node;
+ // Make the node available via its name if doing so would not mask
+ // any properties of tamed.
+ var key = opt_keyAttrib && node.getAttribute(opt_keyAttrib);
+ if (key && !(/_$/.test(key) || (key in tamed)
+ || key === String(key & 0x7fffffff))) {
+ tamed[key] = node;
+ }
+ }
+ tamed.item = function (k) {
+ k &= 0x7fffffff;
+ if (isNaN(k)) { throw new Error(); }
+ return this[k] || null;
+ };
+ // TODO(mikesamuel): if opt_keyAttrib, could implement getNamedItem
+ return tamed;
+ }
+
+ /**
+ * Base class for a Node wrapper. Do not create directly -- use the
+ * tameNode factory instead.
+ * @constructor
+ */
+ function TameNode(node, editable) {
+ this.node___ = node;
+ this.editable___ = editable;
+ }
+ TameNode.prototype.secret___ = tameNodeSecret;
+ TameNode.prototype.getNodeType = function () {
+ return this.node___.nodeType;
+ };
+ TameNode.prototype.getNodeName = function () {
+ return this.node___.nodeName;
+ };
+ TameNode.prototype.getNodeValue = function () {
+ return this.node___.nodeValue;
+ };
+ TameNode.prototype.appendChild = function (child) {
+ // Child must be editable since appendChild can remove it from its parent.
+ if (child.secret___ !== tameNodeSecret
+ || !this.editable___ || !child.editable___) {
+ throw new Error();
+ }
+ this.node___.appendChild(child.node___);
+ };
+ TameNode.prototype.insertBefore = function (child) {
+ if (child.secret___ !== tameNodeSecret
+ || !this.editable___ || !child.editable___) {
+ throw new Error();
+ }
+ this.node___.insertBefore(child.node___);
+ };
+ TameNode.prototype.removeChild = function (child) {
+ if (child.secret___ !== tameNodeSecret || !this.editable___) {
+ throw new Error();
+ }
+ this.node___.removeChild(child.node___);
+ };
+ TameNode.prototype.replaceChild = function (child, replacement) {
+ if (child.secret___ !== tameNodeSecret
+ || replacement.secret___ !== tameNodeSecret
+ || !this.editable___
+ || !replacement.editable___) {
+ throw new Error();
+ }
+ this.node___.replaceChild(child.node___, replacement.node___);
+ };
+ TameNode.prototype.getFirstChild = function () {
+ return tameNode(this.node___.firstChild);
+ };
+ TameNode.prototype.getLastChild = function () {
+ return tameNode(this.node___.lastChild);
+ };
+ TameNode.prototype.getNextSibling = function () {
+ return tameNode(this.node___.nextSibling);
+ };
+ TameNode.prototype.getPrevSibling = function () {
+ return tameNode(this.node___.prevSibling);
+ };
+ TameNode.prototype.getElementsByTagName = function (tagName) {
+ return tameNodeList(
+ this.node___.getElementsByTagName(String(tagName)), this.editable___);
+ };
+ ___.ctor(TameNode, undefined, 'TameNode');
+ ___.all2(
+ ___.allowMethod, TameNode,
+ ['getNodeType', 'getNodeValue', 'getNodeName',
+ 'appendChild', 'insertBefore', 'removeChild', 'replaceChild',
+ 'getFirstChild', 'getLastChild', 'getNextSibling', 'getPrevSibling',
+ 'getElementsByTagName']);
+ exportFields(TameNode, ['nodeType', 'nodeValue', 'nodeName', 'firstChild',
+ 'lastChild', 'nextSibling', 'prevSibling']);
+
+ function TameTextNode(node, editable) {
+ assert(node.nodeType === 3);
+ TameNode.call(this, node, editable);
+ }
+ extend(TameTextNode, TameNode);
+ TameTextNode.prototype.setNodeValue = function (value) {
+ if (!this.editable___) { throw new Error(); }
+ this.node___.nodeValue = String(value || '');
+ };
+ TameTextNode.prototype.getData = TameTextNode.prototype.getNodeValue;
+ TameTextNode.prototype.setData = TameTextNode.prototype.setNodeValue;
+ TameTextNode.prototype.toString = function () {
+ return '#text';
+ };
+ ___.ctor(TameTextNode, undefined, 'TameNode');
+ ___.all2(___.allowMethod, TameTextNode,
+ ['setNodeValue', 'getData', 'setData']);
+ exportFields(TameTextNode, ['nodeValue', 'data']);
+
+
+ function TameElement(node, editable) {
+ assert(node.nodeType === 1);
+ TameNode.call(this, node, editable);
+ }
+ extend(TameElement, TameNode);
+ TameElement.prototype.getId = function () {
+ return this.getAttribute('ID') || '';
+ };
+ TameElement.prototype.setId = function (newId) {
+ return this.setAttribute('ID', newId);
+ };
+ TameElement.prototype.getAttribute = function (name) {
+ name = String(name).toUpperCase();
+ var type = html4.ATTRIBS[name];
+ if (type === undefined || !html4.ATTRIBS.hasOwnProperty(name)) {
+ return null;
+ }
+ var value = this.node___.getAttribute(name);
+ if ('string' !== typeof value) { return value; }
+ switch (type) {
+ case html4.atype.IDREF:
+ var n = idPrefix.length;
+ if (value && value.length >= n
+ && idPrefix === value.substring(0, n)) {
+ return value.substring(n);
+ }
+ return '';
+ default:
+ return value;
+ }
+ };
+ TameElement.prototype.setAttribute = function (name, value) {
+ if (!this.editable___) { throw new Error(); }
+ name = String(name).toUpperCase();
+ var type = html4.ATTRIBS[name];
+ if (type === undefined || !html4.ATTRIBS.hasOwnProperty(name)) {
+ throw new Error();
+ }
+ var sanitizedValue = rewriteAttribute(
+ this.node___.tagName, name, type, value);
+ if (sanitizedValue !== null) {
+ this.node___.setAttribute(name, sanitizedValue);
+ }
+ };
+ TameElement.prototype.getClassName = function () {
+ return getAttribute('class');
+ };
+ TameElement.prototype.setClassName = function (classes) {
+ if (!this.editable___) { throw new Error(); }
+ return this.getAttribute('class', String(classes));
+ };
+ TameElement.prototype.getTagName = TameNode.prototype.getNodeName;
+ TameElement.prototype.getInnerHTML = function () {
+ var tagName = this.node___.tagName.toUpperCase();
+ if (!html4.ELEMENTS.hasOwnProperty(tagName)) {
+ return ''; // unknown node
+ }
+ var flags = html4.ELEMENTS[tagName];
+ var innerHtml = this.node___.innerHTML;
+ if (flags & html4.eflags.CDATA) {
+ innerHtml = html.escapeAttrib(innerHtml);
+ } else if (flags & html4.eflags.RCDATA) {
+ // Make sure we return PCDATA.
+ // TODO(mikesamuel): for RCDATA we only need to escape & if they're not
+ // part of an entity.
+ innerHtml = html.normalizeRCData(innerHtml);
+ } else {
+ innerHtml = tameInnerHtml(innerHtml);
+ }
+ // TODO(mikesamuel): rewrite ids
+ return innerHtml;
+ };
+ TameElement.prototype.setInnerHTML = function (html) {
+ if (!this.editable___) { throw new Error(); }
+ var tagName = this.node___.tagName.toUpperCase();
+ if (!html4.ELEMENTS.hasOwnProperty(tagName)) { throw new Error(); }
+ var flags = html4.ELEMENTS[tagName];
+ if (flags & html4.eflags.UNSAFE) { throw new Error(); }
+ html = String(html || '');
+ if (flags & html4.eflags.RCDATA) {
+ html = html.normalizeRCData(html);
+ } else {
+ html = sanitizeHtml(html);
+ }
+ this.node___.innerHTML = html;
+ };
+ TameElement.prototype.addEventListener = function (name, listener, bubble) {
+ if (!this.editable___) { throw new Error(); }
+ if ('function' !== typeof listener) {
+ throw new Error();
+ }
+ name = String(name);
+ if (this.node___.addEventListener) {
+ this.node___.addEventListener(
+ name,
+ function (event) {
+ return plugin_dispatchEvent___(
+ this, event || window.event, ___.getId(outers),
+ listener);
+ }, bubble === undefined ? undefined : Boolean(bubble));
+ } else {
+ var thisNode = this;
+ this.node___.attachEvent('on' + name, dispatcher);
+ }
+ };
+ TameElement.prototype.toString = function () {
+ return '<' + this.node___.tagName + '>';
+ };
+ ___.ctor(TameElement, TameNode, 'TameElement');
+ ___.all2(
+ ___.allowMethod, TameElement,
+ ['addEventListener', 'getAttribute', 'setAttribute',
+ 'getClassName', 'setClassName', 'getId', 'setId',
+ 'getInnerHTML', 'setInnerHTML', 'getTagName']);
+ exportFields(TameElement, ['className', 'id', 'innerHTML', 'tagName']);
+
+
+ function TameFormElement(node, editable) {
+ TameElement.call(this, node, editable);
+ }
+ extend(TameFormElement, TameElement);
+ TameFormElement.prototype.getElements = function () {
+ return tameNodeList(this.node___.elements, this.editable___, 'name');
+ };
+ ___.ctor(TameFormElement, TameElement, 'TameFormElement');
+ ___.all2(___.allowMethod, TameFormElement, ['getElements']);
+ exportFields(TameFormElement, ['elements']);
+
+
+ function TameInputElement(node, editable) {
+ TameElement.call(this, node, editable);
+ }
+ extend(TameInputElement, TameElement);
+ TameInputElement.prototype.getValue = function () {
+ var value = this.node___.value;
+ return value == null ? null : String(value); // Return null for undefined.
+ };
+ TameInputElement.prototype.setValue = function (newValue) {
+ if (!this.editable___) { throw new Error(); }
+ // == matches undefined
+ this.node___.value = (newValue == null ? '' : '' + value);
+ };
+ TameInputElement.prototype.focus = function () {
+ if (!this.editable___) { throw new Error(); }
+ this.node___.focus();
+ };
+ TameInputElement.prototype.blur = function () {
+ if (!this.editable___) { throw new Error(); }
+ this.node___.blur();
+ };
+ TameInputElement.prototype.getForm = function () {
+ return tameNode(this.node___.form);
+ };
+ ___.ctor(TameInputElement, TameElement, 'TameInputElement');
+ ___.all2(___.allowMethod, TameInputElement,
+ ['getValue', 'setValue', 'focus', 'getForm']);
+ exportFields(TameInputElement, ['value', 'form']);
+
+
+ function TameEvent(event) {
+ this.event___ = event;
+ }
+ TameEvent.prototype.getType = function () {
+ return String(this.event___.type);
+ };
+ TameEvent.prototype.getTarget = function () {
+ return tameNode(this.event___.target, true);
+ };
+ TameEvent.prototype.getPageX = function () {
+ return Number(this.event___.pageX);
+ };
+ TameEvent.prototype.getPageY = function () {
+ return Number(this.event___.pageY);
+ };
+ TameEvent.prototype.stopPropagation = function () {
+ // TODO(mikesamuel): make sure event doesn't propagate to dispatched
+ // events for this gadget only.
+ // But don't allow it to stop propagation to the container.
+ return this.event___.stopPropagation();
+ };
+ TameEvent.prototype.getAltKey = function () {
+ return Boolean(this.event___.altKey);
+ };
+ TameEvent.prototype.getCtrlKey = function () {
+ return Boolean(this.event___.ctrlKey);
+ };
+ TameEvent.prototype.getMetaKey = function () {
+ return Boolean(this.event___.metaKey);
+ };
+ TameEvent.prototype.getShiftKey = function () {
+ return Boolean(this.event___.shiftKey);
+ };
+ TameEvent.prototype.getButton = function () {
+ var e = this.event___;
+ return e.button && Number(e.button);
+ };
+ TameEvent.prototype.getClientX = function () {
+ return Number(this.event___.clientX);
+ };
+ TameEvent.prototype.getClientY = function () {
+ return Number(this.event___.clientY);
+ };
+ TameEvent.prototype.getWhich = function () {
+ var w = this.event___.which;
+ return w && Number(w);
+ };
+ TameEvent.prototype.getKeyCode = function () {
+ var kc = this.event___.keyCode;
+ return kc && Number(kc);
+ };
+ TameEvent.prototype.toString = function () { return 'Not a real event'; };
+ TameEvent.prototype.current
+ ___.ctor(TameEvent, undefined, 'TameEvent');
+ ___.all2(___.allowMethod, TameEvent,
+ ['getType', 'getTarget', 'getPageX', 'getPageY', 'stopPropagation',
+ 'getAltKey', 'getCtrlKey', 'getMetaKey', 'getShiftKey',
+ 'getButton', 'getClientX', 'getClientY',
+ 'getKeyCode', 'getWhich']);
+ exportFields(TameEvent, ['type', 'target', 'pageX', 'pageY', 'altKey',
+ 'ctrlKey', 'metaKey', 'shiftKey', 'button',
+ 'clientX', 'clientY', 'keyCode', 'which']);
+
+
+ function TameDocument(doc, editable) {
+ this.doc___ = doc;
+ this.editable___ = editable;
+ }
+ TameDocument.prototype.createElement = function (tagName) {
+ if (!this.editable___) { throw new Error(); }
+ tagName = String(tagName).toUpperCase();
+ if (!html4.ELEMENTS.hasOwnProperty(tagName)) { throw new Error(); }
+ var flags = html4.ELEMENTS[tagName];
+ if (flags & html4.eflags.UNSAFE) { throw new Error(); }
+ var newEl = this.doc___.createElement(tagName);
+ if (elementPolicies.hasOwnProperty(tagName)) {
+ var attribs = elementPolicies[tagName]([]);
+ if (attribs) {
+ for (var i = 0; i < attribs.length; i += 2) {
+ newEl.setAttribute(attribs[i], attribs[i + 1]);
+ }
+ }
+ }
+ return tameNode(newEl, true);
+ };
+ TameDocument.prototype.createTextNode = function (text) {
+ if (!this.editable___) { throw new Error(); }
+ return tameNode( // == matches undefined
+ this.doc___.createTextNode(text != null ? '' + text : ''), true);
+ };
+ TameDocument.prototype.getElementById = function (id) {
+ id = idPrefix + id;
+ var node = this.doc___.getElementById(id);
+ return tameNode(node, this.editable___);
+ };
+ TameDocument.prototype.toString = function () { return '[Fake Document]'; };
+ ___.ctor(TameDocument, undefined, 'TameDocument');
+ ___.all2(___.allowMethod, TameDocument,
+ ['createElement', 'createTextNode', 'getElementById']);
+
+
+ outers.tameNode___ = tameNode;
+ outers.tameEvent___ = function (event) { return new TameEvent(event); };
+ outers.document = new TameDocument(document, true);
+ }
+
+ return attachDocumentStub;
+})();
+
+/**
+ * Function called from rewritten event handlers to dispatch an event safely.
+ */
+function plugin_dispatchEvent___(thisNode, event, pluginId, handler) {
+ (typeof console !== 'undefined' && console.log) &&
+ console.log(
+ 'Dispatch %s event thisNode=%o, event=%o, pluginId=%o, handler=%o',
+ event.type, thisNode, event, pluginId, handler);
+ var outers = ___.getOuters(pluginId);
+ switch (typeof handler) {
+ case 'string':
+ handler = outers[handler];
+ break;
+ case 'function':
+ break;
+ default:
+ throw new Error(
+ 'Expected function as event handler, not ' + typeof handler);
+ }
+ return (___.asSimpleFunc(handler))(
+ outers.tameNode___(thisNode), outers.tameEvent___(event));
+}
Modified: incubator/shindig/trunk/features/caja/feature.xml
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/features/caja/feature.xml?rev=635936&r1=635935&r2=635936&view=diff
==============================================================================
--- incubator/shindig/trunk/features/caja/feature.xml (original)
+++ incubator/shindig/trunk/features/caja/feature.xml Tue Mar 11 07:21:16 2008
@@ -23,7 +23,10 @@
<name>caja</name>
<gadget>
<script src="caja.js"></script>
+ <script src="html4-defs.js"></script>
<script src="html-sanitizer.js"></script>
+ <script src="unicode.js"></script>
+ <script src="domita.js"></script>
<script src="log-to-console.js"></script>
<script src="permissive.js"></script>
</gadget>