sibujs 1.2.0 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +29 -25
- package/dist/browser.cjs +804 -2
- package/dist/browser.d.cts +591 -1
- package/dist/browser.d.ts +591 -1
- package/dist/browser.js +50 -8
- package/dist/build.cjs +654 -144
- package/dist/build.js +14 -12
- package/dist/cdn.global.js +188 -7
- package/dist/chunk-2BYQDGN3.js +742 -0
- package/dist/chunk-32DY64NT.js +282 -0
- package/dist/chunk-3AIRKM3B.js +1263 -0
- package/dist/chunk-3X2YG6YM.js +505 -0
- package/dist/chunk-5X6PP2UK.js +28 -0
- package/dist/chunk-77L6NL3X.js +1097 -0
- package/dist/chunk-BGN5ZMP4.js +26 -0
- package/dist/chunk-BTU3TJDS.js +365 -0
- package/dist/chunk-CHF5OHIA.js +61 -0
- package/dist/chunk-CMBFNA7L.js +27 -0
- package/dist/chunk-DAHRH4ON.js +331 -0
- package/dist/chunk-EBGIRKQY.js +616 -0
- package/dist/chunk-EUZND3CB.js +27 -0
- package/dist/chunk-F3FA4F32.js +292 -0
- package/dist/chunk-JAKHTMQU.js +1000 -0
- package/dist/chunk-JCI5M6U6.js +956 -0
- package/dist/chunk-KQPDEVVS.js +398 -0
- package/dist/chunk-NEKUBFPT.js +60 -0
- package/dist/chunk-NYVAC6P5.js +37 -0
- package/dist/chunk-PTQJDMRT.js +146 -0
- package/dist/chunk-QWZG56ET.js +2744 -0
- package/dist/chunk-TSOKIX5Z.js +654 -0
- package/dist/chunk-VRW3FULF.js +725 -0
- package/dist/chunk-WZSPOOER.js +84 -0
- package/dist/chunk-YT6HQ6AM.js +14 -0
- package/dist/chunk-ZD6OAMTH.js +277 -0
- package/dist/contracts-DDrwxvJ-.d.cts +245 -0
- package/dist/contracts-DDrwxvJ-.d.ts +245 -0
- package/dist/data.cjs +35 -2
- package/dist/data.d.cts +7 -0
- package/dist/data.d.ts +7 -0
- package/dist/data.js +9 -8
- package/dist/devtools.cjs +122 -0
- package/dist/devtools.d.cts +69 -461
- package/dist/devtools.d.ts +69 -461
- package/dist/devtools.js +127 -6
- package/dist/ecosystem.cjs +23 -6
- package/dist/ecosystem.d.cts +1 -1
- package/dist/ecosystem.d.ts +1 -1
- package/dist/ecosystem.js +10 -9
- package/dist/extras.cjs +1207 -65
- package/dist/extras.d.cts +5 -5
- package/dist/extras.d.ts +5 -5
- package/dist/extras.js +69 -24
- package/dist/index.cjs +663 -144
- package/dist/index.d.cts +397 -17
- package/dist/index.d.ts +397 -17
- package/dist/index.js +39 -17
- package/dist/introspect-BumjnBKr.d.cts +477 -0
- package/dist/introspect-CZrlcaYy.d.ts +477 -0
- package/dist/introspect-Cb0zgpi2.d.cts +477 -0
- package/dist/introspect-Y2xNXGSf.d.ts +477 -0
- package/dist/motion.js +4 -4
- package/dist/patterns.cjs +51 -2
- package/dist/patterns.d.cts +18 -8
- package/dist/patterns.d.ts +18 -8
- package/dist/patterns.js +7 -7
- package/dist/performance.js +4 -4
- package/dist/plugins.cjs +428 -81
- package/dist/plugins.d.cts +27 -4
- package/dist/plugins.d.ts +27 -4
- package/dist/plugins.js +156 -37
- package/dist/ssr-4PBXAOO3.js +40 -0
- package/dist/ssr-Do_SiVoL.d.cts +201 -0
- package/dist/ssr-Do_SiVoL.d.ts +201 -0
- package/dist/ssr.cjs +312 -60
- package/dist/ssr.d.cts +10 -1
- package/dist/ssr.d.ts +10 -1
- package/dist/ssr.js +13 -10
- package/dist/tagFactory-DaJ0YWX6.d.cts +47 -0
- package/dist/tagFactory-DaJ0YWX6.d.ts +47 -0
- package/dist/testing.cjs +233 -2
- package/dist/testing.d.cts +42 -1
- package/dist/testing.d.ts +42 -1
- package/dist/testing.js +129 -2
- package/dist/ui.cjs +374 -3
- package/dist/ui.d.cts +252 -2
- package/dist/ui.d.ts +252 -2
- package/dist/ui.js +328 -8
- package/dist/widgets.js +7 -7
- package/package.json +1 -1
package/dist/plugins.cjs
CHANGED
|
@@ -42,11 +42,40 @@ var init_dev = __esm({
|
|
|
42
42
|
}
|
|
43
43
|
});
|
|
44
44
|
|
|
45
|
+
// src/utils/sanitize.ts
|
|
46
|
+
function sanitizeUrl(url) {
|
|
47
|
+
const trimmed = url.replace(/[\x00-\x20\x7f-\x9f]+/g, "").trim();
|
|
48
|
+
if (!trimmed) return "";
|
|
49
|
+
const lower = trimmed.toLowerCase();
|
|
50
|
+
if (lower.startsWith("javascript:") || lower.startsWith("data:") || lower.startsWith("vbscript:") || lower.startsWith("blob:")) {
|
|
51
|
+
return "";
|
|
52
|
+
}
|
|
53
|
+
return trimmed;
|
|
54
|
+
}
|
|
55
|
+
function sanitizeCSSValue(value) {
|
|
56
|
+
const lower = value.toLowerCase().replace(/\s+/g, "");
|
|
57
|
+
if (lower.includes("url(") || lower.includes("expression(") || lower.includes("javascript:") || lower.includes("-moz-binding")) {
|
|
58
|
+
return "";
|
|
59
|
+
}
|
|
60
|
+
return value;
|
|
61
|
+
}
|
|
62
|
+
function isUrlAttribute(attr) {
|
|
63
|
+
return URL_ATTRIBUTES.has(attr);
|
|
64
|
+
}
|
|
65
|
+
var URL_ATTRIBUTES;
|
|
66
|
+
var init_sanitize = __esm({
|
|
67
|
+
"src/utils/sanitize.ts"() {
|
|
68
|
+
"use strict";
|
|
69
|
+
URL_ATTRIBUTES = /* @__PURE__ */ new Set(["href", "src", "action", "formaction", "cite", "poster", "background", "srcset"]);
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
|
|
45
73
|
// src/platform/ssr.ts
|
|
46
74
|
var ssr_exports = {};
|
|
47
75
|
__export(ssr_exports, {
|
|
48
76
|
collectStream: () => collectStream,
|
|
49
77
|
deserializeState: () => deserializeState,
|
|
78
|
+
escapeScriptJson: () => escapeScriptJson,
|
|
50
79
|
hydrate: () => hydrate,
|
|
51
80
|
hydrateIslands: () => hydrateIslands,
|
|
52
81
|
hydrateProgressively: () => hydrateProgressively,
|
|
@@ -62,12 +91,24 @@ __export(ssr_exports, {
|
|
|
62
91
|
suspenseSwapScript: () => suspenseSwapScript,
|
|
63
92
|
trustHTML: () => trustHTML
|
|
64
93
|
});
|
|
94
|
+
function isSafeAttrName(name) {
|
|
95
|
+
return SAFE_ATTR_NAME.test(name);
|
|
96
|
+
}
|
|
97
|
+
function isEventHandlerAttr2(name) {
|
|
98
|
+
if (name.length < 3) return false;
|
|
99
|
+
const lower = name.toLowerCase();
|
|
100
|
+
return lower[0] === "o" && lower[1] === "n" && lower.charCodeAt(2) >= 97 && lower.charCodeAt(2) <= 122;
|
|
101
|
+
}
|
|
65
102
|
function ssrErrorComment(err) {
|
|
66
103
|
if (_isDev7) {
|
|
67
|
-
|
|
104
|
+
const msg = escapeHtml(err instanceof Error ? err.message : String(err));
|
|
105
|
+
return `<!--SSR error: ${safeCommentText(msg)}-->`;
|
|
68
106
|
}
|
|
69
107
|
return "<!--SSR error-->";
|
|
70
108
|
}
|
|
109
|
+
function safeCommentText(text2) {
|
|
110
|
+
return text2.replace(/-->/g, "-->").replace(/--!>/g, "--!>").replace(/<!--/g, "<!--").replace(/--$/g, "---");
|
|
111
|
+
}
|
|
71
112
|
function renderToString(element) {
|
|
72
113
|
if (element instanceof DocumentFragment) {
|
|
73
114
|
return Array.from(element.childNodes).map((child) => {
|
|
@@ -82,16 +123,30 @@ function renderToString(element) {
|
|
|
82
123
|
return escapeHtml(element.textContent || "");
|
|
83
124
|
}
|
|
84
125
|
if (element.nodeType === 8) {
|
|
85
|
-
|
|
86
|
-
return `<!--${content}-->`;
|
|
126
|
+
return `<!--${safeCommentText(element.textContent || "")}-->`;
|
|
87
127
|
}
|
|
88
128
|
if (!(element instanceof HTMLElement)) {
|
|
89
|
-
return element.textContent || "";
|
|
129
|
+
return escapeHtml(element.textContent || "");
|
|
90
130
|
}
|
|
91
131
|
const tag = element.tagName.toLowerCase();
|
|
132
|
+
if (tag === "script" || tag === "style") {
|
|
133
|
+
return _isDev7 ? `<!--ssr:${tag}-stripped-->` : "";
|
|
134
|
+
}
|
|
135
|
+
if (!/^[a-z][a-z0-9-]*$/i.test(tag)) {
|
|
136
|
+
return _isDev7 ? "<!--ssr:invalid-tag-->" : "";
|
|
137
|
+
}
|
|
92
138
|
let html2 = `<${tag}`;
|
|
93
139
|
for (const attr of Array.from(element.attributes)) {
|
|
94
|
-
|
|
140
|
+
const rawName = attr.name;
|
|
141
|
+
if (!isSafeAttrName(rawName)) continue;
|
|
142
|
+
if (isEventHandlerAttr2(rawName)) continue;
|
|
143
|
+
const lowerName = rawName.toLowerCase();
|
|
144
|
+
let value = attr.value;
|
|
145
|
+
if (URL_ATTRS.has(lowerName)) {
|
|
146
|
+
value = sanitizeUrl(value);
|
|
147
|
+
if (!value) continue;
|
|
148
|
+
}
|
|
149
|
+
html2 += ` ${rawName}="${escapeAttr(value)}"`;
|
|
95
150
|
}
|
|
96
151
|
if (element.dataset && !element.dataset.sibuHydrate) {
|
|
97
152
|
html2 += ` data-sibu-ssr="true"`;
|
|
@@ -110,8 +165,25 @@ function renderToString(element) {
|
|
|
110
165
|
html2 += `</${tag}>`;
|
|
111
166
|
return html2;
|
|
112
167
|
}
|
|
113
|
-
function hydrate(component, container) {
|
|
168
|
+
function hydrate(component, container, options = {}) {
|
|
114
169
|
const clientTree = component();
|
|
170
|
+
if (options.diagnostics) {
|
|
171
|
+
const mismatches = [];
|
|
172
|
+
collectMismatches(container.firstElementChild, clientTree, "", mismatches);
|
|
173
|
+
if (mismatches.length > 0) {
|
|
174
|
+
const first = mismatches[0];
|
|
175
|
+
if (options.onMismatch) {
|
|
176
|
+
options.onMismatch(first);
|
|
177
|
+
} else if (_isDev7) {
|
|
178
|
+
console.warn(
|
|
179
|
+
`[Sibu hydration] ${first.message}
|
|
180
|
+
at ${first.path}
|
|
181
|
+
server: ${first.serverValue}
|
|
182
|
+
client: ${first.clientValue}`
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
115
187
|
hydrateNode(container.firstElementChild, clientTree);
|
|
116
188
|
container.setAttribute("data-sibu-hydrated", "true");
|
|
117
189
|
}
|
|
@@ -123,9 +195,119 @@ function hydrateNode(serverNode, clientNode) {
|
|
|
123
195
|
hydrateNode(serverChildren[i2], clientChildren[i2]);
|
|
124
196
|
}
|
|
125
197
|
}
|
|
198
|
+
function collectMismatches(serverNode, clientNode, path2, out, max = 5) {
|
|
199
|
+
if (out.length >= max) return;
|
|
200
|
+
const nodePath = path2 || clientNode?.tagName?.toLowerCase() || "(root)";
|
|
201
|
+
if (!serverNode && clientNode) {
|
|
202
|
+
out.push({
|
|
203
|
+
kind: "child-count",
|
|
204
|
+
path: nodePath,
|
|
205
|
+
serverValue: "(missing)",
|
|
206
|
+
clientValue: clientNode.tagName.toLowerCase(),
|
|
207
|
+
message: "Client rendered a node that the server did not emit."
|
|
208
|
+
});
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
if (serverNode && !clientNode) {
|
|
212
|
+
out.push({
|
|
213
|
+
kind: "child-count",
|
|
214
|
+
path: nodePath,
|
|
215
|
+
serverValue: serverNode.tagName.toLowerCase(),
|
|
216
|
+
clientValue: "(missing)",
|
|
217
|
+
message: "Server rendered a node that the client did not produce."
|
|
218
|
+
});
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
if (!serverNode || !clientNode) return;
|
|
222
|
+
if (serverNode.tagName !== clientNode.tagName) {
|
|
223
|
+
out.push({
|
|
224
|
+
kind: "tag",
|
|
225
|
+
path: nodePath,
|
|
226
|
+
serverValue: serverNode.tagName.toLowerCase(),
|
|
227
|
+
clientValue: clientNode.tagName.toLowerCase(),
|
|
228
|
+
message: "Element tag mismatch \u2014 server and client disagree on the element type."
|
|
229
|
+
});
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
const skipAttrs = /* @__PURE__ */ new Set(["data-sibu-ssr", "data-sibu-hydrated", "data-sibu-island"]);
|
|
233
|
+
const serverAttrs = /* @__PURE__ */ new Map();
|
|
234
|
+
for (const a2 of Array.from(serverNode.attributes)) {
|
|
235
|
+
if (!skipAttrs.has(a2.name)) serverAttrs.set(a2.name, a2.value);
|
|
236
|
+
}
|
|
237
|
+
const clientAttrs = /* @__PURE__ */ new Map();
|
|
238
|
+
for (const a2 of Array.from(clientNode.attributes)) {
|
|
239
|
+
if (!skipAttrs.has(a2.name)) clientAttrs.set(a2.name, a2.value);
|
|
240
|
+
}
|
|
241
|
+
for (const [name, value] of serverAttrs) {
|
|
242
|
+
if (out.length >= max) return;
|
|
243
|
+
if (!clientAttrs.has(name)) {
|
|
244
|
+
out.push({
|
|
245
|
+
kind: "attribute",
|
|
246
|
+
path: `${nodePath}[${name}]`,
|
|
247
|
+
serverValue: value,
|
|
248
|
+
clientValue: "(missing)",
|
|
249
|
+
message: `Attribute "${name}" present on server but missing on client.`
|
|
250
|
+
});
|
|
251
|
+
} else if (clientAttrs.get(name) !== value) {
|
|
252
|
+
out.push({
|
|
253
|
+
kind: "attribute",
|
|
254
|
+
path: `${nodePath}[${name}]`,
|
|
255
|
+
serverValue: value,
|
|
256
|
+
clientValue: clientAttrs.get(name) ?? "",
|
|
257
|
+
message: `Attribute "${name}" differs between server and client.`
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
for (const [name, value] of clientAttrs) {
|
|
262
|
+
if (out.length >= max) return;
|
|
263
|
+
if (!serverAttrs.has(name)) {
|
|
264
|
+
out.push({
|
|
265
|
+
kind: "attribute",
|
|
266
|
+
path: `${nodePath}[${name}]`,
|
|
267
|
+
serverValue: "(missing)",
|
|
268
|
+
clientValue: value,
|
|
269
|
+
message: `Attribute "${name}" present on client but missing on server.`
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
const serverChildren = Array.from(serverNode.children);
|
|
274
|
+
const clientChildren = Array.from(clientNode.children);
|
|
275
|
+
const max2 = Math.max(serverChildren.length, clientChildren.length);
|
|
276
|
+
for (let i2 = 0; i2 < max2; i2++) {
|
|
277
|
+
if (out.length >= max) return;
|
|
278
|
+
const childPath = `${nodePath} > ${clientChildren[i2]?.tagName?.toLowerCase() ?? serverChildren[i2]?.tagName?.toLowerCase() ?? "?"}:nth-child(${i2 + 1})`;
|
|
279
|
+
collectMismatches(serverChildren[i2] ?? null, clientChildren[i2] ?? null, childPath, out, max);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
126
282
|
function trustHTML(html2) {
|
|
127
283
|
return html2;
|
|
128
284
|
}
|
|
285
|
+
function buildAttrString(attrs, { allowEventHandlers = false } = {}) {
|
|
286
|
+
if (!attrs) return "";
|
|
287
|
+
const out = [];
|
|
288
|
+
for (const rawKey of Object.keys(attrs)) {
|
|
289
|
+
if (!Object.hasOwn(attrs, rawKey)) continue;
|
|
290
|
+
if (!isSafeAttrName(rawKey)) continue;
|
|
291
|
+
if (!allowEventHandlers && isEventHandlerAttr2(rawKey)) continue;
|
|
292
|
+
const lowerKey = rawKey.toLowerCase();
|
|
293
|
+
let value = String(attrs[rawKey]);
|
|
294
|
+
if (URL_ATTRS.has(lowerKey)) {
|
|
295
|
+
value = sanitizeUrl(value);
|
|
296
|
+
if (!value) continue;
|
|
297
|
+
}
|
|
298
|
+
out.push(`${rawKey}="${escapeAttr(value)}"`);
|
|
299
|
+
}
|
|
300
|
+
return out.join(" ");
|
|
301
|
+
}
|
|
302
|
+
function isDangerousMetaRefresh(metaProps) {
|
|
303
|
+
const httpEquiv = metaProps["http-equiv"];
|
|
304
|
+
if (typeof httpEquiv !== "string") return false;
|
|
305
|
+
if (httpEquiv.toLowerCase() !== "refresh") return false;
|
|
306
|
+
const content = metaProps.content;
|
|
307
|
+
if (typeof content !== "string") return false;
|
|
308
|
+
const normalized = content.replace(/[\x00-\x20\x7f-\x9f]+/g, "").toLowerCase();
|
|
309
|
+
return normalized.includes("url=javascript:") || normalized.includes("url=data:") || normalized.includes("url=vbscript:") || normalized.includes("url=blob:");
|
|
310
|
+
}
|
|
129
311
|
function renderToDocument(component, options = {}) {
|
|
130
312
|
let content;
|
|
131
313
|
try {
|
|
@@ -133,14 +315,22 @@ function renderToDocument(component, options = {}) {
|
|
|
133
315
|
} catch (err) {
|
|
134
316
|
content = ssrErrorComment(err);
|
|
135
317
|
}
|
|
136
|
-
const metaTags = (options.meta || []).map(
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
).
|
|
142
|
-
|
|
143
|
-
|
|
318
|
+
const metaTags = (options.meta || []).map((attrs) => {
|
|
319
|
+
if (isDangerousMetaRefresh(attrs)) return "";
|
|
320
|
+
const pairs = buildAttrString(attrs);
|
|
321
|
+
return pairs ? `<meta ${pairs} />` : "";
|
|
322
|
+
}).filter(Boolean).join("\n ");
|
|
323
|
+
const linkTags = (options.links || []).map((attrs) => {
|
|
324
|
+
const pairs = buildAttrString(attrs);
|
|
325
|
+
return pairs ? `<link ${pairs} />` : "";
|
|
326
|
+
}).filter(Boolean).join("\n ");
|
|
327
|
+
const scriptTags = (options.scripts || []).map((src) => {
|
|
328
|
+
const safe = sanitizeUrl(String(src));
|
|
329
|
+
if (!safe) return "";
|
|
330
|
+
return `<script src="${escapeAttr(safe)}"></script>`;
|
|
331
|
+
}).filter(Boolean).join("\n ");
|
|
332
|
+
const bodyAttrPairs = buildAttrString(options.bodyAttrs);
|
|
333
|
+
const bodyAttrs = bodyAttrPairs ? ` ${bodyAttrPairs}` : "";
|
|
144
334
|
return `<!DOCTYPE html>
|
|
145
335
|
<html>
|
|
146
336
|
<head>
|
|
@@ -173,18 +363,34 @@ async function* renderToStream(element) {
|
|
|
173
363
|
return;
|
|
174
364
|
}
|
|
175
365
|
if (element.nodeType === 8) {
|
|
176
|
-
|
|
177
|
-
yield `<!--${content}-->`;
|
|
366
|
+
yield `<!--${safeCommentText(element.textContent || "")}-->`;
|
|
178
367
|
return;
|
|
179
368
|
}
|
|
180
369
|
if (!(element instanceof HTMLElement)) {
|
|
181
|
-
yield element.textContent || "";
|
|
370
|
+
yield escapeHtml(element.textContent || "");
|
|
182
371
|
return;
|
|
183
372
|
}
|
|
184
373
|
const tag = element.tagName.toLowerCase();
|
|
374
|
+
if (tag === "script" || tag === "style") {
|
|
375
|
+
if (_isDev7) yield `<!--ssr:${tag}-stripped-->`;
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
if (!/^[a-z][a-z0-9-]*$/i.test(tag)) {
|
|
379
|
+
if (_isDev7) yield "<!--ssr:invalid-tag-->";
|
|
380
|
+
return;
|
|
381
|
+
}
|
|
185
382
|
let openTag = `<${tag}`;
|
|
186
383
|
for (const attr of Array.from(element.attributes)) {
|
|
187
|
-
|
|
384
|
+
const rawName = attr.name;
|
|
385
|
+
if (!isSafeAttrName(rawName)) continue;
|
|
386
|
+
if (isEventHandlerAttr2(rawName)) continue;
|
|
387
|
+
const lowerName = rawName.toLowerCase();
|
|
388
|
+
let value = attr.value;
|
|
389
|
+
if (URL_ATTRS.has(lowerName)) {
|
|
390
|
+
value = sanitizeUrl(value);
|
|
391
|
+
if (!value) continue;
|
|
392
|
+
}
|
|
393
|
+
openTag += ` ${rawName}="${escapeAttr(value)}"`;
|
|
188
394
|
}
|
|
189
395
|
if (VOID_ELEMENTS.has(tag)) {
|
|
190
396
|
yield `${openTag} />`;
|
|
@@ -232,8 +438,9 @@ function hydrateIslands(container, islands) {
|
|
|
232
438
|
const markers = container.querySelectorAll("[data-sibu-island]");
|
|
233
439
|
for (const marker2 of Array.from(markers)) {
|
|
234
440
|
const id = marker2.getAttribute("data-sibu-island") ?? "";
|
|
441
|
+
if (!Object.hasOwn(islands, id)) continue;
|
|
235
442
|
const factory = islands[id];
|
|
236
|
-
if (
|
|
443
|
+
if (typeof factory !== "function") continue;
|
|
237
444
|
const clientTree = factory();
|
|
238
445
|
hydrateNode(marker2, clientTree);
|
|
239
446
|
marker2.setAttribute("data-sibu-hydrated", "true");
|
|
@@ -245,8 +452,9 @@ function hydrateProgressively(container, islands, options) {
|
|
|
245
452
|
const cleanups = [];
|
|
246
453
|
for (const marker2 of Array.from(markers)) {
|
|
247
454
|
const id = marker2.getAttribute("data-sibu-island") ?? "";
|
|
455
|
+
if (!Object.hasOwn(islands, id)) continue;
|
|
248
456
|
const factory = islands[id];
|
|
249
|
-
if (
|
|
457
|
+
if (typeof factory !== "function") continue;
|
|
250
458
|
const observer = new IntersectionObserver(
|
|
251
459
|
(entries) => {
|
|
252
460
|
for (const entry of entries) {
|
|
@@ -285,22 +493,30 @@ function ssrSuspense(props) {
|
|
|
285
493
|
return { element: wrapper, promise };
|
|
286
494
|
}
|
|
287
495
|
function suspenseSwapScript(id, nonce) {
|
|
288
|
-
|
|
496
|
+
if (!SAFE_SUSPENSE_ID.test(id)) {
|
|
497
|
+
throw new Error(
|
|
498
|
+
`[SibuJS SSR] suspenseSwapScript: id must match [A-Za-z0-9_-]+ (got: ${JSON.stringify(id.slice(0, 32))})`
|
|
499
|
+
);
|
|
500
|
+
}
|
|
289
501
|
const nonceAttr = nonce ? ` nonce="${escapeAttr(nonce)}"` : "";
|
|
290
|
-
return `<script${nonceAttr}>(function(){var t=document.getElementById("sibu-resolved-${
|
|
502
|
+
return `<script${nonceAttr}>(function(){var t=document.getElementById("sibu-resolved-${id}");var f=document.querySelector('[data-sibu-suspense-id="${id}"]');if(t&&f){while(t.firstChild)f.appendChild(t.firstChild);t.remove();f.removeAttribute("data-sibu-suspense-id");}})()</script>`;
|
|
291
503
|
}
|
|
292
|
-
async function* renderToSuspenseStream(element, pendingBoundaries = []) {
|
|
504
|
+
async function* renderToSuspenseStream(element, pendingBoundaries = [], options) {
|
|
293
505
|
yield* renderToStream(element);
|
|
294
506
|
if (pendingBoundaries.length > 0) {
|
|
295
507
|
const resolved = await Promise.all(pendingBoundaries);
|
|
296
508
|
for (const { id, html: html2 } of resolved) {
|
|
297
|
-
|
|
298
|
-
yield
|
|
509
|
+
if (!SAFE_SUSPENSE_ID.test(id)) continue;
|
|
510
|
+
yield `<div hidden id="sibu-resolved-${id}">${html2}</div>`;
|
|
511
|
+
yield suspenseSwapScript(id, options?.nonce);
|
|
299
512
|
}
|
|
300
513
|
}
|
|
301
514
|
}
|
|
515
|
+
function escapeScriptJson(json) {
|
|
516
|
+
return json.replace(/</g, "\\u003c").replace(/>/g, "\\u003e").replace(/&/g, "\\u0026").replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029");
|
|
517
|
+
}
|
|
302
518
|
function serializeState(state, nonce) {
|
|
303
|
-
const json = JSON.stringify(state)
|
|
519
|
+
const json = escapeScriptJson(JSON.stringify(state));
|
|
304
520
|
const nonceAttr = nonce ? ` nonce="${escapeAttr(nonce)}"` : "";
|
|
305
521
|
return `<script${nonceAttr}>window.${SSR_DATA_ATTR}=${json}</script>`;
|
|
306
522
|
}
|
|
@@ -315,14 +531,30 @@ function escapeHtml(str) {
|
|
|
315
531
|
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
316
532
|
}
|
|
317
533
|
function escapeAttr(str) {
|
|
318
|
-
return str.replace(/&/g, "&").replace(/"/g, """).replace(/</g, "<").replace(/>/g, ">");
|
|
534
|
+
return str.replace(/&/g, "&").replace(/"/g, """).replace(/'/g, "'").replace(/</g, "<").replace(/>/g, ">");
|
|
319
535
|
}
|
|
320
|
-
var _isDev7, VOID_ELEMENTS, suspenseIdCounter, SSR_DATA_ATTR;
|
|
536
|
+
var _isDev7, SAFE_ATTR_NAME, URL_ATTRS, VOID_ELEMENTS, suspenseIdCounter, SAFE_SUSPENSE_ID, SSR_DATA_ATTR;
|
|
321
537
|
var init_ssr = __esm({
|
|
322
538
|
"src/platform/ssr.ts"() {
|
|
323
539
|
"use strict";
|
|
324
540
|
init_dev();
|
|
541
|
+
init_sanitize();
|
|
325
542
|
_isDev7 = isDev();
|
|
543
|
+
SAFE_ATTR_NAME = /^[A-Za-z_:][-A-Za-z0-9_.:]*$/;
|
|
544
|
+
URL_ATTRS = /* @__PURE__ */ new Set([
|
|
545
|
+
"href",
|
|
546
|
+
"src",
|
|
547
|
+
"action",
|
|
548
|
+
"formaction",
|
|
549
|
+
"cite",
|
|
550
|
+
"poster",
|
|
551
|
+
"background",
|
|
552
|
+
"srcset",
|
|
553
|
+
"ping",
|
|
554
|
+
"manifest",
|
|
555
|
+
"data",
|
|
556
|
+
"xlink:href"
|
|
557
|
+
]);
|
|
326
558
|
VOID_ELEMENTS = /* @__PURE__ */ new Set([
|
|
327
559
|
"area",
|
|
328
560
|
"base",
|
|
@@ -340,6 +572,7 @@ var init_ssr = __esm({
|
|
|
340
572
|
"wbr"
|
|
341
573
|
]);
|
|
342
574
|
suspenseIdCounter = 0;
|
|
575
|
+
SAFE_SUSPENSE_ID = /^[A-Za-z0-9_-]+$/;
|
|
343
576
|
SSR_DATA_ATTR = "__SIBU_SSR_DATA__";
|
|
344
577
|
}
|
|
345
578
|
});
|
|
@@ -424,28 +657,7 @@ module.exports = __toCommonJS(plugins_exports);
|
|
|
424
657
|
|
|
425
658
|
// src/reactivity/bindAttribute.ts
|
|
426
659
|
init_dev();
|
|
427
|
-
|
|
428
|
-
// src/utils/sanitize.ts
|
|
429
|
-
function sanitizeUrl(url) {
|
|
430
|
-
const trimmed = url.replace(/[\x00-\x20\x7f-\x9f]+/g, "").trim();
|
|
431
|
-
if (!trimmed) return "";
|
|
432
|
-
const lower = trimmed.toLowerCase();
|
|
433
|
-
if (lower.startsWith("javascript:") || lower.startsWith("data:") || lower.startsWith("vbscript:") || lower.startsWith("blob:")) {
|
|
434
|
-
return "";
|
|
435
|
-
}
|
|
436
|
-
return trimmed;
|
|
437
|
-
}
|
|
438
|
-
function sanitizeCSSValue(value) {
|
|
439
|
-
const lower = value.toLowerCase().replace(/\s+/g, "");
|
|
440
|
-
if (lower.includes("url(") || lower.includes("expression(") || lower.includes("javascript:") || lower.includes("-moz-binding")) {
|
|
441
|
-
return "";
|
|
442
|
-
}
|
|
443
|
-
return value;
|
|
444
|
-
}
|
|
445
|
-
var URL_ATTRIBUTES = /* @__PURE__ */ new Set(["href", "src", "action", "formaction", "cite", "poster", "background", "srcset"]);
|
|
446
|
-
function isUrlAttribute(attr) {
|
|
447
|
-
return URL_ATTRIBUTES.has(attr);
|
|
448
|
-
}
|
|
660
|
+
init_sanitize();
|
|
449
661
|
|
|
450
662
|
// src/reactivity/track.ts
|
|
451
663
|
init_dev();
|
|
@@ -655,7 +867,20 @@ function cleanup(subscriber) {
|
|
|
655
867
|
|
|
656
868
|
// src/reactivity/bindAttribute.ts
|
|
657
869
|
var _isDev3 = isDev();
|
|
870
|
+
function isEventHandlerAttr(name) {
|
|
871
|
+
if (name.length < 3) return false;
|
|
872
|
+
const lower = name.toLowerCase();
|
|
873
|
+
return lower[0] === "o" && lower[1] === "n" && lower.charCodeAt(2) >= 97 && lower.charCodeAt(2) <= 122;
|
|
874
|
+
}
|
|
658
875
|
function bindAttribute(el, attr, getter) {
|
|
876
|
+
if (isEventHandlerAttr(attr)) {
|
|
877
|
+
if (_isDev3)
|
|
878
|
+
devWarn(
|
|
879
|
+
`bindAttribute: refusing to bind event-handler attribute "${attr}". Use on:{ ${attr.slice(2)}: fn } instead.`
|
|
880
|
+
);
|
|
881
|
+
return () => {
|
|
882
|
+
};
|
|
883
|
+
}
|
|
659
884
|
function commit() {
|
|
660
885
|
let value;
|
|
661
886
|
try {
|
|
@@ -756,6 +981,9 @@ function bindChildNode(placeholder, getter) {
|
|
|
756
981
|
return track(commit);
|
|
757
982
|
}
|
|
758
983
|
|
|
984
|
+
// src/core/rendering/tagFactory.ts
|
|
985
|
+
init_sanitize();
|
|
986
|
+
|
|
759
987
|
// src/core/rendering/dispose.ts
|
|
760
988
|
init_dev();
|
|
761
989
|
var elementDisposers = /* @__PURE__ */ new WeakMap();
|
|
@@ -921,16 +1149,20 @@ function appendChildren(el, nodes) {
|
|
|
921
1149
|
var tagFactory = (tag, ns) => (first, second) => {
|
|
922
1150
|
const el = ns ? document.createElementNS(ns, tag) : document.createElement(tag);
|
|
923
1151
|
if (first === void 0) return el;
|
|
924
|
-
if (
|
|
1152
|
+
if (typeof first === "string") {
|
|
1153
|
+
if (second !== void 0) {
|
|
1154
|
+
el.setAttribute("class", first);
|
|
1155
|
+
appendChildren(el, second);
|
|
1156
|
+
return el;
|
|
1157
|
+
}
|
|
925
1158
|
el.textContent = first;
|
|
926
1159
|
return el;
|
|
927
1160
|
}
|
|
928
|
-
if (
|
|
929
|
-
el.
|
|
930
|
-
appendChildren(el, second);
|
|
1161
|
+
if (typeof first === "number") {
|
|
1162
|
+
el.textContent = String(first);
|
|
931
1163
|
return el;
|
|
932
1164
|
}
|
|
933
|
-
if (Array.isArray(first) || first instanceof Node) {
|
|
1165
|
+
if (Array.isArray(first) || first instanceof Node || typeof first === "function") {
|
|
934
1166
|
appendChildren(el, first);
|
|
935
1167
|
return el;
|
|
936
1168
|
}
|
|
@@ -939,7 +1171,7 @@ var tagFactory = (tag, ns) => (first, second) => {
|
|
|
939
1171
|
if (pClass != null) applyClass(el, pClass);
|
|
940
1172
|
const pId = props.id;
|
|
941
1173
|
if (pId != null) el.id = pId;
|
|
942
|
-
const pNodes = props.nodes;
|
|
1174
|
+
const pNodes = second !== void 0 ? second : props.nodes;
|
|
943
1175
|
if (pNodes != null) appendChildren(el, pNodes);
|
|
944
1176
|
const pOn = props.on;
|
|
945
1177
|
if (pOn) {
|
|
@@ -1245,6 +1477,11 @@ function effect(effectFn, options) {
|
|
|
1245
1477
|
}
|
|
1246
1478
|
|
|
1247
1479
|
// src/plugins/router.ts
|
|
1480
|
+
init_sanitize();
|
|
1481
|
+
function isSafeNavigationTarget(path2) {
|
|
1482
|
+
if (path2 === "") return true;
|
|
1483
|
+
return sanitizeUrl(path2) !== "";
|
|
1484
|
+
}
|
|
1248
1485
|
var LRUCache = class {
|
|
1249
1486
|
constructor(maxSize = 100) {
|
|
1250
1487
|
this.cache = /* @__PURE__ */ new Map();
|
|
@@ -1730,6 +1967,11 @@ var _SibuRouter = class _SibuRouter {
|
|
|
1730
1967
|
try {
|
|
1731
1968
|
await this.navigator.navigate(async (signal2) => {
|
|
1732
1969
|
const targetPath = this.resolvePath(to);
|
|
1970
|
+
if (!isSafeNavigationTarget(targetPath)) {
|
|
1971
|
+
const from2 = this.currentRouteGetter();
|
|
1972
|
+
const toContext2 = this.createRouteContext(targetPath);
|
|
1973
|
+
throw new NavigationFailureError("aborted", from2, toContext2);
|
|
1974
|
+
}
|
|
1733
1975
|
const from = this.currentRouteGetter();
|
|
1734
1976
|
const toContext = this.createRouteContext(targetPath);
|
|
1735
1977
|
if (this.isSameRoute(from, toContext)) {
|
|
@@ -1759,6 +2001,9 @@ var _SibuRouter = class _SibuRouter {
|
|
|
1759
2001
|
const beforeEachResult = await this.guards.runBeforeEach(to, from, signal2);
|
|
1760
2002
|
if (beforeEachResult !== true) {
|
|
1761
2003
|
if (typeof beforeEachResult === "string") {
|
|
2004
|
+
if (!isSafeNavigationTarget(beforeEachResult)) {
|
|
2005
|
+
throw new NavigationFailureError("aborted", from, to);
|
|
2006
|
+
}
|
|
1762
2007
|
return this.performNavigation(this.createRouteContext(beforeEachResult), from, options, signal2, depth + 1);
|
|
1763
2008
|
}
|
|
1764
2009
|
throw new NavigationFailureError("aborted", from, to);
|
|
@@ -1774,6 +2019,9 @@ var _SibuRouter = class _SibuRouter {
|
|
|
1774
2019
|
const result = await guard(to, from);
|
|
1775
2020
|
if (result !== true) {
|
|
1776
2021
|
if (typeof result === "string") {
|
|
2022
|
+
if (!isSafeNavigationTarget(result)) {
|
|
2023
|
+
throw new NavigationFailureError("aborted", from, to);
|
|
2024
|
+
}
|
|
1777
2025
|
return this.performNavigation(this.createRouteContext(result), from, options, signal2, depth + 1);
|
|
1778
2026
|
}
|
|
1779
2027
|
throw new NavigationFailureError("aborted", from, to);
|
|
@@ -1788,12 +2036,18 @@ var _SibuRouter = class _SibuRouter {
|
|
|
1788
2036
|
`[SibuJS Router] Redirect to absolute URL "${redirectPath}" detected. Use relative paths for safer redirects.`
|
|
1789
2037
|
);
|
|
1790
2038
|
}
|
|
2039
|
+
if (typeof redirectPath === "string" && !isSafeNavigationTarget(redirectPath)) {
|
|
2040
|
+
throw new NavigationFailureError("aborted", from, to);
|
|
2041
|
+
}
|
|
1791
2042
|
return this.performNavigation(this.createRouteContext(redirectPath), from, options, signal2, depth + 1);
|
|
1792
2043
|
}
|
|
1793
2044
|
}
|
|
1794
2045
|
const beforeResolveResult = await this.guards.runBeforeResolve(to, from, signal2);
|
|
1795
2046
|
if (beforeResolveResult !== true) {
|
|
1796
2047
|
if (typeof beforeResolveResult === "string") {
|
|
2048
|
+
if (!isSafeNavigationTarget(beforeResolveResult)) {
|
|
2049
|
+
throw new NavigationFailureError("aborted", from, to);
|
|
2050
|
+
}
|
|
1797
2051
|
return this.performNavigation(this.createRouteContext(beforeResolveResult), from, options, signal2, depth + 1);
|
|
1798
2052
|
}
|
|
1799
2053
|
throw new NavigationFailureError("aborted", from, to);
|
|
@@ -1953,13 +2207,31 @@ var NavigationFailureError = class extends Error {
|
|
|
1953
2207
|
}
|
|
1954
2208
|
};
|
|
1955
2209
|
var globalRouter = null;
|
|
2210
|
+
function normalizeRoutes(routes) {
|
|
2211
|
+
return routes.map((route2) => {
|
|
2212
|
+
const normalizedChildren = route2.children && route2.children.length > 0 ? normalizeRoutes(route2.children) : route2.children;
|
|
2213
|
+
if ("lazy" in route2 && typeof route2.lazy === "function") {
|
|
2214
|
+
const { lazy: importFn, ...rest } = route2;
|
|
2215
|
+
const asyncRoute = {
|
|
2216
|
+
...rest,
|
|
2217
|
+
component: lazy(importFn),
|
|
2218
|
+
children: normalizedChildren
|
|
2219
|
+
};
|
|
2220
|
+
return asyncRoute;
|
|
2221
|
+
}
|
|
2222
|
+
if (normalizedChildren !== route2.children) {
|
|
2223
|
+
return { ...route2, children: normalizedChildren };
|
|
2224
|
+
}
|
|
2225
|
+
return route2;
|
|
2226
|
+
});
|
|
2227
|
+
}
|
|
1956
2228
|
function createRouter(routesOrOptions, options = {}) {
|
|
1957
2229
|
if (globalRouter) {
|
|
1958
2230
|
globalRouter.destroy();
|
|
1959
2231
|
}
|
|
1960
2232
|
let routes;
|
|
1961
2233
|
if (Array.isArray(routesOrOptions)) {
|
|
1962
|
-
routes = routesOrOptions;
|
|
2234
|
+
routes = normalizeRoutes(routesOrOptions);
|
|
1963
2235
|
} else {
|
|
1964
2236
|
options = routesOrOptions;
|
|
1965
2237
|
routes = [];
|
|
@@ -1969,7 +2241,7 @@ function createRouter(routesOrOptions, options = {}) {
|
|
|
1969
2241
|
}
|
|
1970
2242
|
function setRoutes(routes) {
|
|
1971
2243
|
if (!globalRouter) throw new Error("Router not initialized. Call createRouter() first.");
|
|
1972
|
-
globalRouter.updateRoutes(routes);
|
|
2244
|
+
globalRouter.updateRoutes(normalizeRoutes(routes));
|
|
1973
2245
|
}
|
|
1974
2246
|
function route() {
|
|
1975
2247
|
if (!globalRouter) throw new Error("Router not initialized. Call createRouter() first.");
|
|
@@ -2550,6 +2822,20 @@ function createMemoryRouter(routes, _initialPath = "/") {
|
|
|
2550
2822
|
|
|
2551
2823
|
// src/plugins/routerSSR.ts
|
|
2552
2824
|
init_ssr();
|
|
2825
|
+
var FORBIDDEN_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
|
|
2826
|
+
function isForbiddenKey(key) {
|
|
2827
|
+
return FORBIDDEN_KEYS.has(key);
|
|
2828
|
+
}
|
|
2829
|
+
function safeDecode(raw) {
|
|
2830
|
+
try {
|
|
2831
|
+
return decodeURIComponent(raw);
|
|
2832
|
+
} catch {
|
|
2833
|
+
return raw;
|
|
2834
|
+
}
|
|
2835
|
+
}
|
|
2836
|
+
function nullObject() {
|
|
2837
|
+
return /* @__PURE__ */ Object.create(null);
|
|
2838
|
+
}
|
|
2553
2839
|
function parseURL(url) {
|
|
2554
2840
|
let remaining = url;
|
|
2555
2841
|
let hash = "";
|
|
@@ -2565,19 +2851,23 @@ function parseURL(url) {
|
|
|
2565
2851
|
remaining = remaining.slice(0, queryIndex);
|
|
2566
2852
|
}
|
|
2567
2853
|
const path2 = remaining || "/";
|
|
2568
|
-
const query =
|
|
2854
|
+
const query = nullObject();
|
|
2569
2855
|
if (queryString) {
|
|
2570
2856
|
const pairs = queryString.split("&");
|
|
2571
2857
|
for (const pair of pairs) {
|
|
2572
2858
|
if (!pair) continue;
|
|
2573
2859
|
const eqIndex = pair.indexOf("=");
|
|
2860
|
+
let key;
|
|
2861
|
+
let value;
|
|
2574
2862
|
if (eqIndex === -1) {
|
|
2575
|
-
|
|
2863
|
+
key = safeDecode(pair);
|
|
2864
|
+
value = "";
|
|
2576
2865
|
} else {
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
query[key] = value;
|
|
2866
|
+
key = safeDecode(pair.slice(0, eqIndex));
|
|
2867
|
+
value = safeDecode(pair.slice(eqIndex + 1));
|
|
2580
2868
|
}
|
|
2869
|
+
if (isForbiddenKey(key)) continue;
|
|
2870
|
+
query[key] = value;
|
|
2581
2871
|
}
|
|
2582
2872
|
}
|
|
2583
2873
|
return { path: path2, query, hash };
|
|
@@ -2635,10 +2925,12 @@ function matchRoute(path2, routes, parentPath = "", parentChain = []) {
|
|
|
2635
2925
|
const compiled = compilePattern(fullPath);
|
|
2636
2926
|
const match = path2.match(compiled.regex);
|
|
2637
2927
|
if (match) {
|
|
2638
|
-
const params =
|
|
2928
|
+
const params = nullObject();
|
|
2639
2929
|
for (let i2 = 0; i2 < compiled.keys.length; i2++) {
|
|
2930
|
+
const key = compiled.keys[i2];
|
|
2931
|
+
if (isForbiddenKey(key)) continue;
|
|
2640
2932
|
if (match[i2 + 1] !== void 0) {
|
|
2641
|
-
params[
|
|
2933
|
+
params[key] = safeDecode(match[i2 + 1]);
|
|
2642
2934
|
}
|
|
2643
2935
|
}
|
|
2644
2936
|
return {
|
|
@@ -2673,7 +2965,7 @@ function resolveServerRouteInternal(url, routes, depth) {
|
|
|
2673
2965
|
return {
|
|
2674
2966
|
route: {
|
|
2675
2967
|
path: normalizedPath,
|
|
2676
|
-
params:
|
|
2968
|
+
params: nullObject(),
|
|
2677
2969
|
query,
|
|
2678
2970
|
hash,
|
|
2679
2971
|
meta: {}
|
|
@@ -2733,20 +3025,26 @@ function renderRouteToString(url, routes, _options) {
|
|
|
2733
3025
|
function renderRouteToDocument(url, routes, options) {
|
|
2734
3026
|
const { html: html2, state } = renderRouteToString(url, routes, options);
|
|
2735
3027
|
const opts = options || {};
|
|
2736
|
-
const metaTags = (opts.meta || []).map(
|
|
2737
|
-
|
|
2738
|
-
|
|
2739
|
-
|
|
2740
|
-
|
|
2741
|
-
|
|
2742
|
-
|
|
2743
|
-
|
|
3028
|
+
const metaTags = (opts.meta || []).map((attrs) => {
|
|
3029
|
+
const pairs = buildSafeAttrString(attrs);
|
|
3030
|
+
return pairs ? `<meta ${pairs} />` : "";
|
|
3031
|
+
}).filter(Boolean).join("\n ");
|
|
3032
|
+
const linkTags = (opts.links || []).map((attrs) => {
|
|
3033
|
+
const pairs = buildSafeAttrString(attrs);
|
|
3034
|
+
return pairs ? `<link ${pairs} />` : "";
|
|
3035
|
+
}).filter(Boolean).join("\n ");
|
|
3036
|
+
const scriptTags = (opts.scripts || []).map((src) => {
|
|
3037
|
+
const safe = sanitizeUrlLocal(String(src));
|
|
3038
|
+
if (!safe) return "";
|
|
3039
|
+
return `<script src="${escapeAttrLocal(safe)}"></script>`;
|
|
3040
|
+
}).filter(Boolean).join("\n ");
|
|
3041
|
+
const stateScript = serializeRouteState(state, opts.nonce);
|
|
2744
3042
|
return `<!DOCTYPE html>
|
|
2745
3043
|
<html>
|
|
2746
3044
|
<head>
|
|
2747
3045
|
<meta charset="UTF-8" />
|
|
2748
3046
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
2749
|
-
${opts.title ? `<title>${
|
|
3047
|
+
${opts.title ? `<title>${escapeHtmlLocal(opts.title)}</title>` : ""}
|
|
2750
3048
|
${metaTags}
|
|
2751
3049
|
${linkTags}
|
|
2752
3050
|
${opts.headExtra || ""}
|
|
@@ -2758,9 +3056,10 @@ function renderRouteToDocument(url, routes, options) {
|
|
|
2758
3056
|
</body>
|
|
2759
3057
|
</html>`;
|
|
2760
3058
|
}
|
|
2761
|
-
function serializeRouteState(state) {
|
|
2762
|
-
const json = JSON.stringify(state)
|
|
2763
|
-
|
|
3059
|
+
function serializeRouteState(state, nonce) {
|
|
3060
|
+
const json = escapeScriptJson(JSON.stringify(state));
|
|
3061
|
+
const nonceAttr = nonce ? ` nonce="${escapeAttrLocal(nonce)}"` : "";
|
|
3062
|
+
return `<script${nonceAttr}>window.${SSR_ROUTE_STATE_KEY}=${json}</script>`;
|
|
2764
3063
|
}
|
|
2765
3064
|
function deserializeRouteState() {
|
|
2766
3065
|
if (typeof window === "undefined") return void 0;
|
|
@@ -2798,11 +3097,59 @@ function createSSRRouter(routes) {
|
|
|
2798
3097
|
}
|
|
2799
3098
|
};
|
|
2800
3099
|
}
|
|
2801
|
-
|
|
3100
|
+
var SAFE_ATTR_NAME2 = /^[A-Za-z_:][-A-Za-z0-9_.:]*$/;
|
|
3101
|
+
function isSafeAttrName2(name) {
|
|
3102
|
+
return SAFE_ATTR_NAME2.test(name);
|
|
3103
|
+
}
|
|
3104
|
+
function isEventHandlerAttr3(name) {
|
|
3105
|
+
if (name.length < 3) return false;
|
|
3106
|
+
const lower = name.toLowerCase();
|
|
3107
|
+
return lower[0] === "o" && lower[1] === "n" && lower.charCodeAt(2) >= 97 && lower.charCodeAt(2) <= 122;
|
|
3108
|
+
}
|
|
3109
|
+
var URL_ATTRS2 = /* @__PURE__ */ new Set([
|
|
3110
|
+
"href",
|
|
3111
|
+
"src",
|
|
3112
|
+
"action",
|
|
3113
|
+
"formaction",
|
|
3114
|
+
"cite",
|
|
3115
|
+
"poster",
|
|
3116
|
+
"background",
|
|
3117
|
+
"srcset",
|
|
3118
|
+
"ping",
|
|
3119
|
+
"manifest",
|
|
3120
|
+
"data",
|
|
3121
|
+
"xlink:href"
|
|
3122
|
+
]);
|
|
3123
|
+
function sanitizeUrlLocal(url) {
|
|
3124
|
+
const trimmed = url.replace(/[\x00-\x20\x7f-\x9f]+/g, "").trim();
|
|
3125
|
+
if (!trimmed) return "";
|
|
3126
|
+
const lower = trimmed.toLowerCase();
|
|
3127
|
+
if (lower.startsWith("javascript:") || lower.startsWith("data:") || lower.startsWith("vbscript:") || lower.startsWith("blob:")) {
|
|
3128
|
+
return "";
|
|
3129
|
+
}
|
|
3130
|
+
return trimmed;
|
|
3131
|
+
}
|
|
3132
|
+
function buildSafeAttrString(attrs) {
|
|
3133
|
+
const out = [];
|
|
3134
|
+
for (const rawKey of Object.keys(attrs)) {
|
|
3135
|
+
if (!Object.hasOwn(attrs, rawKey)) continue;
|
|
3136
|
+
if (!isSafeAttrName2(rawKey)) continue;
|
|
3137
|
+
if (isEventHandlerAttr3(rawKey)) continue;
|
|
3138
|
+
const lowerKey = rawKey.toLowerCase();
|
|
3139
|
+
let value = String(attrs[rawKey]);
|
|
3140
|
+
if (URL_ATTRS2.has(lowerKey)) {
|
|
3141
|
+
value = sanitizeUrlLocal(value);
|
|
3142
|
+
if (!value) continue;
|
|
3143
|
+
}
|
|
3144
|
+
out.push(`${rawKey}="${escapeAttrLocal(value)}"`);
|
|
3145
|
+
}
|
|
3146
|
+
return out.join(" ");
|
|
3147
|
+
}
|
|
3148
|
+
function escapeHtmlLocal(str) {
|
|
2802
3149
|
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
2803
3150
|
}
|
|
2804
|
-
function
|
|
2805
|
-
return str.replace(/&/g, "&").replace(/"/g, """).replace(/</g, "<").replace(/>/g, ">");
|
|
3151
|
+
function escapeAttrLocal(str) {
|
|
3152
|
+
return str.replace(/&/g, "&").replace(/"/g, """).replace(/'/g, "'").replace(/</g, "<").replace(/>/g, ">");
|
|
2806
3153
|
}
|
|
2807
3154
|
|
|
2808
3155
|
// src/plugins/plugin.ts
|