Skip to content

Commit 4c5dd9f

Browse files
authored
Merge pull request #3816 from tanhauhau/tanhauhau/text-content-instead-of-inner-html
use textContent instead of innerHtml, preventing XSS
2 parents e931a56 + da4bd41 commit 4c5dd9f

File tree

16 files changed

+62
-21
lines changed

16 files changed

+62
-21
lines changed

src/compiler/compile/render_dom/wrappers/AwaitBlock.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ export default class AwaitBlockWrapper extends Wrapper {
6969
super(renderer, block, parent, node);
7070

7171
this.cannot_use_innerhtml();
72+
this.not_static_content();
7273

7374
block.add_dependencies(this.node.expression.dependencies);
7475

src/compiler/compile/render_dom/wrappers/EachBlock.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ export default class EachBlockWrapper extends Wrapper {
7676
) {
7777
super(renderer, block, parent, node);
7878
this.cannot_use_innerhtml();
79+
this.not_static_content();
7980

8081
const { dependencies } = node.expression;
8182
block.add_dependencies(dependencies);

src/compiler/compile/render_dom/wrappers/Element/Attribute.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ export default class AttributeWrapper {
1919

2020
if (node.dependencies.size > 0) {
2121
parent.cannot_use_innerhtml();
22+
parent.not_static_content();
2223

2324
block.add_dependencies(node.dependencies);
2425

src/compiler/compile/render_dom/wrappers/Element/index.ts

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -217,17 +217,17 @@ export default class ElementWrapper extends Wrapper {
217217
});
218218

219219
if (this.parent) {
220-
if (node.actions.length > 0) this.parent.cannot_use_innerhtml();
221-
if (node.animation) this.parent.cannot_use_innerhtml();
222-
if (node.bindings.length > 0) this.parent.cannot_use_innerhtml();
223-
if (node.classes.length > 0) this.parent.cannot_use_innerhtml();
224-
if (node.intro || node.outro) this.parent.cannot_use_innerhtml();
225-
if (node.handlers.length > 0) this.parent.cannot_use_innerhtml();
226-
227-
if (this.node.name === 'option') this.parent.cannot_use_innerhtml();
228-
229-
if (renderer.options.dev) {
220+
if (node.actions.length > 0 ||
221+
node.animation ||
222+
node.bindings.length > 0 ||
223+
node.classes.length > 0 ||
224+
node.intro || node.outro ||
225+
node.handlers.length > 0 ||
226+
this.node.name === 'option' ||
227+
renderer.options.dev
228+
) {
230229
this.parent.cannot_use_innerhtml(); // need to use add_location
230+
this.parent.not_static_content();
231231
}
232232
}
233233

@@ -291,7 +291,7 @@ export default class ElementWrapper extends Wrapper {
291291
}
292292

293293
// insert static children with textContent or innerHTML
294-
if (!this.node.namespace && this.can_use_innerhtml && this.fragment.nodes.length > 0) {
294+
if (!this.node.namespace && (this.can_use_innerhtml || this.can_use_textcontent()) && this.fragment.nodes.length > 0) {
295295
if (this.fragment.nodes.length === 1 && this.fragment.nodes[0].node.type === 'Text') {
296296
block.chunks.create.push(
297297
// @ts-ignore todo: should it be this.fragment.nodes[0].node.data instead?
@@ -315,7 +315,7 @@ export default class ElementWrapper extends Wrapper {
315315
literal.quasis.push(state.quasi);
316316

317317
block.chunks.create.push(
318-
b`${node}.innerHTML = ${literal};`
318+
b`${node}.${this.can_use_innerhtml ? 'innerHTML': 'textContent'} = ${literal};`
319319
);
320320
}
321321
} else {
@@ -361,6 +361,10 @@ export default class ElementWrapper extends Wrapper {
361361
}
362362
}
363363

364+
can_use_textcontent() {
365+
return this.is_static_content && this.fragment.nodes.every(node => node.node.type === 'Text' || node.node.type === 'MustacheTag');
366+
}
367+
364368
get_render_statement() {
365369
const { name, namespace } = this.node;
366370

src/compiler/compile/render_dom/wrappers/IfBlock.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ export default class IfBlockWrapper extends Wrapper {
9595
super(renderer, block, parent, node);
9696

9797
this.cannot_use_innerhtml();
98+
this.not_static_content();
9899

99100
this.branches = [];
100101

src/compiler/compile/render_dom/wrappers/InlineComponent/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ export default class InlineComponentWrapper extends Wrapper {
3434
super(renderer, block, parent, node);
3535

3636
this.cannot_use_innerhtml();
37+
this.not_static_content();
3738

3839
if (this.node.expression) {
3940
block.add_dependencies(this.node.expression.dependencies);

src/compiler/compile/render_dom/wrappers/RawMustacheTag.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ export default class RawMustacheTagWrapper extends Tag {
1919
) {
2020
super(renderer, block, parent, node);
2121
this.cannot_use_innerhtml();
22+
this.not_static_content();
2223
}
2324

2425
render(block: Block, parent_node: Identifier, _parent_nodes: Identifier) {

src/compiler/compile/render_dom/wrappers/Slot.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ export default class SlotWrapper extends Wrapper {
2929
) {
3030
super(renderer, block, parent, node);
3131
this.cannot_use_innerhtml();
32+
this.not_static_content();
3233

3334
this.fragment = new FragmentWrapper(
3435
renderer,

src/compiler/compile/render_dom/wrappers/shared/Tag.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,10 @@ export default class Tag extends Wrapper {
1111

1212
constructor(renderer: Renderer, block: Block, parent: Wrapper, node: MustacheTag | RawMustacheTag) {
1313
super(renderer, block, parent, node);
14+
15+
this.cannot_use_innerhtml();
1416
if (!this.is_dependencies_static()) {
15-
this.cannot_use_innerhtml();
17+
this.not_static_content();
1618
}
1719

1820
block.add_dependencies(node.expression.dependencies);

src/compiler/compile/render_dom/wrappers/shared/Wrapper.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ export default class Wrapper {
1414

1515
var: Identifier;
1616
can_use_innerhtml: boolean;
17+
is_static_content: boolean;
1718

1819
constructor(
1920
renderer: Renderer,
@@ -35,6 +36,7 @@ export default class Wrapper {
3536
});
3637

3738
this.can_use_innerhtml = !renderer.options.hydratable;
39+
this.is_static_content = !renderer.options.hydratable;
3840

3941
block.wrappers.push(this);
4042
}
@@ -44,6 +46,11 @@ export default class Wrapper {
4446
if (this.parent) this.parent.cannot_use_innerhtml();
4547
}
4648

49+
not_static_content() {
50+
this.is_static_content = false;
51+
if (this.parent) this.parent.not_static_content();
52+
}
53+
4754
get_or_create_anchor(block: Block, parent_node: Identifier, parent_nodes: Identifier) {
4855
// TODO use this in EachBlock and IfBlock — tricky because
4956
// children need to be created first

test/js/samples/hoisted-const/expected.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ function create_fragment(ctx) {
1414
return {
1515
c() {
1616
b = element("b");
17-
b.innerHTML = `${get_answer()}`;
17+
b.textContent = `${get_answer()}`;
1818
},
1919
m(target, anchor) {
2020
insert(target, b, anchor);

test/js/samples/hoisted-let/expected.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ function create_fragment(ctx) {
1414
return {
1515
c() {
1616
b = element("b");
17-
b.innerHTML = `${get_answer()}`;
17+
b.textContent = `${get_answer()}`;
1818
},
1919
m(target, anchor) {
2020
insert(target, b, anchor);

test/js/samples/non-mutable-reference/expected.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ function create_fragment(ctx) {
1414
return {
1515
c() {
1616
h1 = element("h1");
17-
h1.innerHTML = `Hello ${name}!`;
17+
h1.textContent = `Hello ${name}!`;
1818
},
1919
m(target, anchor) {
2020
insert(target, h1, anchor);

test/js/samples/unchanged-expression/expected.js

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,11 @@ import {
1414

1515
function create_fragment(ctx) {
1616
let div0;
17+
let p0;
18+
let t1;
19+
let p1;
20+
let t4;
21+
let p2;
1722
let t7;
1823
let div1;
1924
let p3;
@@ -23,11 +28,14 @@ function create_fragment(ctx) {
2328
return {
2429
c() {
2530
div0 = element("div");
26-
27-
div0.innerHTML = `<p>Hello world</p>
28-
<p>Hello ${world1}</p>
29-
<p>Hello ${world2}</p>`;
30-
31+
p0 = element("p");
32+
p0.textContent = "Hello world";
33+
t1 = space();
34+
p1 = element("p");
35+
p1.textContent = `Hello ${world1}`;
36+
t4 = space();
37+
p2 = element("p");
38+
p2.textContent = `Hello ${world2}`;
3139
t7 = space();
3240
div1 = element("div");
3341
p3 = element("p");
@@ -36,6 +44,11 @@ function create_fragment(ctx) {
3644
},
3745
m(target, anchor) {
3846
insert(target, div0, anchor);
47+
append(div0, p0);
48+
append(div0, t1);
49+
append(div0, p1);
50+
append(div0, t4);
51+
append(div0, p2);
3952
insert(target, t7, anchor);
4053
insert(target, div1, anchor);
4154
append(div1, p3);
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export default {
2+
html: `<p>&lt;b\nstyle='color:\nred;'&gt;RED?!?&lt;/b&gt;</p>`,
3+
};
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<script>
2+
const content = `<b style='color: red;'>RED?!?</b>`
3+
</script>
4+
5+
<p>{content}</p>

0 commit comments

Comments
 (0)