- PVSM.RU - https://www.pvsm.ru -

Я знал, как валидировать email-адрес. Пока не прочитал RFC

От переводчика: прочитав статью [1], начал было отвечать в комментариях, но решил, что текст, на которую я собирался ссылаться, достоин отдельной публикации. Встречайте!

Если вы знаете, как валидировать email-адрес, поднимите руку. Те из вас, кто поднял руку — опустите её немедленно, пока вас кто-нибудь не увидел: это достаточно глупо — сидеть в одиночестве за клавиатурой с поднятой рукой; я говорил в переносном смысле.

До вчерашнего дня я бы тоже поднял руку (в переносном смысле). Мне нужно было проверить валидность email-адреса на сервере. Я это уже делал несколько сот тысяч раз (не шучу — я считал) при помощи классного регулярного выражения из моей личной библиотеки.

В этот раз меня почему-то потянуло ещё раз осмыслить мои предположения. Я никогда не читал (и даже не пролистывал) RFC по email-адресам. Я попросту основывал мою реализацию на основе того, что я подразумевал под корректным email-адресом. Ну, вы в курсе, что обычно говорят о том, кто подразумевает [2]. [прим. перев. Игра слов: «when you assume, you make an ass of you and me» — «когда вы подразумеваете, вы делаете /./удака из себя и из меня»]

И обнаружил кое-что занимательное: почти все регулярные выражения, представлены в интернете как «проверяющие корректность email-адреса», излишне строги.

Оказывается, что локальная часть email-адреса — то, что перед знаком "@" — допускает гораздо более широкое разнообразие символов, чем вы думаете. Согласно разделу 2.3.10 RFC 2821, который определяет SMTP, часть перед знаком "@" называется локальной частью (часть после знака — это домен получателя) и предназначена для интерпретации исключительно сервером получателя.

Следовательно — и благодаря длинной череде проблем, вызванных промежуточными хостами, пытавшимися оптимизировать передачу путём изменения их [адресов — перев.], локальная часть ДОЛЖНА быть интерпретирована (и ей должен быть назначен семантический смысл) исключительно сервером, указанным в доменной части адреса.

Раздел 3.4.1 [3] RFC 2822 [4] описывает дополнительные детали спецификации email-адреса (выделено мной — авт.).

Адресная спецификация представляет собой определённый идентификатор в сети Internet, содержащий локально интерпретируемую строку, за которой следует знак «эт» ("@", ASCII-код 64), за которым, в свою очередь, следует домен сети Internet. Локально интерпретируемая строка представляет собой либо обрамлённую кавычками строку, либо точечный атом.

Точечный атом — это набор атомов, разделённых точками. В свою очередь, атом определён в разделе 3.2.4 [5] как набор алфавитно-цифровых символов и может включать в себя любые из нижеследующих символов (знаете, те самые, которыми обычно заменяют мат)…

