﻿# 使用 Regular Expression 驗證密碼：使用 JavaScript 的陷阱

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

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

我[當時文章](/post/2008/05/09/Using-Regular-Expression-to-validate-password)的範例語法如下：

```
^(?=.*\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](http://www.my-debugbar.com/wiki/IETester/HomePage) 測試了 IE5 , IE6 , IE7 等不同瀏覽器版本，得到的結果不太一樣，其測試結果如下：

| IE8 ,Firefox 3, Chrome 3/4 | IE5, IE5.5, IE6, IE7 |
| --- | --- |
| 1\. Testing 12345 Failed  <br>2\. Testing abcde Failed  <br>  <br>3\. Testing a1 Failed  <br>4\. Testing a12 Failed  <br>5\. Testing a123 Failed  <br>6\. Testing a1234 OK  <br>7\. Testing a12345 OK  <br>8\. Testing a123456 OK  <br>  <br>9\. Testing ab1 Failed  <br>10\. Testing ab12 Failed  <br>11\. Testing ab123 OK  <br>12\. Testing ab1234 OK  <br>13\. Testing ab12345 OK  <br>14\. Testing ab123456 OK  <br>  <br>15\. Testing abc1 Failed  <br>16\. Testing abc12 OK  <br>17\. Testing abc123 OK  <br>18\. Testing abc1234 OK  <br>19\. Testing abc12345 OK  <br>20\. Testing abc123456 OK  <br>  <br>21\. Testing 1a Failed  <br>22\. Testing 1ab Failed  <br>23\. Testing 1abc Failed  <br>24\. Testing 1abcd OK  <br>25\. Testing 1abcde OK  <br>26\. Testing 1abcdef OK  <br>  <br>27\. Testing 12a Failed  <br>28\. Testing 12ab Failed  <br>29\. Testing 12abc OK  <br>30\. Testing 12abcd OK  <br>31\. Testing 12abcde OK  <br>32\. Testing 12abcdef OK  <br>  <br>33\. Testing 123a Failed  <br>34\. Testing 123ab OK  <br>35\. Testing 123abc OK  <br>36\. Testing 123abcd OK  <br>37\. Testing 123abcde OK  <br>38\. Testing 123abcdef OK  <br>  <br>39\. Testing 123a Failed  <br>40\. Testing 123a1 OK  <br>41\. Testing 123a1b OK  <br>42\. Testing 123a1b2 OK  <br>43\. Testing 123a1b2c OK  <br>44\. Testing 123a1b2c3 OK | 1\. Testing 12345 Failed  <br>2\. Testing abcde Failed  <br>  <br>3\. Testing a1 Failed  <br>4\. Testing a12 Failed  <br>5\. Testing a123 Failed  <br>6\. Testing a1234 Failed  <br>7\. Testing a12345 OK  <br>8\. Testing a123456 OK  <br>  <br>9\. Testing ab1 Failed  <br>10\. Testing ab12 Failed  <br>11\. Testing ab123 Failed  <br>12\. Testing ab1234 Failed  <br>13\. Testing ab12345 OK  <br>14\. Testing ab123456 OK  <br>  <br>15\. Testing abc1 Failed  <br>16\. Testing abc12 Failed  <br>17\. Testing abc123 Failed  <br>18\. Testing abc1234 Failed  <br>19\. Testing abc12345 OK  <br>20\. Testing abc123456 OK  <br>  <br>21\. Testing 1a Failed  <br>22\. Testing 1ab Failed  <br>23\. Testing 1abc Failed  <br>24\. Testing 1abcd Failed  <br>25\. Testing 1abcde OK  <br>26\. Testing 1abcdef OK  <br>  <br>27\. Testing 12a Failed  <br>28\. Testing 12ab Failed  <br>29\. Testing 12abc Failed  <br>30\. Testing 12abcd Failed  <br>31\. Testing 12abcde OK  <br>32\. Testing 12abcdef OK  <br>  <br>33\. Testing 123a Failed  <br>34\. Testing 123ab Failed  <br>35\. Testing 123abc Failed  <br>36\. Testing 123abcd Failed  <br>37\. Testing 123abcde OK  <br>38\. Testing 123abcdef OK  <br>  <br>39\. Testing 123a Failed  <br>40\. Testing 123a1 Failed  <br>41\. Testing 123a1b Failed  <br>42\. Testing 123a1b2 Failed  <br>43\. Testing 123a1b2c OK  <br>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](https://web.archive.org/web/20250409114943/http://browsershots.org/) 網站幫我額外測試另外 46 個在 Windows 平台上不同種類、版本的瀏覽器，以及 56 個跨平台各主要瀏覽器，我不得不說在大多數唸的出名字的瀏覽器中只有 IE 有這種邏輯錯誤的狀況 ( 當然我們還是要給 IE8 鼓鼓掌 )。

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

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

-   線上檢測網址：[http://blog.miniasp.com/js\_regex\_tests.htm](/js_regex_tests.htm)
-   下載測試程式：[js\_regex\_tests.htm (1.32 kb)](https://stwillblogassets.blob.core.windows.net/files/js_regex_tests.htm)
-   Browsershots：[https://web.archive.org/web/20100410051454/http://browsershots.org:80/http://blog.miniasp.com/js\_regex\_tests.htm](https://web.archive.org/web/20100410051454/http://browsershots.org:80/http://blog.miniasp.com/js_regex_tests.htm "http://browsershots.org/http://blog.miniasp.com/js_regex_tests.htm")

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

**相關連結**

-   [整理一些 Regex 的學習資源](/post/2008/03/23/Regular-Expression-Regex-Learning-Resources)
-   [使用 Regular Expression 驗證密碼](/post/2008/05/09/Using-Regular-Expression-to-validate-password)
-   [介紹好網站：Regular Expression Library](/post/2008/08/10/Useful-websites-Regular-Expression-Library)
-   [Lookahead and Lookbehind Zero-Width Assertions](http://www.regular-expressions.info/lookaround.html#lookahead)
-   [Using Regular Expressions with JavaScript and ActionScript](http://www.regular-expressions.info/javascript.html)
-   [.NET Framework 開發人員手冊 - 非回溯的向右合樣和向左合樣](http://msdn.microsoft.com/zh-tw/library/e902768h.aspx)
-   [Version Information (Windows Scripting - JScript)](http://msdn.microsoft.com/en-us/library/s4esdbwz\(VS.85\).aspx)
-   [JavaScript RegExp Object](http://www.w3schools.com/jsref/jsref_obj_regexp.asp)
-   [XRegExp](http://xregexp.com/)
