luchenwei 发布的文章

input标签有中文邮箱后缀名引起的问题

前提介绍

最近,在公司的某个项目中,测试人员发现了一个很有意思的Bug。当用户的个人资料页面的邮箱联系方式是中文后缀时,例如"mi@测试.com",在Android原生平台上会显示乱码的现象,而在浏览器中显示则正常,结果如下面两张图所示。在另一个同事寻找资料无果的情况下,于是,我开始了一步步分析这个问题的过程。
image.png
Android设备
image.png
浏览器中

分析过程    

因为两个环境中调用的都是同一个接口返回的数据,所以以个人多年的工作经验来看,这个问题可能是编码方面的问题。于是,我列出了以下几种情况:

  1. Web端进行了转码,然后传入到后台服务器中,Android端拿到数据后,由于编码不同,所以出现中文部分出现了乱码。
  2. Web端没有转码,但是后台服务器进行了转码,Android端拿到数据后,由于编码不同,所以出现中文部分出现了乱码。
  3. Web端和后台服务都没有转码,Android端的输入框控件内部进行了一次转码,然后导致中文部分出现了乱码。
  4. Web端和后台服务器都进行了转码,也就是进行了两次转码,然后导致Android端出现中文部分了乱码的原因。

针对以上的几种情况,我分别询问了各位同事和抓包看返回的数据,发现:1、Web端、服务器端都没有进行转码操作。2、在出现乱码的Android端,拿到的数据就是接口返回的原始数据。而且,我也尝试过对几种常见的编码进行转换操作,看看能不能找到问题所在,但是都不是我想要的结果。那,问题究竟在哪?

深入分析的过程

在初次分析无果的情况下,我开始考虑会不会是因为Web端的使用的第三方框架的原因。因为Web端使用了Element UI,有没有可能Element UI对<input />标签进行了二次封装,并进行了转码操作?同时,经Web端的同事提醒,<input />标签的type属性为"email"。于是,我决定,我直接新建了一个新的HTML文件,为了不受其它影响,里面就只有一个<input />标签。

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <input type="email" />
</body>

</html>

然后,在Chrome 浏览器中打开,输入测试字段"mi@测试.com"。结果显示一切正常,中文依然正确显示,似乎没有任何问题。但是本着要打破砂锅问到底的精神,我试着在浏览器中改了一下标签的type值,我尝试着将"email"改成"text"。没想到,问题复现了。

屏幕录制2020-03-1717.15.08.mov (4.17MB)将type="email"改成type="text"以后,浏览器中中文部分会出现乱码,而再改回去以后,显示又一切正常。另一方面,我试着在在Chrome控制台中获取input值,看看原始值到底是什么。结果,也有了惊奇的发现。
image.png    此时input标签的type值依然是"email",但在控制台中打印出来的结果居然是乱码的字符串,现在看来,问题的关键点很有可能就是在这个type="email"上!

为什么是type="email"?

有过Web开发的同学们应该对这个属性是很熟悉的了,至于type的其它类型,就不在本文中阐述了,我们直接看MDN上关于<input type="email">的相关资料,试着去了解一下,当type="email"的时候,浏览器究竟做了什么?(更多细节可以点击这个链接进行查看)。

带有 "email" (电子邮箱)类型标记的输入框元素()能够让用户输入或编辑一个电子邮箱地址,此外,如果指定了multiple属性,用户还可以输入多个电子邮箱地址。在表单提交前,输入框会自动验证输入值是否是一个或多个合法的电子邮箱地址(非空值且符合电子邮箱地址格式). CSS伪标签 :valid 和 :invalid 能够在校验后自动应用。

根据引用MDN网站上的文字的描述,我的一个猜想是,可能是浏览器在做验证的时候进行了转码操作,同时,我又看到网页中的这个相关描述。然后,我点击了DOMString来进行更深入的了解。

image.png

DOMString 是一个UTF-16字符串。由于JavaScript已经使用了这样的字符串,所以DOMString 直接映射到 一个String

看到这两个描述,我第一反应是,难道"mix@测试.com"是被转码成了UTF-16?但是,转码后的字符串并不是常见的UTF-16的格式。所以直觉告诉我,事情没有那么简单,这不是我想要的结果。于是我继续在当前的MDN网页上查看相关资料,在网页的下端,一段内容引起了我的注意。引起我注意的不仅仅是那段正则,还有下面的提示--W3C bug 15489
image.png
打开链接后我发现W3C bug 15489 是2012年由一位叫 Mathias Bynens 外国工程师 提出来的问题,在这个讨论问题的帖子的页面中,我惊喜的发现,这个网页中所提的bug正是我苦苦寻找的问题所在。
image.png
顺着这个结果,我发现关键的线索--Punycode!我立马打开的新的网页,找一个在线Punycode编码的网站。BingGo!
image.png

什么是Punycode?

那么,问题的所以在看来就是这个Punycode了,那什么是Punycode呢?引用百度百科的里的解释:

Punycode(译为:域名代码)是一种表示Unicode码和ASCII码的有限的字符集。例如:“münchen”(德国慕尼黑)会被编码为“mnchen-3ya”。

同时,了解到一个新的知识点--IDNs(国际化域名 Internationalized Domain Names)。早期的DNS是只支持英文域名解析。在IDNs推出以后,为了保证兼容以前的DNS,所以,对IDNs进行Punycode转码,转码后的Punycode就由26个字母+10个数字,还有“-”组成。
顺着这个,我接着往下看,这个页面里发现了一个更有意思和详细的资料。
image.png

最后的总结

所以,经过几乎1个小时的时间,经过各种资料的汇总,这个问题终于被摸清楚了。
在浏览器中type属性为email的input标签能正常显示中文后缀的原因,是因为现代浏览器自动加入中文域名的转码。正如上文所说,是浏览器软件里面主动加入了中文域名自动转码,所以中文后缀才会显示正常。同时,这也就解释了为什么在type="email"的情况下。浏览器控制台中获取的value会依然是乱码后的字符串。
那么,既然知道问题的所在,解决的方法自然也很简单了,通过搜索引擎或者自己写一个关于Punycode转码的方法应该就能将问题顺利解决了。