Skip to content

Commit b93956f

Browse files
committed
fix escaping in resolution done outside of DOM Expressions
1 parent 4d824b0 commit b93956f

File tree

2 files changed

+65
-5
lines changed

2 files changed

+65
-5
lines changed

.changeset/clever-months-cover.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"solid-js": patch
3+
---
4+
5+
fix escaping in resolution done outside of DOM Expressions

packages/solid/src/server/rendering.ts

Lines changed: 60 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,61 @@ export type ComponentProps<T extends ValidComponent> = T extends Component<infer
2929
? JSX.IntrinsicElements[T]
3030
: Record<string, unknown>;
3131

32+
// these methods are duplicates from solid-js/web
33+
// we need a better solution for this in the future
34+
function escape(s: any, attr?: boolean) {
35+
const t = typeof s;
36+
if (t !== "string") {
37+
if (!attr && t === "function") return escape(s());
38+
if (!attr && Array.isArray(s)) {
39+
for (let i = 0; i < s.length; i++) s[i] = escape(s[i]);
40+
return s;
41+
}
42+
if (attr && t === "boolean") return String(s);
43+
return s;
44+
}
45+
const delim = attr ? '"' : "<";
46+
const escDelim = attr ? "&quot;" : "&lt;";
47+
let iDelim = s.indexOf(delim);
48+
let iAmp = s.indexOf("&");
49+
50+
if (iDelim < 0 && iAmp < 0) return s;
51+
52+
let left = 0,
53+
out = "";
54+
55+
while (iDelim >= 0 && iAmp >= 0) {
56+
if (iDelim < iAmp) {
57+
if (left < iDelim) out += s.substring(left, iDelim);
58+
out += escDelim;
59+
left = iDelim + 1;
60+
iDelim = s.indexOf(delim, left);
61+
} else {
62+
if (left < iAmp) out += s.substring(left, iAmp);
63+
out += "&amp;";
64+
left = iAmp + 1;
65+
iAmp = s.indexOf("&", left);
66+
}
67+
}
68+
69+
if (iDelim >= 0) {
70+
do {
71+
if (left < iDelim) out += s.substring(left, iDelim);
72+
out += escDelim;
73+
left = iDelim + 1;
74+
iDelim = s.indexOf(delim, left);
75+
} while (iDelim >= 0);
76+
} else
77+
while (iAmp >= 0) {
78+
if (left < iAmp) out += s.substring(left, iAmp);
79+
out += "&amp;";
80+
left = iAmp + 1;
81+
iAmp = s.indexOf("&", left);
82+
}
83+
84+
return left < s.length ? out + s.substring(left) : out;
85+
}
86+
3287
function resolveSSRNode(node: any): string {
3388
const t = typeof node;
3489
if (t === "string") return node;
@@ -304,7 +359,7 @@ export function ErrorBoundary(props: {
304359
});
305360
if (error) return displayFallback();
306361
sync = false;
307-
return { t: `<!--!$e${id}-->${resolveSSRNode(res)}<!--!$/e${id}-->` };
362+
return { t: `<!--!$e${id}-->${resolveSSRNode(escape(res))}<!--!$/e${id}-->` };
308363
}
309364

310365
// Suspense Context
@@ -376,9 +431,9 @@ export function createResource<T, S>(
376431
): ResourceReturn<T> | ResourceReturn<T | undefined> {
377432

378433
if (typeof fetcher !== "function") {
379-
source = true as ResourceSource<S>;
380-
fetcher = source as ResourceFetcher<S, T>;
381434
options = (fetcher || {}) as ResourceOptions<T> | ResourceOptions<undefined>;
435+
fetcher = source as ResourceFetcher<S, T>;
436+
source = true as ResourceSource<S>;
382437
}
383438

384439
const contexts = new Set<SuspenseContextType>();
@@ -585,7 +640,7 @@ export function Suspense(props: { fallback?: string; children: string }) {
585640
completed: () => {
586641
const res = runSuspense();
587642
if (suspenseComplete(value)) {
588-
done!(resolveSSRNode(res));
643+
done!(resolveSSRNode(escape(res)));
589644
}
590645
}
591646
});
@@ -623,7 +678,7 @@ export function Suspense(props: { fallback?: string; children: string }) {
623678
if (ctx.async) {
624679
setHydrateContext({ ...ctx, count: 0, id: ctx.id + "0F", noHydrate: true });
625680
const res = {
626-
t: `<template id="pl-${id}"></template>${resolveSSRNode(props.fallback)}<!--pl-${id}-->`
681+
t: `<template id="pl-${id}"></template>${resolveSSRNode(escape(props.fallback))}<!--pl-${id}-->`
627682
};
628683
setHydrateContext(ctx);
629684
return res;

0 commit comments

Comments
 (0)