Skip to content

Commit 2a86323

Browse files
authored
fix: SSR template escaping (#12007)
We need to escape at the point of serialization to catch all cases, not just the ones we wrapped with the escape method previously fixes #12005
1 parent 84ad208 commit 2a86323

File tree

4 files changed

+21
-19
lines changed

4 files changed

+21
-19
lines changed

.changeset/late-zebras-argue.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: SSR template escaping

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

Lines changed: 14 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -40,14 +40,9 @@ import {
4040
} from '../../../../internal/server/hydration.js';
4141
import { filename, locator } from '../../../state.js';
4242

43-
export const block_open = string(BLOCK_OPEN);
44-
export const block_close = string(BLOCK_CLOSE);
45-
export const block_anchor = string(BLOCK_ANCHOR);
46-
47-
/** @param {string} value */
48-
function string(value) {
49-
return b.literal(sanitize_template_string(value));
50-
}
43+
export const block_open = b.literal(BLOCK_OPEN);
44+
export const block_close = b.literal(BLOCK_CLOSE);
45+
export const block_anchor = b.literal(BLOCK_ANCHOR);
5146

5247
/**
5348
* @param {import('estree').Node} node
@@ -93,7 +88,8 @@ function serialize_template(template, out = b.id('$$payload.out'), operator = '+
9388
if (!last) quasis.push((last = b.quasi('', false)));
9489

9590
if (node.type === 'Literal') {
96-
last.value.raw += node.value;
91+
last.value.raw +=
92+
typeof node.value === 'string' ? sanitize_template_string(node.value) : node.value;
9793
} else if (node.type === 'TemplateLiteral') {
9894
last.value.raw += node.quasis[0].value.raw;
9995
quasis.push(...node.quasis.slice(1));
@@ -1203,14 +1199,14 @@ const template_visitors = {
12031199
throw new Error('Node should have been handled elsewhere');
12041200
},
12051201
RegularElement(node, context) {
1206-
context.state.template.push(string(`<${node.name}`));
1202+
context.state.template.push(b.literal(`<${node.name}`));
12071203
const body = serialize_element_attributes(node, context);
1208-
context.state.template.push(string('>'));
1204+
context.state.template.push(b.literal('>'));
12091205

12101206
if ((node.name === 'script' || node.name === 'style') && node.fragment.nodes.length === 1) {
12111207
context.state.template.push(
1212-
string(/** @type {import('#compiler').Text} */ (node.fragment.nodes[0]).data),
1213-
string(`</${node.name}>`)
1208+
b.literal(/** @type {import('#compiler').Text} */ (node.fragment.nodes[0]).data),
1209+
b.literal(`</${node.name}>`)
12141210
);
12151211

12161212
return;
@@ -1285,7 +1281,7 @@ const template_visitors = {
12851281
}
12861282

12871283
if (!VoidElements.includes(node.name) && namespace !== 'foreign') {
1288-
state.template.push(string(`</${node.name}>`));
1284+
state.template.push(b.literal(`</${node.name}>`));
12891285
}
12901286

12911287
if (state.options.dev) {
@@ -1524,9 +1520,9 @@ const template_visitors = {
15241520
},
15251521
TitleElement(node, context) {
15261522
// title is guaranteed to contain only text/expression tag children
1527-
const template = [string('<title>')];
1523+
const template = [b.literal('<title>')];
15281524
process_children(node.fragment.nodes, { ...context, state: { ...context.state, template } });
1529-
template.push(string('</title>'));
1525+
template.push(b.literal('</title>'));
15301526

15311527
context.state.init.push(...serialize_template(template, b.id('$$payload.title'), '='));
15321528
},
@@ -1802,7 +1798,7 @@ function serialize_element_attributes(node, context) {
18021798
).value;
18031799
if (name !== 'class' || literal_value) {
18041800
context.state.template.push(
1805-
string(
1801+
b.literal(
18061802
` ${attribute.name}${
18071803
DOMBooleanAttributes.includes(name) && literal_value === true
18081804
? ''
@@ -1830,7 +1826,7 @@ function serialize_element_attributes(node, context) {
18301826

18311827
if (events_to_capture.size !== 0) {
18321828
for (const event of events_to_capture) {
1833-
context.state.template.push(string(` ${event}="this.__e=event"`));
1829+
context.state.template.push(b.literal(` ${event}="this.__e=event"`));
18341830
}
18351831
}
18361832

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

33
export default test({
4-
html: '<code>`${foo}\\n`</code>\n<div title="`${foo}\\n`">foo</div>\n<div>`${foo}\\n`</div>'
4+
html: '<code>`${foo}\\n`</code>\n`\n<div title="`${foo}\\n`">foo</div>\n<div>`${foo}\\n`</div>'
55
});

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,6 @@
33
</script>
44

55
<code>`$&#123;foo}\n`</code>
6+
{@html "`"}
67
<div title="`$&#123;foo}\n`">foo</div>
78
<Widget value="`$&#123;foo}\n`"/>

0 commit comments

Comments
 (0)