export const MAX_SAFE_NAME_BYTES = 120; const FALLBACK = "attachment"; const WINDOWS_RESERVED = /^(con|prn|aux|nul|com[0-9]|lpt[0-9])(\..*)?$/i; function truncateUtf8(text: string, maxBytes: number): string { let out = ""; for (const ch of text) { if (Buffer.byteLength(out + ch, "utf8") > maxBytes) break; out += ch; } return out; } /** * Library-safe single path segment: no directories, control or reserved * characters, leading dots, or unbounded length. Extension is preserved when * truncating; a sniffed extension is appended when the name has none. */ export function safeFileName(raw: string, sniffedExtension: string | null = null): string { const basename = raw.normalize("NFKC").split(/[/\\]/).pop() ?? ""; let name = basename .replace(/[\p{Cc}\p{Cf}]/gu, "") .replace(/[<>:"|?*#%{}^~[\]`]/g, "_") .replace(/\s+/g, " ") .replace(/_+/g, "_") .trim() .replace(/^[.\s_-]+/, "") .replace(/[.\s]+$/, ""); if (name === "") name = FALLBACK; if (WINDOWS_RESERVED.test(name)) name = `_${name}`; const dot = name.lastIndexOf("."); let stem = dot > 0 ? name.slice(0, dot) : name; let ext = dot > 0 ? name.slice(dot + 1).toLowerCase() : ""; if (ext === "" && sniffedExtension !== null) ext = sniffedExtension; if (Buffer.byteLength(ext, "utf8") > 16) { stem = name; ext = sniffedExtension ?? ""; } const suffix = ext === "" ? "" : `.${ext}`; stem = truncateUtf8(stem, MAX_SAFE_NAME_BYTES - Buffer.byteLength(suffix, "utf8")).trim(); if (stem === "") stem = FALLBACK; return `${stem}${suffix}`; }