Skip to content

Commit 9864138

Browse files
fix: escape more template-literal-related characters (#13262)
* fix: escape more template-literal-related characters Escape `{` at the start of a string, because it could be preceeded by a `$`, which in combination loads to the following characters being treated as a value Fixes #13258 (used the opportunity to merge closely-related tests into one) * sanitize template strings once assembled (#13263) * only sanitize template quasis once assembled * changeset * remove old changeset --------- Co-authored-by: Rich Harris <[email protected]>
1 parent d9369d8 commit 9864138

File tree

7 files changed

+57
-31
lines changed

7 files changed

+57
-31
lines changed

.changeset/good-zebras-turn.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"svelte": patch
3+
---
4+
5+
fix: wait until template strings are complete before sanitizing

packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/utils.js

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,10 +54,10 @@ export function build_template_literal(values, visit, state) {
5454
const node = values[i];
5555

5656
if (node.type === 'Text') {
57-
quasi.value.raw += sanitize_template_string(node.data);
57+
quasi.value.cooked += node.data;
5858
} else if (node.type === 'ExpressionTag' && node.expression.type === 'Literal') {
5959
if (node.expression.value != null) {
60-
quasi.value.raw += sanitize_template_string(node.expression.value + '');
60+
quasi.value.cooked += node.expression.value + '';
6161
}
6262
} else {
6363
if (contains_multiple_call_expression) {
@@ -91,6 +91,10 @@ export function build_template_literal(values, visit, state) {
9191
}
9292
}
9393

94+
for (const quasi of quasis) {
95+
quasi.value.raw = sanitize_template_string(/** @type {string} */ (quasi.value.cooked));
96+
}
97+
9498
const value = b.template(quasis, expressions);
9599

96100
return { value, has_state, has_call };

packages/svelte/src/compiler/phases/3-transform/server/visitors/shared/utils.js

Lines changed: 31 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -42,12 +42,11 @@ export function process_children(nodes, { visit, state }) {
4242
const node = sequence[i];
4343

4444
if (node.type === 'Text' || node.type === 'Comment') {
45-
quasi.value.raw += sanitize_template_string(
46-
node.type === 'Comment' ? `<!--${node.data}-->` : escape_html(node.data)
47-
);
45+
quasi.value.cooked +=
46+
node.type === 'Comment' ? `<!--${node.data}-->` : escape_html(node.data);
4847
} else if (node.type === 'ExpressionTag' && node.expression.type === 'Literal') {
4948
if (node.expression.value != null) {
50-
quasi.value.raw += sanitize_template_string(escape_html(node.expression.value + ''));
49+
quasi.value.cooked += escape_html(node.expression.value + '');
5150
}
5251
} else {
5352
expressions.push(b.call('$.escape', /** @type {Expression} */ (visit(node.expression))));
@@ -57,6 +56,10 @@ export function process_children(nodes, { visit, state }) {
5756
}
5857
}
5958

59+
for (const quasi of quasis) {
60+
quasi.value.raw = sanitize_template_string(/** @type {string} */ (quasi.value.cooked));
61+
}
62+
6063
state.template.push(b.template(quasis, expressions));
6164
}
6265

@@ -95,8 +98,8 @@ function is_statement(node) {
9598
* @returns {Statement[]}
9699
*/
97100
export function build_template(template, out = b.id('$$payload.out'), operator = '+=') {
98-
/** @type {TemplateElement[]} */
99-
let quasis = [];
101+
/** @type {string[]} */
102+
let strings = [];
100103

101104
/** @type {Expression[]} */
102105
let expressions = [];
@@ -105,39 +108,50 @@ export function build_template(template, out = b.id('$$payload.out'), operator =
105108
const statements = [];
106109

107110
const flush = () => {
108-
statements.push(b.stmt(b.assignment(operator, out, b.template(quasis, expressions))));
109-
quasis = [];
111+
statements.push(
112+
b.stmt(
113+
b.assignment(
114+
operator,
115+
out,
116+
b.template(
117+
strings.map((cooked, i) => b.quasi(cooked, i === strings.length - 1)),
118+
expressions
119+
)
120+
)
121+
)
122+
);
123+
strings = [];
110124
expressions = [];
111125
};
112126

113127
for (let i = 0; i < template.length; i++) {
114128
const node = template[i];
115129

116130
if (is_statement(node)) {
117-
if (quasis.length !== 0) {
131+
if (strings.length !== 0) {
118132
flush();
119133
}
120134

121135
statements.push(node);
122136
} else {
123-
let last = quasis.at(-1);
124-
if (!last) quasis.push((last = b.quasi('', false)));
137+
if (strings.length === 0) {
138+
strings.push('');
139+
}
125140

126141
if (node.type === 'Literal') {
127-
last.value.raw +=
128-
typeof node.value === 'string' ? sanitize_template_string(node.value) : node.value;
142+
strings[strings.length - 1] += node.value;
129143
} else if (node.type === 'TemplateLiteral') {
130-
last.value.raw += node.quasis[0].value.raw;
131-
quasis.push(...node.quasis.slice(1));
144+
strings[strings.length - 1] += node.quasis[0].value.cooked;
145+
strings.push(...node.quasis.slice(1).map((q) => /** @type {string} */ (q.value.cooked)));
132146
expressions.push(...node.expressions);
133147
} else {
134148
expressions.push(node);
135-
quasis.push(b.quasi('', i + 1 === template.length || is_statement(template[i + 1])));
149+
strings.push('');
136150
}
137151
}
138152
}
139153

140-
if (quasis.length !== 0) {
154+
if (strings.length !== 0) {
141155
flush();
142156
}
143157

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
import { test } from '../../test';
22

33
export default test({
4-
html: '<code>`${foo}\\n`</code>\n`\n<div title="`${foo}\\n`">foo</div>\n<div>`${foo}\\n`</div>'
4+
html:
5+
'<code>`${foo}\\n`</code>\n`\n<div title="`${foo}\\n`">foo</div>\n<div>`${foo}\\n`</div>' +
6+
'<div>/ $clicks: 0 `tim$es` \\</div><div>$dollars `backticks` pyramid /\\</div>' +
7+
'<p>${ ${ ${</p>'
58
});

packages/svelte/tests/runtime-legacy/samples/escape-template-literals/main.svelte

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,14 @@
66
{@html "`"}
77
<div title="`$&#123;foo}\n`">foo</div>
88
<Widget value="`$&#123;foo}\n`"/>
9+
<div>
10+
/ $clicks: {0} `tim${"e"}s` \
11+
</div>
12+
<div>
13+
$dollars `backticks` pyramid /\
14+
</div>
15+
<p>
16+
${'{'}
17+
&dollar;{'{'}
18+
{'$'}{'{'}
19+
</p>

packages/svelte/tests/runtime-runes/samples/backtick-template/_config.js

Lines changed: 0 additions & 5 deletions
This file was deleted.

packages/svelte/tests/runtime-runes/samples/backtick-template/main.svelte

Lines changed: 0 additions & 6 deletions
This file was deleted.

0 commit comments

Comments
 (0)