從發現.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代碼傳參數,只能寫死,不管調用方要不要修改返回值,都只能得到修改后的結果,尷尬不尷尬。
