简而言之
互联网上的任何自定义正则表达式,包括URI::MailTo::EMAIL_REGEXP
,都是错误的。
这里是你应该使用的内容:
RFC_5322 = /\A(?:[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])+)\])\z/i
RFC_5322_light = /\A[a-z0-9!#$%&'*+\/=?^_‘{|}~-]+(?:\.[a-z0-9!#$%&'*+\/=?^_‘{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\z/i
RFC_5322_with_length = /\A(?=[a-z0-9@.!#$%&'*+\/=?^_‘{|}~-]{6,254}\z)(?=[a-z0-9.!#$%&'*+\/=?^_‘{|}~-]{1,64}@)[a-z0-9!#$%&'*+\/=?^_‘{|}~-]+(?:\.[a-z0-9!#$%&'*+\/=?^_‘{|}~-]+)*@(?:(?=[a-z0-9-]{1,63}\.)[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+(?=[a-z0-9-]{1,63}\z)[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\z/i
细节
最后一个定义电子邮件地址格式的RFC是RFC5322 - 互联网消息格式。
您可以查看3.4.1. Addr-Spec规范。如果我们只看第一部分,@
将本地部分(左侧)和域名(右侧)分开。
addr-spec = local-part "@" domain
local-part = dot-atom / quoted-string / obs-local-part
例如,本地部分可以包含在此处定义的点原子或引用字符串中:
这有点复杂,但是您的电子邮件地址可能包含许多ASCII特殊字符,这些字符被许多正则表达式(如#
、$
、&
等)排除在外。
另一方面,URI::MailTo::EMAIL_REGEXP
在{{link3:ruby/lib/uri/mailto.rb
}}中定义为以下正则表达式:
EMAIL_REGEXP = /\A[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])?)*\z/
这个正则表达式上方的注释表明他们遵循了
https://html.spec.whatwg.org/multipage/input.html#valid-e-mail-address 的建议。
但是 WHATWG 规范添加了以下非常重要的注释:
这个需求是故意违反 RFC 5322 的规定,它定义了一种电子邮件地址的语法,同时太严格(在“@”字符之前),太模糊(在“@”字符之后),并且过于松散(允许以大多数用户不熟悉的方式使用注释、空白字符和带引号的字符串),因此在此处没有实际用途。
所以,WHATWG 告诉我们他们没有遵守标准化电子邮件地址格式的 RFC。他们说 RFC 5322 中的域部分太模糊了,但 RFC 5322 给出了这个注释,告诉我们必须检查其他 RFC 来获取更完整的域格式规范:
注意:此处提供了地址规范的自由语法。然而,域部分包含由其他协议(例如[RFC1034]、[RFC1035]、[RFC1123]、[RFC5321])指定和使用的寻址信息。因此,实现必须符合它们所用上下文中地址的语法。
WHATWG 还告诉我们,RFC 5322 中的本地部分过于严格。但是,请看遵循 WHATWG 规范的 URI::MailTo::EMAIL_REGEXP:
URI::MailTo::EMAIL_REGEXP.match?('.@toto.fr')
URI::MailTo::EMAIL_REGEXP.match?('-@z')
URI::MailTo::EMAIL_REGEXP.match?('++++++++.........@z')
相反,WHATWG规范(以及URI::MailTo::EMAIL_REGEXP
)过于宽松。
因此,我在https://emailregex.com/找到了一个通用电子邮件正则表达式(RFC 5322官方标准)(请参见summary)。
解释和替代方案可以在https://www.regular-expressions.info/email.html中找到。
# Blind RFC 5322
\A(?:[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])+)\])\z
# RFC 5322, practical version (omit IP addresses, domain-specific addresses, the syntax using double quotes and square brackets)
\A[a-z0-9!#$%&'*+/=?^_‘{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_‘{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\z
# RFC 5322, practical version (similar as previous + length limits enfocing)
\A(?=[a-z0-9@.!#$%&'*+/=?^_‘{|}~-]{6,254}\z)(?=[a-z0-9.!#$%&'*+/=?^_‘{|}~-]{1,64}@)[a-z0-9!#$%&'*+/=?^_‘{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_‘{|}~-]+)*@(?:(?=[a-z0-9-]{1,63}\.)[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+(?=[a-z0-9-]{1,63}\z)[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\z
正如您在下面的截图中所看到的,WHATWG / URI::MailTo::EMAIL_REGEXP
接受的地址都是无效的。
![invalid email addresses](https://istack.dev59.com/qx1Tm.webp)
让我们在本地做同样的事情:
RFC_5322 = /\A(?:[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])+)\])\z/i
现在我们可以比较两者(在 Ruby 3.2.0 上):
URI::MailTo::EMAIL_REGEXP.match?('.@toto.fr')
URI::MailTo::EMAIL_REGEXP.match?('-@z')
URI::MailTo::EMAIL_REGEXP.match?('++++++++.........@z')
URI::MailTo::EMAIL_REGEXP.match?('invalíd@mail.com')
URI::MailTo::EMAIL_REGEXP.match?('invalid%$£"@domain.com')
URI::MailTo::EMAIL_REGEXP.match?('invalid£@domain.com')
URI::MailTo::EMAIL_REGEXP.match?('invali"d@domain.com')
URI::MailTo::EMAIL_REGEXP.match?('.dot..dot.@example.org')
URI::MailTo::EMAIL_REGEXP.match?('!#$%’*+-/=?^_`{|}~@example.org')
URI::MailTo::EMAIL_REGEXP.match?('sometest@gmail.com')
URI::MailTo::EMAIL_REGEXP.match?('some+test@gmail.com')
URI::MailTo::EMAIL_REGEXP.match?('stuart.sillitoe@prodirectsport.net')
URI::MailTo::EMAIL_REGEXP.match?('_valid@mail.com')
URI::MailTo::EMAIL_REGEXP.match?('valid%$@domain.com')
URI::MailTo::EMAIL_REGEXP.match?('"valid"@domain.com')
RFC_5322.match?('.@toto.fr')
RFC_5322.match?('-@z')
RFC_5322.match?('++++++++.........@z')
RFC_5322.match?('invalíd@mail.com')
RFC_5322.match?('invalid%$£"@domain.com')
RFC_5322.match?('invalid£@domain.com')
RFC_5322.match?('invali"d@domain.com')
RFC_5322.match?('.dot..dot.@example.org')
RFC_5322.match?('!#$%’*+-/=?^_`{|}~@example.org')
RFC_5322.match?('sometest@gmail.com')
RFC_5322.match?('some+test@gmail.com')
RFC_5322.match?('stuart.sillitoe@prodirectsport.net')
RFC_5322.match?('_valid@mail.com')
RFC_5322.match?('valid%$@domain.com')
RFC_5322.match?('"valid"@domain.com')
RFC_5322_light.match?('.@toto.fr')
RFC_5322_light.match?('-@z')
RFC_5322_light.match?('++++++++.........@z')
RFC_5322_light.match?('invalíd@mail.com')
RFC_5322_light.match?('invalid%$£"@domain.com')
RFC_5322_light.match?('invalid£@domain.com')
RFC_5322_light.match?('invali"d@domain.com')
RFC_5322_light.match?('.dot..dot.@example.org')
RFC_5322_light.match?('!#$%’*+-/=?^_`{|}~@example.org')
RFC_5322_light.match?('sometest@gmail.com')
RFC_5322_light.match?('some+test@gmail.com')
RFC_5322_light.match?('stuart.sillitoe@prodirectsport.net')
RFC_5322_light.match?('_valid@mail.com')
RFC_5322_light.match?('valid%$@domain.com')
RFC_5322_light.match?('"valid"@domain.com')
警告,此测试尚未完成,且未涵盖所有情况。