问题描述
我们使用 JavaMail 发送带有 PDF 附件的邮件。当文件名中存在 Unicode 字符时,附件似乎被命名为 UTF 编码名称。进一步检查邮件头后发现文件名 MIME 中的 ?
被删除。例如
预期:
Content-disposition: attachment;
filename="=?utf8?Q?hinzugef=C3=BCgte.pdf?="
获得:
Content-disposition: attachment;
filename="=utf8Qhinzugef=C3=BCgte.pdf="
因此,附件中的文件名是 =utf8Qhinzugef=C3=BCgte.pdf=
,我们无法打开它。
如果我手动修改 .eml 文件并在正确的位置添加 ?
并在 Outlook 中打开它,该文件会按预期以 PDF 格式显示。
此问题已在 Exchange 服务器中报告,我们无法在 Gmail 或 Fake SMTP(在我的机器上,用于测试邮件)中重现它
示例代码:
MimeBodyPart mbp2 = new MimeBodyPart();
String attFileName = file.getName();
String i18nFileName = new String(attFileName.getBytes(),"UTF-8");
String mimeType = mimeMap.getContentType(attFileName);
attStream = new FileInputStream(att);
ByteArrayDataSource bas = new ByteArrayDataSource(attStream,mimeType);
mbp2.setDataHandler(new DataHandler(bas));
mbp2.setFileName(MimeUtility.encodeText(i18nFileName));
mp.addBodyPart(mbp2);
if (attStream != null) {
attStream.close();
}
为什么会这样?任何线索都会非常有帮助
解决方法
这是错误的编码开始。
- 您实施的是 RFC 2047,但这根本不适用于 HTTP。
-
RFC 6266 § 4.3 解释了如何处理该 HTTP 标头的
filename=
参数,然后参考 - RFC 5897,已被关于如何合并非 ASCII 的 RFC 8187 § 3.2.3 废弃。
通用形式是 filename*=UTF-8''Na%C3%AFve%20file.txt
,它与您实施的 RFC 2047 在几个方面有所不同:
-
应该使用
-
filename*=
- 注意参数后面的 星号。这是为了表示扩展符号 - 否则字符集和百分比编码都不会出现。 - 使用扩展符号时,不需要也不允许将值括在
"
引号"
中。 - 同样,前缀
=?
、后缀?=
和?Q?
编码参数都是预期的。从逻辑上讲,它们也没有任何意义,因为只有 引用编码 可用,并且整个非 ASCII 范围是完整的,而不仅仅是某个地方。 -
''
部分用于可选的语言代码 - 对于英语可能是'en'
,但实际上没有人关心这一点。 - 剩下的就很简单了:UTF-8 字符序列的每个字节都被引用编码。 空格也必须用引号编码(说:
%20
)。 - 正确的字符集是
UTF-8
,而utf8
是错误的 - 不要依赖于被那个非官方别名所接受,尽管它偶尔会被容忍。
换句话说:客户的行为是正确的。如果我使用 Thunderbird 68 并按 CTRL+Q 查看电子邮件的来源,或者将电子邮件另存为 .EML 文件然后查看该文件,我有一个 {{3 }} 其中每个附件都有标题
Content-Disposition: inline;
filename*=utf-8''L%20%2D%20qualita%CC%88t.pdf
Content-Type: application/pdf;
x-unix-mode=0644;
name="=?utf-8?Q?L_-_qualita=CC=88t=2Epdf?="
不要混淆,因为您现在看到了这两种变体 - 它们仍然具有不同的目的和不同的上下文。您需要的主要是文件名(尽管提供名称也无妨)。如果您仔细观察,这些值也不同(前者有空格,后者使用下划线 - 但这是发件人的自由决定)。 UTF-8 字符序列 %CC%88
或 =CC=88
是代码点 U+0308 = ̈
COMBINING DIAERESIS(将 a
放在 ä
之前)。>
multipart 解释了 2011 年 HTTP 浏览器对待 RFC 5897 的不同。