首頁  >  文章  >  後端開發  >  php實作RFC相容的電子郵件地址驗證

php實作RFC相容的電子郵件地址驗證

WBOY
WBOY原創
2016-07-25 08:56:421439瀏覽
  1. /*

  2. 版權所有2009 Dominic Sayers
  3. (dominic_sayers@hotmail.com)
  4. (http://www.dominicsayers.com)
  5. 此來源文件受通用公共歸屬授權版本1.0 (CPAL) 授權的約束。

  6. 授權條款可透過萬維網取得:http://www.opensource.org/licenses/cpal_1.0
  7. */
  8. function is_email ($email, $checkDNS = false) {
  9. //檢查$email 是否為有效位址
  10. // (http://tools.ietf.org/html/rfc3696)
  11. // (http://tools.ietf.org/ html/rfc5322#section-3.4 .1)
  12. // (http://tools.ietf.org/html/rfc5321#section-4.1.3)
  13. // (http://tools.ietf. org/html/rfc4291#section -2.2)
  14. // (http://tools.ietf.org/html/rfc1123#section-2.1)
  15. // 現代電子郵件地址由「本地」組成部分」與
  16. // 透過at 符號(「@」)分隔的「網域部分」(完全限定的網域名稱)。 $index = strrpos($email,'@');
  17. if ($index === false) 回傳false; // 沒有at 符號
  18. if ($index === 0) return false; // 沒有本地部分
  19. if ($index > 64) return false; // 局部部分太長
  20. ; $localPart = substr($email, 0, $index);

  21. $domain = substr($email, $index + 1);
  22. $domainLength = strlen($domain);
  23. if ($domainLength === 0) return false ; // 沒有域部分
  24. if ($domainLength > 255) return false; // 域部分太長
  25. // 讓我們檢查本地部分的RFC 合規性...

  26. //
  27. // 句點(「.」)可能...出現,但不能用於開始或結束
  28. //局部部分,也不能出現兩個或多個連續的句點。
  29. // (http://tools.ietf.org/html/rfc3696#section-3)
  30. if (preg_match('/^\.|\.\.|\.$/', $localPart ) > 0) 回傳假; // 點放在錯誤的位置
  31. ; // 除了

  32. // at 符號(「@」)、反斜線、雙引號、逗號或方括號以外的任何ASCII 圖形(列印)字元都可以
  33. // 出現而無需引號。 如果任何排除字元清單
  34. // 要出現,則必須將它們加引號
  35. // (http://tools.ietf.org/html/rfc3696#section-3)
  36. if ( preg_match( '/^"(?:.)*"$/', $localPart) > 0) {
  37. // 局部部分是引號的字串
  38. if (preg_match('/(?:.) + [^\\]"(?:.)+/', $localPart) > 0) return false; // 帶引號的字串內未轉義的引號字元
  39. } else {
  40. if (preg_match ('/[ @\[\]\\",]/', $localPart) > 0)
  41. // 檢查所有排除的字元是否被轉義
  42. $stripped = preg_replace('/\\[ @\[\]\ \",]/', '', $localPart);
  43. if (preg_match('/[ @\[\]\\",]/', $stripped) > 0) return false; // 不帶引號的排除字元
  44. }
  45. // 現在讓我們檢查網域部分...

  46. // 網域也可以替換為方括號內的IP位址

  47. // (http://tools.ietf.org/html/rfc3696#section-3)
  48. // (http:// tools.ietf.org/html/ rfc5321#section-4.1.3)
  49. // (http://tools.ietf.org/html/rfc4291#section-2.2)
  50. if (preg_match('/^ \[(.)+]$ /', $domain) === 1) {
  51. // 這是一個位址文字
  52. $addressLiteral = substr($domain, 1, $domainLength - 2);
  53. $matchesIP = array() ;
  54. // 從位址文字結尾擷取IPv4 部分(如果有的話)
  55. if (preg_match('/\b(?:(?:25[0-5]|2) [0 -4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][ 0- 9]|[01]?[0-9][0-9]?)$/', $addressLiteral, $matchesIP) > 0) {
  56. $index = strrpos($addressLiteral, $matchesIP[ 0]) ;
  57. if ($index === 0) {
  58. // 除了有效的IPv4 位址之外什麼都沒有,所以...
  59. return true;
  60. }else {
  61. // 假設嘗試使用混合位址(IPv6 + IPv4)
  62. if ($addressLiteral[$index - 1] !== ':') return false; // IPv4 位址前面的字元必須是' :'
  63. if (substr($addressLiteral, 0, 5) !== 'IPv6:') return false; // RFC5321 第4.1.3 節
  64. ; $IPv6 = substr ($addressLiteral, 5, ($index ===7) ? 2 : $index - 6);

  65. $groupMax = 6;
  66. }
  67. } else {
  68. // 必須是純IPv6的嘗試
  69. if (substr($addressLiteral, 0, 5) !== 'IPv6:') return false; // RFC5321 第4.1.3 節
  70. $IPv6 = substr($addressLiteral, 5);
  71. $groupMax = 8;
  72. }
  73. $groupCount = preg_match_all('/^[0-9a-fA-F]{0,4}|\:[0-9a -fA-F]{0,4}|(.)/', $IPv6, $匹配IP);

  74. $index = strpos($IPv6,'::');
  75. if ($index === false) {
  76. // 我們需要正確的組數
  77. if ($groupCount !== $groupMax) return false; // RFC5321 第4.1.3 節
  78. } else {
  79. if ($index !== strrpos($IPv6,'::')) return false; // 多'::'
  80. $groupMax = ($index === 0 || $index === (strlen($IPv6) - 2)) ? $groupMax : $groupMax - 1;
  81. if ($groupCount > $groupMax) return false; // 位址
  82. 中的IPv6 群組過多}
  83. // 檢查不符的字元

  84. array_multisort($matchesIP[1], SORT_DESC);
  85. if ($matchesIP[1][0] !== '' ) return false; // 位址中存在非法字元
  86. ; // 這是一個有效的IPv6 位址,所以...

  87. return true;
  88. } else {
  89. // 這是一個網域名稱...
  90. // RFC-952 中指定了合法網際網路主機名稱的語法

  91. // 主機名稱語法的一個面向在此變更:
  92. // 對第一個字元的限制放寬以允許
  93. // 字母或數字。
  94. // (http://tools.ietf.org/html/rfc1123#section-2.1)
  95. //
  96. // NB RFC 1123 更新了RFC 1035,但目前從閱讀RFC 來看這一點並不明顯1035.
  97. //
  98. // 最常見的應用程序,包括電子郵件和Web,通常不允許...轉義字符串
  99. // (http://tools. ietf.org/html/rfc3696 #section-2)
  100. //
  101. // 字母字元、數字和連字符集之外的字元不得出現在網域名稱中
  102. // SMTP 用戶端或伺服器的標籤
  103. // ( http://tools.ietf.org/html/rfc5321#section-4.1.2)
  104. //
  105. // RFC5321 禁止在網域中使用尾隨點用於SMTP
  106. // (http://tools.ietf.org/html/rfc5321#section-4.1.2)
  107. $matches = array();
  108. $groupCount = preg_match_all('/(?:[0- 9a-zA-Z][0-9a-zA-Z-]{0,61}[0-9a-zA-Z]|[a- zA-Z])(?:\.|$)|(. )/', $domain, $matches);
  109. $level = count($matches[0]);
  110. if ($level == 1) 回傳false; // 郵件主機不能是TLD

  111. ; $TLD = $matches[0][$level - 1];

  112. if (substr($TLD, strlen($TLD) - 1, 1) === '.') return false; // TLD 不能以點
  113. 結尾if (preg_match('/^[0-9]+$/', $TLD) > 0) return false; // TLD 不能是全數字
  114. // 檢查不符的字元

  115. array_multisort($matches[1], SORT_DESC);
  116. if ($matches[1][0] !== '') return false; // 域中存在非法字符,或標籤長度超過63 個字符
  117. ; // 檢查DNS?

  118. if ($checkDNS && function_exists('checkdnsrr')) {
  119. if (!(checkdnsrr($domain, 'A') || checkdnsrr($domain, 'MX'))) {
  120. return錯誤的; // 域其實不存在
  121. }
  122. }
  123. // 排除所有其他因素,剩下的一定是事實。

  124. // (福爾摩斯、四簽名)
  125. 回傳 true;
  126. }
  127. }
  128. function unitTest ($email, $reason = '') {

  129. $expected = ($reason === '') ?真:假;
  130. $valid = is_email ($email);
  131. $not = ($valid) ? '' : ' 不是';
  132. $unexpected = ($valid !== $expected) ? ' 這齣乎意料! ' : '';
  133. $reason = ($reason === '') ? "" : " 原因:$reason";
  134. return "地址$email 無效。
  135. // 電子郵件驗證器測試案例(Dominic Sayers,2009 年1 月)

  136. // 有效位址
  137. echo unitTest('first.last@example.com') ;
  138. echo unitTest('1234567890123456789012345678901234567890123456789012345678901234@example.com') echo unitTest('"first\"last" @example.com'); // 不完全確定這是否有效
  139. echo unitTest('first\@last@example.com');
  140. echo unitTest('"first@last"@example.com' );
  141. echo unitTest('first\\last@example.com'); // 注意,即使在單引號字串中也會被轉義這是測試"first\last"@example.com
  142. echo unitTest('first.last@x23456789.x23456789.x23456789.x23456789.x23456789.x23456789.x23456789x23456762. 789.x23456789.x23456789.x23456789 .
  143. x23456789.x23456789.x23456789.x23456789.x23456789 .x23456789.x23456789.x23456789.x23456789.x23456789.x23456789.x234 5');
  144. echo unitTest(first); first.last@[IPv6: ::12.34.56.78]');
  145. echo unitTest('first.last@[IPv6:1111:2222:3333::4444:12.34.56.78]' );
  146. echoitTlastT. [IPv6:1111:2222:3333:4444:5555:6666:12.34.56.78]');
  147. echo unitTest('first.last@[IPv6:: :1111:2222:3333356:5356:56:5356:53] ');
  148. echo unitTest('first.last@[IPv6:1111:2222:3333::4444:5555:6666]');
  149. echo unitTest('first.last@[IPv6:11111:222111: :3333:4444:5555:6666::]');
  150. echo unitTest('first.last@[IPv6:1111:2222:3333:4444:5555:6666:7777:8888]; echo unitTest('first.last@x23456789012345678901234567890123456789012345678901234567890123.example.com'); cho unitTest('first.last@123.example.com' );
  151. //無效位址

  152. echo unitTest('first.last', "No @ 」);
  153. echo unitTest('@example.com', "沒有本地部分");
  154. echo unitTest('12345678901234567890123456789012345678901234567890123456789012345@exple.com .last@example.com', "本地部分以點開頭");
  155. echo unitTest('first.last.@example.com', "本地部分以點結尾");
  156. echo unitTest('first..last@example.com', "本地部分有連續的點");
  157. echo unitTest('"first"last"@example.com', "本地部分包含未轉義的排除字元");
  158. echo unitTest('first\\@last @example.com', "本地部分包含未轉義的排除字元");
  159. echo unitTest('first.last@', "無域");
  160. echo unitTest('first.last@x23456789 .x23456789.x23456789.x23456789.x23456789.x23456789.x23456789. x23456789.x23456789.x23456 x23456789.x23456789.x23456 x23456789.x23456789.x23456789x. 23456789.
  161. x23456789.x23456789.x23456789.x23456789.x23456 789.x23456789.x23456789.234x5689.35x3x .x23456789.x23456',「網域超過255個字元」);
  162. echo unitTest('first.last@[.12.34.56.78]', "IPv4 位址前面只能有':'");
  163. echo unitTest('first.last@[12.34.56.789] ', "無法解釋為IPv4,因此IPv6 標記遺失");
  164. echo unitTest('first.last@[::12.34.56.78]', "IPv6標記遺失"); unitTest('first.last@[IPv5:::12.34.56.78]', "IPv6 標籤錯誤");
  165. echo unitTest('first.last@[IPv6:1111:2222:3333: :4444:5555 :12.34.56.78]',“IPv6 組太多(最多4 個)”);
  166. echo unitTest('first.last@[IPv6:1111:2222:3333:444:5455: 56.78]', "IPv6 組不足");
  167. echo unitTest('first.last@[IPv6:1111:2222:3333:4444:5555:6666:7777:12.34.56.78]' (最多6 個)");
  168. echo unitTest('first.last@[IPv6:1111:2222:3333:4444:5555:6666:7777]', "IPv6 組不足");
  169. echoununit ('first.last@[IPv6:1111:2222:3333:4444:5555:6666:7777:8888:9999]', "IPv6 組太多(最多8 個)");
  170. echo unitTest('ffirst .last@[IPv6:1111:2222::3333::4444:5555:6666]', "太多'::' (可以沒有或一個)");
  171. echo unitTest('first.last@[ IPv6:1111:2222:3333::4444:5555:6666:7777]', "IPv6 群組太多(最多6 個)");
  172. echo unitTest('first.last@[IPv6:1111:222: 333x::4444:5555]', "x 在IPv6 位址中無效");
  173. echo unitTest('first.last@[IPv6:1111:2222:33333::4444:5555]', "IPv位址中的有效群組");
  174. echo unitTest('first.last@example.123', "TLD 不能全是數字");
  175. echo unitTest('first.last@com', "郵件主機主機必須為二級或更低級別");
  176. echo unitTest('first.last@-xample.com', "標籤不能以連字符開頭");
  177. echo unitTest('first.last@exampl -.com', "標籤不能以連字號結尾");
  178. echo unitTest('first.last@x2345678901234567890123456789012345678901234567890123456789034567890123456740707456789023456740245678903456789位.
  179. // RFC3696 的測試範例(2004年2月,http://tools.ietf.org/html/rfc3696#section-3)

  180. echo unitTest('Abc\@def@example.com') ;
  181. echo unitTest('Fred\ Bloggs@example.com');
  182. echo unitTest('Joe.\\Blow@example.com');
  183. echo unitTest('"Abc@def"@example.com');
  184. echo unitTest('"Fred Bloggs"@example.com');
  185. echo unitTest('user+mailbox@example.com');
  186. echo unitTest('customer/department=shipping@example.com');
  187. echo unitTest('$A12345@example.com');
  188. echo unitTest('!def!xyz%abc@example.com');
  189. echo unitTest('_somename@example.com');
  190. // Doug Lovell 的測試範例(LinuxJournal,2007 年 6 月,http://www.linuxjournal.com/文章/9585)

  191. echo unitTest("dclo@us.ibm.com");
  192. echo unitTest("abc\@def@example.com");
  193. echo unitTest("abc\\@example.com");
  194. echo unitTest("Fred\ Bloggs@example.com");
  195. echo unitTest("Joe.\\Blow@example.com");
  196. echo unitTest(""Abc@def"@example.com");
  197. echo unitTest(""Fred Bloggs"@example.com");
  198. echo unitTest("customer/department=shipping@example.com");
  199. echo unitTest("$A12345@example.com");
  200. echo unitTest("!def!xyz%abc@example.com");
  201. echo unitTest("_somename@example.com");
  202. echo unitTest("user+mailbox@example.com");
  203. echo unitTest("peter.piper@example.com");
  204. echo unitTest("Doug\ \"Ace\"\ Lovell@example.com");
  205. echo unitTest(""Doug \"Ace\" L."@example.com");
  206. echo unitTest("abc@def@example.com", "Doug Lovell 說這應該失敗");
  207. echo unitTest("abc\\@def@example.com", "Doug Lovell 說這應該失敗");
  208. echo unitTest("abc\@example.com", "Doug Lovell 說這應該失敗");
  209. echo unitTest("@example.com", "Doug Lovell 說這應該失敗");
  210. echo unitTest("doug@", "Doug Lovell 說這應該失敗");
  211. echo unitTest(""qu@example.com", "Doug Lovell 說這應該失敗");
  212. echo unitTest("ote"@example.com", "Doug Lovell 說這應該失敗");
  213. echo unitTest(".dot@example.com", "Doug Lovell 說這應該會失敗");
  214. echo unitTest("dot.@example.com", "Doug Lovell 說這應該失敗");
  215. echo unitTest("two..dot@example.com", "Doug Lovell 說這應該失敗");
  216. echo unitTest(""Doug "Ace" L."@example.com", "Doug Lovell 說這應該失敗");
  217. echo unitTest("Doug\ \"Ace\"\ L\.@example.com", "Doug Lovell 說這應該失敗");
  218. echo unitTest("hello world@example.com", "Doug Lovell 說這應該失敗");
  219. echo unitTest("gatsby@f.sc.ot.t.f.i.tzg.era.l.d.", "Doug Lovell 說這應該會失敗");
  220. ?>
複製程式碼


陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn