unhead 2.1.9 → 2.1.11

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/dist/index.mjs CHANGED
@@ -1,7 +1,7 @@
1
- export { u as useHead, a as useHeadSafe, b as useSeoMeta, c as useServerHead, d as useServerHeadSafe, e as useServerSeoMeta } from './shared/unhead.BPM0-cfG.mjs';
1
+ export { u as useHead, a as useHeadSafe, b as useSeoMeta, c as useServerHead, d as useServerHeadSafe, e as useServerSeoMeta } from './shared/unhead.DwlZTAdp.mjs';
2
2
  export { c as createHeadCore, a as createUnhead } from './shared/unhead.D_nrZZPH.mjs';
3
3
  export { u as useScript } from './shared/unhead.BnoAbrHA.mjs';
4
- import './shared/unhead.CApf5sj3.mjs';
4
+ import './shared/unhead.DxVOmucZ.mjs';
5
5
  import './shared/unhead.DQc16pHI.mjs';
6
6
  import './shared/unhead.yem5I2v_.mjs';
7
7
  import 'hookable';
package/dist/legacy.mjs CHANGED
@@ -1,12 +1,12 @@
1
1
  import { a as createUnhead } from './shared/unhead.D_nrZZPH.mjs';
2
- import { D as DeprecationsPlugin, P as PromisesPlugin, T as TemplateParamsPlugin, A as AliasSortingPlugin } from './shared/unhead.ckV6dpEQ.mjs';
3
- export { u as useHead, a as useHeadSafe, b as useSeoMeta, c as useServerHead, d as useServerHeadSafe, e as useServerSeoMeta } from './shared/unhead.BPM0-cfG.mjs';
2
+ import { D as DeprecationsPlugin, P as PromisesPlugin, T as TemplateParamsPlugin, A as AliasSortingPlugin } from './shared/unhead.Bx2DpYJy.mjs';
3
+ export { u as useHead, a as useHeadSafe, b as useSeoMeta, c as useServerHead, d as useServerHeadSafe, e as useServerSeoMeta } from './shared/unhead.DwlZTAdp.mjs';
4
4
  export { u as useScript } from './shared/unhead.BnoAbrHA.mjs';
5
5
  import 'hookable';
6
6
  import './shared/unhead.fVVqDC1O.mjs';
7
7
  import './shared/unhead.yem5I2v_.mjs';
8
8
  import './shared/unhead.CbpEuj3y.mjs';
9
- import './shared/unhead.CApf5sj3.mjs';
9
+ import './shared/unhead.DxVOmucZ.mjs';
10
10
  import './shared/unhead.DQc16pHI.mjs';
11
11
  import './shared/unhead.BYvz9V1x.mjs';
12
12
 
