diff --git a/src/compiler/compile/render_dom/wrappers/AwaitBlock.ts b/src/compiler/compile/render_dom/wrappers/AwaitBlock.ts index aa3e775d72..d20d8f6f94 100644 --- a/src/compiler/compile/render_dom/wrappers/AwaitBlock.ts +++ b/src/compiler/compile/render_dom/wrappers/AwaitBlock.ts @@ -69,6 +69,7 @@ export default class AwaitBlockWrapper extends Wrapper { super(renderer, block, parent, node); this.cannot_use_innerhtml(); + this.not_static_content(); block.add_dependencies(this.node.expression.dependencies); diff --git a/src/compiler/compile/render_dom/wrappers/EachBlock.ts b/src/compiler/compile/render_dom/wrappers/EachBlock.ts index 922679c39f..33b4a9cbd5 100644 --- a/src/compiler/compile/render_dom/wrappers/EachBlock.ts +++ b/src/compiler/compile/render_dom/wrappers/EachBlock.ts @@ -76,6 +76,7 @@ export default class EachBlockWrapper extends Wrapper { ) { super(renderer, block, parent, node); this.cannot_use_innerhtml(); + this.not_static_content(); const { dependencies } = node.expression; block.add_dependencies(dependencies); diff --git a/src/compiler/compile/render_dom/wrappers/Element/Attribute.ts b/src/compiler/compile/render_dom/wrappers/Element/Attribute.ts index 2cd284108c..d50a8238d9 100644 --- a/src/compiler/compile/render_dom/wrappers/Element/Attribute.ts +++ b/src/compiler/compile/render_dom/wrappers/Element/Attribute.ts @@ -19,6 +19,7 @@ export default class AttributeWrapper { if (node.dependencies.size > 0) { parent.cannot_use_innerhtml(); + parent.not_static_content(); block.add_dependencies(node.dependencies); diff --git a/src/compiler/compile/render_dom/wrappers/Element/index.ts b/src/compiler/compile/render_dom/wrappers/Element/index.ts index ac7e80c299..a80db84169 100644 --- a/src/compiler/compile/render_dom/wrappers/Element/index.ts +++ b/src/compiler/compile/render_dom/wrappers/Element/index.ts @@ -217,17 +217,17 @@ export default class ElementWrapper extends Wrapper { }); if (this.parent) { - if (node.actions.length > 0) this.parent.cannot_use_innerhtml(); - if (node.animation) this.parent.cannot_use_innerhtml(); - if (node.bindings.length > 0) this.parent.cannot_use_innerhtml(); - if (node.classes.length > 0) this.parent.cannot_use_innerhtml(); - if (node.intro || node.outro) this.parent.cannot_use_innerhtml(); - if (node.handlers.length > 0) this.parent.cannot_use_innerhtml(); - - if (this.node.name === 'option') this.parent.cannot_use_innerhtml(); - - if (renderer.options.dev) { + if (node.actions.length > 0 || + node.animation || + node.bindings.length > 0 || + node.classes.length > 0 || + node.intro || node.outro || + node.handlers.length > 0 || + this.node.name === 'option' || + renderer.options.dev + ) { this.parent.cannot_use_innerhtml(); // need to use add_location + this.parent.not_static_content(); } } @@ -291,7 +291,7 @@ export default class ElementWrapper extends Wrapper { } // insert static children with textContent or innerHTML - if (!this.node.namespace && this.can_use_innerhtml && this.fragment.nodes.length > 0) { + if (!this.node.namespace && (this.can_use_innerhtml || this.can_use_textcontent()) && this.fragment.nodes.length > 0) { if (this.fragment.nodes.length === 1 && this.fragment.nodes[0].node.type === 'Text') { block.chunks.create.push( // @ts-ignore todo: should it be this.fragment.nodes[0].node.data instead? @@ -315,7 +315,7 @@ export default class ElementWrapper extends Wrapper { literal.quasis.push(state.quasi); block.chunks.create.push( - b`${node}.innerHTML = ${literal};` + b`${node}.${this.can_use_innerhtml ? 'innerHTML': 'textContent'} = ${literal};` ); } } else { @@ -361,6 +361,10 @@ export default class ElementWrapper extends Wrapper { } } + can_use_textcontent() { + return this.is_static_content && this.fragment.nodes.every(node => node.node.type === 'Text' || node.node.type === 'MustacheTag'); + } + get_render_statement() { const { name, namespace } = this.node; diff --git a/src/compiler/compile/render_dom/wrappers/IfBlock.ts b/src/compiler/compile/render_dom/wrappers/IfBlock.ts index dff7851b5a..6ef505cd3b 100644 --- a/src/compiler/compile/render_dom/wrappers/IfBlock.ts +++ b/src/compiler/compile/render_dom/wrappers/IfBlock.ts @@ -95,6 +95,7 @@ export default class IfBlockWrapper extends Wrapper { super(renderer, block, parent, node); this.cannot_use_innerhtml(); + this.not_static_content(); this.branches = []; diff --git a/src/compiler/compile/render_dom/wrappers/InlineComponent/index.ts b/src/compiler/compile/render_dom/wrappers/InlineComponent/index.ts index f6e3039243..c4a848b456 100644 --- a/src/compiler/compile/render_dom/wrappers/InlineComponent/index.ts +++ b/src/compiler/compile/render_dom/wrappers/InlineComponent/index.ts @@ -34,6 +34,7 @@ export default class InlineComponentWrapper extends Wrapper { super(renderer, block, parent, node); this.cannot_use_innerhtml(); + this.not_static_content(); if (this.node.expression) { block.add_dependencies(this.node.expression.dependencies); diff --git a/src/compiler/compile/render_dom/wrappers/RawMustacheTag.ts b/src/compiler/compile/render_dom/wrappers/RawMustacheTag.ts index 60a6223783..c6ddde5da2 100644 --- a/src/compiler/compile/render_dom/wrappers/RawMustacheTag.ts +++ b/src/compiler/compile/render_dom/wrappers/RawMustacheTag.ts @@ -19,6 +19,7 @@ export default class RawMustacheTagWrapper extends Tag { ) { super(renderer, block, parent, node); this.cannot_use_innerhtml(); + this.not_static_content(); } render(block: Block, parent_node: Identifier, _parent_nodes: Identifier) { diff --git a/src/compiler/compile/render_dom/wrappers/Slot.ts b/src/compiler/compile/render_dom/wrappers/Slot.ts index fb90afab5b..4d575d21d3 100644 --- a/src/compiler/compile/render_dom/wrappers/Slot.ts +++ b/src/compiler/compile/render_dom/wrappers/Slot.ts @@ -29,6 +29,7 @@ export default class SlotWrapper extends Wrapper { ) { super(renderer, block, parent, node); this.cannot_use_innerhtml(); + this.not_static_content(); this.fragment = new FragmentWrapper( renderer, diff --git a/src/compiler/compile/render_dom/wrappers/shared/Tag.ts b/src/compiler/compile/render_dom/wrappers/shared/Tag.ts index 01cba3918a..20bfd3597b 100644 --- a/src/compiler/compile/render_dom/wrappers/shared/Tag.ts +++ b/src/compiler/compile/render_dom/wrappers/shared/Tag.ts @@ -11,8 +11,10 @@ export default class Tag extends Wrapper { constructor(renderer: Renderer, block: Block, parent: Wrapper, node: MustacheTag | RawMustacheTag) { super(renderer, block, parent, node); + + this.cannot_use_innerhtml(); if (!this.is_dependencies_static()) { - this.cannot_use_innerhtml(); + this.not_static_content(); } block.add_dependencies(node.expression.dependencies); diff --git a/src/compiler/compile/render_dom/wrappers/shared/Wrapper.ts b/src/compiler/compile/render_dom/wrappers/shared/Wrapper.ts index 5527ba3f7b..4bf8c20bd8 100644 --- a/src/compiler/compile/render_dom/wrappers/shared/Wrapper.ts +++ b/src/compiler/compile/render_dom/wrappers/shared/Wrapper.ts @@ -14,6 +14,7 @@ export default class Wrapper { var: Identifier; can_use_innerhtml: boolean; + is_static_content: boolean; constructor( renderer: Renderer, @@ -35,6 +36,7 @@ export default class Wrapper { }); this.can_use_innerhtml = !renderer.options.hydratable; + this.is_static_content = !renderer.options.hydratable; block.wrappers.push(this); } @@ -44,6 +46,11 @@ export default class Wrapper { if (this.parent) this.parent.cannot_use_innerhtml(); } + not_static_content() { + this.is_static_content = false; + if (this.parent) this.parent.not_static_content(); + } + get_or_create_anchor(block: Block, parent_node: Identifier, parent_nodes: Identifier) { // TODO use this in EachBlock and IfBlock — tricky because // children need to be created first diff --git a/test/js/samples/hoisted-const/expected.js b/test/js/samples/hoisted-const/expected.js index 997b6635e6..334b575f78 100644 --- a/test/js/samples/hoisted-const/expected.js +++ b/test/js/samples/hoisted-const/expected.js @@ -14,7 +14,7 @@ function create_fragment(ctx) { return { c() { b = element("b"); - b.innerHTML = `${get_answer()}`; + b.textContent = `${get_answer()}`; }, m(target, anchor) { insert(target, b, anchor); diff --git a/test/js/samples/hoisted-let/expected.js b/test/js/samples/hoisted-let/expected.js index 4489e2f1af..c7f64b8ea6 100644 --- a/test/js/samples/hoisted-let/expected.js +++ b/test/js/samples/hoisted-let/expected.js @@ -14,7 +14,7 @@ function create_fragment(ctx) { return { c() { b = element("b"); - b.innerHTML = `${get_answer()}`; + b.textContent = `${get_answer()}`; }, m(target, anchor) { insert(target, b, anchor); diff --git a/test/js/samples/non-mutable-reference/expected.js b/test/js/samples/non-mutable-reference/expected.js index c385ad9fe5..d6d54f9277 100644 --- a/test/js/samples/non-mutable-reference/expected.js +++ b/test/js/samples/non-mutable-reference/expected.js @@ -14,7 +14,7 @@ function create_fragment(ctx) { return { c() { h1 = element("h1"); - h1.innerHTML = `Hello ${name}!`; + h1.textContent = `Hello ${name}!`; }, m(target, anchor) { insert(target, h1, anchor); diff --git a/test/js/samples/unchanged-expression/expected.js b/test/js/samples/unchanged-expression/expected.js index ec16490f85..25f3042ae4 100644 --- a/test/js/samples/unchanged-expression/expected.js +++ b/test/js/samples/unchanged-expression/expected.js @@ -14,6 +14,11 @@ import { function create_fragment(ctx) { let div0; + let p0; + let t1; + let p1; + let t4; + let p2; let t7; let div1; let p3; @@ -23,11 +28,14 @@ function create_fragment(ctx) { return { c() { div0 = element("div"); - - div0.innerHTML = `
Hello world
-Hello ${world1}
-Hello ${world2}
`; - + p0 = element("p"); + p0.textContent = "Hello world"; + t1 = space(); + p1 = element("p"); + p1.textContent = `Hello ${world1}`; + t4 = space(); + p2 = element("p"); + p2.textContent = `Hello ${world2}`; t7 = space(); div1 = element("div"); p3 = element("p"); @@ -36,6 +44,11 @@ function create_fragment(ctx) { }, m(target, anchor) { insert(target, div0, anchor); + append(div0, p0); + append(div0, t1); + append(div0, p1); + append(div0, t4); + append(div0, p2); insert(target, t7, anchor); insert(target, div1, anchor); append(div1, p3); diff --git a/test/runtime/samples/unchanged-expression-xss/_config.js b/test/runtime/samples/unchanged-expression-xss/_config.js new file mode 100644 index 0000000000..fa5453dbf3 --- /dev/null +++ b/test/runtime/samples/unchanged-expression-xss/_config.js @@ -0,0 +1,3 @@ +export default { + html: `<b\nstyle='color:\nred;'>RED?!?</b>
`, +}; diff --git a/test/runtime/samples/unchanged-expression-xss/main.svelte b/test/runtime/samples/unchanged-expression-xss/main.svelte new file mode 100644 index 0000000000..c2003d07ae --- /dev/null +++ b/test/runtime/samples/unchanged-expression-xss/main.svelte @@ -0,0 +1,5 @@ + + +{content}
\ No newline at end of file