Validação de email

10

Escreva uma função ou programa para validar um endereço de email no RFC 5321 (algumas regras gramaticais encontradas no 5322 ) com o relaxamento que você pode ignorar comentários e dobrar espaços em branco ( CFWS) e literais de endereço generalizados. Isso fornece a gramática

Mailbox              = Local-part "@" ( Domain / address-literal )

Local-part           = Dot-string / Quoted-string
Dot-string           = Atom *("."  Atom)
Atom                 = 1*atext
atext                = ALPHA / DIGIT /    ; Printable US-ASCII
                       "!" / "#" /        ;  characters not including
                       "$" / "%" /        ;  specials.  Used for atoms.
                       "&" / "'" /
                       "*" / "+" /
                       "-" / "/" /
                       "=" / "?" /
                       "^" / "_" /
                       "`" / "{" /
                       "|" / "}" /
                       "~"
Quoted-string        = DQUOTE *QcontentSMTP DQUOTE
QcontentSMTP         = qtextSMTP / quoted-pairSMTP
qtextSMTP            = %d32-33 / %d35-91 / %d93-126
quoted-pairSMTP      = %d92 %d32-126

Domain               = sub-domain *("." sub-domain)
sub-domain           = Let-dig [Ldh-str]
Let-dig              = ALPHA / DIGIT
Ldh-str              = *( ALPHA / DIGIT / "-" ) Let-dig

address-literal      = "[" ( IPv4-address-literal / IPv6-address-literal ) "]"
IPv4-address-literal = Snum 3("."  Snum)
IPv6-address-literal = "IPv6:" IPv6-addr
Snum                 = 1*3DIGIT
                       ; representing a decimal integer value in the range 0 through 255

Nota: Eu pulei a definição de IPv6-addrporque esta RFC específica erra e desaprova, por exemplo ::1. A especificação correta está na RFC 2373 .

Restrições

Você não pode usar nenhuma chamada existente da biblioteca de validação de email. No entanto, você pode usar as bibliotecas de rede existentes para verificar os endereços IP.

Se você escrever uma função / método / operador / equivalente, ela deve pegar uma string e retornar um valor booleano ou verdade / falso, conforme apropriado para o seu idioma. Se você escrever um programa, ele deve usar uma única linha do stdin e indicar válido ou inválido através do código de saída.

Casos de teste

Os seguintes casos de teste estão listados em blocos para compactação. O primeiro bloco são casos que devem passar:

[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
email@[123.123.123.123]
"email"@domain.com
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
""@domain.com
"e"@domain.com
"\@"@domain.com
email@domain
"Abc\@def"@example.com
"Fred Bloggs"@example.com
"Joe\\Blow"@example.com
"Abc@def"@example.com
customer/[email protected]
[email protected]
!def!xyz%[email protected]
[email protected]
_somename@[IPv6:::1]
[email protected]
[email protected]
[email protected]

Os seguintes casos de teste não devem passar:

plainaddress
#@%^%#$@#$@#.com
@domain.com
Joe Smith <[email protected]>
email.domain.com
email@[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected] (Joe Smith)
[email protected]
[email protected]
email@[IPv6:127.0.0.1]
email@[127.0.0]
email@[.127.0.0.1]
email@[127.0.0.1.]
email@IPv6:::1]
[email protected]]
email@[256.123.123.123]
Peter Taylor
fonte
como IPv6-addrfoi deixado indefinido e há casos de teste com endereços IPv6, existe uma maneira correta de validá-los?
23413 ardnew
Por que deveria [email protected]e [email protected]falhar?
grc
11
@ardnew, adicionei um link para a RFC relevante. Não quero incorporá-lo porque a pergunta já é bastante longa.
Peter Taylor
@ GRC, boa pergunta. Eu as verifiquei, porque ninguém levantou isso durante os vários meses em que a pergunta estava na caixa de areia , mas não consigo ver por que elas deveriam falhar, então as mudei para o lado "Passar".
Peter Taylor
Os limites de comprimento também são necessários? 254 para o endereço de e-mail inteiro / 64 para a parte local / 63 para cada rótulo de domínio?
precisa saber é o seguinte

