The Will Will Web

記載著 Will 在網路世界的學習心得與技術分享

使用 Regular Expression 驗證密碼:使用 JavaScript 的陷阱

我在前年有寫過一篇【 使用 Regular Expression 驗證密碼複雜度 】文章,當時撰寫的技巧完全是針對 .NET 提供的 Regular Expression 而寫,雖然我的文章在標籤的地方有特別提到 .NET,但還是有人將文章裡提供的 Regular Expression 直接抄去給 JavaScript 使用,結果當然是養出一堆莫名其妙的臭蟲(Bug)。

我在當時的文章中採用的 .NET Regular Expression 的右合樣(英文稱為 Lookahead 或稱為 Positive Lookahead ),該功能其實在所有瀏覽器都有支援,但是邪惡的 IE5 , IE6 , IE7 卻實做出錯誤的樣式比對規則。至於 Regular Expression 的左合樣 (Negative Lookbehind) 則是在任何瀏覽器的 JavaScript 都不支援。

當時文章的範例語法如下:

^(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{6,30}$

而我自己設計了一個 JavaScript 單元測試案例,共有 44 個 Test Case 去測試一個較簡單的樣式,終於研究出 IE7 以下版本對於右合樣的處理行為!

如下樣式,用來比對測試的字串是否符合:至少一個小寫字母、一個數字,且最少 5 個字元以上!

^(?=.*[a-z])(?=.*\d).{5,}$

首先,這個樣式在 IE 8 , Firefox 3.6 , Google Chrome 皆可正確比對,我另外利用 IETester 測試了 IE5 , IE6 , IE7 等不同瀏覽器版本,得到的結果不太一樣,其測試結果如下:

IE8 ,Firefox 3, Chrome 3/4 IE5, IE5.5, IE6, IE7
1. Testing 12345 Failed
2. Testing abcde Failed

3. Testing a1 Failed
4. Testing a12 Failed
5. Testing a123 Failed
6. Testing a1234 OK
7. Testing a12345 OK
8. Testing a123456 OK

9. Testing ab1 Failed
10. Testing ab12 Failed
11. Testing ab123 OK
12. Testing ab1234 OK
13. Testing ab12345 OK
14. Testing ab123456 OK

15. Testing abc1 Failed
16. Testing abc12 OK
17. Testing abc123 OK
18. Testing abc1234 OK
19. Testing abc12345 OK
20. Testing abc123456 OK

21. Testing 1a Failed
22. Testing 1ab Failed
23. Testing 1abc Failed
24. Testing 1abcd OK
25. Testing 1abcde OK
26. Testing 1abcdef OK

27. Testing 12a Failed
28. Testing 12ab Failed
29. Testing 12abc OK
30. Testing 12abcd OK
31. Testing 12abcde OK
32. Testing 12abcdef OK

33. Testing 123a Failed
34. Testing 123ab OK
35. Testing 123abc OK
36. Testing 123abcd OK
37. Testing 123abcde OK
38. Testing 123abcdef OK

39. Testing 123a Failed
40. Testing 123a1 OK
41. Testing 123a1b OK
42. Testing 123a1b2 OK
43. Testing 123a1b2c OK
44. Testing 123a1b2c3 OK
1. Testing 12345 Failed
2. Testing abcde Failed

3. Testing a1 Failed
4. Testing a12 Failed
5. Testing a123 Failed
6. Testing a1234 Failed
7. Testing a12345 OK
8. Testing a123456 OK

9. Testing ab1 Failed
10. Testing ab12 Failed
11. Testing ab123 Failed
12. Testing ab1234 Failed
13. Testing ab12345 OK
14. Testing ab123456 OK

15. Testing abc1 Failed
16. Testing abc12 Failed
17. Testing abc123 Failed
18. Testing abc1234 Failed
19. Testing abc12345 OK
20. Testing abc123456 OK

21. Testing 1a Failed
22. Testing 1ab Failed
23. Testing 1abc Failed
24. Testing 1abcd Failed
25. Testing 1abcde OK
26. Testing 1abcdef OK

27. Testing 12a Failed
28. Testing 12ab Failed
29. Testing 12abc Failed
30. Testing 12abcd Failed
31. Testing 12abcde OK
32. Testing 12abcdef OK

33. Testing 123a Failed
34. Testing 123ab Failed
35. Testing 123abc Failed
36. Testing 123abcd Failed
37. Testing 123abcde OK
38. Testing 123abcdef OK

39. Testing 123a Failed
40. Testing 123a1 Failed
41. Testing 123a1b Failed
42. Testing 123a1b2 Failed
43. Testing 123a1b2c OK
44. Testing 123a1b2c3 OK


因為右、左合樣的寬度永遠為零,所以照理說處理上述樣式時 (?=.*[a-z])(?=.*\d) 樣式不應該佔用任何比對的空間,但是在 IE5, IE6, IE7 卻會佔用,因而導致樣式處理發生邏輯不正確的情況!

拿上述測試案例的 abc1234 來說,它先比對 (?=.*[a-z]) 樣式,發現沒比對到,便跳至 (?=.*\d) 樣式比對,這個樣式比對後便把 .* (即 abc 部分) 的比對給吃掉了,也就是之後的 .{5,} 樣式不會再比對 abc 這三個字元,所以光是比對 1234  就會因為不足 5 個字元而發生樣式比對失敗!

拿上述測試案例的 abc12345 來說,它先比對 (?=.*[a-z]) 樣式,發現沒比對到,便跳至 (?=.*\d) 樣式比對,這個樣式比對後便把 .* (即 abc 部分) 的比對給吃掉了,也就是之後的 .{5,} 樣式不會再比對 abc 這三個字元,接著再比對 12345  就會因為符合至少 5 個字元的樣式要求而比對成功!

另外,我也利用 Browsershots 網站幫我額外測試另外 46 個在 Windows 平台上不同種類、版本的瀏覽器,以及 56 個跨平台各主要瀏覽器,我不得不說在大多數唸的出名字的瀏覽器中只有 IE 有這種邏輯錯誤的狀況 ( 當然我們還是要給 IE8 鼓鼓掌 )。

所以若有人要在 JavaScript 中實做 Regular Expression 必須特別特別小心,否則寫出了 Bug 還不自知,孰知這個 Bug 不是開發人員的錯,但客戶總是怪我們對吧!遇到這種瀏覽器的 Bug 真是如同天色暗了、天空陰了、空氣涼了這樣的感覺。

各位有興趣也可以自行測試看看,以下是測試網頁:

再次聲明:Regular Expression 是學一次用一輩子的技能,我認為是任何程式設計師必學的技能之一。

相關連結