記錄一次.Net框架Bug發(fā)現(xiàn)和提交過程:SmtpClient一處代碼編寫錯(cuò)誤導(dǎo)致異步發(fā)送郵件時(shí)DeliveryFormat配置項(xiàng)無法正確工作,.Net Framework和.Net Core均受影響

問題已經(jīng)發(fā)到了開發(fā)者社區(qū)
https://developercommunity.visualstudio.com/content/problem/381046/smtpclient%E4%B8%80%E5%A4%84%E4%BB%A3%E7%A0%81%E7%BC%96%E5%86%99%E9%94%99%E8%AF%AF%E5%AF%BC%E8%87%B4%E5%BC%82%E6%AD%A5%E5%8F%91%E9%80%81%E9%82%AE%E4%BB%B6%E6%97%B6deliveryformat%E9%85%8D%E7%BD%AE%E9%A1%B9%E6%97%A0%E6%B3%95%E6%AD%A3%E7%A1%AE%E5%B7%A5%E4%BD%9C.html

涉及到的Github倉庫:
https://github.com/xiangyuecn/DKIM-Smtp-csharp

.Net開發(fā)者社區(qū)富文本編輯器太難用了,還是簡書的編輯器好用,然后掘金的版面好看,最后還是喜歡cnblog里面可以修改版面css。

盡瞎說大實(shí)話。
重新碼一份好看的。

出問題的地方

出現(xiàn)問題的函數(shù):

//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) {
    ...
    //注意這個(gè)ServerSupportsEai,這個(gè)位置是allowUnicode參數(shù)
    message.BeginSend(writer, DeliveryMethod != SmtpDeliveryMethod.Network,
                            ServerSupportsEai, new AsyncCallback(SendMessageCallback), result.AsyncState);
    ...
}

ServerSupportsEai所在位置為allowUnicode參數(shù)。SmtpClient中所有涉及到allowUnicode參數(shù)的地方,賦值都為IsUnicodeSupported()返回值。但唯一這一處是例外。

我們看看IsUnicodeSupported函數(shù):

//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) {
    ...
    //如果服務(wù)器支持SMTPUTF8,那么ServerSupportsEai=true
    else if (String.Compare(extension, 0, "SMTPUTF8", 0, 8, StringComparison.OrdinalIgnoreCase) == 0) {
        ((SmtpPooledStream)pooledStream).serverSupportsEai = true;
    }
    ...
}

產(chǎn)生的現(xiàn)象

SendMailCallbackSmtpClient.SendAsyncSendMailAsync會(huì)調(diào)用SendAsync)調(diào)用的,so,異步操作已經(jīng)完全不受我們設(shè)置的DeliveryFormat參數(shù)控制了,UTF-8內(nèi)容(如中文)轉(zhuǎn)不轉(zhuǎn)碼完全看對方郵件服務(wù)器心情?。?!

SmtpClient對象DeliveryFormat屬性賦值為SmtpDeliveryFormat.SevenBit,要求郵件使用 7 位 ASCII 的傳遞格式,并且用異步方法來發(fā)送;本來Subject附件文件名等里面的UTF-8內(nèi)容(如中文)將會(huì)被轉(zhuǎn)碼;但如果郵件服務(wù)器EHLO返回了SMTPUTF8,那么SmtpClient對象將會(huì)將UTF-8內(nèi)容不轉(zhuǎn)碼直接發(fā)送出去!導(dǎo)致發(fā)送出去的數(shù)據(jù)內(nèi)容和預(yù)期的數(shù)據(jù)內(nèi)容不一致?。?!

同步方法Send不受此影響。

解決辦法

SendMailCallback函數(shù)中的ServerSupportsEai應(yīng)該換成統(tǒng)一的IsUnicodeSupported,Bug就解決。

受影響版本

  • .Net Framework 4.5 - 4.7.2(最新版),估計(jì)是全系列
  • .Net Core 看最新版也是受此影響

一次.Net框架Bug的發(fā)現(xiàn)記錄

