Skip to content

Commit d02e238

Browse files
committed
fix(compiler): escape html for safer template output
Copied from https://github.com/vuejs/core/pull/13919/files
1 parent 713b936 commit d02e238

File tree

4 files changed

+88
-4
lines changed

4 files changed

+88
-4
lines changed

packages/compiler/src/transforms/transformText.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { escapeHtml } from '@vue/shared'
12
import {
23
DynamicFlag,
34
IRNodeTypes,
@@ -88,7 +89,7 @@ export const transformText: NodeTransform = (node, context) => {
8889
} else if (node.type === 'JSXText') {
8990
const value = resolveJSXText(node)
9091
if (value) {
91-
context.template += value
92+
context.template += escapeHtml(value)
9293
} else {
9394
context.dynamic.flags |= DynamicFlag.NON_TEMPLATE
9495
}
@@ -136,7 +137,7 @@ function processTextContainer(children: TextLike[], context: TransformContext) {
136137
const values = createTextLikeExpressions(children, context)
137138
const literals = values.map(getLiteralExpressionValue)
138139
if (literals.every((l) => l != null)) {
139-
context.childrenTemplate = literals.map((l) => String(l))
140+
context.childrenTemplate = literals.map((l) => escapeHtml(String(l)))
140141
} else {
141142
context.childrenTemplate = [' ']
142143
context.registerOperation({

packages/compiler/src/transforms/vText.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { createDOMCompilerError, DOMErrorCodes } from '@vue/compiler-dom'
2-
import { isVoidTag } from '@vue/shared'
2+
import { escapeHtml, isVoidTag } from '@vue/shared'
33
import { IRNodeTypes } from '../ir'
44
import {
55
getLiteralExpressionValue,
@@ -35,7 +35,7 @@ export const transformVText: DirectiveTransform = (dir, node, context) => {
3535

3636
const literal = getLiteralExpressionValue(exp)
3737
if (literal != null) {
38-
context.childrenTemplate = [String(literal)]
38+
context.childrenTemplate = [escapeHtml(String(literal))]
3939
} else {
4040
context.childrenTemplate = [' ']
4141
context.registerOperation({
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2+
3+
exports[`compiler: text transform > consecutive text 1`] = `
4+
"
5+
const n0 = _createNodes(() => (msg))
6+
return n0
7+
"
8+
`;
9+
10+
exports[`compiler: text transform > no consecutive text 1`] = `
11+
"
12+
const n0 = _createNodes("hello world")
13+
return n0
14+
"
15+
`;
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
// TODO: add tests for this transform
2+
import { NodeTypes } from '@vue/compiler-dom'
3+
import { describe, expect, it } from 'vitest'
4+
import {
5+
IRNodeTypes,
6+
transformChildren,
7+
transformElement,
8+
transformText,
9+
transformVBind,
10+
transformVOn,
11+
} from '../../src'
12+
13+
import { makeCompile } from './_utils'
14+
15+
const compileWithTextTransform = makeCompile({
16+
nodeTransforms: [transformElement, transformChildren, transformText],
17+
directiveTransforms: {
18+
bind: transformVBind,
19+
on: transformVOn,
20+
},
21+
})
22+
23+
describe('compiler: text transform', () => {
24+
it('no consecutive text', () => {
25+
const { code, ir, helpers } = compileWithTextTransform(
26+
'<>{ "hello world" }</>',
27+
)
28+
expect(code).toMatchSnapshot()
29+
expect(helpers).contains.all.keys('createNodes')
30+
expect(ir.block.operation).toMatchObject([
31+
{
32+
type: IRNodeTypes.CREATE_NODES,
33+
values: [
34+
{
35+
type: NodeTypes.SIMPLE_EXPRESSION,
36+
content: 'hello world',
37+
isStatic: true,
38+
},
39+
],
40+
},
41+
])
42+
})
43+
44+
it('consecutive text', () => {
45+
const { code, ir, helpers } = compileWithTextTransform('<>{ msg }</>')
46+
expect(code).toMatchSnapshot()
47+
expect(helpers).contains.all.keys('createNodes')
48+
expect(ir.block.operation).toMatchObject([
49+
{
50+
type: IRNodeTypes.CREATE_NODES,
51+
values: [
52+
{
53+
type: NodeTypes.SIMPLE_EXPRESSION,
54+
content: '() => (msg)',
55+
isStatic: false,
56+
},
57+
],
58+
},
59+
])
60+
expect(ir.block.effect.length).toBe(0)
61+
})
62+
63+
it('escapes raw static text when generating the template string', () => {
64+
const { ir } = compileWithTextTransform('<code>&lt;script&gt;</code>')
65+
expect(ir.templates[0]).toContain('<code>&lt;script&gt;</code>')
66+
expect(ir.templates[0]).not.toContain('<code><script></code>')
67+
})
68+
})

0 commit comments

Comments
 (0)