/** * Build a Content-Disposition header value that is safe for non-ASCII * filenames (Czech diacritics are the norm here: "příloha č. 1.pdf"). * * Node's HTTP layer THROWS on header values containing characters above * U+00FF, so the raw filename must never be interpolated into the header * directly — that turns a download of a diacritic-named file into a 500. * Per RFC 5987 / RFC 6266 the real UTF-8 name is carried in the `filename*` * parameter (percent-encoded), while `filename` holds an ASCII-only fallback * for legacy clients. Modern browsers prefer `filename*`. */ export function contentDisposition( type: "inline" | "attachment", fileName: string, ): string { const asciiFallback = fileName .replace(/[^\x20-\x7e]/g, "_") .replace(/["\\]/g, ""); // encodeURIComponent leaves ' ( ) * raw, but they are not RFC 5987 // attr-chars (the apostrophe is even the ext-value delimiter) — strict // parsers would reject the filename* and fall back to the mangled ASCII // name. Percent-encode them like the `content-disposition` npm package. const encoded = encodeURIComponent(fileName).replace( /['()*]/g, (c) => `%${c.charCodeAt(0).toString(16).toUpperCase()}`, ); return `${type}; filename="${asciiFallback}"; filename*=UTF-8''${encoded}`; }