排查邮件标题乱码问题,竟发现企业微信BUG!

今早有用户反馈一个问题:系统发送的公告邮件,在企微客户端打开标题有乱码。我们查了一下各个客户端,邮件系统 Web 端打开是正常的,PC 客户端也是正常的,企微的 PC 客户端也是正常的。因此,问题被归结为客户端兼容性问题,按惯例大概是不了了之。

虽然企微平时问题不少,但作为工作IM,重度使用。按照企微以前的尿性,问题修复很慢很慢的。因此,就算问题不是出在自己系统上,还是想看看到底为什么,能否绕过去。

想知道企微为什么不兼容,就得从网络协议层面出发,看看这份邮件报文长什么样,是否有什么特殊之处。本来以为必须通过 POP 协议请求邮件原文,差点想放弃。好在,邮件系统 Web 端上可以查看原文。

查看原文发现,原来是邮件标题头 Subject 被企微截短了:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Received: ****
Mime-Version: 1.0
Date: Fri, 20 Dec 2024 10:55:07 +0800
From: ****
To: ***
Subject: =?UTF-8?q?=E4=B8=80=E4=BA=8C=E4=B8=89=E5=9B=9B=E4=BA=94=E5=85=AD=E4=B8=83?=
 =?UTF-8?q?=E5=85=AB=E4=B9=9D=E5=8D=81=E4=B8=80=E4=BA=8C=E4=B8=89=E5=9B=9B?=
 =?UTF-8?q?=E4=BA=94=E5=85=AD=E4=B8=83=E5=85=AB=E4=B9=9D=E5=BB=BF=E4=B8=80?=
 =?UTF-8?q?=E4=BA=8C=E4=B8=89=E5=9B=9B=E4=BA=94=E5=85=AD=E4=B8=83=E5=85=AB?=
 =?UTF-8?q?=E4=B9=9D=E4=B8=89=E4=B8=80=E4=BA=8C=E4=B8=89=E5=9B=9B=E4=BA=94?=
 =?UTF-8?q?=E5=85=AD=E4=B8=83=E5=85=AB=E4=B9=9D=E5=9B=9B=E4=B8=80=E4=BA=8C?=
 =?UTF-8?q?=E4=B8=89=E5=9B=9B=E4=BA=94=E5=85=AD=E4=B8=83=E5=85=AB=E4=B9=9D?=
 =?UTF-8?q?=E4=BA=94=E4=B8=80=E4=BA=8C=E4=B8=89=E5=9B=9B=E4=BA=94=E5=85=AD?=
 =?UTF-8?q?=E4=B8=83=E5=85=AB=E4=B9=9D=E5=85=AD=E4=B8=80=E4=BA=8C=E4=B8=89?=
 =?UTF-8?q?=E5=9B=9B=E4=BA=94=E5=85=AD=E4=B8=83=E5=85=AB=E4=B9=9D=E4=B8=83?=
 =?UTF-8?q?=E4=B8=80=E4=BA=8C=E4=B8=89=E5=9B=9B=E4=BA=94=E5=85=AD=E4=B8=83?=
 =?UTF-8?q?=E5=85=AB=E4=B9=9D=E5=85=AB=E4=B8=80=E4=BA=8C=E4=B8=89=E5=9B=9B?=
 =?UTF-8?q?=E4=BA=94=E5=85=AD=E4=B8=83=E5=85=AB=E4=B9=9D=E4=B9=9D=E4=B8=80?=
 =?UTF-8?q?=E4=BA=8C=E4=B8=89=E5=9B=9B=E4=BA=94=E5=85=AD=E4=B8=83=E5=85=AB?=
 =?UTF-8?q?=E4=B9=9D=E7=99=BE?=
Content-Type: text/html; charset=UTF-8
Content-Transfer-Encoding: quoted-printable
X-CM-TRANSID:DFgsCgDxTz6K3GRnfAt2AA--.486S3

企微客户端解析邮件时,是先对 Subject 头进行截短,截到 =83=E5=85 这个位置,然后再解码。由于这一行的结尾标识 ?= 丢掉了,所以就解码不出来,造成乱码。

问了一下 AI ,发现这样一行是一种编码方式:

1
=?UTF-8?q?=E4=B8=80=E4=BA=8C=E4=B8=89=E5=9B=9B=E4=BA=94=E5=85=AD=E4=B8=83?=
  • =? 表示编码的开头;
  • UTF-8 表示字符集( chatset );
  • q 表示 Quoted-Printable 编码,在 RFC2045 中有定义;
  • ?= 之后是编码后的内容;
  • 最后的 ?= 表示编码结束;

不得不说,目前的 AI 小助理还是很好用的,换以前要去看黑压压一大片的 RFC 文档。猜猜我是用了哪个厂的 AI 产品?哈哈哈😄