Respostas:

2

Python 3.3, 261

import re,ipaddress
try:v,p=re.match(r'^(?!\.)(((^|\.)[\w!#-\'*+\-/=?^-~]+)+|"([ !#-[\]-~]|\\[ -~])*")@(((?!-)[a-zA-Z\d-]+(?<!-)($|\.))+|\[(IPv6:)?(.*)\])(?<!\.)$',input()).groups()[7:];exec("if p:ipaddress.IPv%dAddress(p)"%(v and 6or 4))
except:v=5
print(v!=5)

O Python 3.3 é necessário para o módulo ipaddress, usado para validar endereços IPv4 e IPv6.

Versão menos golfe:

import re, ipaddress

dot_string = r'(?!\.)((^|\.)[\w!#-\'*+\-/=?^-~]+)+'
    # negative lookahead to check that string doesn't start with .
    # each atom must start with a . or the beginning of the string

quoted_string = r'"([ !#-[\]-~]|\\[ -~])*"'
    # - is used for character ranges (also in dot_string)

domain = r'((?!-)[a-zA-Z\d-]+(?<!-)($|\.))+(?<!\.)'
    # negative lookahead/lookbehind to check each subdomain doesn't start/end with -
    # each domain must end with a . or the end of the string
    # negative lookbehind to check that string doesn't end with .

address_literal = r'\[(IPv6:)?(.*)\]'
    # captures the is_IPv6 and ip_address groups

final_regex = r'^(%s|%s)@(%s|%s)$' % (dot_string, quoted_string, domain, address_literal)

try:
    is_IPv6, ip_address = re.match(final_regex, input(), re.VERBOSE).groups()[7:]
        # if input doesn't match, calling .groups() will throw an exception

    if ip_address:
        exec("ipaddress.IPv%dAddress(ip_address)" % (6 if is_IPv6 else 4))
            # IPv4Address or IPv6Address will throw an exception if ip_address isn't valid
except:
    is_IPv6 = 5

print(is_IPv6 != 5)
    # is_IPv6 is used as a flag to tell whether an exception was thrown
grc
fonte
muito agradável. Não consigo encontrar imediatamente nenhum padrão duplicado (para substituir por um identificador de variável mais curto). mas parece que ALPHAno BNF aumentado e os literais de caracteres que constroem a Quoted-stringnão diferenciam maiúsculas de minúsculas. você pode raspar alguns caracteres especificando a distinção entre maiúsculas e minúsculas e abandonando um desses intervalos de classe de caracteres? btw, se você estiver se sentindo brincalhão, pode dar uma breve descrição de como você desenvolveu isso?
24413 ardnew
@ardnew: Obrigado. Eu adicionei uma versão menos golfe com alguns comentários tentando explicar algumas das partes mais complicadas. Desenvolvi o regex em quatro partes individuais (sequência de pontos, sequência entre aspas, domínio e literal de endereço), depois as juntei e adicionei a validação de IP. Escusado será dizer que o golfe ficou muito confuso.
grc
Sem limites de comprimento?
MichaelRushton
2

PHP 5.4.9, 495

function _($e){return preg_match('/^(?!(?>"?(?>\\\[ -~]|[^"])"?){255,})(?!"?(?>\\\[ -~]|[^"]){65,}"?@)(?>([!#-\'*+\/-9=?^-~-]+)(?>\.(?1))*|"(?>[ !#-\[\]-~]|\\\[ -~])*")@(?!.*[^.]{64,})(?>([a-z0-9](?>[a-z0-9-]*[a-z0-9])?)(?>\.(?2)){0,126}|\[(?:(?>IPv6:(?>([a-f0-9]{1,4})(?>:(?3)){7}|(?!(?:.*[a-f0-9][:\]]){8,})((?3)(?>:(?3)){0,6})?::(?4)?))|(?>(?>IPv6:(?>(?3)(?>:(?3)){5}:|(?!(?:.*[a-f0-9]:){6,})(?5)?::(?>((?3)(?>:(?3)){0,4}):)?))?(25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d)(?>\.(?6)){3}))\])$/iD', $e);}

