菜雞抓蟲: Url 變得怪怪的?你可能是零寬空格(ZWSP)的受害者!
這週遇到個想不到的坑,特別來記錄一下。故事是這樣的--
在需要呼叫其他 API 服務時,發生了以下怪事:
- 打某支查詢 API,突然查不到任何東西,或是跳出參數錯誤
- 有些需要用參數組成 URL 的 API 跑出 Not Found
- 第一組資料呼叫成功,第二組突然路徑錯誤
- 寫入的時候,資料莫名其妙多了個
?
- 例如原先的資料是
ABC
,不知怎地變成了ABC?
- 例如原先的資料是
由於這些操作都涉及到同一個參數,直覺上就是我們這邊給的參數出了點問題,馬上進入找犯人的環節。直接中斷點標記下去,反覆觀察該字串,但它就是一個普通的字串 "ABC"
,完全看不出什麼端倪。
正要覺得參數沒有問題的時候,赫然發現組出來的 Url 相當不對勁:在該參數的後方,多出了 %E2%80%8B
這串神秘東西!
當下我驚呆了,我們傳出去的 Url 裡,並不是預想的 /api/product/ABC
,而是 /api/product/ABC%e2%80%8b
!真是赤裸裸的背叛!這串鬼東西到底是什麼來頭?!
一查下去,原來這東西叫做 零寬空格(Zero-width space, ZWSP)
顧名思義,就是完全沒有寬度的空白字元。這東西在 Unicode 叫做 U+200B
,我們比較常見到的是編碼之後的樣子 %e2%80%8b
或 \xe2\x80\x8b
。他還有另外兩個兄弟 U+200C
、U+200D
,平常在泰文、高棉文之類的地方工作,這東西的特色就是:肉眼不可見、殺人於無形。
它有多可怕,讓我們直接用 Linqpad 來試看看吧。
現在我們有選手 A 和選手 B,其中選手 A 偷偷嗑了禁藥 ZWSP:
var a = "ABC" + '\u200B';
var b = "ABC";
讓我們打印出來看看:
$"a:{a}".Dump();
$"b:{b}".Dump();
看起來完全一模一樣,連反白都分辨不出來!
a.Length.Dump(); // 4
b.Length.Dump(); // 3
看來其中一個傢伙明顯比較長。
(a == b).Dump(); // False
(a.Equals(b)).Dump(); // False
看來這兩個傢伙完全不一樣!
目前看來,從長度和比較運算等方面都會發現它們並不一樣,但是肉眼卻分不出來。
這樣就會產生一些看起來像是 ("ABC" == "ABC") // False
的神奇場景,除了揉眼睛然後哭喊「明明就一樣」以外無從下手。更可怕的是當我們拿去組 Url,問題就出來啦:
HttpUtility.UrlEncode(a).Dump(); // ABC%e2%80%8b
HttpUtility.UrlEncode(b).Dump(); // ABC
真是完蛋。
同時在查資料的時候,也發現 NET Framework 3.5 之後的 Trim()
並不把這個零寬空格當成空白,因此單純用 Trim()
是不會把這鬼東西砍掉的。
所以如果你有以下情況,你可能是零寬空格的受害者!
- 存到資料庫的資料莫名多一個
?
(有看不見或編碼錯誤的字元) - 呼叫 API 服務的時候,不是參數錯誤,就是直接報錯找不到(檢查組完的 URL)
這邊也順便記一下怎麼解決的,雖然是用相當暴力的方式:
(a.Replace("\u200B", "") == b).Dump(); // true
沒錯,我直接 Replace
掉它了囧,勉強度過了這次危機。
備註:如果有更好的處理方式,也歡迎提供給我呦,感謝~
雖然我更疑惑的是,資料裡面到底為啥會出現這種東西啦囧……
最後感謝一下這次讓我得到幫助的網路文章。每次 Debug 都要感謝前人們的禮物,謝謝。
其他文章
哈囉,如果你也有 LikeCoin,也覺得我的文章有幫上忙的話,還請不吝給我拍拍手呦,謝謝~ ;)