diff --git a/extensions/feishu/src/docx-table-ops.ts b/extensions/feishu/src/docx-table-ops.ts index 27c9c2caaa1..83a3cd5b017 100644 --- a/extensions/feishu/src/docx-table-ops.ts +++ b/extensions/feishu/src/docx-table-ops.ts @@ -105,10 +105,15 @@ export function calculateAdaptiveColumnWidths( } } - // Handle empty table + // Handle empty table: distribute width equally, clamped to [MIN, MAX] so + // wide tables (e.g. 15+ columns) don't produce sub-50 widths that Feishu + // rejects as invalid column_width values. const totalLength = maxLengths.reduce((a, b) => a + b, 0); if (totalLength === 0) { - const equalWidth = Math.floor(totalWidth / column_size); + const equalWidth = Math.max( + MIN_COLUMN_WIDTH, + Math.min(MAX_COLUMN_WIDTH, Math.floor(totalWidth / column_size)), + ); return new Array(column_size).fill(equalWidth); } diff --git a/extensions/feishu/src/docx.ts b/extensions/feishu/src/docx.ts index 4cc16ef6feb..eecbcbbe314 100644 --- a/extensions/feishu/src/docx.ts +++ b/extensions/feishu/src/docx.ts @@ -410,18 +410,38 @@ async function resolveUploadInput( // data URI: data:image/png;base64,xxxx if (imageInput?.startsWith("data:")) { - const [header, data] = imageInput.split(","); + const commaIdx = imageInput.indexOf(","); + if (commaIdx === -1) { + throw new Error("Invalid data URI: missing comma separator."); + } + const header = imageInput.slice(0, commaIdx); + const data = imageInput.slice(commaIdx + 1); + // Only base64-encoded data URIs are supported; reject plain/URL-encoded ones. + if (!header.includes(";base64")) { + throw new Error( + `Invalid data URI: missing ';base64' marker. ` + + `Expected format: data:image/png;base64,`, + ); + } + // Validate the payload is actually base64 before decoding; Node's decoder + // is permissive and would silently accept garbage bytes otherwise. + const trimmedData = data.trim(); + if (trimmedData.length === 0 || !/^[A-Za-z0-9+/]+=*$/.test(trimmedData)) { + throw new Error( + `Invalid data URI: base64 payload contains characters outside the standard alphabet.`, + ); + } const mimeMatch = header.match(/data:([^;]+)/); const ext = mimeMatch?.[1]?.split("/")[1] ?? "png"; // Estimate decoded byte count from base64 length BEFORE allocating the // full buffer to avoid spiking memory on oversized payloads. - const estimatedBytes = Math.ceil((data.length * 3) / 4); + const estimatedBytes = Math.ceil((trimmedData.length * 3) / 4); if (estimatedBytes > maxBytes) { throw new Error( `Image data URI exceeds limit: estimated ${estimatedBytes} bytes > ${maxBytes} bytes`, ); } - const buffer = Buffer.from(data, "base64"); + const buffer = Buffer.from(trimmedData, "base64"); return { buffer, fileName: explicitFileName ?? `image.${ext}` }; }