E apenas para mais interesse, aqui está uma gramática RFC 5322 que permite CFWS aninhados e peças locais obsoletas:

(764)

function _($e){return preg_match('/^(?!(?>(?1)"?(?>\\\[ -~]|[^"])"?(?1)){255,})(?!(?>(?1)"?(?>\\\[ -~]|[^"])"?(?1)){65,}@)((?>(?>(?>((?>(?>(?>\x0D\x0A)?[\t ])+|(?>[\t ]*\x0D\x0A)?[\t ]+)?)(\((?>(?2)(?>[\x01-\x08\x0B\x0C\x0E-\'*-\[\]-\x7F]|\\\[\x00-\x7F]|(?3)))*(?2)\)))+(?2))|(?2))?)([!#-\'*+\/-9=?^-~-]+|"(?>(?2)(?>[\x01-\x08\x0B\x0C\x0E-!#-\[\]-\x7F]|\\\[\x00-\x7F]))*(?2)")(?>(?1)\.(?1)(?4))*(?1)@(?!(?1)[a-z\d-]{64,})(?1)(?>([a-z\d](?>[a-z\d-]*[a-z\d])?)(?>(?1)\.(?!(?1)[a-z\d-]{64,})(?1)(?5)){0,126}|\[(?:(?>IPv6:(?>([a-f\d]{1,4})(?>:(?6)){7}|(?!(?:.*[a-f\d][:\]]){8,})((?6)(?>:(?6)){0,6})?::(?7)?))|(?>(?>IPv6:(?>(?6)(?>:(?6)){5}:|(?!(?:.*[a-f\d]:){6,})(?8)?::(?>((?6)(?>:(?6)){0,4}):)?))?(25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d)(?>\.(?9)){3}))\])(?1)$/isD', $e);}

E se os limites de comprimento não forem um requisito:

RFC 5321 (414)

function _($e){return preg_match('/^(?>([!#-\'*+\/-9=?^-~-]+)(?>\.(?1))*|"(?>[ !#-\[\]-~]|\\\[ -~])*")@(?>([a-z0-9](?>[a-z0-9-]*[a-z0-9])?)(?>\.(?2)){0,126}|\[(?:(?>IPv6:(?>([a-f0-9]{1,4})(?>:(?3)){7}|(?!(?:.*[a-f0-9][:\]]){8,})((?3)(?>:(?3)){0,6})?::(?4)?))|(?>(?>IPv6:(?>(?3)(?>:(?3)){5}:|(?!(?:.*[a-f0-9]:){6,})(?5)?::(?>((?3)(?>:(?3)){0,4}):)?))?(25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d)(?>\.(?6)){3}))\])$/iD', $e);}

RFC 5322 (636)

function _($e){return preg_match('/^((?>(?>(?>((?>(?>(?>\x0D\x0A)?[\t ])+|(?>[\t ]*\x0D\x0A)?[\t ]+)?)(\((?>(?2)(?>[\x01-\x08\x0B\x0C\x0E-\'*-\[\]-\x7F]|\\\[\x00-\x7F]|(?3)))*(?2)\)))+(?2))|(?2))?)([!#-\'*+\/-9=?^-~-]+|"(?>(?2)(?>[\x01-\x08\x0B\x0C\x0E-!#-\[\]-\x7F]|\\\[\x00-\x7F]))*(?2)")(?>(?1)\.(?1)(?4))*(?1)@(?1)(?>([a-z\d](?>[a-z\d-]*[a-z\d])?)(?>(?1)\.(?1)(?5)){0,126}|\[(?:(?>IPv6:(?>([a-f\d]{1,4})(?>:(?6)){7}|(?!(?:.*[a-f\d][:\]]){8,})((?6)(?>:(?6)){0,6})?::(?7)?))|(?>(?>IPv6:(?>(?6)(?>:(?6)){5}:|(?!(?:.*[a-f\d]:){6,})(?8)?::(?>((?6)(?>:(?6)){0,4}):)?))?(25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d)(?>\.(?9)){3}))\])(?1)$/isD', $e);}
MichaelRushton
fonte