多年来,我慢慢地开发了一个正则表达式,它可以正确验证大多数电子邮件地址,假设它们不使用IP地址作为服务器部分。
我在几个PHP程序中使用它,而且大多数时候都可以使用。然而,不时有人与我联系,他们对使用它的网站有问题,我最终不得不做出一些调整(最近我意识到我不允许四个字符的TLD)。
你有或见过验证电子邮件的最佳正则表达式是什么?
我见过几种使用函数的解决方案,这些函数使用了几个较短的表达式,但我宁愿在简单函数中使用一个长的复杂表达式,而不是在更复杂的函数中使用几个短表达式。
多年来,我慢慢地开发了一个正则表达式,它可以正确验证大多数电子邮件地址,假设它们不使用IP地址作为服务器部分。
我在几个PHP程序中使用它,而且大多数时候都可以使用。然而,不时有人与我联系,他们对使用它的网站有问题,我最终不得不做出一些调整(最近我意识到我不允许四个字符的TLD)。
你有或见过验证电子邮件的最佳正则表达式是什么?
我见过几种使用函数的解决方案,这些函数使用了几个较短的表达式,但我宁愿在简单函数中使用一个长的复杂表达式,而不是在更复杂的函数中使用几个短表达式。
当前回答
完全符合RFC 822的正则表达式由于其长度而效率低下且晦涩难懂。幸运的是,RFC 822被两次取代,当前的电子邮件地址规范是RFC 5322。RFC 5322产生了一个正则表达式,如果研究几分钟就可以理解,并且对于实际使用来说足够有效。
一个符合RFC 5322的正则表达式可以在页面顶部找到:http://emailregex.com/但使用的IP地址模式在互联网上浮动,错误是允许点分隔地址中的任何无符号字节十进制值为00,这是非法的。它的其余部分似乎与RFC 5322语法一致,并通过了几个使用grep-Po的测试,包括域名、IP地址、坏域名以及带引号和不带引号的帐户名。
纠正了IP模式中的00错误,我们获得了一个有效且相当快的正则表达式。(抓取呈现的版本,而不是markdown,以获取实际代码。)
(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^ _`{|}~-]+)*|“(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-#x09\x0b#x0c\x10e-\x7f])*”)@(?:(?:[?:[a-z0-9](?:a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])|\[(?:(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9]))\.){3}(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9])|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-#x09\x0b#x0c\x10e-\x7f】)+)\])
or:
(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9]))\.){3}(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9])|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])
下面是上述正则表达式的有限状态机图,它比正则表达式本身更清楚
Perl和PCRE中更复杂的模式(例如PHP中使用的正则表达式库)可以正确地解析RFC 5322而不会出现问题。Python和C#也可以做到这一点,但它们使用的语法与前两种不同。然而,如果您被迫使用许多功能较弱的模式匹配语言之一,那么最好使用真正的解析器。
同样重要的是要理解,根据RFC验证它绝对不会告诉您该地址是否确实存在于所提供的域中,或者输入该地址的人是否是其真正的所有者。人们总是以这种方式向其他人注册邮件列表。修复这一问题需要一种更高级的验证,包括向该地址发送一条消息,该消息包含一个确认令牌,该令牌与该地址在同一网页上输入。
确认令牌是知道您获得输入者地址的唯一方法。这就是为什么现在大多数邮件列表都使用该机制来确认注册。毕竟,任何人都可以放下president@whitehouse.gov,这甚至会被解析为合法,但不太可能是另一端的人。
对于PHP,您不应该使用“用PHP验证电子邮件地址”中给出的模式,我引用的正确方法是:
常见的用法和广泛的草率编码可能会为电子邮件地址建立一个事实上的标准,这比记录的正式标准更具限制性。
这并不比其他所有非RFC模式更好。它甚至不足以处理RFC 822,更不用说RFC 5322了。然而,这一个是。
如果你想变得花哨和迂腐,就实现一个完整的状态引擎。正则表达式只能充当基本过滤器。正则表达式的问题是,从用户的角度来看,告诉某人他们完全有效的电子邮件地址是无效的(假阳性),因为你的正则表达式无法处理它,这是粗鲁和不礼貌的。用于此目的的状态引擎可以验证甚至纠正电子邮件地址,否则这些地址将被视为无效,因为它根据每个RFC分解电子邮件地址。这可能会带来更愉悦的体验,比如
指定的电子邮件地址'myemail@address,com'无效。你是说myemail@address.com'?
另请参阅验证电子邮件地址,包括注释。或比较电子邮件地址验证正则表达式。
Debugex演示
其他回答
我找到了一个符合RFC 2822的正则表达式。RFC 5322的先前标准。这个正则表达式表现得相当好,将覆盖大多数情况,但是随着RFC 5322成为标准,可能会有一些漏洞需要堵塞。
^(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?: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]?|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])$
文档中说,您不应该使用上面的正则表达式,而是倾向于这种风格,这有点容易管理。
[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?
我注意到这是区分大小写的,所以我实际上对这个平台做了修改。
^[a-zA-Z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-zA-Z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?\.)+[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?$
电子邮件地址的正则表达式为:
/^("(?:[!#-\[\]-\u{10FFFF}]|\\[\t -\u{10FFFF}])*"|[!#-'*+\-/-9=?A-Z\^-\u{10FFFF}](?:\.?[!#-'*+\-/-9=?A-Z\^-\u{10FFFF}])*)@([!#-'*+\-/-9=?A-Z\^-\u{10FFFF}](?:\.?[!#-'*+\-/-9=?A-Z\^-\u{10FFFF}])*|\[[!-Z\^-\u{10FFFF}]*\])$/u
此正则表达式与RFC 5321、RFC 5322和RFC 6532中指定的非过时电子邮件地址的地址规范ABNF 100%相同。
此外,您必须验证:
电子邮件地址格式为UTF-8(或ASCII,如果无法发送到国际化电子邮件地址)地址不超过320个UTF-8字节用户部分(第一个匹配组)不超过64个UTF-8字节域部分(第二个匹配组)不超过255个UTF-8字节
完成所有这些的最简单方法是使用现有函数。在PHP中,请使用filter_VALIDATE_EMAIL和filter_FLAG_EMAIL_UNICODE(如果可以发送到国际化电子邮件地址)查看filter_var函数:
$email_valid = filter_var($email_input, FILTER_VALIDATE_EMAIL, FILTER_FLAG_EMAIL_UNICODE);
然而,也许您正在构建这样一个函数,实际上实现这一点的最简单方法是使用正则表达式。
记住,这只验证电子邮件地址不会导致语法错误。验证地址是否可以接收电子邮件的唯一方法是实际发送电子邮件。
接下来,我将讨论如何生成此正则表达式。
我写了一个新的答案,因为这里的大多数答案都犯了这样一个错误:要么指定了一个限制性太强的模式(因此没有很好地老化);或者它们呈现一个正则表达式,该表达式实际上与MIME消息的标头匹配,而不是电子邮件地址本身。
只要没有递归部分,从ABNF生成正则表达式是完全可能的。
RFC 5322规定了在MIME消息中发送什么是合法的;将此视为合法电子邮件地址的上限。
然而,完全遵循ABNF将是一个错误:这种模式在技术上表示了如何在MIME消息中编码电子邮件地址,并允许字符串不属于电子邮件地址,如折叠空格和注释;它还支持不合法生成的过时表单(但服务器出于历史原因读取)。电子邮件地址不包括这些。
RFC 5322解释了:
原子和点原子都被解释为单个单元,包括组成它的字符串。语义上,可选其余角色周围的评论和FWS不属于原子;原子只是一个原子中的一行文本字符,或点原子中的atext和“.”字符。
在某些定义中,将有非终端的名称以“obs-”开头。这些“obs-”元素是指在第4节中过时的语法。在所有情况下,这些产品为了生成合法的互联网信息,不得用作此类信息的一部分。
如果您从RFC 5322中的addr规范中删除CFWS、BWS和obs-*规则,并对结果执行一些优化(我使用了“green”),则可以生成此正则表达式,用斜线引用并锚定(适用于ECMAScript和兼容方言,为清晰起见,添加了换行符):
/^("(?:[!#-\[\]-~]|\\[\t -~])*"|[!#-'*+\-/-9=?A-Z\^-~](?:\.?[!#-'*+\-/-9=?A-Z\^-~])*)
@([!#-'*+\-/-9=?A-Z\^-~](?:\.?[!#-'*+\-/-9=?A-Z\^-~])*|\[[!-Z\^-~]*\])$/
这只支持ASCII电子邮件地址。要支持RFC 6532国际化电子邮件地址,请将~字符替换为\u{10FFFF}(PHP,带u标志的ECMAScript)或\uFFFF(用于UTF-16实现,如.NET和旧版ECMAScript/JavaScript):
/^("(?:[!#-\[\]-\u{10FFFF}]|\\[\t -\u{10FFFF}])*"|[!#-'*+\-/-9=?A-Z\^-\u{10FFFF}](?:\.?[!#-'*+\-/-9=?A-Z\^-\u{10FFFF}])*)@([!#-'*+\-/-9=?A-Z\^-\u{10FFFF}](?:\.?[!#-'*+\-/-9=?A-Z\^-\u{10FFFF}])*|\[[!-Z\^-\u{10FFFF}]*\])$/u
这是有效的,因为我们使用的ABNF不是递归的,因此形成了可以转换为正则表达式的非递归正则语法。
它是这样分解的:
用户部分(@之前)可以是点原子或带引号的字符串“([!#-\[\]-~]|\\[\t-~])*”指定用户的引号字符串形式,例如root@home“@example.com。它允许双引号内的任何非控制字符;但空格、制表符、双引号和反斜杠必须用反斜杠转义。[!#-'*+\-/-9=?A-Z\^-~]是用户点原子的第一个字符。(\.?[!#-'*+\-/-9=?A-Z\^-~])*与点原子的其余部分匹配,允许点(除了在另一个点之后或作为最终字符)。@表示域。域部分可以是点原子或域文字。[!#-'*+\-/-9=?A-Z\^-~](\.?[!#-'*+\-/-9=?A-Z \^-~])*与上面的点原子形式相同,但这里它表示域名和IPv4地址。\[[!-Z\^-~]*\]将匹配IPv6地址和主机名的未来定义。
此正则表达式允许所有符合规范的电子邮件地址,并且可以在MIME消息中逐字使用(除了行长度限制,在这种情况下必须添加折叠空格)。
这还设置了非捕获组,使得match[1]将是用户,match[2]将是主机。(但是,如果匹配项[1]以双引号开头,则过滤掉反斜杠转义符以及开头和结尾双引号:“root”@example.com和root@example.com识别同一收件箱。)
最后,请注意,RFC 5321对电子邮件地址的长度设置了限制。用户部分最多可为64字节,域部分最多为255字节。包括@字符在内,整个地址的限制为320字节。地址是UTF-8编码后的字节数;而不是字符。
注意,RFC 5322 ABNF为域名定义了一个允许的语法,允许当前已知的名称无效。这也允许域名将来成为合法的。这不应该是一个问题,因为这应该以与不存在的域名相同的方式处理。
始终考虑用户键入的电子邮件地址有效,但他们无法访问的可能性。验证电子邮件地址的唯一简单方法是发送电子邮件。
这是改编自我的文章电子邮件地址和语法。
我们还有另一个选择,就是使用具有EmailAddressAttribute的DataAnnotations。这不仅可以应用于类的属性,还可以在运行时加以利用。
使用System.ComponentModel.DataAnnotations;
典型用途
public class Person
{
public int Id { get; set; }
[EmailAddress]
public string Email { get; set; }
}
运行时
var emailAddressAttribute = new EmailAddressAttribute();
if (emailAddressAttribute.IsValid("name@email.com"))
{
//email is valid
}
else
{
//email is invalid
}
如果您需要一个简单的表单来验证,可以使用https://regexr.com/3e48o
^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$
让r=新RegExp(String.raw`^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$`);//应该是真的console.log(r.test('name@domain.tld'));console.log(r.test('name@domain.co.tld'));console.log(r.test('name@domain.co'));//应为falseconsole.log(r.test('@domain.tld'));console.log(r.test('name@.tld'));console.log(r.test('name@domain.'));console.log(r.test('namedomain.tld'));console.log(r.test(“”));//现在基本的客户端验证已经完成,从服务器端发送一个令牌来验证用户是否有权访问电子邮件
这个问题被问了很多,但我认为你应该退后一步,问问自己为什么要从语法上验证电子邮件地址?真正的好处是什么?
它不会捕捉常见的拼写错误。它并不阻止人们输入无效或虚构的电子邮件地址,或输入其他人的地址。
如果您想验证电子邮件是否正确,您别无选择,只能发送确认电子邮件并让用户回复。在许多情况下,出于安全原因或道德原因(例如,您不能违背某人的意愿签署服务),您必须发送确认邮件。