|
1 | 1 | import { fetchWithSsrFGuard } from "../infra/net/fetch-guard.js"; |
2 | 2 | import type { SsrFPolicy } from "../infra/net/ssrf.js"; |
3 | 3 | import { logWarn } from "../logger.js"; |
4 | | -import { estimateBase64DecodedBytes } from "./base64.js"; |
| 4 | +import { canonicalizeBase64, estimateBase64DecodedBytes } from "./base64.js"; |
5 | 5 | import { readResponseWithLimit } from "./read-response-with-limit.js"; |
6 | 6 |
|
7 | 7 | type CanvasModule = typeof import("@napi-rs/canvas"); |
@@ -309,17 +309,21 @@ export async function extractImageContentFromSource( |
309 | 309 | throw new Error("input_image base64 source missing 'data' field"); |
310 | 310 | } |
311 | 311 | rejectOversizedBase64Payload({ data: source.data, maxBytes: limits.maxBytes, label: "Image" }); |
| 312 | + const canonicalData = canonicalizeBase64(source.data); |
| 313 | + if (!canonicalData) { |
| 314 | + throw new Error("input_image base64 source has invalid 'data' field"); |
| 315 | + } |
312 | 316 | const mimeType = normalizeMimeType(source.mediaType) ?? "image/png"; |
313 | 317 | if (!limits.allowedMimes.has(mimeType)) { |
314 | 318 | throw new Error(`Unsupported image MIME type: ${mimeType}`); |
315 | 319 | } |
316 | | - const buffer = Buffer.from(source.data, "base64"); |
| 320 | + const buffer = Buffer.from(canonicalData, "base64"); |
317 | 321 | if (buffer.byteLength > limits.maxBytes) { |
318 | 322 | throw new Error( |
319 | 323 | `Image too large: ${buffer.byteLength} bytes (limit: ${limits.maxBytes} bytes)`, |
320 | 324 | ); |
321 | 325 | } |
322 | | - return { type: "image", data: source.data, mimeType }; |
| 326 | + return { type: "image", data: canonicalData, mimeType }; |
323 | 327 | } |
324 | 328 |
|
325 | 329 | if (source.type === "url" && source.url) { |
@@ -362,10 +366,14 @@ export async function extractFileContentFromSource(params: { |
362 | 366 | throw new Error("input_file base64 source missing 'data' field"); |
363 | 367 | } |
364 | 368 | rejectOversizedBase64Payload({ data: source.data, maxBytes: limits.maxBytes, label: "File" }); |
| 369 | + const canonicalData = canonicalizeBase64(source.data); |
| 370 | + if (!canonicalData) { |
| 371 | + throw new Error("input_file base64 source has invalid 'data' field"); |
| 372 | + } |
365 | 373 | const parsed = parseContentType(source.mediaType); |
366 | 374 | mimeType = parsed.mimeType; |
367 | 375 | charset = parsed.charset; |
368 | | - buffer = Buffer.from(source.data, "base64"); |
| 376 | + buffer = Buffer.from(canonicalData, "base64"); |
369 | 377 | } else if (source.type === "url" && source.url) { |
370 | 378 | if (!limits.allowUrl) { |
371 | 379 | throw new Error("input_file URL sources are disabled by config"); |
|
0 commit comments