nuxt-og-image 7.0.5 → 7.0.7

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.
@@ -2,7 +2,7 @@ import { readFile } from 'node:fs/promises';
2
2
  import { pathToFileURL } from 'node:url';
3
3
  import { resolveModulePath } from 'exsolve';
4
4
  import { dirname, join } from 'pathe';
5
- import { e as extractVariantBaseClasses, r as resolveVariantPrefixes, h as resolveExtractedVars, l as resolveVarsDeep, s as simplifyCss, p as postProcessStyles, m as loadLightningCss, a as extractCssVars, c as extractUniversalVars, d as extractPerClassVars, g as extractVarsFromCss, f as extractClassStyles } from '../shared/nuxt-og-image.C4hc520M.mjs';
5
+ import { e as extractVariantBaseClasses, r as resolveVariantPrefixes, h as resolveExtractedVars, l as resolveVarsDeep, s as simplifyCss, p as postProcessStyles, m as loadLightningCss, a as extractCssVars, c as extractUniversalVars, d as extractPerClassVars, g as extractVarsFromCss, f as extractClassStyles } from '../shared/nuxt-og-image.CS4ArVpM.mjs';
6
6
  import 'node:fs';
7
7
  import 'node:module';
8
8
  import '@nuxt/kit';
@@ -1,8 +1,8 @@
1
1
  import { readFile } from 'node:fs/promises';
2
2
  import { defu } from 'defu';
3
3
  import { logger } from '../../dist/runtime/logger.js';
4
- import { e as extractVariantBaseClasses, s as simplifyCss, a as extractCssVars, b as extractPropertyInitialValues, c as extractUniversalVars, d as extractPerClassVars, f as extractClassStyles, p as postProcessStyles, r as resolveVariantPrefixes, i as importOptionalPeer, g as extractVarsFromCss, h as resolveExtractedVars } from '../shared/nuxt-og-image.C4hc520M.mjs';
5
- export { j as importResolvedModule, k as resolveOptionalModulePath } from '../shared/nuxt-og-image.C4hc520M.mjs';
4
+ import { e as extractVariantBaseClasses, s as simplifyCss, a as extractCssVars, b as extractPropertyInitialValues, c as extractUniversalVars, d as extractPerClassVars, f as extractClassStyles, p as postProcessStyles, r as resolveVariantPrefixes, i as importOptionalPeer, g as extractVarsFromCss, h as resolveExtractedVars } from '../shared/nuxt-og-image.CS4ArVpM.mjs';
5
+ export { j as importResolvedModule, k as resolveOptionalModulePath } from '../shared/nuxt-og-image.CS4ArVpM.mjs';
6
6
  import 'node:fs';
7
7
  import 'node:module';
8
8
  import 'node:url';
package/dist/module.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "nuxt": "^4.6.1 || ^5.0.0-0"
5
5
  },
6
6
  "configKey": "ogImage",
