场景实训 - 匹配邮箱

  • 作者:KK

  • 发表日期:2017.11.28


要点速读

  • JS正则:

    /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/
    
  • PHP正则:

    /^[a-z0-9.!#$%&\'*+\/=?^_`{|}~-]+@[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])+\.(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)*$/i
    

格式

邮箱的格式是有国际标准定义的,定义描述在 RFC822号文档(以太网接口文本消息格式标准) 这里,邮箱只是这个消息标准中的一部分,它很长,一般人都读不完,因为里面还有一些外部引用,类似“相关部分请参考 某某某某标准……”你又要去延伸阅读另一个标准。

RFC文档也不是一定就永远定的,它会随着实际的应用进行修正,RFC1690号修正记录(针对RFC822) 中提及了关于长度的修正。

该修正案表示,上一版本定义邮箱的@符号前面最多有64个字符,@符号后面最多255个字符,加上@符号本身一共320个字符,这是 IETF 工作组当初的邮箱格式定义。然而在邮件通讯时执行的RCPT命令最长是256个字符,所以当命令左右先包上<>符号的时候,只剩下254个字符可以填充邮箱字符串了,因此邮箱字符串的总长度应该是254个字符才能被 RCPT命令执行发送。


故事

研究这些国际组织的定义规范是费时费力的事情,也不是每一个人都有耐心做到的,也在工作进度的压力之下,侧重了许多开发者按照自己平时对邮箱的接触理解写了一个适合大多数情况能用的表达式,校验通过就部署使用,因此导致了email校验变成了一个比较分化的逻辑。

现在更多开发者其实都是上网搜个“正则邮箱验证”找个随便试了几下能用的表达式就部署进项目里了(也包括了曾经的我),其实这些表达式往往没有考虑到一些特殊情况,也是网上一些人凭自己的理解写出来的,没有严格参考国际邮箱格式定义标准,导致一些特殊情况的邮箱无法在站点注册等等。

RFC 标准只定义概念和规范,不提供代码的实现,据说目前校验逻辑最完整的表达式在 Paul Warren 的博客文章 ,但这个校验表达式过于极端,支持和不支持人都有,实际上还有其它实现的表达式。

以下JS和PHP的表达式基本上都只是校验格式,并不对总长度进行校验,要限制254个长度字符请自行另外增加字符串长度校验,通常来说都要追加这个校验,因为我们不可能提供无限大的储存空间存下比如超出65535个字符的邮箱


我见过哪些特殊案例被不严谨的正则误伤?

  1. -号的,比如ab-cd@xx.com

  2. 内容超长的,比如coooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooffice@yahoo.com.cn,这是我一位好友的邮箱(长度不完全正确,示例一下,但模样差不多)

  3. domain部分不带.号只有一个整体的,比如abc@xxx,在虚拟主机方面,我们其实可以建立一个不带后缀的主机名,所以极端情况下这种邮箱可以存在

  4. 太短的,比如1@xxx.com,中国联通的就要求 @ 号前面最少应该3位开头,导致我朋友无法用他的公司邮箱注册业务


JS代码

以下表达式是从 W3C 官网上抄来的,是 W3C 国际组织的指导表达式

var pattern = /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/;
//var pattern = /^[a-z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?(?:\.[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)*$/;	//缩写版

console.log( pattern.test('ab-cd@qq.com') );  // true
console.log( pattern.test('abc@qq.com') );  // true
console.log( pattern.test('abc@163.com') );  // true
console.log( pattern.test('abc_def@qq.com') );  // true
console.log( pattern.test('abc-def@qq.com') );  // true
console.log( pattern.test('abc-def_123@qq.com') );  // true
console.log( pattern.test('123@qq.com') );  // true
console.log( pattern.test('abc@xxx') );  // true

console.log( pattern.test('abc') );  // false
console.log( pattern.test('123') );  // false
console.log( pattern.test('中文@qq.com') );  // false

PHP代码

如果你只是为了做参数校验,那在PHP有中封装的函数可以校验邮箱,推荐使用 filter_var 函数进行校验,示例代码:

echo filter_var('xxx@yy.com', FILTER_VALIDATE_EMAIL);  // xxx@yy.com  返回原文
echo filter_var('@yy.com', FILTER_VALIDATE_EMAIL); // 空

然后正则的话可以用在数据采集方面做匹配工作:

$pattern = '/^[a-z0-9.!#$%&\'*+\/=?^_`{|}~-]+@[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])+\.(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)*$/i';

preg_match($pattern, 'abc@qq.com', $matchResult1);  //1
preg_match($pattern, 'abc@163.com', $matchResult2);  // 1
preg_match($pattern, 'abc_def@qq.com', $matchResult3);  // 1
preg_match($pattern, 'abc-def@qq.com', $matchResult4);  // 1
preg_match($pattern, 'abc-def_123@qq.com', $matchResult5);  // 1
preg_match($pattern, '123@qq.com', $matchResult6);  // 1
preg_match($pattern, 'abc@xxx', $matchResult9);  // 1

preg_match($pattern, 'abc', $matchResult7);  // 0
preg_match($pattern, '123', $matchResult8);  // 0
preg_match($pattern, '中文@qq.com', $matchResult10);  // 0

header('Content-type:text/plain');
print_r([
    $matchResult1[0],   //abc@qq.com
    $matchResult2[0],   //abc@163.com
    $matchResult3[0],   //abc_def@qq.com
    $matchResult4[0],   //abc-def@qq.com
    $matchResult5[0],   //abc-def_123@qq.com
    $matchResult6[0],   //123@qq.com
    $matchResult7,  // abc@xxx
    $matchResult8,  // 空
    $matchResult9,  // 空
    $matchResult10,  // 空
]);