@@ -44,7 +44,7 @@ interface InferSeoMetaPluginOptions {
44
44
  /**
45
45
  * Transform the og description.
46
46
  *
47
- * @param title
47
+ * @param description
48
48
  */
49
49
  ogDescription?: ((description?: string) => string);
50
50
  /**
package/dist/plugins.d.ts CHANGED
@@ -44,7 +44,7 @@ interface InferSeoMetaPluginOptions {
44
44
  /**
45
45
  * Transform the og description.
46
46
  *
47
- * @param title
47
+ * @param description
48
48
  */
49
49
  ogDescription?: ((description?: string) => string);
50
50
  /**
package/dist/plugins.mjs CHANGED
@@ -1,6 +1,6 @@
1
- export { A as AliasSortingPlugin, D as DeprecationsPlugin, P as PromisesPlugin, T as TemplateParamsPlugin } from './shared/unhead.ckV6dpEQ.mjs';
2
- import { d as defineHeadPlugin } from './shared/unhead.CApf5sj3.mjs';
3
- export { F as FlatMetaPlugin, S as SafeInputPlugin } from './shared/unhead.CApf5sj3.mjs';
1
+ export { A as AliasSortingPlugin, D as DeprecationsPlugin, P as PromisesPlugin, T as TemplateParamsPlugin } from './shared/unhead.Bx2DpYJy.mjs';
2
+ import { d as defineHeadPlugin } from './shared/unhead.DxVOmucZ.mjs';
3
+ export { F as FlatMetaPlugin, S as SafeInputPlugin } from './shared/unhead.DxVOmucZ.mjs';
4
4
  import './shared/unhead.CbpEuj3y.mjs';
5
5
  import './shared/unhead.BYvz9V1x.mjs';
6
6
  import './shared/unhead.DQc16pHI.mjs';
@@ -1,4 +1,4 @@
1
- import { d as defineHeadPlugin } from './unhead.CApf5sj3.mjs';
1
+ import { d as defineHeadPlugin } from './unhead.DxVOmucZ.mjs';
2
2
  import { s as sortTags } from './unhead.CbpEuj3y.mjs';
3
3
  import { p as processTemplateParams } from './unhead.BYvz9V1x.mjs';
4
4
 
@@ -1,4 +1,4 @@
1
- import { S as SafeInputPlugin, F as FlatMetaPlugin } from './unhead.CApf5sj3.mjs';
1
+ import { S as SafeInputPlugin, F as FlatMetaPlugin } from './unhead.DxVOmucZ.mjs';
2
2
 
3
3
  function useHead(unhead, input, options = {}) {
4
4
  return unhead.push(input || {}, options);
@@ -0,0 +1,236 @@
1
+ import { u as unpackMeta } from './unhead.DQc16pHI.mjs';
2
+
3
+ function defineHeadPlugin(plugin) {
4
+ return plugin;
5
+ }
6
+
7
+ const FlatMetaPlugin = /* @__PURE__ */ defineHeadPlugin({
8
+ key: "flatMeta",
9
+ hooks: {
10
+ "entries:normalize": (ctx) => {
11
+ const tagsToAdd = [];
12
+ ctx.tags = ctx.tags.map((t) => {
13
+ if (t.tag !== "_flatMeta") {
14
+ return t;
15
+ }
16
+ tagsToAdd.push(unpackMeta(t.props).map((p) => ({
17
+ ...t,
18
+ tag: "meta",
19
+ props: p
20
+ })));
21
+ return false;
22
+ }).filter(Boolean).concat(...tagsToAdd);
23
+ }
24
+ }
25
+ });
26
+
27
+ const WhitelistAttributes = {
28
+ htmlAttrs: /* @__PURE__ */ new Set(["class", "style", "lang", "dir"]),
29
+ bodyAttrs: /* @__PURE__ */ new Set(["class", "style"]),
30
+ meta: /* @__PURE__ */ new Set(["name", "property", "charset", "content", "media"]),
31
+ noscript: /* @__PURE__ */ new Set([]),
32
+ style: /* @__PURE__ */ new Set(["media", "nonce", "title", "blocking"]),
33
+ script: /* @__PURE__ */ new Set(["type", "textContent", "nonce", "blocking"]),
34
+ link: /* @__PURE__ */ new Set(["color", "crossorigin", "fetchpriority", "href", "hreflang", "imagesrcset", "imagesizes", "integrity", "media", "referrerpolicy", "rel", "sizes", "type"])
35
+ };
36
+ const BlockedLinkRels = /* @__PURE__ */ new Set(["canonical", "modulepreload", "prerender", "preload", "prefetch", "dns-prefetch", "preconnect", "manifest", "pingback"]);
37
+ const SafeAttrName = /^[a-z][a-z0-9\-]*[a-z0-9]$/i;
38
+ const HtmlEntityHex = /&#x([0-9a-f]{1,6});?/gi;
39
+ const HtmlEntityDec = /&#(\d{1,7});?/g;
40
+ 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;
41
+ const ControlChars = /[\x00-\x20]+/g;
42
+ const NamedEntityMap = {
43
+ tab: " ",
44
+ newline: "\n",
45
+ colon: ":",
46
+ semi: ";",
47
+ lpar: "(",
48
+ rpar: ")",
49
+ sol: "/",
50
+ bsol: "\\",
51
+ comma: ",",
52
+ period: ".",
53
+ excl: "!",
54
+ num: "#",
55
+ dollar: "$",
56
+ percnt: "%",
57
+ amp: "&",
58
+ apos: "'",
59
+ ast: "*",
60
+ plus: "+",
61
+ lt: "<",
62
+ gt: ">",
63
+ equals: "=",
64
+ quest: "?",
65
+ at: "@",
66
+ lsqb: "[",
67
+ rsqb: "]",
68
+ lcub: "{",
69
+ rcub: "}",
70
+ vert: "|",
71
+ hat: "^",
72
+ grave: "`",
73
+ tilde: "~",
74
+ nbsp: "\xA0"
75
+ };
76
+ function safeFromCodePoint(codePoint) {
77
+ if (codePoint > 1114111 || codePoint < 0 || Number.isNaN(codePoint))
78
+ return "";
79
+ return String.fromCodePoint(codePoint);
80
+ }
81
+ function decodeHtmlEntities(str) {
82
+ return str.replace(HtmlEntityHex, (_, hex) => safeFromCodePoint(Number.parseInt(hex, 16))).replace(HtmlEntityDec, (_, dec) => safeFromCodePoint(Number(dec))).replace(HtmlEntityNamed, (_, name) => NamedEntityMap[name.toLowerCase()] || "");
83
+ }
84
+ function hasDangerousProtocol(url) {
85
+ const entityDecoded = decodeHtmlEntities(url);
86
+ const cleaned = entityDecoded.replace(ControlChars, "");
87
+ let decoded;
88
+ try {
89
+ decoded = decodeURIComponent(cleaned);
90
+ } catch {
91
+ decoded = cleaned;
92
+ }
93
+ const sanitized = decoded.replace(ControlChars, "");
94
+ const lower = sanitized.toLowerCase();
95
+ return lower.startsWith("javascript:") || lower.startsWith("data:") || lower.startsWith("vbscript:");
96
+ }
97
+ function stripProtoKeys(obj) {
98
+ if (Array.isArray(obj))
99
+ return obj.map(stripProtoKeys);
100
+ if (obj && typeof obj === "object") {
101
+ const clean = {};
102
+ for (const key of Object.keys(obj)) {
103
+ if (key === "__proto__" || key === "constructor")
104
+ continue;
105
+ clean[key] = stripProtoKeys(obj[key]);
106
+ }
107
+ return clean;
108
+ }
109
+ return obj;
110
+ }
111
+ function acceptDataAttrs(value, allowId = true) {
112
+ return Object.fromEntries(
113
+ Object.entries(value || {}).filter(([key]) => (allowId && key === "id" || key.startsWith("data-")) && SafeAttrName.test(key))
114
+ );
115
+ }
116
+ function makeTagSafe(tag) {
117
+ let next = {};
118
+ const { tag: type, props: prev } = tag;
119
+ switch (type) {
120
+ // title: textContent is escaped in rendering (tagToString), no props needed
121
+ case "title":
122
+ break;
123
+ // virtual tags, not rendered to HTML — but sanitize to prevent injection if rendered
124
+ case "titleTemplate":
125
+ case "templateParams":
126
+ next = prev;
127
+ break;
128
+ case "htmlAttrs":
129
+ case "bodyAttrs":
130
+ WhitelistAttributes[type].forEach((attr) => {
131
+ if (prev[attr]) {
132
+ next[attr] = prev[attr];
133
+ }
134
+ });
135
+ delete tag.innerHTML;
136
+ delete tag.textContent;
137
+ tag.props = { ...acceptDataAttrs(prev, false), ...next };
138
+ return !Object.keys(tag.props).length ? false : tag;
139
+ case "style":
140
+ next = acceptDataAttrs(prev);
141
+ WhitelistAttributes.style.forEach((key) => {
142
+ if (prev[key]) {
143
+ next[key] = prev[key];
144
+ }
145
+ });
146
+ break;
147
+ // meta is safe, except for http-equiv
148
+ case "meta":
149
+ WhitelistAttributes.meta.forEach((key) => {
150
+ if (prev[key]) {
151
+ next[key] = prev[key];
152
+ }
153
+ });
154
+ break;
155
+ // link tags we block preloading, prerendering, prefetching, dns-prefetch, preconnect, manifest, etc
156
+ case "link":
157
+ WhitelistAttributes.link.forEach((key) => {
158
+ const val = prev[key];
159
+ if (!val) {
160
+ return;
161
+ }
162
+ if (key === "rel" && (typeof val !== "string" || BlockedLinkRels.has(val.toLowerCase()))) {
163
+ return;
164
+ }
165
+ if (key === "href" || key === "imagesrcset") {
166
+ if (typeof val !== "string") {
167
+ return;
168
+ }
169
+ const urls = key === "imagesrcset" ? val.split(",").map((s) => s.trim()) : [val];
170
+ if (urls.some((u) => hasDangerousProtocol(u))) {
171
+ return;
172
+ }
173
+ next[key] = val;
174
+ } else if (val) {
175
+ next[key] = val;
176
+ }
177
+ });
178
+ if (!next.href && !next.imagesrcset || !next.rel) {
179
+ return false;
180
+ }
181
+ break;
182
+ case "noscript":
183
+ WhitelistAttributes.noscript.forEach((key) => {
184
+ if (prev[key]) {
185
+ next[key] = prev[key];
186
+ }
187
+ });
188
+ break;
189
+ // we only allow JSON in scripts
190
+ case "script":
191
+ if (!tag.textContent || typeof prev.type !== "string" || !prev.type.endsWith("json")) {
192
+ return false;
193
+ }
194
+ try {
195
+ const jsonVal = typeof tag.textContent === "string" ? JSON.parse(tag.textContent) : tag.textContent;
196
+ tag.textContent = JSON.stringify(stripProtoKeys(jsonVal), null, 0);
197
+ } catch {
198
+ return false;
199
+ }
200
+ WhitelistAttributes.script.forEach((s) => {
201
+ if (s !== "textContent" && prev[s]) {
202
+ next[s] = prev[s];
203
+ }
204
+ });
205
+ break;
206
+ }
207
+ delete tag.innerHTML;
208
+ if (type !== "title" && type !== "script") {
209
+ delete tag.textContent;
210
+ }
211
+ tag.props = { ...acceptDataAttrs(prev), ...next };
212
+ if (!Object.keys(tag.props).length && !tag.tag.endsWith("Attrs") && !tag.textContent) {
213
+ return false;
214
+ }
215
+ return tag;
216
+ }
217
+ const SafeInputPlugin = (
218
+ /* @PURE */
219
+ defineHeadPlugin({
220
+ key: "safe",
221
+ hooks: {
222
+ "entries:normalize": (ctx) => {
223
+ if (ctx.entry.options?._safe) {
224
+ ctx.tags = ctx.tags.reduce((acc, tag) => {
225
+ const safeTag = makeTagSafe(tag);
226
+ if (safeTag)
227
+ acc.push(safeTag);
228
+ return acc;
229
+ }, []);
230
+ }
231
+ }
232
+ }
233
+ })
234
+ );
235
+
236
+ export { FlatMetaPlugin as F, SafeInputPlugin as S, defineHeadPlugin as d };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "unhead",
3
3
  "type": "module",
4
- "version": "2.1.9",
4
+ "version": "2.1.11",
5
5
  "description": "Full-stack <head> manager built for any framework.",
6
6
  "author": {
7
7
  "name": "Harlan Wilton",
@@ -1,148 +0,0 @@
1
- import { u as unpackMeta } from './unhead.DQc16pHI.mjs';
2
-
3
- function defineHeadPlugin(plugin) {
4
- return plugin;
5
- }
6
-
7
- const FlatMetaPlugin = /* @__PURE__ */ defineHeadPlugin({
8
- key: "flatMeta",
9
- hooks: {
10
- "entries:normalize": (ctx) => {
11
- const tagsToAdd = [];
12
- ctx.tags = ctx.tags.map((t) => {
13
- if (t.tag !== "_flatMeta") {
14
- return t;
15
- }
16
- tagsToAdd.push(unpackMeta(t.props).map((p) => ({
17
- ...t,
18
- tag: "meta",
19
- props: p
20
- })));
21
- return false;
22
- }).filter(Boolean).concat(...tagsToAdd);
23
- }
24
- }
25
- });
26
-
27
- const WhitelistAttributes = {
28
- htmlAttrs: /* @__PURE__ */ new Set(["class", "style", "lang", "dir"]),
29
- bodyAttrs: /* @__PURE__ */ new Set(["class", "style"]),
30
- meta: /* @__PURE__ */ new Set(["name", "property", "charset", "content", "media"]),
31
- noscript: /* @__PURE__ */ new Set(["textContent"]),
32
- style: /* @__PURE__ */ new Set(["media", "textContent", "nonce", "title", "blocking"]),
33
- script: /* @__PURE__ */ new Set(["type", "textContent", "nonce", "blocking"]),
34
- link: /* @__PURE__ */ new Set(["color", "crossorigin", "fetchpriority", "href", "hreflang", "imagesrcset", "imagesizes", "integrity", "media", "referrerpolicy", "rel", "sizes", "type"])
35
- };
36
- function acceptDataAttrs(value) {
37
- return Object.fromEntries(
38
- Object.entries(value || {}).filter(([key]) => key === "id" || key.startsWith("data-"))
39
- );
40
- }
41
- function makeTagSafe(tag) {
42
- let next = {};
43
- const { tag: type, props: prev } = tag;
44
- switch (type) {
45
- // always safe
46
- case "title":
47
- case "titleTemplate":
48
- case "templateParams":
49
- next = prev;
50
- break;
51
- case "htmlAttrs":
52
- case "bodyAttrs":
53
- WhitelistAttributes[type].forEach((attr) => {
54
- if (prev[attr]) {
55
- next[attr] = prev[attr];
56
- }
57
- });
58
- break;
59
- case "style":
60
- next = acceptDataAttrs(prev);
61
- WhitelistAttributes.style.forEach((key) => {
62
- if (prev[key]) {
63
- next[key] = prev[key];
64
- }
65
- });
66
- break;
67
- // meta is safe, except for http-equiv
68
- case "meta":
69
- WhitelistAttributes.meta.forEach((key) => {
70
- if (prev[key]) {
71
- next[key] = prev[key];
72
- }
73
- });
74
- break;
75
- // link tags we don't allow stylesheets, scripts, preloading, prerendering, prefetching, etc
76
- case "link":
77
- WhitelistAttributes.link.forEach((key) => {
78
- const val = prev[key];
79
- if (!val) {
80
- return;
81
- }
82
- if (key === "rel" && (val === "canonical" || val === "modulepreload" || val === "prerender" || val === "preload" || val === "prefetch")) {
83
- return;
84
- }
85
- if (key === "href") {
86
- if (val.includes("javascript:") || val.includes("data:")) {
87
- return;
88
- }
89
- next[key] = val;
90
- } else if (val) {
91
- next[key] = val;
92
- }
93
- });
94
- if (!next.href && !next.imagesrcset || !next.rel) {
95
- return false;
96
- }
97
- break;
98
- case "noscript":
99
- WhitelistAttributes.noscript.forEach((key) => {
100
- if (prev[key]) {
101
- next[key] = prev[key];
102
- }
103
- });
104
- break;
105
- // we only allow JSON in scripts
106
- case "script":
107
- if (!tag.textContent || !prev.type?.endsWith("json")) {
108
- return false;
109
- }
110
- WhitelistAttributes.script.forEach((s) => {
111
- if (prev[s] === "textContent") {
112
- try {
113
- const jsonVal = typeof prev[s] === "string" ? JSON.parse(prev[s]) : prev[s];
114
- next[s] = JSON.stringify(jsonVal, null, 0);
115
- } catch {
116
- }
117
- } else if (prev[s]) {
118
- next[s] = prev[s];
119
- }
120
- });
121
- break;
122
- }
123
- if (!Object.keys(next).length && !tag.tag.endsWith("Attrs")) {
124
- return false;
125
- }
126
- tag.props = { ...acceptDataAttrs(prev), ...next };
127
- return tag;
128
- }
129
- const SafeInputPlugin = (
130
- /* @PURE */
131
- defineHeadPlugin({
132
- key: "safe",
133
- hooks: {
134
- "entries:normalize": (ctx) => {
135
- if (ctx.entry.options?._safe) {
136
- ctx.tags = ctx.tags.reduce((acc, tag) => {
137
- const safeTag = makeTagSafe(tag);
138
- if (safeTag)
139
- acc.push(safeTag);
140
- return acc;
141
- }, []);
142
- }
143
- }
144
- }
145
- })
146
- );
147
-
148
- export { FlatMetaPlugin as F, SafeInputPlugin as S, defineHeadPlugin as d };