From 7a443c5c70c0f01f25d2151644bb5e884e771f90 Mon Sep 17 00:00:00 2001 From: Emanuel Tesar Date: Thu, 5 Sep 2019 16:28:37 +0200 Subject: [PATCH 1/4] feat(web): Integrate trusted types into Vue --- .../web/runtime/modules/dom-props.js | 4 +- src/platforms/web/security.js | 23 ++ src/platforms/web/util/compat.js | 3 +- src/shared/util.js | 17 +- test/unit/features/trusted-types.spec.js | 219 ++++++++++++++++++ 5 files changed, 259 insertions(+), 7 deletions(-) create mode 100644 src/platforms/web/security.js create mode 100644 test/unit/features/trusted-types.spec.js diff --git a/src/platforms/web/runtime/modules/dom-props.js b/src/platforms/web/runtime/modules/dom-props.js index a5c3dc7f4aa..4297151fab2 100644 --- a/src/platforms/web/runtime/modules/dom-props.js +++ b/src/platforms/web/runtime/modules/dom-props.js @@ -2,6 +2,7 @@ import { isDef, isUndef, extend, toNumber } from 'shared/util' import { isSVG } from 'web/util/index' +import {convertToTrustedType} from 'web/security' let svgContainer @@ -20,6 +21,7 @@ function updateDOMProps (oldVnode: VNodeWithData, vnode: VNodeWithData) { for (key in oldProps) { if (!(key in props)) { + // TT_TODO: when (how) is this even called elm[key] = '' } } @@ -51,7 +53,7 @@ function updateDOMProps (oldVnode: VNodeWithData, vnode: VNodeWithData) { } else if (key === 'innerHTML' && isSVG(elm.tagName) && isUndef(elm.innerHTML)) { // IE doesn't support innerHTML for SVG elements svgContainer = svgContainer || document.createElement('div') - svgContainer.innerHTML = `${cur}` + svgContainer.innerHTML = convertToTrustedType(`${cur}`) const svg = svgContainer.firstChild while (elm.firstChild) { elm.removeChild(elm.firstChild) diff --git a/src/platforms/web/security.js b/src/platforms/web/security.js new file mode 100644 index 00000000000..dffcd76354b --- /dev/null +++ b/src/platforms/web/security.js @@ -0,0 +1,23 @@ +/* @flow */ + +type TrustedTypePolicy = { + // value returned is actually an object with toString method returning the wrapped value + createHTML: (value: any) => string; +}; + +let policy: TrustedTypePolicy +export function convertToTrustedType(value: any) { + // create policy lazily to simplify testing + const tt = getTrustedTypes() + if (tt && !policy) { + policy = tt.createPolicy('vue', {createHTML: (s) => s}); + } + + if (!tt) return value; + else return policy.createHTML(value); +} + +export function getTrustedTypes() { + // TrustedTypes have been renamed to trustedTypes https://github.com/WICG/trusted-types/issues/177 + return window.trustedTypes || window.TrustedTypes; +} diff --git a/src/platforms/web/util/compat.js b/src/platforms/web/util/compat.js index d95759cce31..8c5b32ce8ad 100644 --- a/src/platforms/web/util/compat.js +++ b/src/platforms/web/util/compat.js @@ -1,12 +1,13 @@ /* @flow */ import { inBrowser } from 'core/util/index' +import {convertToTrustedType} from 'web/security' // check whether current browser encodes a char inside attribute values let div function getShouldDecode (href: boolean): boolean { div = div || document.createElement('div') - div.innerHTML = href ? `` : `
` + div.innerHTML = convertToTrustedType(href ? `` : `
`) return div.innerHTML.indexOf(' ') > 0 } diff --git a/src/shared/util.js b/src/shared/util.js index 9f240c77b14..7d76afd8115 100644 --- a/src/shared/util.js +++ b/src/shared/util.js @@ -83,11 +83,18 @@ export function isPromise (val: any): boolean { * Convert a value to a string that is actually rendered. */ export function toString (val: any): string { - return val == null - ? '' - : Array.isArray(val) || (isPlainObject(val) && val.toString === _toString) - ? JSON.stringify(val, null, 2) - : String(val) + // TrustedTypes have been renamed to trustedTypes https://github.com/WICG/trusted-types/issues/177 + const tt = window.trustedTypes || window.TrustedTypes; + // TrustedURLs are deprecated and will be removed soon: https://github.com/WICG/trusted-types/pull/204 + if (tt && (tt.isHTML(val) || tt.isScript(val) || tt.isScriptURL(val) || (tt.isURL && tt.isURL(val)))) { + return val; + } else { + return val == null + ? '' + : Array.isArray(val) || (isPlainObject(val) && val.toString === _toString) + ? JSON.stringify(val, null, 2) + : String(val) + } } /** diff --git a/test/unit/features/trusted-types.spec.js b/test/unit/features/trusted-types.spec.js new file mode 100644 index 00000000000..eee96d54865 --- /dev/null +++ b/test/unit/features/trusted-types.spec.js @@ -0,0 +1,219 @@ +// NOTE: We emulate trusted types behaviour such that the tests +// are deterministic. These tests needs to be updated if the trusted +// types API changes. +// +// You can find trusted types repository here: +// https://github.com/WICG/trusted-types +// +// TODO: replace testing setup with polyfill, once it exports +// enforcing API. + +import Vue from 'vue' + +// we don't differentiate between different types of trusted values +const createTrustedValue = (value) => `TRUSTED${value}`; +const isTrustedValue = (value) => value.startsWith('TRUSTED'); +const unwrapTrustedValue = (value) => value.substr('TRUSTED'.length); + +const unsafeHtml = ''; +const unsafeScript = 'alert(0)'; + +describe('rendering with trusted types enforced', () => { + let descriptorEntries = []; + let setAttributeDescriptor; + let policy; + // NOTE: trusted type error is not propagated from v-html directive and application will not + // render the dangerous html, but will continue rendering other components. If the error is + // thrown by unsafe setAttribute call (e.g. srcdoc in iframe) the rendering fails completely. + // We log the errors, before throwing so we can be sure that trusted types work. + let errorLog; + + function emulateSetAttribute() { + // enforce trusted values only on properties in this array + const unsafeAttributeList = ['srcdoc', 'onclick']; + setAttributeDescriptor = Object.getOwnPropertyDescriptor(Element.prototype, 'setAttribute'); + Object.defineProperty(Element.prototype, 'setAttribute', { + value: function(name, value) { + let args = [name, value]; + unsafeAttributeList.forEach((attr) => { + if (attr === name) { + if (isTrustedValue(value)) { + args = [name, unwrapTrustedValue(value)]; + } else { + errorLog.push(createTTErrorMessage(attr, value)); + throw new Error(value); + } + } + }); + setAttributeDescriptor.value.apply(this, args); + } + }); + } + + function emulateTrustedTypesOnProperty(object, prop) { + const desc = Object.getOwnPropertyDescriptor(object, prop); + descriptorEntries.push({object, prop, desc}); + Object.defineProperty(object, prop, { + set: function(value) { + if (isTrustedValue(value)) { + desc.set.apply(this, [unwrapTrustedValue(value)]); + } else { + errorLog.push(createTTErrorMessage(prop, value)); + throw new Error(value); + } + }, + }); + } + + function removeAllTrustedTypesEmulation() { + descriptorEntries.forEach(({object, prop, desc}) => { + Object.defineProperty(object, prop, desc); + }); + descriptorEntries = []; + + Object.defineProperty( + Element.prototype, 'setAttribute', setAttributeDescriptor); + } + + function createTTErrorMessage(name, value) { + return `TT ERROR: ${name} ${value}`; + } + + beforeEach(() => { + window.trustedTypes = { + createPolicy: () => { + return { + createHTML: createTrustedValue, + createScript: createTrustedValue, + createScriptURL: createTrustedValue, + }; + }, + isHTML: (v) => isTrustedValue(v), + isScript: (v) => isTrustedValue(v), + isScriptURL: (v) => isTrustedValue(v), + }; + + emulateTrustedTypesOnProperty(Element.prototype, 'innerHTML'); + emulateTrustedTypesOnProperty(HTMLIFrameElement.prototype, 'srcdoc'); + emulateSetAttribute(); + + // TODO: this needs to be changed once we use trusted types polyfill + policy = window.trustedTypes.createPolicy(); + + errorLog = []; + }); + + afterEach(() => { + removeAllTrustedTypesEmulation(); + delete window.trustedTypes; + }); + + it('Trusted types emulation works', () => { + const el = document.createElement('div'); + expect(el.innerHTML).toBe(''); + el.innerHTML = policy.createHTML('val'); + expect(el.innerHTML, 'val'); + + expect(() => { + el.innerHTML = 'val'; + }).toThrow(); + }); + + // html interpolation is safe because it's put into DOM as text node + it('interpolation is trusted', () => { + const vm = new Vue({ + data: { + unsafeHtml, + }, + template: '
{{unsafeHtml}}
' + }) + + vm.$mount(); + expect(vm.$el.textContent).toBe(document.createTextNode(unsafeHtml).textContent); + }); + + describe('throws on untrusted values', () => { + it('v-html directive', () => { + const vm = new Vue({ + data: { + unsafeHtml, + }, + template: '
' + }) + + vm.$mount(); + expect(errorLog).toEqual([createTTErrorMessage('innerHTML', unsafeHtml)]); + }); + + it('attribute interpolation', () => { + const vm = new Vue({ + data: { + unsafeHtml, + }, + template: '' + }) + + expect(() => { + vm.$mount(); + }).toThrow(); + expect(errorLog).toEqual([createTTErrorMessage('srcdoc', unsafeHtml)]); + }); + + it('on* events', () => { + const vm = new Vue({ + data: { + unsafeScript, + }, + template: '' + }) + + expect(() => { + vm.$mount(); + }).toThrow(); + expect(errorLog).toEqual([createTTErrorMessage('onclick', unsafeScript)]); + }); + }); + + describe('runs without error on trusted values', () => { + it('v-html directive', () => { + const vm = new Vue({ + data: { + safeHtml: policy.createHTML('safeHtmlValue'), + }, + template: '
' + }) + + vm.$mount(); + expect(vm.$el.innerHTML).toBe('safeHtmlValue'); + expect(errorLog).toEqual([]); + }); + + it('attribute interpolation', () => { + const vm = new Vue({ + data: { + safeScript: policy.createScript('safeScriptValue'), + }, + template: '' + }) + + vm.$mount(); + expect(vm.$el.srcdoc).toBe('safeScriptValue'); + expect(errorLog).toEqual([]); + }); + + it('on* events', () => { + const vm = new Vue({ + data: { + safeScript: policy.createScript('safeScriptValue'), + }, + template: '' + }) + + vm.$mount(); + const onClickFn = vm.$el.onclick.toString(); + const onClickBody = onClickFn.substring(onClickFn.indexOf("{") + 1, onClickFn.lastIndexOf("}")); + expect(onClickBody.trim()).toBe('safeScriptValue'); + expect(errorLog).toEqual([]); + }); + }); +}); From 1740085cfdadae4444711a5e3a716ee77f7ba1a7 Mon Sep 17 00:00:00 2001 From: Emanuel Tesar Date: Fri, 6 Sep 2019 17:31:22 +0200 Subject: [PATCH 2/4] feat(web): Make the integration more secure, fix tests --- src/core/config.js | 11 +- .../web/runtime/modules/dom-props.js | 4 +- src/platforms/web/security.js | 26 ++-- src/platforms/web/util/compat.js | 4 +- src/shared/util.js | 17 ++- test/unit/features/trusted-types.spec.js | 115 ++++++++++++++++-- 6 files changed, 152 insertions(+), 25 deletions(-) diff --git a/src/core/config.js b/src/core/config.js index cae9d3dd395..039dbb5c8ae 100644 --- a/src/core/config.js +++ b/src/core/config.js @@ -33,6 +33,9 @@ export type Config = { // legacy _lifecycleHooks: Array; + + // trusted types (https://github.com/WICG/trusted-types) + trustedTypesPolicyName: string; }; export default ({ @@ -126,5 +129,11 @@ export default ({ /** * Exposed for legacy reasons */ - _lifecycleHooks: LIFECYCLE_HOOKS + _lifecycleHooks: LIFECYCLE_HOOKS, + + /** + * Trusted Types policy name which will be used by Vue. More + * info about Trusted Types on https://github.com/WICG/trusted-types. + */ + trustedTypesPolicyName: 'vue' }: Config) diff --git a/src/platforms/web/runtime/modules/dom-props.js b/src/platforms/web/runtime/modules/dom-props.js index 4297151fab2..00cf182674f 100644 --- a/src/platforms/web/runtime/modules/dom-props.js +++ b/src/platforms/web/runtime/modules/dom-props.js @@ -2,7 +2,7 @@ import { isDef, isUndef, extend, toNumber } from 'shared/util' import { isSVG } from 'web/util/index' -import {convertToTrustedType} from 'web/security' +import {maybeCreateDangerousSvgHTML} from 'web/security' let svgContainer @@ -53,7 +53,7 @@ function updateDOMProps (oldVnode: VNodeWithData, vnode: VNodeWithData) { } else if (key === 'innerHTML' && isSVG(elm.tagName) && isUndef(elm.innerHTML)) { // IE doesn't support innerHTML for SVG elements svgContainer = svgContainer || document.createElement('div') - svgContainer.innerHTML = convertToTrustedType(`${cur}`) + svgContainer.innerHTML = maybeCreateDangerousSvgHTML(cur) const svg = svgContainer.firstChild while (elm.firstChild) { elm.removeChild(elm.firstChild) diff --git a/src/platforms/web/security.js b/src/platforms/web/security.js index dffcd76354b..fff83f2ed33 100644 --- a/src/platforms/web/security.js +++ b/src/platforms/web/security.js @@ -1,23 +1,31 @@ /* @flow */ +import Vue from 'core/index' +import {getTrustedTypes, isTrustedValue} from 'shared/util' type TrustedTypePolicy = { // value returned is actually an object with toString method returning the wrapped value createHTML: (value: any) => string; }; -let policy: TrustedTypePolicy -export function convertToTrustedType(value: any) { +let policy: ?TrustedTypePolicy +// we need this function to clear the policy in tests +Vue.prototype.$clearTrustedTypesPolicy = function() { + policy = undefined +} + +export function maybeCreateDangerousSvgHTML(value: any): string { // create policy lazily to simplify testing const tt = getTrustedTypes() if (tt && !policy) { - policy = tt.createPolicy('vue', {createHTML: (s) => s}); + policy = tt.createPolicy(Vue.config.trustedTypesPolicyName, {createHTML: (s) => s}); } - if (!tt) return value; - else return policy.createHTML(value); + if (!tt) return `${value}`; + else if (!isTrustedValue(value)) throw new Error('Expected svg innerHTML to be TrustedHTML!'); + // flow complains 'policy' may be undefined + else return (policy: any).createHTML(`${value}`); } -export function getTrustedTypes() { - // TrustedTypes have been renamed to trustedTypes https://github.com/WICG/trusted-types/issues/177 - return window.trustedTypes || window.TrustedTypes; -} +export function getTrustedShouldDecodeInnerHTML(href: boolean): string { + return href ? `
` : `
` +} \ No newline at end of file diff --git a/src/platforms/web/util/compat.js b/src/platforms/web/util/compat.js index 8c5b32ce8ad..a95339778a6 100644 --- a/src/platforms/web/util/compat.js +++ b/src/platforms/web/util/compat.js @@ -1,13 +1,13 @@ /* @flow */ import { inBrowser } from 'core/util/index' -import {convertToTrustedType} from 'web/security' +import {getTrustedShouldDecodeInnerHTML} from 'web/security' // check whether current browser encodes a char inside attribute values let div function getShouldDecode (href: boolean): boolean { div = div || document.createElement('div') - div.innerHTML = convertToTrustedType(href ? `` : `
`) + div.innerHTML = getTrustedShouldDecodeInnerHTML(href) return div.innerHTML.indexOf(' ') > 0 } diff --git a/src/shared/util.js b/src/shared/util.js index 7d76afd8115..b5aea3b3eba 100644 --- a/src/shared/util.js +++ b/src/shared/util.js @@ -79,14 +79,23 @@ export function isPromise (val: any): boolean { ) } +export function getTrustedTypes() { + // TrustedTypes have been renamed to trustedTypes https://github.com/WICG/trusted-types/issues/177 + return typeof window !== 'undefined' && (window.trustedTypes || window.TrustedTypes); +} + +export function isTrustedValue(value: any): boolean { + const tt = getTrustedTypes(); + if (!tt) return false; + // TrustedURLs are deprecated and will be removed soon: https://github.com/WICG/trusted-types/pull/204 + else return tt.isHTML(value) || tt.isScript(value) || tt.isScriptURL(value) || (tt.isURL && tt.isURL(value)) +} + /** * Convert a value to a string that is actually rendered. */ export function toString (val: any): string { - // TrustedTypes have been renamed to trustedTypes https://github.com/WICG/trusted-types/issues/177 - const tt = window.trustedTypes || window.TrustedTypes; - // TrustedURLs are deprecated and will be removed soon: https://github.com/WICG/trusted-types/pull/204 - if (tt && (tt.isHTML(val) || tt.isScript(val) || tt.isScriptURL(val) || (tt.isURL && tt.isURL(val)))) { + if (isTrustedValue(val)) { return val; } else { return val == null diff --git a/test/unit/features/trusted-types.spec.js b/test/unit/features/trusted-types.spec.js index eee96d54865..9b074b8cf5c 100644 --- a/test/unit/features/trusted-types.spec.js +++ b/test/unit/features/trusted-types.spec.js @@ -11,9 +11,8 @@ import Vue from 'vue' // we don't differentiate between different types of trusted values -const createTrustedValue = (value) => `TRUSTED${value}`; -const isTrustedValue = (value) => value.startsWith('TRUSTED'); -const unwrapTrustedValue = (value) => value.substr('TRUSTED'.length); +const createTrustedValue = (value) => ({toString: () => value, isTrusted: true}) +const isTrustedValue = (value) => value && value.isTrusted const unsafeHtml = ''; const unsafeScript = 'alert(0)'; @@ -27,6 +26,7 @@ describe('rendering with trusted types enforced', () => { // thrown by unsafe setAttribute call (e.g. srcdoc in iframe) the rendering fails completely. // We log the errors, before throwing so we can be sure that trusted types work. let errorLog; + let vuePolicyName; function emulateSetAttribute() { // enforce trusted values only on properties in this array @@ -38,7 +38,7 @@ describe('rendering with trusted types enforced', () => { unsafeAttributeList.forEach((attr) => { if (attr === name) { if (isTrustedValue(value)) { - args = [name, unwrapTrustedValue(value)]; + args = [name, value.toString()]; } else { errorLog.push(createTTErrorMessage(attr, value)); throw new Error(value); @@ -55,8 +55,9 @@ describe('rendering with trusted types enforced', () => { descriptorEntries.push({object, prop, desc}); Object.defineProperty(object, prop, { set: function(value) { + console.log('set', value, prop); if (isTrustedValue(value)) { - desc.set.apply(this, [unwrapTrustedValue(value)]); + desc.set.apply(this, [value.toString()]); } else { errorLog.push(createTTErrorMessage(prop, value)); throw new Error(value); @@ -81,7 +82,12 @@ describe('rendering with trusted types enforced', () => { beforeEach(() => { window.trustedTypes = { - createPolicy: () => { + createPolicy: (name) => { + // capture the name of the vue policy so we can test it. Relies on fact + // that there are only 2 policies (for vue and for tests). + if (name !== 'test-policy') { + vuePolicyName = name; + } return { createHTML: createTrustedValue, createScript: createTrustedValue, @@ -98,9 +104,10 @@ describe('rendering with trusted types enforced', () => { emulateSetAttribute(); // TODO: this needs to be changed once we use trusted types polyfill - policy = window.trustedTypes.createPolicy(); + policy = window.trustedTypes.createPolicy('test-policy'); errorLog = []; + vuePolicyName = ''; }); afterEach(() => { @@ -119,6 +126,100 @@ describe('rendering with trusted types enforced', () => { }).toThrow(); }); + describe('vue policy', () => { + let innerHTMLDescriptor; + + // simulate svg elements in Internet Explorer which don't have 'innerHTML' property + beforeEach(() => { + innerHTMLDescriptor = Object.getOwnPropertyDescriptor( + Element.prototype, + 'innerHTML', + ); + delete Element.prototype.innerHTML; + Object.defineProperty( + HTMLDivElement.prototype, + 'innerHTML', + innerHTMLDescriptor, + ); + }); + + afterEach(() => { + Vue.prototype.$clearTrustedTypesPolicy(); + + delete HTMLDivElement.prototype.innerHTML; + Object.defineProperty( + Element.prototype, + 'innerHTML', + innerHTMLDescriptor, + ); + }); + + it('uses default policy name "vue"', () => { + // we need to trigger creation of vue policy + const vm = new Vue({ + render: (c) => { + return c('svg', { + domProps: { + innerHTML: policy.createHTML('safe html'), + }, + }); + } + }) + + vm.$mount(); + expect(vuePolicyName).toBe('vue'); + }); + + it('policy name can be configured', () => { + Vue.config.trustedTypesPolicyName = 'userProvidedPolicyName'; + + // we need to trigger creation of vue policy + const vm = new Vue({ + render: (c) => { + return c('svg', { + domProps: { + innerHTML: policy.createHTML('safe html'), + }, + }); + } + }) + + vm.$mount(); + expect(vuePolicyName).toBe('userProvidedPolicyName'); + }); + + it('will throw an error on untrusted html', () => { + const vm = new Vue({ + render: (c) => { + return c('svg', { + domProps: { + innerHTML: unsafeHtml, + }, + }); + } + }) + + expect(() => { + vm.$mount(); + }).toThrowError('Expected svg innerHTML to be TrustedHTML!'); + }); + + it('passes if payload is TrustedHTML', () => { + const vm = new Vue({ + render: (c) => { + return c('svg', { + domProps: { + innerHTML: policy.createHTML('safe html'), + }, + }); + } + }) + + vm.$mount(); + expect(vm.$el.textContent).toBe('safe html'); + }); + }); + // html interpolation is safe because it's put into DOM as text node it('interpolation is trusted', () => { const vm = new Vue({ From 2822b67ffa6577ee3db4b8ee0bd8918cc4735f11 Mon Sep 17 00:00:00 2001 From: Emanuel Tesar Date: Mon, 16 Sep 2019 18:14:47 +0200 Subject: [PATCH 3/4] feat(web): Fix PR comments --- src/platforms/web/security.js | 50 ++++++++++++++++++++++++++--------- src/shared/util.js | 8 ++++-- 2 files changed, 44 insertions(+), 14 deletions(-) diff --git a/src/platforms/web/security.js b/src/platforms/web/security.js index fff83f2ed33..82cf171ec85 100644 --- a/src/platforms/web/security.js +++ b/src/platforms/web/security.js @@ -8,24 +8,50 @@ type TrustedTypePolicy = { }; let policy: ?TrustedTypePolicy -// we need this function to clear the policy in tests -Vue.prototype.$clearTrustedTypesPolicy = function() { - policy = undefined -} - -export function maybeCreateDangerousSvgHTML(value: any): string { - // create policy lazily to simplify testing +// create policy lazily to simplify testing +function getOrCreatePolicy() { const tt = getTrustedTypes() if (tt && !policy) { policy = tt.createPolicy(Vue.config.trustedTypesPolicyName, {createHTML: (s) => s}); } - if (!tt) return `${value}`; - else if (!isTrustedValue(value)) throw new Error('Expected svg innerHTML to be TrustedHTML!'); - // flow complains 'policy' may be undefined - else return (policy: any).createHTML(`${value}`); + return policy +} + +if (process.env.NODE_ENV !== 'production') { + // we need this function to clear the policy in tests + Vue.prototype.$clearTrustedTypesPolicy = function() { + policy = undefined + } +} + +// TODO: remove once https://github.com/WICG/trusted-types/issues/36 is resolved. +function isApplicationUsingTrustedTypes() { + // This check actually checks whether Trusted Types are enforced or not. + // However application might still use them in report only mode. + // This has also a side effect that CSP violation will be triggered. + try { + document.createElement('div').innerHTML = 'string'; + return false; + } catch (e) { + return true; + } +} + +export function maybeCreateDangerousSvgHTML(value: any): string { + const tt = getTrustedTypes() + + if (!tt || !isApplicationUsingTrustedTypes()) return `${value}`; + else if (!isTrustedValue(value)) { + throw new Error('Expected svg innerHTML to be TrustedHTML!'); + } + // flow complains that 'getOrCreatePolicy()' might return null. + else return (getOrCreatePolicy(): any).createHTML(`${value}`); } export function getTrustedShouldDecodeInnerHTML(href: boolean): string { - return href ? `` : `
` + const html = href ? `` : `
`; + const p = getOrCreatePolicy() + if (!p) return html; + else return p.createHTML(html); } \ No newline at end of file diff --git a/src/shared/util.js b/src/shared/util.js index b5aea3b3eba..733f3e0a0e8 100644 --- a/src/shared/util.js +++ b/src/shared/util.js @@ -79,9 +79,13 @@ export function isPromise (val: any): boolean { ) } +let trustedTypes = undefined export function getTrustedTypes() { - // TrustedTypes have been renamed to trustedTypes https://github.com/WICG/trusted-types/issues/177 - return typeof window !== 'undefined' && (window.trustedTypes || window.TrustedTypes); + if (trustedTypes === undefined) { + // TrustedTypes have been renamed to trustedTypes https://github.com/WICG/trusted-types/issues/177 + trustedTypes = typeof window !== 'undefined' ? (window.trustedTypes || window.TrustedTypes) : null; + } + return trustedTypes; } export function isTrustedValue(value: any): boolean { From 361ce4c27772592453d1c73576f8e5cccac313ad Mon Sep 17 00:00:00 2001 From: Emanuel Tesar Date: Tue, 17 Sep 2019 12:48:43 +0200 Subject: [PATCH 4/4] feat(web): Fix maybeCreateDangerousSvgHTML function --- src/platforms/web/security.js | 20 ++------------------ test/unit/features/trusted-types.spec.js | 3 ++- 2 files changed, 4 insertions(+), 19 deletions(-) diff --git a/src/platforms/web/security.js b/src/platforms/web/security.js index 82cf171ec85..ae0ffa19bbc 100644 --- a/src/platforms/web/security.js +++ b/src/platforms/web/security.js @@ -25,27 +25,11 @@ if (process.env.NODE_ENV !== 'production') { } } -// TODO: remove once https://github.com/WICG/trusted-types/issues/36 is resolved. -function isApplicationUsingTrustedTypes() { - // This check actually checks whether Trusted Types are enforced or not. - // However application might still use them in report only mode. - // This has also a side effect that CSP violation will be triggered. - try { - document.createElement('div').innerHTML = 'string'; - return false; - } catch (e) { - return true; - } -} - export function maybeCreateDangerousSvgHTML(value: any): string { const tt = getTrustedTypes() - if (!tt || !isApplicationUsingTrustedTypes()) return `${value}`; - else if (!isTrustedValue(value)) { - throw new Error('Expected svg innerHTML to be TrustedHTML!'); - } - // flow complains that 'getOrCreatePolicy()' might return null. + if (!tt || !isTrustedValue(value)) return `${value}`; + // flow complains that 'getOrCreatePolicy()' might return null else return (getOrCreatePolicy(): any).createHTML(`${value}`); } diff --git a/test/unit/features/trusted-types.spec.js b/test/unit/features/trusted-types.spec.js index 9b074b8cf5c..c2b592f0572 100644 --- a/test/unit/features/trusted-types.spec.js +++ b/test/unit/features/trusted-types.spec.js @@ -201,7 +201,8 @@ describe('rendering with trusted types enforced', () => { expect(() => { vm.$mount(); - }).toThrowError('Expected svg innerHTML to be TrustedHTML!'); + }).toThrow(); + expect(errorLog).toEqual([createTTErrorMessage('innerHTML', `${unsafeHtml}`)]); }); it('passes if payload is TrustedHTML', () => {