fix(feishu): address sixth-round codex bot review feedback

- docx-table-ops: apply MIN/MAX_COLUMN_WIDTH clamping in the empty-table
  branch so tables with 15+ columns don't produce sub-50 widths that Feishu
  rejects as invalid column_width values.
- docx.ts (data URI branch): validate the ';base64' marker before decoding
  so plain/URL-encoded data URIs are rejected with a clear error; also validate
  the payload against the base64 alphabet (same guard already applied in the
  plain-base64 branch) so malformed inputs fail fast rather than producing
  opaque downstream Feishu errors.
This commit is contained in:
Elarwei
2026-02-28 16:17:03 +08:00
committed by Tak Hoffman
parent b768deec09
commit a3aeeb9f20
2 changed files with 30 additions and 5 deletions

View File

@@ -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);
}

View File

@@ -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,<base64data>`,
);
}
// 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}` };
}