非典型 JavaMail 郵件附件名亂碼問題
阿新 • • 發佈:2019-01-26
------------ 記一個JavaMail
附件亂碼的問題
說到亂碼,很多人都遇到過,“哎呀,你這個編碼是不是UTF-8!”,“你這個會不會作業系統不一致導致的?”,“肯定是兩邊編碼不一致”。不過我們今天說的問題,還真不是這個問題導致的。
問題
用JavaMail 發郵件,帶上附件,閃電郵客戶端收到後,附件名有時亂碼,有時非亂碼。檢視Java端程式碼:
String name = MimeUtility.encodeText(name, null) + ".xlsx";
messageBodyPart.setFileName(name);
貌似已經做過編碼轉換了。
令人感到奇怪的是,稍微改變附件名,亂碼就消失了。
發現
收集各種資訊的時候,突然發現 Ubuntu的小夥伴 在 ThunderBird下收郵件表示沒有異常,Mac黨表示自帶的郵件工具收件也沒有問題。那是不是作業系統字元編碼問題呢? 可是遺憾的是,在網頁版上,依舊是亂碼。 這時候初步懷疑是郵件系統不相容的問題了,來看郵件原始碼:
Content-Type: application/octet-stream;
name*0="=?utf-8?B?5rWL6K+V5qCH6aKYLS0tMDAx5oiR6KaB5LiK?==?utf-8?B?5";
name*1 ="a2mQUJDREXvvIzlkKzor7TopoHotrPlpJ/plb8=?=.xlsx"
Content-Transfer-Encoding: base64
Content-Disposition: attachment;
filename*0="?utf-8?B?5rWL6K+V5qCH6aKYLS0tMDAx5oiR6KaB5LiK?==?utf-8?B?5";
filename*1="a2mQUJDREXvvIzlkKzor7TopoHotrPlpJ/plb8=?=.xlsx"
這串就是 有些系統亂碼有些系統 正常顯示的 郵件原始碼。。
對比在郵件客戶端上的非亂碼郵件:
Content-Type: application/octet-stream; name="=?utf-8?B?5rWL6K+V?=.xlsx"
Content-Transfer-Encoding: base64
Content-Disposition: attachment; filename="=?utf-8?B?5rWL6K+V?=.xlsx"
filename*0,filename*1 和 filename 的區別,猜測也許就是郵件客戶端不支援這種filename*0,filename*1 協議導致的問題。
分析
有了以上的想法,就開始來看原始碼。
MimeBodyPart 這個類中的 setFileName 方法 用到一個 ParameterList 在ParameterList 的 toString 類中找到下面一段:
if (v instanceof MultiValue) {
// ....
ns = name + i + "*";
//...
}
} else if (v instanceof Value) {
/// ...
} else {
if (value.length() > 60 &&
splitLongParameters && encodeParameters) {
int seg = 0;
name += "*";
/// ....
}
這個類在郵件附件屬於 MultiValue 會把 名字用name + i 隔開 ,在名字大於 60個字元的時候也會主動截斷,這也就是 javamail 中 附件的命名規則,名字太長會被截斷~~!
中文在base64 加密後,超過60個字元那是妥妥的有可能。這種截斷檔名的模式在某些客戶端,並不能很好的支援。
解決
解決就很容易了, 程式碼裡有 splitLongParameters 這個引數, 觀察了下 對應於一個環境變數,如果想不截斷檔名,只要在程式執行之初加上:
System.setProperty("mail.mime.splitlongparameters","false");
就可以了。測試,解決。。。
另外由於發現了這個問題,google到了 java mail 的完整配置,
總結
郵件系統的不相容是導致這個錯誤的根本原因,還真不是編碼問題,所以有的時候看問題還不能那麼想當然。這個測試未必能測出,畢竟要滿足名字足夠長這個條件。在這裡分享這個問題,以免JAVA黨同學重複踩坑