7
- "version": "7.0.5",
7
+ "version": "7.0.7",
8
8
  "builder": {
9
9
  "@nuxt/module-builder": "1.0.3",
10
10
  "unbuild": "3.6.1"
package/dist/module.mjs CHANGED
@@ -7,7 +7,7 @@ import 'defu';
7
7
  import 'fnv1a-64';
8
8
  import 'nuxtseo-shared/kit';
9
9
  import 'pathe';
10
- export { n as default } from './shared/nuxt-og-image.C4hc520M.mjs';
10
+ export { n as default } from './shared/nuxt-og-image.CS4ArVpM.mjs';
11
11
  import '../dist/runtime/logger.js';
12
12
  import '../dist/runtime/shared/publish.js';
13
13
  import 'node:crypto';
@@ -6,7 +6,7 @@ import { fetchLocalAsset } from "../../../util/fetchLocalAsset.js";
6
6
  import { getFetchTimeout } from "../../../util/fetchTimeout.js";
7
7
  import { logger } from "../../../util/logger.js";
8
8
  import { getOgImageOrigin } from "../../../util/origin.js";
9
- import { fetchWithRedirectValidation, isBlockedUrl, parseSameOriginPath } from "../../../util/ssrf.js";
9
+ import { fetchWithRedirectValidation, isBlockedUrl, parseSameOriginPath, resolveSameOriginUrl } from "../../../util/ssrf.js";
10
10
  import { getImageDimensions } from "../../utils/image-detector.js";
11
11
  import { defineTransformer } from "../plugins.js";
12
12
  const RE_URL_LEADING = /^url\(['"]?/;
@@ -51,6 +51,8 @@ async function doResolveSrcToBuffer(src, kind, { e, publicStoragePath, runtimeCo
51
51
  logger.debug(`[og-image] ${kind} fetch failed (${url}): ${err?.message || err}`);
52
52
  };
53
53
  if (src.startsWith("/")) {
54
+ if (!resolveSameOriginUrl(src, getOgImageOrigin(e)))
55
+ return { blocked: true };
54
56
  let buffer2;
55
57
  if (import.meta.prerender || import.meta.dev) {
56
58
  const srcWithoutBase = src.replace(runtimeConfig.app.baseURL, "/");
@@ -136,7 +138,7 @@ export default defineTransformer([
136
138
  applyImageDimensions(node, result.buffer);
137
139
  return;
138
140
  }
139
- if (isRelative) {
141
+ if (isRelative && import.meta.dev) {
140
142
  node.props.src = withBase(src, `${getOgImageOrigin(ctx.e)}`);
141
143
  return;
142
144
  }
@@ -161,7 +163,7 @@ export default defineTransformer([
161
163
  node.props.style.backgroundImage = `url(${toBufferSourceAsBase64(result.buffer)})`;
162
164
  return;
163
165
  }
164
- if (!import.meta.dev && !src.startsWith("/"))
166
+ if (!import.meta.dev)
165
167
  delete node.props.style.backgroundImage;
166
168
  }
167
169
  }
@@ -16,6 +16,7 @@ export type FontBinaryResult = {
16
16
  export declare function isWoff2Binary(source: BufferSource): boolean;
17
17
  /**
18
18
  * Parse loaded font bytes into a binary that every Takumi build accepts.
19
- * TTF, OTF, font collections and WOFF2 pass through; WOFF inflates to TTF or OTF.
19
+ * Check TTF, OTF and collection table bounds, plus WOFF2 header and file length.
20
+ * Accepted bytes pass through; WOFF inflates to TTF or OTF.
20
21
  */
21
22
  export declare function parseFontBinary(source: BufferSource): Promise<FontBinaryResult>;
@@ -28,6 +28,61 @@ function readSignature(data) {
28
28
  return void 0;
29
29
  return new DataView(data.buffer, data.byteOffset, data.byteLength).getUint32(0);
30
30
  }
31
+ function sfntError(data, offset = 0) {
32
+ if (offset + 12 > data.byteLength)
33
+ return "the SFNT header is truncated";
34
+ const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
35
+ const signature = view.getUint32(offset);
36
+ if (signature !== SIGNATURES.truetype && signature !== SIGNATURES.appleTrueType && signature !== SIGNATURES.openType)
37
+ return "the SFNT signature is invalid";
38
+ const numTables = view.getUint16(offset + 4);
39
+ if (numTables === 0)
40
+ return "the SFNT file has no tables";
41
+ if (offset + 12 + numTables * SFNT_TABLE_RECORD_SIZE > data.byteLength)
42
+ return "the SFNT table directory is truncated";
43
+ for (let index = 0; index < numTables; index++) {
44
+ const record = offset + 12 + index * SFNT_TABLE_RECORD_SIZE;
45
+ if (view.getUint32(record + 8) + view.getUint32(record + 12) > data.byteLength)
46
+ return "an SFNT table runs past the end of the file";
47
+ }
48
+ }
49
+ function collectionError(data) {
50
+ if (data.byteLength < 12)
51
+ return "the font collection header is truncated";
52
+ const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
53
+ const version = view.getUint32(4);
54
+ if (version !== 65536 && version !== 131072)
55
+ return "the font collection version is invalid";
56
+ const numFonts = view.getUint32(8);
57
+ if (numFonts === 0)
58
+ return "the font collection has no fonts";
59
+ const headerSize = 12 + numFonts * 4 + (version === 131072 ? 12 : 0);
60
+ if (headerSize > data.byteLength)
61
+ return "the font collection directory is truncated";
62
+ for (let index = 0; index < numFonts; index++) {
63
+ const offset = view.getUint32(12 + index * 4);
64
+ if (offset < headerSize)
65
+ return "a font collection offset overlaps its header";
66
+ const error = sfntError(data, offset);
67
+ if (error)
68
+ return error;
69
+ }
70
+ }
71
+ function woff2Error(data) {
72
+ if (data.byteLength < 48)
73
+ return "the WOFF2 header is truncated";
74
+ const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
75
+ if (view.getUint32(8) !== data.byteLength)
76
+ return "the WOFF2 length does not match the file";
77
+ if (view.getUint16(12) === 0)
78
+ return "the WOFF2 file has no tables";
79
+ const compressedSize = view.getUint32(20);
80
+ if (compressedSize === 0 || compressedSize > data.byteLength - 48)
81
+ return "the WOFF2 compressed data is missing or truncated";
82
+ }
83
+ function checked(data, error) {
84
+ return error ? { _tag: "Err", reason: error } : { _tag: "Ok", data };
85
+ }
31
86
  async function inflate(data, size) {
32
87
  const output = new Uint8Array(size);
33
88
  const reader = new Blob([data]).stream().pipeThrough(new DecompressionStream("deflate")).getReader();
@@ -112,12 +167,14 @@ export async function parseFontBinary(source) {
112
167
  case SIGNATURES.truetype:
113
168
  case SIGNATURES.appleTrueType:
114
169
  case SIGNATURES.openType:
170
+ return checked(data, sfntError(data));
115
171
  case SIGNATURES.collection:
172
+ return checked(data, collectionError(data));
116
173
  case SIGNATURES.woff2:
117
- return { _tag: "Ok", data };
174
+ return checked(data, woff2Error(data));
118
175
  case SIGNATURES.woff:
119
176
  return decodeWoff(data).then(
120
- (sfnt) => ({ _tag: "Ok", data: sfnt }),
177
+ (sfnt) => checked(sfnt, sfntError(sfnt)),
121
178
  (error) => ({ _tag: "Err", reason: `it is a WOFF file that could not be decoded: ${error.message}` })
122
179
  );
123
180
  default:
@@ -10,7 +10,7 @@ export async function tryCloudflareAssetsFetch(event, path, signal) {
10
10
  return;
11
11
  const origin = getRequestURL(event).origin;
12
12
  const url = new URL(path, origin).href;
13
- const res = await assets.fetch(url, signal ? { signal } : void 0).catch(() => {
13
+ const res = await assets.fetch(url, { signal, redirect: "manual" }).catch(() => {
14
14
  return null;
15
15
  });
16
16
  if (!res || !res.ok)
@@ -15,7 +15,7 @@ export interface FetchLocalAssetOptions {
15
15
  * Resolve a same-origin asset path to bytes, trying (in order):
16
16
  * 1. Cloudflare Workers ASSETS binding
17
17
  * 2. Nitro localFetch through the event-aware bridge, which resolves dynamic routes too
18
- * 3. External `$fetch` to the Nitro origin (opt-in)
18
+ * 3. External fetch with redirect validation (opt-in)
19
19
  *
20
20
  * All steps share a single `AbortSignal.timeout(fetchTimeout)` so a broken
21
21
  * URL can't burn N× the configured budget.
@@ -1,8 +1,13 @@
1
- import { $fetch } from "ofetch";
2
1
  import { fetchWithEvent } from "#nuxtseo/nitro";
3
2
  import { tryCloudflareAssetsFetch } from "./cloudflareAssets.js";
4
3
  import { getOgImageOrigin } from "./origin.js";
4
+ import { fetchWithRedirectValidation, resolveSameOriginUrl } from "./ssrf.js";
5
5
  export async function fetchLocalAsset(event, path, options) {
6
+ const absolute = resolveSameOriginUrl(path, getOgImageOrigin(event));
7
+ if (!path.startsWith("/") || !absolute)
8
+ return;
9
+ const url = new URL(absolute);
10
+ path = `${url.pathname}${url.search}`;
6
11
  const { fetchTimeout, headers, includeExternalFallback = false, onStepFailure } = options;
7
12
  const deadline = AbortSignal.timeout(fetchTimeout);
8
13
  let expired = false;
@@ -21,23 +26,24 @@ export async function fetchLocalAsset(event, path, options) {
21
26
  return result;
22
27
  result = await race(fetchWithEvent(event, path, {
23
28
  responseType: "arrayBuffer",
29
+ redirect: "manual",
24
30
  signal: deadline,
25
31
  timeout: fetchTimeout,
26
- headers
32
+ headers,
33
+ onResponse({ response }) {
34
+ if (!response.ok)
35
+ throw new Error(response.statusText || String(response.status));
36
+ }
27
37
  }).catch((err) => {
28
38
  onStepFailure?.(path, err);
29
39
  return void 0;
30
40
  }));
31
41
  if (result || expired || !includeExternalFallback)
32
42
  return result;
33
- const absolute = `${getOgImageOrigin(event)}${path}`;
34
- return await race($fetch(absolute, {
35
- responseType: "arrayBuffer",
43
+ return await race(fetchWithRedirectValidation(absolute, {
36
44
  signal: deadline,
37
45
  timeout: fetchTimeout,
38
- headers
39
- }).catch((err) => {
40
- onStepFailure?.(absolute, err);
41
- return void 0;
42
- }));
46
+ headers,
47
+ trustedHost: url.host
48
+ }).then((buffer) => buffer ?? void 0));
43
49
  }
@@ -5212,12 +5212,18 @@ _ogImagePrerenderPaths?: Map<string, string>`
5212
5212
  /// <reference path="./og-image-bindings.d.ts" />
5213
5213
  import type { OgImageIconsData, OgImageOptions, OgImageRenderEventContext, OgImageRuntimeConfig, VNode } from '${typesPath}'
5214
5214
 
5215
+ declare module '@nuxt/schema' {
5216
+ interface AppRouteRulesExtensions {
5217
+ ogImage?: false | OgImageOptions & Record<string, any>
5218
+ }
5219
+ }
5220
+
5215
5221
  ${nitroAugmentations}
5216
5222
 
5217
5223
  export {}
5218
5224
  `;
5219
5225
  }
5220
- }, { nitro: true, nuxt: true });
5226
+ }, { nitro: true, node: true, nuxt: true });
5221
5227
  }
5222
5228
 
5223
5229
  const RE_INLINE_CONFIG = /const\s+inlineConfig\s*=\s*(\{[\s\S]*?\n\})/;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "nuxt-og-image",
3
3
  "type": "module",
4
- "version": "7.0.5",
4
+ "version": "7.0.7",
5
5
  "description": "Enlightened OG Image generation for Nuxt.",
6
6
  "author": {
7
7
  "website": "https://harlanzw.com",
@@ -130,7 +130,7 @@
130
130
  "magicast": "^0.5.5",
131
131
  "mocked-exports": "^0.1.1",
132
132
  "nuxt-site-config": "^5.0.0",
133
- "nuxtseo-shared": "^6.0.0",
133
+ "nuxtseo-shared": "^6.0.3",
134
134
  "nypm": "^0.6.10",
135
135
  "object-identity": "^0.2.3",
136
136
  "ofetch": "^1.5.1",