功能预览

功能介绍

下载附件时中文文件名乱码,响应头里两个文件名参数为什么要一起写

附件下载时中文文件名乱码,多半是响应头里只写了 filename 或只写了 filename*。规范文档给出的做法是两个一起写:带星号的参数按 UTF-8 百分号编码且优先级更高,不带星号的保留 ASCII 形式兼容旧客户端。本文同时说明目录分隔符为什么要替换。

附件下载 响应头

功能介绍

下载附件时中文文件名显示成乱码,原因通常不在文件本身,而在 Content-Disposition 这个响应头只写了其中一个文件名参数。规范文档的写法很清楚:filename 与 filename* 的差别在于后者采用 RFC 5987 第 3.2 节定义的编码,也就是 UTF-8 百分号编码;当同一个字段值里两个参数同时存在并且都能被理解时,带星号的那个优先;为了兼容性建议两个一起写。

两个参数分别给谁读

带星号的参数解决的是「非 ASCII 字符怎么在头部里安全表达」,它把中文名按 UTF-8 编码后再百分号转义,浏览器解码即可还原;不带星号的参数承担的是兜底,写法上应把非 ASCII 字符替换成 ASCII 近似值,例如把带重音的字母换成普通字母,中文场景可以把原始文件名转成拼音或加日期前缀的英文形式。

只写不带星号的那个,中文在部分浏览器里会被原样透传成乱码;只写带星号的,遇到不认这个参数的旧客户端就会退化成默认名,用户拿到一串没有后缀的名字,反而更麻烦。

参数 编码方式 优先级 承担的角色
filename ASCII 近似值 较低 兼容旧客户端的兜底名
filename* UTF-8 百分号编码 两者都在时被采用 准确还原中文名

为什么建议不要在兜底参数里用百分号转义

文档里有一条容易被踩的提醒:尽量避免在不带星号的 filename 里使用百分号转义序列,因为各浏览器处理不一致,有的会解码、有的不会。也就是说,同一个头部写法在两类浏览器里会得到两个不同的文件名,排查时很容易被误判成程序问题。

正确的分工是:需要转义的内容放在带星号的参数里,那里转义是有规范支持的;兜底参数只放纯 ASCII 的可读名字。

目录分隔符为什么必须替换

文件名里出现斜杠时,处理不当会变成目录穿越。稳妥的做法是在生成响应头之前就把原始名里的路径分隔符统一替换掉,同时把「.」和「..」这类片段单独处理,不要指望下游组件替你做净化。

这一步和上传侧的校验是两件事:上传时决定文件落在哪个目录、以什么后缀保存,下载时决定用户保存对话框里显示什么名字。两层各管一段,任何一层偷懒都会暴露。

站内的附件链路该注意什么

以 AnQiCMS 这类内容管理系统为例,附件有两个入口:一是后台上传,二是通过 /api/import/archive 这类导入接口批量带正文资源。前者要注意响应头拼接的位置通常在下载处理逻辑里,编码要显式做,不要依赖框架默认值;后者批量导入时原始文件名来自压缩包或表格字段,更要在写入前完成分隔符替换与重名处理。

自 v3.6.5 起后台还支持把附件以 base64 编码内容通过接口上传,这条路径同样要保留原始名的处理规则:编码方式解决的是内容传输,文件名净化解决的是显示与落盘,两件事不能互相替代。

改完之后建议至少用三类客户端各下载一次同一个中文附件,确认保存对话框里的名字与后缀都正确,再看响应头里两个参数是否同时存在。

常见问题

问:只写 filename* 能不能省事? 答:新客户端能正确显示,但不支持该参数的旧客户端会退回默认名,规范文档因此建议两个一起写以兼顾兼容性。

问:中文名可以直接写在不带星号的参数里吗? 答:不建议。头部字段按字面透传时非 ASCII 字符容易出问题,这正是带星号参数存在的原因,兜底参数应当只放 ASCII 近似名。

问:文件后缀要不要一起规范化? 答:要。后缀大小写混杂或缺失会同时影响下载显示与内容类型判断,建议在上传阶段就完成后缀白名单校验,下载阶段只做名字还原。