为了对比测试,我用邮件系统的 Web 客户端发了一封一模一样的邮件,发现企微客户端上也不会乱码。对比邮件原文后发现,Subject 邮件头的写法是不一样的:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
Subject: =?UTF-8?b?5LiA5LqM5LiJ5Zub5LqU5YWt5LiD5YWr5Lmd5Y2B5LiA5LqM5LiJ5Zub5LqU?=
 =?UTF-8?b?5YWt5LiD5YWr5Lmd5bu/5LiA5LqM5LiJ5Zub5LqU5YWt5LiD5YWr5Lmd5LiJ?=
 =?UTF-8?b?5LiA5LqM5LiJ5Zub5LqU5YWt5LiD5YWr5Lmd5Zub5LiA5LqM5LiJ5Zub5LqU?=
 =?UTF-8?b?5YWt5LiD5YWr5Lmd5LqU5LiA5LqM5LiJ5Zub5LqU5YWt5LiD5YWr5Lmd5YWt?=
 =?UTF-8?b?5LiA5LqM5LiJ5Zub5LqU5YWt5LiD5YWr5Lmd5LiD5LiA5LqM5LiJ5Zub5LqU?=
 =?UTF-8?b?5YWt5LiD5YWr5Lmd5YWr5LiA5LqM5LiJ5Zub5LqU5YWt5LiD5YWr5Lmd5Lmd?=
 =?UTF-8?b?5LiA5LqM5LiJ5Zub5LqU5YWt5LiD5YWr5Lmd55m+5LiA5LqM5LiJ5Zub5LqU?=
 =?UTF-8?b?5YWt5LiD5YWr5Lmd5Y2B5LiA5LqM5LiJ5Zub5LqU5YWt5LiD5YWr5Lmd5bu/?=
 =?UTF-8?b?5LiA5LqM5LiJ5Zub5LqU5YWt5LiD5YWr5Lmd5LiJ5LiA5LqM5LiJ5Zub5LqU?=
 =?UTF-8?b?5YWt5LiD5YWr5Lmd5Zub5LiA5LqM5LiJ5Zub5LqU5YWt5LiD5YWr5Lmd5LqU?=
 =?UTF-8?b?5LiA5LqM5LiJ5Zub5LqU5YWt5LiD5YWr5Lmd5YWt5LiA5LqM5LiJ5Zub5LqU?=
 =?UTF-8?b?5YWt5LiD5YWr5Lmd5LiD5LiA5LqM5LiJ5Zub5LqU5YWt5LiD5YWr5Lmd5YWr?=
 =?UTF-8?b?5LiA5LqM5LiJ5Zub5LqU5YWt5LiD5YWr5Lmd5Lmd5LiA5LqM5LiJ5Zub5LqU?=
 =?UTF-8?b?5YWt5LiD5YWr5Lmd55m+?=

每一行的结构还是一样的,只不过编码部分变成了:

  • b 表示 base64 编码;

由此定位到问题是编码方式不同导致的,但为什么 Quoted-Printable 会乱码呢?是不是换成 base64 就不会呢?想回答这两个问题,那得进一步看看这两种编码方式的区别。同样先问问 AI 🤔

Quoted-Printable

Quoted-Printable 是一种内容传输编码,它把一个字节中的非 ASCII 部分变成成 =xx 的形式。其中,xx 是该字节的十六进制形式。 ASCII 部分则原封不变,因此完全兼容 ASCII

例子 UTF-8字节 编码结果
小菜 0xb0 0xe5 0xe8 0x8f 0x9c 0x8f (共6字节) =B0=E5=E8=8f=9C=8F (共18字节)
fasion 0x61 0x66 0x69 0x73 0x6e 0x6f (共6字节) fasion (共6字节)

由此可见,对于非 ASCII 字节,编码后所需的字节数放大了 3 倍。因此,Quoted-Printable 更适用于 ASCII 文本,或者只有极少量非 ASCII 的场景。

base64

base64 是另一种内容传输编码,它对所有输入的字节都一视同仁,统一编码成 64 进制可打印字符。同样,base64 编码结果所需的字节数也会放大,大概是 1.5 倍左右。

编码方式 优点 缺点 适用场景
Quoted-Printable ASCII不会放大 非ASCII放大3倍 大部分为ASCII的场景
base64 ASCII放大1.5倍 非ASCII只放大1.5倍 大部分为非ASCII的场景

至此,案件水落石出:Quoted-Printable 编码方式内容放大倍数很大,更容易达到企微手机端的限制,导致内容被截短而出现乱码现象。因此,我们调整了发件组件的默认编码设置。

但问题并未彻底解决,如果标题很大,理论上还是会触发长度限制。为此,我们测了一下,发现:

  • Quoted-Printable 编码方式,标题达到大约40+汉字,就会触发企微截短 BUG
  • base64 编码方式,标题达到大约100+汉字,也会触发企微截短 BUG

40100 ,跟放大倍数 31.5 ,大致是吻合的。

问题来了:请问企微,这些问题你们想修一修吗?🐶

  • 标题能不能不要截短?
  • 标题就算真的必须截短,能不能解码后再截短?并且在客户端上有相关提示?
  • 还有不少小问题,比如推送企微协作群( AppChat )消息不能 at 人,而群机器却可以?
  • etc

订阅更新,获取更多学习资料,请关注我们的公众号:

【随笔】系列文章首发于公众号【小菜学编程】,敬请关注: