记录一次.Net框架Bug发现和提交过程:.Net Framework和.Net Core均受影响
SmtpClient一处代码编写错误导致异步发送邮件时DeliveryFormat配置项无法正确工作,异步操作已经完全不受我们设置属性控制了,UTF-8内容(如中文)转不转码完全看对方邮件服务器心情!
涉及到的Github仓库:
https://github.com/xiangyuecn/DKIM-Smtp-csharp
.Net开发者社区富文本编辑器太难用了,还是简书的编辑器好用,然后掘金的版面好看,最后还是喜欢cnblog里面可以修改版面css。
尽瞎说大实话。
重新码一份好看的。
出问题的地方
出现问题的函数:
//https://source.dot.net/#System.Net.Mail/System/Net/Mail/SmtpClient.cs,966 //https://referencesource.microsoft.com/#System/net/System/Net/mail/SmtpClient.cs,892 void SendMailCallback(IAsyncResult result) { ... //注意这个ServerSupportsEai,这个位置是allowUnicode参数 message.BeginSend(writer, DeliveryMethod != SmtpDeliveryMethod.Network, ServerSupportsEai, new AsyncCallback(SendMessageCallback), result.AsyncState); ... }
ServerSupportsEai所在位置为
allowUnicode参数。
SmtpClient中所有涉及到
allowUnicode参数的地方,赋值都为
IsUnicodeSupported()返回值。但唯一这一处是例外。
我们看看
IsUnicodeSupported函数:
//https://referencesource.microsoft.com/#System/net/System/Net/mail/SmtpClient.cs,382 private bool IsUnicodeSupported() { if (DeliveryMethod == SmtpDeliveryMethod.Network) { //注意看这里的ServerSupportsEai和SmtpDeliveryFormat return (ServerSupportsEai && (DeliveryFormat == SmtpDeliveryFormat.International)); } else { return (DeliveryFormat == SmtpDeliveryFormat.International); } }
DeliveryFormat我们可以赋值,我们来找找
ServerSupportsEai是在哪里取值的:
//https://referencesource.microsoft.com/#System/net/System/Net/mail/smtpconnection.cs,280 internal void ParseExtensions(string[] extensions) { ... //如果服务器支持SMTPUTF8,那么ServerSupportsEai=true else if (String.Compare(extension, 0, "SMTPUTF8", 0, 8, StringComparison.OrdinalIgnoreCase) == 0) { ((SmtpPooledStream)pooledStream).serverSupportsEai = true; } ... }
产生的现象
SendMailCallback是
SmtpClient.SendAsync(
SendMailAsync会调用
SendAsync)调用的,so,异步操作已经完全不受我们设置的
DeliveryFormat参数控制了,UTF-8内容(如中文)转不转码完全看对方邮件服务器心情!!!
SmtpClient对象
DeliveryFormat属性赋值为
SmtpDeliveryFormat.SevenBit,要求邮件使用 7 位 ASCII 的传递格式,并且用异步方法来发送;本来
Subject、
附件文件名等里面的UTF-8内容(如中文)将会被转码;但如果邮件服务器
EHLO返回了
SMTPUTF8,那么
SmtpClient对象将会将UTF-8内容不转码直接发送出去!导致发送出去的数据内容和预期的数据内容不一致!!!
同步方法
Send不受此影响。
解决办法
SendMailCallback函数中的
ServerSupportsEai应该换成统一的
IsUnicodeSupported,Bug就解决。
受影响版本
- .Net Framework 4.5 - 4.7.2(最新版),估计是全系列
- .Net Core 看最新版也是受此影响
一次.Net框架Bug的发现记录
DKIM签名功能写好后测试了很多个邮箱,都能通过验证。但隔一天测试却发现没有一个邮箱通过验证,并且下载下来的邮件源码body部分和本地额外保存的一份有很大出入,表现在邮件主题、附件文件名,本地是Base64编码,下载下来的是中文汉字。
首先发现问题的是outlook邮箱,他们家会告诉你DKIM签名是否正确,本地直接发送邮件没有一个通过签名验证的,但通过邮箱服务器发送却都是好的。对比直发和服务器发的邮件源码区别,发现邮箱服务器的没有中文,直发的里面中文的地方全是中文。
看样子中文部分有问题,然后试着把邮件里面的中文全部换成英文,发送,又可以了!想了一下昨天测试好像全部是英文,因为邮件内容写了一次基本上就不会改了。
到了这时候,感觉还以为是outlook服务器进行了什么处理,难道邮箱服务器发邮件用的协议和我们用Smtp协议发邮件的协议有出入?但并没有找到什么相关的资料。然后测试了QQ邮箱、网易yeah.net,并且抓了一下包看了一下,发现切换
SmtpClient.DeliveryFormat参数,使用
SevenBit(此值为默认值)(中文会被编码)QQ邮箱没问题,网易有问题;使用
International(中文不编码)QQ邮箱有问题,网易反倒没问题。
抓包发现使用
SevenBit时,中文部分给QQ邮箱发送的是Base64编码,给网易发送的是中文内容,本地保存的是Base64编码(和签名时使用到的邮件内容一致);使用
International时正好相反。签名的数据和发送的数据不一致,导致了不管怎么改这个参数,都有一个是错的。
为什么会这样?查阅.Net源码,一路看编码部分,发现基本上每个涉及到字符编码、发送的地方都会传入
allowUnicode参数,所有
allowUnicode
=
SmtpClient.IsUnicodeSupported(),但有唯一的一处例外:
[此处忽略,见上文]
SendMailCallback是
SmtpClient.SendAsync(
SendMailAsync会调用
SendAsync)调用的,so,异步操作已经完全不受我们设置的
DeliveryFormat参数控制了,中文转不转码完全看对方邮件服务器心情!!!函数中的
ServerSupportsEai应该换成统一的
IsUnicodeSupported,Bug就解决。
但,我们没法去改这个地方,那么上Hook吧,把
SmtpClient.ServerSupportsEaiHook一下,如果是
SendMailCallback调用的就
return IsUnicodeSupported()。
但,DotNetDetour库可以Hook
String.Length属性,但没法Hook
SmtpClient.ServerSupportsEai属性,不知道啥原因。最后调试烦了放弃了。
结尾使用
SmtpClient.Send没有这种问题,就把异步操作全部换成了同步,代码还少了不少。Bug修理完毕,给outlook、QQ、网易发英文、中文邮件都能通过DKIM签名验证。
- 记录一次使用terminal进行git管理与提交到Github的过程
- 记录一次软件Bug发生的过程
- 记录一次软件Bug发生的过程
- 记录一次bug解决过程:可维护性和性能优化
- 记录一次bug解决过程:eclipse集成lombok插件
- 1001.A+B Format (20)(思路,bug发现及其修改,提交记录)
- 记录一次bug解决过程:mybatis中$和#的使用
- 记录一次bug解决过程:velocity中获取url中的参数
- 记录一次bug解决过程:git深入学习和JDK8新特性
- 记录一次bug解决过程:else未补全导致数据泄露和代码优化
- 记录一次bug解决过程:resultType和手动开启事务
- 记录一次bug解决过程:eclipse Installed JREs 配置引出的问题
- 记一次线上分布式redis服务BUG发现与修复过程
- 记录一次celery+rabbitmq的bug修复过程
- 记录一次处理weblogic故障的过程
- 取得前一次MySQL操作所影响的记录行数
- 批量更新与逐条更新分块提交的一次实例:修改客户信息表700万条中50万条记录
- 记录一次给网站服务器添加SSL(https)的过程
- 自动添加控件,一次提交多条记录。