10

五倍券官網當機亂碼為什麼「滚」出來?

 2 years ago
source link: https://blog.darkthread.net/blog/browser-utf8-big5-currupt/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

五倍券官網當機亂碼為什麼「滚」出來?

2021-09-26 12:50 PM 4 3,585

前幾天的五倍券開搶,九點一到不意外地系統當機了。話說,幾百萬人同時殺進來,系統撐不住很正常,完全沒事才叫意外,能很快復原就算設計者功力不差了。依據事前事後的網路評論,感覺會設計高流量系統的鄉民路人真不少,不管有沒寫過程式都能說出一番道理,分流啦、動態擴充伺服器、預先做好壓測、不能用 ASP.NET 要用 GO 語言寫... 以我對系統設計的淺薄了解,每項說法都有道理(除了 ASP.NET 不行得用 GO 之外,你知道工程師賴以維生的 StackOverflow 是用 ASP.NET 寫的嗎? 就不是南拳北拳的問題呀),但絕對不是要做好某一項就能安全下莊,而是每個細節都得考慮周到,否則系統通常會在最脆弱的一環爆炸給你看,提醒你哪裡還沒做好。

設計高流量系統不是我的專業,但五倍券當機時噴出的亂碼卻引起我的好奇。當時出現的錯誤訊息因中文編碼解析錯誤出現「甇斗?滚?嗵?⊥?蓥蝙?鍂??」,因其中夾雜「滚」字還意外引發討論成為話題 (冏!五倍券無法綁定 亂碼藏字叫你「滾」),依經驗是多是套錯 UTF8、BIG5 編碼造成,之前研究過,例如:中文亂碼"蕞蕞蕞蕞"是怎麼來的?中文亂碼「嚙踝蕭嚙踝蕭」是怎麼來的?,而依據報導,有網友切換編碼將 BIG5 改成 UTF8 即可看到原本的錯誤訊息是 IIS 過度忙碌時噴出的 HTTP 503 訊息「此服務無法使用。」,故推測狀況為系統回傳的是 UTF8 編碼,但瀏覽器誤判為 BIG5 變成亂碼,寫了程式驗證將「此服務無法使用。」用 UTF8 轉成 byte[] 再用 BIG5 轉回 String,原本預期會看到「甇斗?滚?嗵?⊥?蓥蝙?鍂??」秒結案,結果卻不然:

是會變成亂碼,但我得到的是「甇斗?? 瘜 蝙?具?」,根本不會出現「滚」,而跟「甇斗?滚?嗵?⊥?蓥蝙?鍂??」只有「甇斗蝙」三個字相同,開啟線上中文編碼解析進一步調查,「滚嗵蓥鍂」四個字根本不是 BIG5 編碼支援的字元,算是 Unicode 難字,在簡體中文 GB2312 編碼才有對映,這到底是怎麼一回事?

註:擷圖之 UTF16 編碼有誤,已於 2.0.1 版修正,感謝讀者 anthonywang 指正。

為了驗證我寫了簡單網頁重現問題:

<html>
	<head>
	<meta http-equiv="Content-Type" content="text/html; charset=Big5">
	</head>
<body>此服務無法使用。</body>
</html>

檔案存成 UTF8,但 meta 宣告故意寫成 charset=Big5,分別用 IE、Edge、Chrome、Firefox 開啟,發現有趣的事:

一樣是 UTF8 誤判成 BIG5,IE、Edge/Chrome(都是 Chromium 核心)、Firefox 出現的亂碼不盡相同,也跟 .NET 轉換結果不同。IE 顯示為「甇斗? ? 瘜 蝙?」(比 .NET 結果少了「具」),Edge/Chrome 就如新聞所提是「甇斗?滚?嗵?⊥?蓥蝙?鍂??」,Firefox 很有趣,沒有「滚」但集兩家之大成「甇斗??嗵?瘜蓥蝙?具??」比 Edge/Chrome 多了「瘜」跟「具」。由此可知,各家瀏覽器將 byte[] 解讀成 BIG5 字元的實作存在一些差異。但,Chrome/Edge 中的「滚」是怎麼來的呢?

為了查出真相,用線上中文編碼解析將「此服務無法使用。」轉成 UTF8 Bytes E6 AD A4 E6 9C 8D E5 8B 99 E7 84 A1 E6 B3 95 E4 BD BF E7 94 A8 E3 80 82:

再用以下 ASP.NET 程式測試不同 byte[] 在瀏覽器硬轉成 BIG5 的結果:

<%@Page Language="C#"%>
<%@Import Namespace="System.Text.RegularExpressions"%>
<script runat="server">
void Page_Load(object sender, EventArgs e)
{
	var hex = Request["h"] ?? "";
	var bytes = new List<byte>();
	foreach (Match m in 
		Regex.Matches(hex, "[0-9a-fA-F]{2}")) 
	{
		bytes.Add(Convert.ToByte(m.Value, 16));
	}
	var big5 = System.Text.Encoding.GetEncoding("big5");
	Response.BinaryWrite(big5.GetBytes("<html><head><meta charset='big5'></head><body>"));
	Response.BinaryWrite(bytes.ToArray());
	Response.BinaryWrite(big5.GetBytes("</body></html>"));
	Response.End();
}
</script>

我很快找出「滚」是 8D E5 變成的:

依據 BIG5 編碼規則,8D E5 落在 0x8140-0xA0FE 使用者自訂字元(造字區),理論上並不是一個通用的有效字元,那 Chrome 為什麼會判別它是「滚」字呢?我想到了 Unicode 補完計劃,並在香港的 Unicode 補完計劃字元表找到答案!

8D E5 是「滚」的 HKSCS 編碼,喚起我的記憶(延伸閱讀:Big5-HKSCS編碼初探(上)Big5-HKSCS編碼初探(下)Big5-HKSCS編碼補遺),

結論:這次五倍券的錯訊息亂碼是 UTF8 編碼硬解成 BIG5 導致,IE 跟 .NET 會依據標準 BIG5 (CodePage 950)解析,而 Chrome/Edge/Firefox 則會自動切換 Big5-HKSCS 企圖解析出更多字元,這就是「滚」字冒出來的原因。

解開一個謎團,開心!


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK