余读

因为邮件乱码引起的思考:内容编码与传输编码

在一次邮件发送功能测试中,意外发现邮件中的部分内容出现了乱码。查看邮件的源码,发现乱码部分对应的是一些 =3D=0D这样奇怪的符号。
经过一番搜索,我找了这篇文章 Why does my email sometimes show up with funny characters like “=0D” in it?

当你看到像 =3D 这样的东西时,你其实看到的是一种被 “quoted-printable” 编码后的符号。事实上 =3D 就是一个 等于 符。=0D 是一个 回车(CR) 符, =0A 是一个 换行(LF) 符,而 =0D=0A 是一个 回车换行(CRLF) 组合符。CRLFCRLF都被用来在纯文本邮件里标记一行的结束。在 “quoted-printable”编码中,所有字符都可以被表达为一个三字符的等号序列。比如,“=41=73=6B=20=4C=65=6F=21” 解码后是 “Ask Leo!”。

Wait,WTF. 我明明设置了邮件编码为 UTF-8 啊,怎么又变成“quoted-printable”了?原来,邮件编码分为 内容编码传输编码

1
2
Content-Type: text/html; charset="UTF-8"
Content-Transfer-Encoding: quoted-printable

以上是那封乱码邮件的部分邮件头,Content-Type 即内容编码(就是我设置的UTF-8),Content-Transfer-Encoding 则是传输编码,用于在信道间编解码。
内容编码和传输编码的区别:内容编码处理的是字符在 内存文件 中的转换,编码是将内存中的字符保存到文件上,解码则是从文件中读取字符到内存。传输编码是处理字符在信道 发送端接收端 的转换,在发送端编码,接收端解码。

为什么要使用“quoted-printable”编码

因为 CRLF 通常被用来标记一封邮件的结尾,当它作为普通符号出现在内容中时,邮件服务器就无法正确的识别它们(误判为邮件的结尾)。因此,需要将它们通过“quoted-printable”编码转化为没有歧义的三字符等号序列。

为什么会乱码

既然这样,又为什么会乱码呢?原因是“quoted-printable”是一个比较老的编码协议,现在许多新的邮件服务器可能已经不支持了(或者未正确配置),导致未被正确解码。

解决方案

好在传输编码不止“quoted-printable”一种。根据维基百科,还有其他四种方式:7bit、base64、8bit 和 binary
其中:

  • 7bit 适用于邮件内容中只包含标准 ASCII 字符
  • 8bit 适用于邮件内容中包含非标准 ASCII 字符(对 ASCII 字符集进行高位扩展的)
  • binary 适用于任何的二进制序列
  • base64 适用于邮件内容中包含中文、日文等多字节字符

显然 base64 是兼容性最强的,但缺点是编码后的内容会比原来大出 1/3 左右。我们可以根据不同使用场景来选择合适的编码。


参考
The Absolute Minimum Every Software Developer Absolutely, Positively Must Know About Unicode and Character Sets (No Excuses!)