Revision: 5470
Author: [email protected]
Date: Tue Jul 2 14:37:42 2013
Log: Whitelist <video> and <audio> content.
https://codereview.appspot.com/10882043
* Add AUDIO::SRC and VIDEO::SRC to whitelist.
* Tame HTMLMediaElement, HTMLAudioElement, and HTMLVideoElement.
* Add Audio ctor.
Supporting changes:
* The PT.filter(false, identity, true, identity) pattern shall now be
known as NP_writePolicyOnly, with comments. More refactoring to come.
* Slightly generalize handling of Image and Option ctors.
[email protected]
http://code.google.com/p/google-caja/source/detail?r=5470
Modified:
/trunk/src/com/google/caja/lang/html/html5-attributes-whitelist.json
/trunk/src/com/google/caja/plugin/domado.js
/trunk/tests/com/google/caja/plugin/es53-test-domado-dom-guest.html
/trunk/tests/com/google/caja/plugin/es53-test-scan-guest.js
=======================================
--- /trunk/src/com/google/caja/lang/html/html5-attributes-whitelist.json
Wed Jun 19 13:54:01 2013
+++ /trunk/src/com/google/caja/lang/html/html5-attributes-whitelist.json
Tue Jul 2 14:37:42 2013
@@ -76,6 +76,8 @@
"OL::REVERSED",
"*::SPELLCHECK",
"LINK::SIZES",
+ "AUDIO::SRC",
+ "VIDEO::SRC",
"TRACK::SRCLANG",
"INPUT::STEP",
"*::TRANSLATE",
@@ -253,10 +255,6 @@
"TODO(kpreid): Stop migrating style elements so that this works"
},
{ "key": "IFRAME::SEAMLESS",
"comment": "TODO(kpreid): further review" },
- { "key": "AUDIO::SRC",
- "comment": "TODO(kpreid): further review" },
- { "key": "VIDEO::SRC",
- "comment": "TODO(kpreid): further review" },
{ "key": "IFRAME::SRCDOC",
"comment": "TODO(kpreid): Implement HTML atype sanitization" }
]
=======================================
--- /trunk/src/com/google/caja/plugin/domado.js Thu Jun 20 09:42:14 2013
+++ /trunk/src/com/google/caja/plugin/domado.js Tue Jul 2 14:37:42 2013
@@ -1851,6 +1851,11 @@
var tamingClassTable = new TamingClassTable();
var inertCtor = tamingClassTable.inertCtor.bind(tamingClassTable);
+ // Table of functions which are what WebIDL calls [NamedConstructor]
+ // (Caveat: In actual browsers, e.g. new Image().constructor ===
Image !==
+ // HTMLImageElement. We don't implement that.)
+ var namedConstructors = {};
+
// The private properties used in TameNodeConf are:
// feral (feral node)
// policy (access policy)
@@ -2628,6 +2633,15 @@
var NP_noArgEditVoidMethod = NP_NoArgEditMethod(noop);
var NP_noArgEditMethodReturningNode =
NP_NoArgEditMethod(defaultTameNode);
+ /**
+ * Property spec for properties which reflect attributes whose
values are
+ * not rewritten (so we can use the underlying property on getting)
but
+ * should, on setting, be restricted by the attribute whitelist.
+ */
+ // This alias exists so as to document *why* we're doing this
particular
+ // configuration.
+ var NP_writePolicyOnly = PT.filter(false, identity, true, identity);
+
var nodeClassNoImplWarnings = {};
var elementTamerCache = {};
function makeTameNodeByType(node) {
@@ -4404,10 +4418,21 @@
// TODO(felix8a): add suffix if href is self
identity),
// TODO(felix8a): fragment rewriting?
- href: PT.filter(false, identity, true, identity)
+ href: NP_writePolicyOnly
}; }
});
+ defineElement({
+ superclass: 'HTMLMediaElement',
+ domClass: 'HTMLAudioElement'
+ });
+ namedConstructors.Audio = innocuous(function AudioCtor(src) {
+ var element = tameDocument.createElement('audio');
+ element.preload = 'auto';
+ if (src !== undefined) { element.src = src; }
+ return element;
+ });
+
defineTrivialElement('HTMLBRElement');
defineElement({
@@ -5317,6 +5342,13 @@
PT.filterProp(Boolean, Boolean))
}; }
});
+ // Per https://developer.mozilla.org/en-US/docs/DOM/Image as of
2012-09-24
+ namedConstructors.Image = innocuous(function ImageCtor(width,
height) {
+ var element = tameDocument.createElement('img');
+ if (width !== undefined) { element.width = width; }
+ if (height !== undefined) { element.height = height; }
+ return element;
+ });
// Common supertype just to save some code -- does not correspond to
real
// HTML, but should be harmless. Ideally we wouldn't export this.
@@ -5394,6 +5426,69 @@
}; }
});
+ defineElement({
+ domClass: 'HTMLMediaElement',
+ properties: function() { return {
+ // TODO(kpreid): audioTracks taming
+ autoplay: NP_writePolicyOnly,
+ // TODO(kpreid): buffered (TimeRanges) taming
+ // TODO(kpreid): controller (MediaController) taming
+ controls: NP_writePolicyOnly,
+ crossOrigin: NP_writePolicyOnly,
+ currentSrc: PT.ro,
+ currentTime: PT.ro,
+ defaultMuted: {
+ // TODO: express this generically
+ enumerable: true,
+ get: nodeAmp(function(privates) {
+ return privates.feral.defaultMuted;
+ }),
+ set: innocuous(function(v) {
+ if (v) {
+ this.setAttribute('muted', '');
+ } else {
+ this.removeAttribute('muted');
+ }
+ })
+ },
+ defaultPlaybackRate: PT.filterProp(identity, Number),
+ duration: PT.ro,
+ ended: PT.ro,
+ // TODO(kpreid): error (MediaError) taming
+ loop: NP_writePolicyOnly,
+ mediaGroup: PT.filterAttr(identity, identity), // rewritten
like id
+ muted: PT.ro, // TODO(kpreid): Pending policy about guest audio
+ networkState: PT.ro,
+ paused: PT.ro,
+ playbackRate: PT.filterProp(identity, Number),
+ // TODO(kpreid): played (TimeRanges) taming
+ preload: NP_writePolicyOnly,
+ readyState: PT.ro,
+ // TODO(kpreid): seekable (TimeRanges) taming
+ seeking: PT.ro,
+ src: NP_writePolicyOnly,
+ // TODO(kpreid): textTracks (TextTrackList) taming
+ // TODO(kpreid): videoTracks (VideoTrackList) taming
+ volume: PT.ro, // TODO(kpreid): Pending policy about guest audio
+ canPlayType: Props.ampMethod(function(privates, type) {
+ return String(privates.feral.canPlayType(String(type)));
+ }),
+ fastSeek: Props.ampMethod(function(privates, time) {
+ // TODO(kpreid): Use generic taming like canvas does
+ privates.policy.requireEditable();
+ privates.feral.fastSeek(+time);
+ }),
+ load: NP_noArgEditVoidMethod,
+ pause: NP_noArgEditVoidMethod,
+ play: Props.ampMethod(function(privates, time) {
+ // TODO(kpreid): Better programmatic control approach
+ if (domicile.handlingUserAction) {
+ privates.feral.play();
+ }
+ })
+ }; }
+ });
+
defineElement({
domClass: 'HTMLOptionElement',
properties: function() { return {
@@ -5410,7 +5505,20 @@
function (x) { return x == null ? '' : '' + x; })
}; }
});
-
+ // Per https://developer.mozilla.org/en-US/docs/DOM/Option
+ // as of 2012-09-24
+ namedConstructors.Option = innocuous(function OptionCtor(
+ text, value, defaultSelected, selected) {
+ var element = tameDocument.createElement('option');
+ if (text !== undefined) { element.text = text; }
+ if (value !== undefined) { element.value = value; }
+ if (defaultSelected !== undefined) {
+ element.defaultSelected = defaultSelected;
+ }
+ if (selected !== undefined) { element.selected = selected; }
+ return element;
+ });
+
defineTrivialElement('HTMLParagraphElement');
defineTrivialElement('HTMLPreElement');
@@ -5457,7 +5565,7 @@
// document.createElement'd scripts for src loading
}),
properties: function() { return {
- src: PT.filter(false, identity, true, identity),
+ src: NP_writePolicyOnly,
setAttribute: Props.ampMethod(function(privates, attrib, value) {
var feral = privates.feral;
privates.policy.requireEditable();
@@ -5480,7 +5588,7 @@
document.createElement('style'));
return {
disabled: PT.filterProp(identity, Boolean),
- media: PT.filter(false, identity, true, identity),
+ media: NP_writePolicyOnly,
scoped: Props.cond('scoped' in styleForFeatureTests,
PT.filterProp(identity, Boolean)),
// TODO(kpreid): property 'sheet'
@@ -5615,38 +5723,20 @@
virtualized: null,
domClass: 'HTMLUnknownElement'
});
-
- // We are now done with all of the specialized element taming
classes.
- // Oddball constructors. There are only two of these and we implement
- // both. (Caveat: In actual browsers, new Image().constructor ==
Image
- // != HTMLImageElement. We don't implement that.)
- // TODO(kpreid): There are more oddball constructors in HTML5, e.g.
Audio,
- // and a notation for it in HTML5's WebIDL. Generalize this to
support
- // that.
-
- // Per https://developer.mozilla.org/en-US/docs/DOM/Image as of
2012-09-24
- function TameImageFun(width, height) {
- var element = tameDocument.createElement('img');
- if (width !== undefined) { element.width = width; }
- if (height !== undefined) { element.height = height; }
- return element;
- }
- cajaVM.def(TameImageFun);
-
- // Per https://developer.mozilla.org/en-US/docs/DOM/Option
- // as of 2012-09-24
- function TameOptionFun(text, value, defaultSelected, selected) {
- var element = tameDocument.createElement('option');
- if (text !== undefined) { element.text = text; }
- if (value !== undefined) { element.value = value; }
- if (defaultSelected !== undefined) {
- element.defaultSelected = defaultSelected;
- }
- if (selected !== undefined) { element.selected = selected; }
- return element;
- }
- cajaVM.def(TameOptionFun);
+ defineElement({
+ superclass: 'HTMLMediaElement',
+ domClass: 'HTMLVideoElement',
+ properties: function() { return {
+ height: PT.rw,
+ width: PT.rw,
+ poster: PT.filterAttr(identity, identity),
+ videoHeight: PT.rw,
+ videoWidth: PT.rw
+ }; }
+ });
+
+ // We are now done with all of the specialized element taming
classes.
// Taming of Events:
@@ -6942,8 +7032,16 @@
}
}
- tameWindow.Image = TameImageFun;
- tameWindow.Option = TameOptionFun;
+ // Register [NamedConstructor]s
+ Object.freeze(namedConstructors); // catch initialization order
errors
+ Object.getOwnPropertyNames(namedConstructors).forEach(function(name)
{
+ Object.defineProperty(tameWindow, name, {
+ enumerable: true,
+ configurable: true,
+ writable: true,
+ value: namedConstructors[name]
+ });
+ });
tameDocument = finishNode(tameDocument);
=======================================
--- /trunk/tests/com/google/caja/plugin/es53-test-domado-dom-guest.html Mon
Jun 24 11:58:58 2013
+++ /trunk/tests/com/google/caja/plugin/es53-test-domado-dom-guest.html Tue
Jul 2 14:37:42 2013
@@ -2259,6 +2259,10 @@
typenames: [],
sample: window.navigator
},
+ {
+ typenames: [],
+ sample: new Audio()
+ },
{
typenames: [],
sample: new Image()
@@ -2669,8 +2673,19 @@
function testOddballConstructors() {
// Note: This test also tests the properties of the img and option
elements.
+ var audio1 = new Audio();
+ assertEquals('AUDIO', audio1.nodeName);
+ assertEquals('audio default src', '', audio1.src);
+ assertEquals('audio implicit preload', 'auto', audio1.preload);
+ var audio2 = new Audio('foo.wav');
+ // TODO(kpreid): This should be assertEquals but URI rewriting is
visible
+ assertContains('audio supplied src', 'foo.wav', audio2.src);
+ document.getElementById('testOddballConstructors').appendChild(audio2);
+ // should not fail - i.e. is a valid element
+
// new Image() is approximately document.createElement('img')
var image1 = new Image();
+ assertEquals('IMG', image1.nodeName);
assertEquals('image default nodeType', 1, image1.nodeType);
assertEquals('image default tagName', 'IMG', image1.tagName);
assertEquals('image default width', 0, image1.width);
@@ -2684,6 +2699,7 @@
// new Option() is approximately document.createElement('option')
var option1 = new Option();
+ assertEquals('OPTION', option1.nodeName);
assertEquals('option default nodeType', 1, option1.nodeType);
assertEquals('option default tagName', 'OPTION', option1.tagName);
assertEquals('option default text', '', option1.text);
=======================================
--- /trunk/tests/com/google/caja/plugin/es53-test-scan-guest.js Fri Jun 21
14:57:00 2013
+++ /trunk/tests/com/google/caja/plugin/es53-test-scan-guest.js Tue Jul 2
14:37:42 2013
@@ -22,8 +22,9 @@
* setTimeout, clearTimeout, setInterval, clearInterval,
* cajaVM, directAccess, inES5Mode, getUrlParam,
* assertTrue, assertEquals, pass, jsunitFail,
- * Event, HTMLInputElement, HTMLTableRowElement,
HTMLTableSectionElement,
- * HTMLTableElement, Image, Option, XMLHttpRequest, Window, Document,
Node,
+ * Event, HTMLInputElement, HTMLMediaElement, HTMLTableRowElement,
+ * HTMLTableSectionElement, HTMLTableElement,
+ * Audio, Image, Option, XMLHttpRequest, Window, Document, Node,
* Attr, Text, CSSStyleDeclaration, CanvasRenderingContext2D,
* CanvasGradient, ImageData, Location
* @overrides window
@@ -669,7 +670,7 @@
}
// TODO(kpreid): factor out recognition
if (typeof object === 'function' &&
- /function Tame(?!XMLHttpRequest|OptionFun|ImageFun)/
+ /function Tame(?!XMLHttpRequest)/
.test(object.toString())) {
noteProblem('Object is a taming ctor', context);
}
@@ -940,6 +941,8 @@
'backgroundColor', 'content', 'foo');
var genCSSColor = G.value(undefined,
null, '', 'red', '#17F', 'octarine',
'}{');
+ var genMediaType = G.value(undefined, '', '/', 'text', 'text/*',
+ 'text/plain', 'image/*', 'image/gif', 'audio/*', 'audio/x-wav');
var genJS = G.value(null, '', '{', 'return true;');
var genEventHandlerSet = G.tuple(G.value(THIS), G.tuple(genJS));
var largestSmallInteger = 63;
@@ -1299,6 +1302,12 @@
argsByIdentity(HTMLInputElement.prototype.select, genNoArgMethod);
argsByIdentity(HTMLInputElement.prototype.stepDown, genNoArgMethod);
argsByIdentity(HTMLInputElement.prototype.stepUp, genNoArgMethod);
+ argsByIdentity(HTMLMediaElement.prototype.canPlayType,
+ genMethod(genMediaType));
+ argsByIdentity(HTMLMediaElement.prototype.fastSeek,
genMethod(genNumber));
+ argsByIdentity(HTMLMediaElement.prototype.load, genNoArgMethod);
+ argsByIdentity(HTMLMediaElement.prototype.pause, genNoArgMethod);
+ argsByIdentity(HTMLMediaElement.prototype.play, genNoArgMethod);
argsByIdentity(HTMLTableRowElement.prototype.insertCell,
genMethod(genSmallInteger));
argsByIdentity(HTMLTableRowElement.prototype.deleteCell,
@@ -1319,6 +1328,7 @@
genMethod(genSmallInteger));
argsByIdentity(Image, genNew()); // TODO args
argsByIdentity(Option, genNew()); // TODO args
+ argsByIdentity(Audio, genNew()); // TODO args
argsByIdentity(XMLHttpRequest, genNew());
argsByIdentity(XMLHttpRequest.prototype.open, G.none); // TODO
stateful
argsByIdentity(XMLHttpRequest.prototype.setRequestHeader, G.none);
--
---
You received this message because you are subscribed to the Google Groups "Google Caja Discuss" group.
To unsubscribe from this group and stop receiving emails from it, send an email
to [email protected].
For more options, visit https://groups.google.com/groups/opt_out.