跳到主要内容

Quoted-Printable(QP)编码到底怎么读:看懂邮件里那些 = 号

用一篇文章讲清 Quoted-Printable 编码解码:为什么老邮件协议只认 7 位 ASCII,=XX 转义怎么运作,一个中文字为什么变三段,以及它和 Base64 各自适合什么数据。

发布于 作者 李雷
#编码 #邮件 #MIME #Quoted-Printable #开发工具

Quoted-Printable(QP)编码到底怎么读:看懂邮件里那些 = 号

第一次盯着一封原始邮件的源码,看到正文里满屏的 =E4=B8=AD=3D,还有一行结尾孤零零挂着个 =,很多人会以为邮件坏了。其实没坏,这是一种叫 Quoted-Printable 的编码,简称 QP,是邮件 MIME 标准(RFC 2045)里规定的一种内容传输编码。它存在的意义,就是让一封大部分是普通字符、只夹了几个特殊字节的邮件,能安全地穿过那些只认 7 位 ASCII 的老系统。

为什么邮件要做这种编码

早期的 SMTP 邮件协议是按 7 位 ASCII 设计的,也就是说,一个字节里只有低 7 位是有效的,最高位会被某些中转服务器丢弃或改写。英文字母、数字、常见标点都落在这 128 个码位里,传起来没问题。可一旦你写中文、写带重音的法文、塞个 emoji,这些字符在 UTF-8 里都需要用到高位字节,直接发出去就可能在半路被改坏。

QP 的解决办法很直接:把那些传不过去的字节,换成几个一定能安全通过的可打印 ASCII 字符来表示,等收件方再换回去。这样邮件正文里绝大多数英文内容保持原样、人眼可读,只有少数特殊字节被转义,信的体积也几乎不变。

=XX 转义规则:一个字节怎么变成 = 后跟两位十六进制

QP 的核心规则只有一句话:凡是落在可打印 ASCII 范围之外的字节,加上等号本身,都写成一个等号接两位大写十六进制。

举个最小的例子。等号这个符号的 ASCII 码是十进制 61,十六进制是 3D,所以一个字面等号在 QP 里写成 =3D。为什么连等号自己都要转义?因为等号被选作了转义的引导符,如果正文里留一个光秃秃的 =,解码器就会把它后面两个字符当成十六进制来读,后面全乱套。

再看行尾的空格。空格码位是 20,正常情况下不必转义,但如果它正好在一行的末尾,就会被写成 =20。原因很现实:不少邮件服务器有个习惯,会把每行结尾的空白删掉,你要是不转义,数据就被悄悄改了。制表符同理,写成 =09

一个真实的输入输出例子

QP 是按字节工作的,不是按字符,这一点用中文最容易看清楚。

中文的「中」字,在 UTF-8 里是三个字节:E4B8AD。所以「中」编码出来是:

=E4=B8=AD

注意,是三段 =XX,不是一段。一个汉字三个字节,就对应三个转义记号。如果换成 emoji,比如 😀,它在 UTF-8 里是四个字节 F0 9F 98 80,编码结果就是 =F0=9F=98=80,四段。等号本身刚才说过,= 编出来是 =3D。把这两个拼一块,「中=」编码后就是 =E4=B8=AD=3D。解码时反过来走一遍,=XX 还原成字节,再把字节流按 UTF-8 重组,「中=」原样回来,不会乱码。

想动手验证,可以直接在 Quoted-Printable 编解码工具 里把「中」打进去编码,再把结果粘回去解码,看它能不能原样往返。

软换行:那个挂在行尾的孤独等号

QP 还规定,编码后的每一行不能超过 76 个字符。一行太长怎么办?它会在 76 列附近插一个软换行:一个 = 作为整行最后一个字符,后面跟着回车换行(CRLF)。

这个行尾的 = 是个信号,告诉解码器「这个断行是我硬加的,不是原文真有的换行」。于是解码器看到它,就把这个 = 和后面的换行一起删掉,把断开的两截重新拼成一个逻辑行。而你自己真正敲下的硬换行,编码方式不一样,会被原样保留。所以下次看到一行末尾挂着个没头没尾的 =,别紧张,那只是折行标记。

QP 和 Base64,到底该用哪个

经常有人把 QP 和 Base64 搞混,觉得都是邮件里的编码,差不多。其实它俩适配的数据正好相反。

QP 不动可打印 ASCII,只转义偶尔冒出来的怪字节。所以一封基本是英文、夹几个重音字符的邮件,用 QP 编码后在原始源码里仍然人眼可读,体积只略微变大。这是它的主场。

Base64 则把一切字节都重新编进一套 64 个字符的字母表,体积会涨大约三分之一,内容也彻底变得不可读。对图片、附件这类几乎每个字节都得转义的二进制数据,Base64 才是对的,因为这时候 QP 把每个字节都写成 =XX,反而比 Base64 还臃肿。

一句经验法则:基本是 ASCII 的文本走 QP,二进制或大量非 ASCII 的数据走 Base64。需要处理后者时,Base64 编码解码工具 更合适;如果只是想看某段文本逐字节的十六进制长什么样,文本转十六进制 也能帮你对照。

我自己踩过的一个坑

我有一次排查线上一个客户名字显示成乱码的问题,数据库里存的就是一串 Caf=C3=A9 这样的东西。一开始以为是数据库编码配错了,折腾了半天。后来把那串原始值丢进 QP 解码,才发现它本身是合法的 Quoted-Printable,C3 A9 解出来正是法文 é,数据没问题。真正的问题在上游:有段代码把已经是 UTF-8 的内容又当成另一种编码处理了一遍,等于编了两次。把可疑值走一遍编码再解码对照,几分钟就定位到是流程的锅,不是数据的锅。从那以后,遇到乱码我第一反应就是先用解码工具看一眼字节,而不是急着改配置。

如果你处理的是 HTML 里的特殊字符乱码,那又是另一套转义规则,可以看 HTML 实体编码工具;思路是相通的,先认清面前这串字符到底是哪种编码,再决定怎么还原。

理解 QP 没什么玄学,记住三件事就够了:它按字节而不是按字符工作、等号是转义引导符、行尾那个孤独的 = 是软换行。把这三点对上,再乱的邮件源码也能读明白。


Made by Toolora · Updated 2026-06-13