Skip to content

Commit

Permalink
Add trusted types support to lit html
Browse files Browse the repository at this point in the history
  • Loading branch information
Emanuel Tesar committed Jul 29, 2019
1 parent 6f2bf43 commit a3677e5
Show file tree
Hide file tree
Showing 6 changed files with 145 additions and 16 deletions.
36 changes: 21 additions & 15 deletions src/lib/parts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {RenderOptions} from './render-options.js';
import {TemplateInstance} from './template-instance.js';
import {TemplateResult} from './template-result.js';
import {createMarker} from './template.js';
import {isTrustedValue} from './trusted-types';

// https://tc39.github.io/ecma262/#sec-typeof-operator
export type Primitive = null|undefined|boolean|number|string|Symbol|bigint;
Expand Down Expand Up @@ -69,25 +70,30 @@ export class AttributeCommitter {
protected _getValue(): unknown {
const strings = this.strings;
const l = strings.length - 1;
let text = '';

for (let i = 0; i < l; i++) {
text += strings[i];
const part = this.parts[i];
if (part !== undefined) {
const v = part.value;
if (isPrimitive(v) || !isIterable(v)) {
text += typeof v === 'string' ? v : String(v);
} else {
for (const t of v) {
text += typeof t === 'string' ? t : String(t);

if (l === 1 && strings[0] === '' && strings[1] === '' && isTrustedValue(this.parts[0].value)) {
// this means that attribute value is an interpolated trusted type and we don't want to stringify it
return this.parts[0].value;
} else {
let text = '';
for (let i = 0; i < l; i++) {
text += strings[i];
const part = this.parts[i];
if (part !== undefined) {
const v = part.value;
if (isPrimitive(v) || !isIterable(v)) {
text += typeof v === 'string' ? v : String(v);
} else {
for (const t of v) {
text += typeof t === 'string' ? t : String(t);
}
}
}
}
}

text += strings[l];
return text;
text += strings[l];
return text;
}
}

commit(): void {
Expand Down
3 changes: 2 additions & 1 deletion src/lib/template-result.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import {reparentNodes} from './dom.js';
import {TemplateProcessor} from './template-processor.js';
import {boundAttributeSuffix, lastAttributeNameRegex, marker, nodeMarker} from './template.js';
import {dangerouslyTurnToTrustedHTML} from './trusted-types';

const commentMarker = ` ${marker} `;

Expand Down Expand Up @@ -100,7 +101,7 @@ export class TemplateResult {

getTemplateElement(): HTMLTemplateElement {
const template = document.createElement('template');
template.innerHTML = this.getHTML();
template.innerHTML = dangerouslyTurnToTrustedHTML(this.getHTML());
return template;
}
}
Expand Down
43 changes: 43 additions & 0 deletions src/lib/trusted-types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
function getTrustedTypes() {
// tslint:disable-next-line
return (window as any).TrustedTypes;
}

const rules = {
createHTML(s: string): string {
return s;
},
};
let policy: typeof rules | undefined;

/**
* Turns the value to trusted HTML. If the application uses Trusted Types the value is transformed into TrustedHTML,
* which can be assigned to execution sink. If the application doesn't use Trusted Types, the return value is the same
* as the argument.
*/
export function dangerouslyTurnToTrustedHTML(value: string): string {
const TrustedTypes = getTrustedTypes();
if (!policy && TrustedTypes !== undefined) {
policy = TrustedTypes.createPolicy('lit-html', rules);
}

if (!policy) {
return value;
} else {
return policy.createHTML(value);
}
}

/**
* Checks whether the value is a Trusted Types object instance.
*/
export function isTrustedValue(value: unknown): boolean {
const TrustedTypes = getTrustedTypes();
if (TrustedTypes === undefined) return false;
else {
return TrustedTypes.isHTML(value) ||
TrustedTypes.isScriptURL(value) ||
TrustedTypes.isURL(value) ||
TrustedTypes.isScript(value);
}
}
66 changes: 66 additions & 0 deletions src/test/lib/trusted-types_test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/**
* @license
* Copyright (c) 2019 The Polymer Project Authors. All rights reserved.
* This code may only be used under the BSD style license found at
* http://polymer.github.io/LICENSE.txt
* The complete set of authors may be found at
* http://polymer.github.io/AUTHORS.txt
* The complete set of contributors may be found at
* http://polymer.github.io/CONTRIBUTORS.txt
* Code distributed by Google as part of the polymer project is also
* subject to an additional IP rights grant found at
* http://polymer.github.io/PATENTS.txt
*/

import {render} from '../../lib/shady-render.js';
import {html} from '../../lit-html.js';
import {unsafeHTML} from '../../directives/unsafe-html';

const assert = chai.assert;

suite('rendering with trusted types enforced', () => {
let container: HTMLDivElement;

suiteSetup(() => {
// tslint:disable-next-line
(window as any).TrustedTypes = {
isHTML: () => true,
createPolicy: () => {
createHTML: (value: string) => `TRUSTED${value}`;
},
isScript: () => false,
isScriptURL: () => false,
isURL: () => false,
};

// simulate trusted types enforcement in a browser
Object.defineProperty(HTMLElement.prototype, 'innerHTML', {set: function(value: string) {
// lit-html internally calls dangerouslyTurnToTrustedHTML with '<!--{{uniqueId}}-->'
if (value.startsWith('<!--{{lit-')) this.prototype.innerHTML = value;
else if (value.startsWith('TRUSTED')) this.prototype.innerHTML = value.substr('TRUSTED'.length);
else throw new Error(value);
}});

// create app root in the DOM
container = document.createElement('div');
document.body.appendChild(container);
});

suiteTeardown(() => {
delete HTMLElement.prototype.innerHTML;
});

test('throws when value is not trusted type', () => {
const result = html`${unsafeHTML('<b>unsafe bold</b>')}`;
assert.throws(() => {
render(result, container, {scopeName: 'div'});
});
});

test('passes when value is trusted type', () => {
const result = html`${unsafeHTML('TRUSTED<b>unsafe bold</b>')}`;
assert.throws(() => {
render(result, container, {scopeName: 'div'});
});
});
});
1 change: 1 addition & 0 deletions test/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
'shady-scoping-shim.html?shadydom=true&shimcssproperties=true',
'no-template.html',
'shady_no-wc.html',
'trusted-types.html',
]);
</script>
</body>
Expand Down
12 changes: 12 additions & 0 deletions test/trusted-types.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<!doctype html>
<html>
<head>
<script src="../node_modules/mocha/mocha.js"></script>
<script src="../node_modules/chai/chai.js"></script>
<script src="../node_modules/wct-mocha/wct-mocha.js"></script>
<script src="../node_modules/@webcomponents/webcomponentsjs/webcomponents-bundle.js"></script>
</head>
<body>
<script type="module" src="./lib/trusted-types_test.js"></script>
</body>
</html>

0 comments on commit a3677e5

Please sign in to comment.