! $ & * - = ^ ` | ~ # % ' + / ? _ { }

Более того, вполне допустимо (хотя не рекомендуется и редко где применяется) иметь закавыченные локальные части, в которых допустимы почти любые символы. Закавычивание может производится либо при помощи символа обратной черты, либо путём обрамления локальной части двойными кавычками.

RFC 3696 [6], Application Techniques for Checking and Transformation of Names, был написан автором протокола SMTP (RFC 2821 [7]) как человекочитаемое руководство по эксплуатации SMTP. В третьем разделе он приводит примеры корректных email-адресов.

Это таки корректные email-адреса!

  • "Abc@def"@example.com
  • "Fred Bloggs"@example.com
  • "Joe\Blow"@example.com
  • "Abc@def"@example.com
  • customer/department=shipping@example.com
  • $A12345@example.com
  • !def!xyz%abc@example.com
  • _somename@example.com

(Аплодисменты автору за использование моей любимой версии Васи Пупкина, Joe Blow.)

Ну-ка, прогоните их через ваш любимый валидатор. Ну как, много прошло?

По приколу я решил попробовать написать регулярное выражение (спасибо, мне уже доложили, теперь у меня две проблемы [8]), через которое они все прошли бы. Вот оно.

^(?!.)("([^"r\]|\["r\])*"|([-a-z0-9!#$%&'*+/=?^_`{|}~] |(?@[a-z0-9][w.-]*[a-z0-9].[a-z][a-z.]*[a-z]$

Учтите, что это выражение подразумевает, что чувствительность к регистру выключена (RegexOptions.IgnoreCase в .NET). Да, весьма уродливое выражение.

Я написал юнит-тест, чтобы продемонстрировать все случаи, которые оно покрывает. Каждая строка содержит email-адрес и флаг — является он корректным или нет.

[RowTest]
[Row(@"NotAnEmail", false)]
[Row(@"@NotAnEmail", false)]
[Row(@"""test\blah""@example.com", true)]
[Row(@"""testblah""@example.com", false)]
[Row(""test\rblah"@example.com", true)]
[Row(""testrblah"@example.com", false)]
[Row(@"""test""blah""@example.com", true)]
[Row(@"""test""blah""@example.com", false)]
[Row(@"customer/department@example.com", true)]
[Row(@"$A12345@example.com", true)]
[Row(@"!def!xyz%abc@example.com", true)]
[Row(@"_Yosemite.Sam@example.com", true)]
[Row(@"~@example.com", true)]
[Row(@".wooly@example.com", false)]
[Row(@"wo..oly@example.com", false)]
[Row(@"pootietang.@example.com", false)]
[Row(@".@example.com", false)]
[Row(@"""Austin@Powers""@example.com", true)]
[Row(@"Ima.Fool@example.com", true)]
[Row(@"""Ima.Fool""@example.com", true)]
[Row(@"""Ima Fool""@example.com", true)]
[Row(@"Ima Fool@example.com", false)]

public void EmailTests(string email, bool expected)
{
  string pattern = @"^(?!.)(""([^""r\]|\[""r\])*""|" 
    + @"([-a-z0-9!#$%&'*+/=?^_`{|}~]|(?<!.).)*)(?<!.)" 
    + @"@[a-z0-9][w.-]*[a-z0-9].[a-z][a-z.]*[a-z]$";

  Regex regex = new Regex(pattern, RegexOptions.IgnoreCase);
  Assert.AreEqual(expected, regex.IsMatch(email)
    , "Problem with '" + email + "'. Expected "  
    + expected + " but was not that.");
}

Прежде, чем вы назовёте меня жутким занудой и педантом (может, вы и правы, но всё равно погодите), я не думаю, что настолько глубокая проверка email-адресов абсолютно необходима. Большинство email-провайдеров имеют более строгие требования к email-адесам. Например, Yahoo требует, чтобы адрес начинался с буквы. Похоже, что имеется стандартизированный более строгий набор правил, которому следует большинство email-провайдеров, но насколько мне известно, он нигде не задокмунтирован.

Думаю, я создам email-адрес типа phil.h@@ck@haacked.com и начну жаловаться в техподдержку на сайтах, которые требуют ввода email-адреса, но не позволяют мне создать учётную запись с этим адресом. Люблю шалить!

Мораль заключается в том, что полезно время от времени бросать вызов предрассудкам и предположениям, а также никогда не подпускать меня к RFC.

P.S. Исправил несколько ошибок, которые я сделал в моём прочтении RFC. Видите? Даже прочитав RFC, я всё ещё не уверен в том, что же я, блин, делаю! Что ещё раз подтверждает тезис о том, что программисты — не читатели [9].

Автор: Wesha

Источник [10]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/veb-dizajn/108772

Ссылки в тексте:

[1] статью: http://habrahabr.ru/company/pechkin/blog/274903/

[2] говорят о том, кто подразумевает: http://jyte.com/cl/when-you-assume-you-make-an-ass-out-of-you-and-me

[3] Раздел 3.4.1: http://tools.ietf.org/html/rfc2822#section-3.4.1

[4] RFC 2822: http://tools.ietf.org/html/rfc2822

[5] разделе 3.2.4: http://tools.ietf.org/html/rfc2822#3.2.4

[6] RFC 3696: http://tools.ietf.org/html/rfc3696

[7] RFC 2821: http://tools.ietf.org/html/rfc2821

[8] теперь у меня две проблемы: http://stproject.info/blog/?p=2968

[9] программисты — не читатели: http://haacked.com/archive/2007/02/27/Why_Cant_Programmers._Read.aspx/

[10] Источник: http://habrahabr.ru/post/274985/