DKIM簽名功能寫好后測試了很多個(gè)郵箱,都能通過驗(yàn)證。但隔一天測試卻發(fā)現(xiàn)沒有一個(gè)郵箱通過驗(yàn)證,并且下載下來的郵件源碼body部分和本地額外保存的一份有很大出入,表現(xiàn)在郵件主題、附件文件名,本地是Base64編碼,下載下來的是中文漢字。

首先發(fā)現(xiàn)問題的是outlook郵箱,他們家會(huì)告訴你DKIM簽名是否正確,本地直接發(fā)送郵件沒有一個(gè)通過簽名驗(yàn)證的,但通過郵箱服務(wù)器發(fā)送卻都是好的。對比直發(fā)和服務(wù)器發(fā)的郵件源碼區(qū)別,發(fā)現(xiàn)郵箱服務(wù)器的沒有中文,直發(fā)的里面中文的地方全是中文。

看樣子中文部分有問題,然后試著把郵件里面的中文全部換成英文,發(fā)送,又可以了!想了一下昨天測試好像全部是英文,因?yàn)猷]件內(nèi)容寫了一次基本上就不會(huì)改了。

到了這時(shí)候,感覺還以為是outlook服務(wù)器進(jìn)行了什么處理,難道郵箱服務(wù)器發(fā)郵件用的協(xié)議和我們用Smtp協(xié)議發(fā)郵件的協(xié)議有出入?但并沒有找到什么相關(guān)的資料。然后測試了QQ郵箱、網(wǎng)易yeah.net,并且抓了一下包看了一下,發(fā)現(xiàn)切換SmtpClient.DeliveryFormat參數(shù),使用SevenBit(此值為默認(rèn)值)(中文會(huì)被編碼)QQ郵箱沒問題,網(wǎng)易有問題;使用International(中文不編碼)QQ郵箱有問題,網(wǎng)易反倒沒問題。

抓包發(fā)現(xiàn)使用SevenBit時(shí),中文部分給QQ郵箱發(fā)送的是Base64編碼,給網(wǎng)易發(fā)送的是中文內(nèi)容,本地保存的是Base64編碼(和簽名時(shí)使用到的郵件內(nèi)容一致);使用International時(shí)正好相反。簽名的數(shù)據(jù)和發(fā)送的數(shù)據(jù)不一致,導(dǎo)致了不管怎么改這個(gè)參數(shù),都有一個(gè)是錯(cuò)的。

為什么會(huì)這樣?查閱.Net源碼,一路看編碼部分,發(fā)現(xiàn)基本上每個(gè)涉及到字符編碼、發(fā)送的地方都會(huì)傳入allowUnicode參數(shù),所有allowUnicode = SmtpClient.IsUnicodeSupported(),但有唯一的一處例外:

[此處忽略,見上文]

SendMailCallbackSmtpClient.SendAsyncSendMailAsync會(huì)調(diào)用SendAsync)調(diào)用的,so,異步操作已經(jīng)完全不受我們設(shè)置的DeliveryFormat參數(shù)控制了,中文轉(zhuǎn)不轉(zhuǎn)碼完全看對方郵件服務(wù)器心情?。。『瘮?shù)中的ServerSupportsEai應(yīng)該換成統(tǒng)一的IsUnicodeSupported,Bug就解決。

但,我們沒法去改這個(gè)地方,那么上Hook吧,把SmtpClient.ServerSupportsEaiHook一下,如果是SendMailCallback調(diào)用的就return IsUnicodeSupported()。

但,DotNetDetour庫可以Hook String.Length屬性,但沒法HookSmtpClient.ServerSupportsEai屬性,不知道啥原因。最后調(diào)試煩了放棄了。

結(jié)尾使用SmtpClient.Send沒有這種問題,就把異步操作全部換成了同步,代碼還少了不少。Bug修理完畢,給outlook、QQ、網(wǎng)易發(fā)英文、中文郵件都能通過DKIM簽名驗(yàn)證。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容