Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Trusted Type Knockout #2580

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
17 changes: 17 additions & 0 deletions spec/defaultBindings/htmlBehaviors.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,4 +50,21 @@ describe('Binding: HTML', function() {
expect(td.tagName).toEqual("TD");
expect('innerText' in td ? td.innerText : td.textContent).toEqual("hello");
});

it('Should assign the TrustedHTML directly to innerHTML', function () {
// Mock Trusted Types.
trustedTypes = {};
trustedTypes.isHTML = function(input) {
if (input.type == "TrustedHTML") {
return true;
}
return false;
};

var html = {"type": "TrustedHTML"};
var model = { htmlProp: html };
testNode.innerHTML = "<span data-bind='html:htmlProp'></span>";
ko.applyBindings(model, testNode);
expect(testNode.childNodes[0].innerHTML).toEqual("[object Object]");
});
});
18 changes: 18 additions & 0 deletions src/binding/bindingProvider.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,16 @@
(function() {
var defaultBindingAttributeName = "data-bind";

if (typeof trustedTypes !== "undefined") {
var knockoutPolicy = trustedTypes['createPolicy']('knockout', {
// This is relatively safe for DOM-based XSS.
// But it is not safe for Store/Reflected XSS.
'createScript': function(opaqueScript) {
return opaqueScript;
}
});
}

ko.bindingProvider = function() {
this.bindingCache = {};
};
Expand Down Expand Up @@ -66,6 +76,14 @@
// Example result: with(sc1) { with(sc0) { return (expression) } }
var rewrittenBindings = ko.expressionRewriting.preProcessBindings(bindingsString, options),
functionBody = "with($context){with($data||{}){return{" + rewrittenBindings + "}}}";

if (typeof trustedTypes !== "undefined") {
// Trusted Types has a bug where it throws on new Function
// even if we pass TrustedScript. So use eval instead.
var f = "(function($context, $element) { " + functionBody + " })";
return eval(knockoutPolicy['createScript'](f));
}

return new Function("$context", "$element", functionBody);
}
})();
Expand Down
10 changes: 8 additions & 2 deletions src/utils.domManipulation.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,12 @@
documentContext.body.appendChild(div);
}

div.innerHTML = markup;
if (typeof trustedTypes !== "undefined" && trustedTypes['isHTML'](html)) {
// Pass TrustedHTML as-is.
div.innerHTML = html;
} else {
div.innerHTML = markup;
}

if (mayRequireCreateElementHack) {
div.parentNode.removeChild(div);
Expand Down Expand Up @@ -112,7 +117,8 @@
html = ko.utils.unwrapObservable(html);

if ((html !== null) && (html !== undefined)) {
if (typeof html != 'string')
// If passed html is a TrustedHTML, do not stringify.
if (typeof html != 'string' && typeof trustedTypes !== "undefined" && !trustedTypes['isHTML'](html))
html = html.toString();

// jQuery contains a lot of sophisticated code to parse arbitrary HTML fragments,
Expand Down
32 changes: 18 additions & 14 deletions src/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,20 +41,24 @@ ko.utils = (function () {
});
var eventsThatMustBeRegisteredUsingAttachEvent = { 'propertychange': true }; // Workaround for an IE9 issue - https://github.com/SteveSanderson/knockout/issues/406

// Detect IE versions for bug workarounds (uses IE conditionals, not UA string, for robustness)
// Note that, since IE 10 does not support conditional comments, the following logic only detects IE < 10.
// Currently this is by design, since IE 10+ behaves correctly when treated as a standard browser.
// If there is a future need to detect specific versions of IE10+, we will amend this.
var ieVersion = document && (function() {
var version = 3, div = document.createElement('div'), iElems = div.getElementsByTagName('i');

// Keep constructing conditional HTML blocks until we hit one that resolves to an empty fragment
while (
div.innerHTML = '<!--[if gt IE ' + (++version) + ']><i></i><![endif]-->',
iElems[0]
) {}
return version > 4 ? version : undefined;
}());
var ieVersion = undefined;
if (typeof trustedTypes === "undefined") {
// Detect IE versions for bug workarounds (uses IE conditionals, not UA string, for robustness)
// Note that, since IE 10 does not support conditional comments, the following logic only detects IE < 10.
// Currently this is by design, since IE 10+ behaves correctly when treated as a standard browser.
// If there is a future need to detect specific versions of IE10+, we will amend this.
ieVersion = document && (function() {
var version = 3, div = document.createElement('div'), iElems = div.getElementsByTagName('i');

// Keep constructing conditional HTML blocks until we hit one that resolves to an empty fragment
while (
div.innerHTML = '<!--[if gt IE ' + (++version) + ']><i></i><![endif]-->',
iElems[0]
) {}
return version > 4 ? version : undefined;
}());
}

var isIe6 = ieVersion === 6,
isIe7 = ieVersion === 7;

Expand Down