- PVSM.RU - https://www.pvsm.ru -
От переводчика: прочитав статью [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/
Нажмите здесь для печати.