果然老外寫的程式, 就是容易忽略掉亞洲語系的需求... 為了這個 Bug, 足足浪費我兩天的時間 [:@], 既然糾出來了, 當然要講一下... 先來看這段 sample code:
MailMessage mail = new MailMessage();
Encoding chtEnc = Encoding.GetEncoding(950);
mail.From = new MailAddress("peter@chicken-house.net", "吳小皮", chtEnc);
mail.To.Add(new MailAddress("annie@chicken-house.net", "吳小妹", chtEnc));
mail.Subject = "今天天氣很好";
mail.SubjectEncoding = chtEnc;
mail.Body = "blah blah blah...";
(new SmtpClient()).Send(mail);
嗯, 執行的很好, 收的到 MAIL, 編碼也沒問題. 不過沒有回應的 code 總是不大 friendly, 加印一行 message 看看...
MailMessage mail = new MailMessage();
Encoding chtEnc = Encoding.GetEncoding(950);
mail.From = new MailAddress("peter@chicken-house.net", "吳小皮", chtEnc);
mail.To.Add(new MailAddress("annie@chicken-house.net", "吳小妹", chtEnc));
mail.Subject = "今天天氣很好";
mail.SubjectEncoding = chtEnc;
mail.Body = "blah blah blah...";
Console.WriteLine("準備寄信 (From: {0})", mail.From);
(new SmtpClient()).Send(mail);
My God !!! 啥米, 這樣就錯? 而且錯的地方讓我丈二金剛摸不著頭腦... 執行的環境試過 XP, 2003, Vista, 中英文版, 都有 windows update 更新所有的 patch, 除了訊息有中英文版不同之外, 錯誤通通一樣, Exception Dump 如下:
準備寄信(From: "吳小皮")未處理的例外狀況: System.Net.Mail.SmtpException:
傳送郵件失敗。 --->System.FormatException:
標頭值中找到無效的字元。
於 System.Net.Mime.HeaderCollection.Set(String name, String value)
於 System.Net.Mail.Message.PrepareHeaders(Boolean sendEnvelope)
於 System.Net.Mail.Message.Send(BaseWriter writer, Boolean sendEnvelope)
於 System.Net.Mail.SmtpClient.Send(MailMessage message)
-- - 內部例外狀況堆疊追蹤的結尾-- -
於 System.Net.Mail.SmtpClient.Send(MailMessage message)
於 Program.Main()
真是它ㄨㄨㄨ的, 怎麼會這樣? 我實際的情況比較慘, 是加了一堆 Console.WriteLine( ) 後才突然發現有問題, 跟本搞不清楚怎麼回事... 試到最後, 確定加了 Console.WriteLine( ) 會有問題, 問題是, 這行到底有什麼了不起的? 不過就是 mail.Form.ToString() ... [:|]
決定繼續挖下去, 先從 Exception 開始查. 前面有一大堆不好追的就跳過去了, 從 dump 的 call stack, 再用 Refactor 去反組譯 .net 的 assembly, 最後這裡看起來最像是 Exception 的源頭:
class: System.Net.Mime.HeaderCollection
method: public override void Set(string name, string value)
截錄片段 source code:
if (!MimeBasePart.IsAnsi(value, false))
{
throw new FormatException(SR.GetString("InvalidHeaderValue"));
}
怎麼看都沒問題, 追過 IsAnsi( ), 裡面沒啥特別的 code, 就 char 的值小於 0xff 就判定 pass. 所以問題應該出在 value 的值送進來判定時就已經有問題了... 再往上追, value 的源頭是 MailAddress 物件的 .ToEncodedString( ) 來的:
class: System.Net.Mail.MailAddress
internal string ToEncodedString()
{
if (this.fullAddress == null)
{
if ((this.encodedDisplayName != null) && (this.encodedDisplayName != string.Empty))
{
StringBuilder builder = new StringBuilder();
MailBnfHelper.GetDotAtomOrQuotedString(this.encodedDisplayName, builder);
builder.Append(" <");
builder.Append(this.Address);
builder.Append('>');
this.fullAddress = builder.ToString();
}
else
{
this.fullAddress = this.Address;
}
}
return this.fullAddress;
}
然後跟加了就會出問題的 ToString( ) 比對著看:
public override string ToString()
{
if (this.fullAddress == null)
{
if ((this.encodedDisplayName != null) && (this.encodedDisplayName != string.Empty))
{
StringBuilder builder = new StringBuilder();
builder.Append('"');
builder.Append(this.DisplayName);
builder.Append("\" <");
builder.Append(this.Address);
builder.Append('>');
this.fullAddress = builder.ToString();
}
else
{
this.fullAddress = this.Address;
}
}
return this.fullAddress;
}
Ouch, 真是想罵人, 問題就在這裡... 看起來是 Microsoft 工程師為了避開重複作編碼的動作, 每次呼叫 ToEncodedString( ) 及 ToString( ) 時都會去看看 fullAddress 這個 private field 是否有值? 有的話代表之前已經作過編碼了, 就直接撿現成. 問題出在第一次呼叫時, 編碼的動作在 ToString( ) 及 ToEncodedString( ) 各寫了一次 (果然沒有做好 refactoring ... 哈哈), 結果 ToString( ) 的這份 code implementation 是錯的, 跟本沒編碼 [:@] ... 我沒事雞婆在寄信前呼叫 ToString( ) 印 Messabe 算我運氣背, 就碰到這個 Bug.. [:@]
花了近兩天, 不過最後有找到 Bug, 而且還是 Microsoft 的 Bug, 哈哈, 總算證明人不是我殺的 [:D], submit Microsoft 的 Bug 有獎金嘛? 不只 Bug, 連問題都找到了... 有的話通知一下 [:D]