import { u as unpackMeta } from './unhead.DQc16pHI.mjs'; function defineHeadPlugin(plugin) { return plugin; } const FlatMetaPlugin = /* @__PURE__ */ defineHeadPlugin({ key: "flatMeta", hooks: { "entries:normalize": (ctx) => { const tagsToAdd = []; ctx.tags = ctx.tags.map((t) => { if (t.tag !== "_flatMeta") { return t; } tagsToAdd.push(unpackMeta(t.props).map((p) => ({ ...t, tag: "meta", props: p }))); return false; }).filter(Boolean).concat(...tagsToAdd); } } }); const WhitelistAttributes = { htmlAttrs: /* @__PURE__ */ new Set(["class", "style", "lang", "dir"]), bodyAttrs: /* @__PURE__ */ new Set(["class", "style"]), meta: /* @__PURE__ */ new Set(["name", "property", "charset", "content", "media"]), noscript: /* @__PURE__ */ new Set([]), style: /* @__PURE__ */ new Set(["media", "nonce", "title", "blocking"]), script: /* @__PURE__ */ new Set(["type", "textContent", "nonce", "blocking"]), link: /* @__PURE__ */ new Set(["color", "crossorigin", "fetchpriority", "href", "hreflang", "imagesrcset", "imagesizes", "integrity", "media", "referrerpolicy", "rel", "sizes", "type"]) }; const BlockedLinkRels = /* @__PURE__ */ new Set(["canonical", "modulepreload", "prerender", "preload", "prefetch", "dns-prefetch", "preconnect", "manifest", "pingback"]); const SafeAttrName = /^[a-z][a-z0-9\-]*[a-z0-9]$/i; const HtmlEntityHex = /&#x([0-9a-f]+);?/gi; const HtmlEntityDec = /&#(\d+);?/g; const HtmlEntityNamed = /&(tab|newline|colon|semi|lpar|rpar|sol|bsol|comma|period|excl|num|dollar|percnt|amp|apos|ast|plus|lt|gt|equals|quest|at|lsqb|rsqb|lcub|rcub|vert|hat|grave|tilde|nbsp);?/gi; const ControlChars = /[\x00-\x20]+/g; const NamedEntityMap = { tab: " ", newline: "\n", colon: ":", semi: ";", lpar: "(", rpar: ")", sol: "/", bsol: "\\", comma: ",", period: ".", excl: "!", num: "#", dollar: "$", percnt: "%", amp: "&", apos: "'", ast: "*", plus: "+", lt: "<", gt: ">", equals: "=", quest: "?", at: "@", lsqb: "[", rsqb: "]", lcub: "{", rcub: "}", vert: "|", hat: "^", grave: "`", tilde: "~", nbsp: "\xA0" }; function safeFromCodePoint(codePoint) { if (codePoint > 1114111 || codePoint < 0 || Number.isNaN(codePoint)) return ""; return String.fromCodePoint(codePoint); } function decodeHtmlEntities(str) { return str.replace(HtmlEntityHex, (_, hex) => safeFromCodePoint(Number.parseInt(hex, 16))).replace(HtmlEntityDec, (_, dec) => safeFromCodePoint(Number(dec))).replace(HtmlEntityNamed, (_, name) => NamedEntityMap[name.toLowerCase()] || ""); } function hasDangerousProtocol(url) { const entityDecoded = decodeHtmlEntities(url); const cleaned = entityDecoded.replace(ControlChars, ""); let decoded; try { decoded = decodeURIComponent(cleaned); } catch { decoded = cleaned; } const sanitized = decoded.replace(ControlChars, ""); const lower = sanitized.toLowerCase(); return lower.startsWith("javascript:") || lower.startsWith("data:") || lower.startsWith("vbscript:"); } function stripProtoKeys(obj) { if (Array.isArray(obj)) return obj.map(stripProtoKeys); if (obj && typeof obj === "object") { const clean = {}; for (const key of Object.keys(obj)) { if (key === "__proto__" || key === "constructor" || key === "prototype") continue; clean[key] = stripProtoKeys(obj[key]); } return clean; } return obj; } function acceptDataAttrs(value, allowId = true) { return Object.fromEntries( Object.entries(value || {}).filter(([key]) => (allowId && key === "id" || key.startsWith("data-")) && SafeAttrName.test(key)) ); } function makeTagSafe(tag) { let next = {}; const { tag: type, props: prev } = tag; switch (type) { // title: textContent is escaped in rendering (tagToString), no props needed case "title": break; // virtual tags, not rendered to HTML — but sanitize to prevent injection if rendered case "titleTemplate": case "templateParams": next = prev; break; case "htmlAttrs": case "bodyAttrs": WhitelistAttributes[type].forEach((attr) => { if (prev[attr]) { next[attr] = prev[attr]; } }); delete tag.innerHTML; delete tag.textContent; tag.props = { ...acceptDataAttrs(prev, false), ...next }; return !Object.keys(tag.props).length ? false : tag; case "style": next = acceptDataAttrs(prev); WhitelistAttributes.style.forEach((key) => { if (prev[key]) { next[key] = prev[key]; } }); break; // meta is safe, except for http-equiv case "meta": WhitelistAttributes.meta.forEach((key) => { if (prev[key]) { next[key] = prev[key]; } }); break; // link tags we block preloading, prerendering, prefetching, dns-prefetch, preconnect, manifest, etc case "link": WhitelistAttributes.link.forEach((key) => { const val = prev[key]; if (!val) { return; } if (key === "rel" && (typeof val !== "string" || BlockedLinkRels.has(val.toLowerCase()))) { return; } if (key === "href" || key === "imagesrcset") { if (typeof val !== "string") { return; } const urls = key === "imagesrcset" ? val.split(",").map((s) => s.trim()) : [val]; if (urls.some((u) => hasDangerousProtocol(u))) { return; } next[key] = val; } else if (val) { next[key] = val; } }); if (!next.href && !next.imagesrcset || !next.rel) { return false; } break; case "noscript": WhitelistAttributes.noscript.forEach((key) => { if (prev[key]) { next[key] = prev[key]; } }); break; // we only allow JSON in scripts case "script": if (!tag.textContent || typeof prev.type !== "string" || !prev.type.endsWith("json")) { return false; } try { const jsonVal = typeof tag.textContent === "string" ? JSON.parse(tag.textContent) : tag.textContent; tag.textContent = JSON.stringify(stripProtoKeys(jsonVal), null, 0); } catch { return false; } WhitelistAttributes.script.forEach((s) => { if (s !== "textContent" && prev[s]) { next[s] = prev[s]; } }); break; } delete tag.innerHTML; if (type !== "title" && type !== "script") { delete tag.textContent; } tag.props = { ...acceptDataAttrs(prev), ...next }; if (!Object.keys(tag.props).length && !tag.tag.endsWith("Attrs") && !tag.textContent) { return false; } return tag; } const SafeInputPlugin = ( /* @PURE */ defineHeadPlugin({ key: "safe", hooks: { "entries:normalize": (ctx) => { if (ctx.entry.options?._safe) { ctx.tags = ctx.tags.reduce((acc, tag) => { const safeTag = makeTagSafe(tag); if (safeTag) acc.push(safeTag); return acc; }, []); } } } }) ); export { FlatMetaPlugin as F, SafeInputPlugin as S, defineHeadPlugin as d };