從發現.NET Framework中SmtpClient的Bug並拿出解決方案,然后給微軟開發者社區提交Bug開始,總共耗時一個多月,對Bug修復的代碼最終被采納,現已合並到.NET Core Libraries (CoreFX)主線中。
修復記錄https://github.com/dotnet/corefx/commit/94b1f1eae84fd4823cfa2bbdde6fc87c46b57908
雖然對Bug修復實際生效的代碼只有20個字符,但意義重大,這個Bug不遇上一點事沒有,遇上了不對框架開刀是非常棘手的,而且備受折磨也稍微有點難摸得清頭腦。
相關詳情見我的另外一篇博客《記錄一次.Net框架Bug發現和提交過程:.Net Framework和.Net Core均受影響》
問題描述
我們用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代碼傳參數,只能寫死,不管調用方要不要修改返回值,都只能得到修改后的結果,尷尬不尷尬。