问题描述
我们用SmtpClient
的SendAsync
、 SendMailAsync
异步方法发送邮件,并且要求使用DeliveryFormat
=
SmtpDeliveryFormat.SevenBit
格式来编码中文内容,本来预期是邮件内容中带中文的Subject
、Attachments file name
都会进行Base64编码。
但实际结果是:如果邮件服务器支持SMTPUTF8
扩展,那么异步发送SevenBit
邮件并不会进行Base64编码,同步方法没有此问题。
问题根源
原因是在SmtpClient.SendMailCallback
方法中,message.BeginSend
allowUnicode
参数直接使用的ServerSupportsEai
,而不是统一的IsUnicodeSupported()
。
private void SendMailCallback(IAsyncResult result) { ...... _message.BeginSend(_writer, DeliveryMethod != SmtpDeliveryMethod.Network, ServerSupportsEai, new AsyncCallback(SendMessageCallback), result.AsyncState); ...... }
把ServerSupportsEai
改成IsUnicodeSupported()
问题解决。就这20个字符的改动~
另外附对现有带Bug的.Net框架修复方法
比如使用的.NET Framework 4.7.2,纯天然原生自带此Bug,我们可以用我们的代码修复它。
最开始测试时以为此方法无效,没想到是Hook错了地方,换到最深层次调用地方,一抓一个准。
使用DotNetDetour库对.Net框架内方法进行Hook,找出SmtpClient.ServerSupportsEai
最结果最终是从SmtpConnection.ServerSupportsEai
得来的,也许是C#编译后把整个调用过程都优化掉了,变成了取值的地方直接调用的SmtpConnection
中的方法,导致Hook前面的方法都是不会被执行,Hook SmtpConnection.ServerSupportsEai
一抓一个准。
附上Hook代码:
public class Hook : IMethodMonitor { public bool ServerSupportsEai { [Monitor("System.Net.Mail", "SmtpConnection")] get { Console.WriteLine("Hook"); return !true?org():false;//什么情况下要Hook? AsyncLocal和CallContext上下文为什么在这里传不进来? } } [Original] public bool org() { return false; } }
另外引出了另外一个折磨人Bug,异步环境下,ServerSupportsEai
的调用栈中上下文怎么会丢失?难道哪里使用了类似ThreadPool.UnsafeXXX
这种效果?我们没法通过CallContext
(AsyncLocal
)给Hook代码传参数,只能写死,不管调用方要不要修改返回值,都只能得到修改后的结果,尴尬不尴尬。
作者:高坚果兄弟
链接:https://www.jianshu.com/p/b3dd5521f586