Github 追 Code 經驗 - GPG 日期亂碼是怎麼來的?
2 |
在 Windows 使用 GPG 加解密及簽章,一般建議用 Gpg4Win,版本較新支援度較好,但有個小問題是 Gpg4Win 所附的 gpg 2.4.3 版,在中文版 Windows 的日期時間顯示會出現亂碼:
Git for Windows 或 Cmder 附的 gpg 2.2.29 版就沒這個問題:
直覺跟 POSIX 語系環境變數有關,但試過設定 LANG、LC_ALL、LC_TIME切到不同語系,但完全無法影響顯示結果。
GPG 是個開源專案,沒有查不出原因的道理,於是 C 語言麻瓜展開冒險,準備追進原始碼找 Bug。
不得不說 Github 的程式碼瀏覽介面變強許多,會自動識別程式語法建立函式來源關聯,追程式碼方便許多。我先在 gnupg Github 專案 搜尋 "Signature made" 找到 mainproc.c 的這段:
if (issuer_fpr)
{
log_info (_("Signature made %s\n"), asctimestamp(sig->timestamp));
log_info (_(" using %s key %s\n"),
astr? astr: "?", issuer_fpr);
}
else if (!keystrlen () || keystrlen () > 8)
{
log_info (_("Signature made %s\n"), asctimestamp(sig->timestamp));
log_info (_(" using %s key %s\n"),
astr? astr: "?", keystr(sig->keyid));
}
由程式得知時間顯示文字來自 asctimestamp() 函式,點選 asctimestamp 函式名稱,Github 馬上指出程式碼在 common/gettime.c:
從 asctimestamp() 很快找到以下這段邏輯:
# if HAVE_W32_SYSTEM
{
static int done;
if (!done)
{
/* The locale names as used by Windows are in the form
* "German_Germany.1252" or "German_Austria.1252" with
* alternate names similar to Unix, e.g. "de-DE". However
* that is the theory. On current Windows and Mingw the
* alternate names do not work. We would need a table to map
* them from the short names as provided by gpgrt to the long
* names and append some code page. For now we use "" and
* take the locale from the user's system settings. Thus the
* standard Unix envvars don't work for time and may mismatch
* with the string translations. The new UCRT available since
* 2018 has a lot of additional support but that will for sure
* break other things. We should move to ISO strings to get
* rid of such problems. */
setlocale (LC_TIME, "");
done = 1;
/* log_debug ("LC_ALL now '%s'\n", setlocale (LC_ALL, NULL)); */
/* log_debug ("LC_TIME now '%s'\n", setlocale (LC_TIME, NULL)); */
}
}
# endif
/* FIXME: we should check whether the locale appends a " %Z" These
* locales from glibc don't put the " %Z": fi_FI hr_HR ja_JP lt_LT
* lv_LV POSIX ru_RU ru_SU sv_FI sv_SE zh_CN. */
strftime (buffer, DIM(buffer)-1, "%c %Z", tp);
# endif
依程式碼跟註,程式在 Windows 環境下會將 LC_TIME 設成 "",讓內建函式 strftime() 依 Windows 的國別設定決定語系及日期時間格式。
按下 Blame 功能,可追到這是 3 年前 Commit 加入的邏輯:
點擊 Commit 查看詳細資訊,確認這是 2.3 加入的功能。
至此真相大白,這合理解釋了為什麼 2.4 版有亂碼且對 LC_TIME 設定刀槍不入(因為在 Windows 會強制將 LC_TIME 覆寫成 "",改吃 Windows 設定),2.2 版還沒加入這段,故表現正常。
為什麼 LC_TIME 設 "" 用 Windows 設定會出亂碼,這串亂碼原本是什麼中文內容?要繼續探究,少不了寫段 C 實測對照。
想想,其實我也不算完全沒能力寫 C,之前整理過 Windows VSCode C/C++ 開發環境安裝指令懶人包,寫幾行程式重現問題應該難不倒我,更何況現在我有 Github Copilot,GO!! (大推:讓會讀心術的 Github Copilot 陪你寫程式)
來問問 Github Copilot。
點擊左側的 Copilot Chat 圖示,問 Github Coplilot 怎麼在 mingw 呼叫 strftime 顯示現在時間,Copilot 沒酸我不做功課連成天只會伸手,奉上程式範例附貼心解說。
稍加修改,分別設定 LC_TIME 為 "zh_TW.UTF-8" 及 "" 後測試 strftime(),謎團解開:
�U�� 是下午 �x�_�зǮɶ� 是台北標準時間
strftime() 在中文語系 Windows 遇到 LC_TIME = "",會出現中文無法顯示,若設定 "zh_TW.UTF-8" 則正常。爬文發現同樣的問題在 PHP 也能重現,依據微軟文件,在 Windows 10 1803 版之後,呼叫 setlocale(LC_TIME, ".UTF8") 啟用 UTF-8 模式應為正解。
但 GnuPG 有自己的 Bug Tracking System,晚點再設法登入通報 Issue,先列為已知問題避開。
這回成功追到 C 程式的 Bug,還能重現問題,算是項小小突破,證明老狗還是能學會新把戲滴,幫自己拍拍手。(其實大部分是 Github 及 Github Copilot 的功勞啦 😄)
Comments
# by Jian
但覺得是 Windows 處理 setlocale (LC_TIME, ""); 的問題勒
# by Jeffrey
to Jian, C 語言處理 Unicode/UTF-8 有不少眉角。文章的微軟文件連結那篇有提,要啟用 UTF-8 模式,請使用 ".UTF8" 作為 setlocale 時的字碼頁。 例如, setlocale(LC_ALL, ".UTF8") 針對地區設定使用目前的預設 Windows ANSI 字碼頁 (ACP) ,並將 UTF-8 用於字碼頁。