От переводчика: прочитав статью, начал было отвечать в комментариях, но решил, что текст, на которую я собирался ссылаться, достоин отдельной публикации. Встречайте!Если вы знаете, как валидировать email-адрес, поднимите руку. Те из вас, кто поднял руку — опустите её немедленно, пока вас кто-нибудь не увидел: это достаточно глупо — сидеть в одиночестве за клавиатурой с поднятой рукой; я говорил в переносном смысле.
До вчерашнего дня я бы тоже поднял руку (в переносном смысле). Мне нужно было проверить валидность email-адреса на сервере. Я это уже делал несколько сот тысяч раз (не шучу — я считал) при помощи классного регулярного выражения из моей личной библиотеки.
В этот раз меня почему-то потянуло ещё раз осмыслить мои предположения. Я никогда не читал (и даже не пролистывал) RFC по email-адресам. Я попросту основывал мою реализацию на основе того, что я подразумевал под корректным email-адресом. Ну, вы в курсе, что обычно говорят о том, кто подразумевает. [прим. перев. Автор имеет в виду игру слов: «when you assume, you make an ass of you and me» — «когда вы (что-то) подразумеваете, вы делаете /./удака из себя и из меня»]
И обнаружил кое-что занимательное: почти все регулярные выражения, представлены в интернете как «проверяющие корректность email-адреса», излишне строги.
Оказывается, что локальная часть email-адреса — то, что перед знаком "@" — допускает гораздо более широкое разнообразие символов, чем вы думаете. Согласно разделу 2.3.10 RFC 2821, который определяет SMTP, часть перед знаком "@" называется локальной частью (часть после знака — это домен получателя) и предназначена для интерпретации исключительно сервером получателя.
Следовательно — и благодаря длинной череде проблем, вызванных промежуточными хостами, пытавшимися оптимизировать передачу путём изменения их [адресов — перев.], локальная часть ДОЛЖНА быть интерпретирована (и ей должен быть назначен семантический смысл) исключительно сервером, указанным в доменной части адреса.Раздел 3.4.1 RFC 2822 описывает дополнительные детали спецификации email-адреса (выделено мной — авт.).
Адресная спецификация представляет собой определённый идентификатор в сети Internet, содержащий локально интерпретируемую строку, за которой следует знак «эт» ("@", ASCII-код 64), за которым, в свою очередь, следует домен сети Internet. Локально интерпретируемая строка представляет собой либо обрамлённую кавычками строку, либо точечный атом.Точечный атом — это набор атомов, разделённых точками. В свою очередь, атом определён в разделе 3.2.4 как набор алфавитно-цифровых символов и может включать в себя любые из нижеследующих символов (знаете, те самые, которыми обычно заменяют мат)…
! \$ & * - = ^ ` | ~ # % ' + / ? _ { }
Более того, вполне допустимо (хотя не рекомендуется и редко где применяется) иметь закавыченные локальные части, в которых допустимы почти любые символы. Закавычивание может производится либо при помощи символа обратной черты, либо путём обрамления локальной части двойными кавычками.
RFC 3696, Application Techniques for Checking and Transformation of Names, был написан автором протокола SMTP (RFC 2821) как человекочитаемое руководство по эксплуатации 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
(Аплодисменты автору RFC за использование моей любимой версии Васи Пупкина — Joe Blow.)
Ну-ка, прогоните их через ваш любимый валидатор. Ну как, много прошло?
По приколу я решил попробовать написать регулярное выражение (спасибо, мне уже доложили, теперь у меня две проблемы), через которое они все прошли бы. Вот оно.
^(?!\.)("([^"\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(@"""test\blah""@example.com", false)]
[Row("\"test\\\rblah\"@example.com", true)]
[Row("\"test\rblah\"@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, я всё ещё не уверен в том, что же я, блин, делаю! Что ещё раз подтверждает тезис о том, что программисты — не читатели.
Комментарии (90)
SergeyGrigorev
13.01.2016 09:18+9Большое количество сайтов, где регистрировался, не понимают символа + в адресе, и это меня прям крайне печалит. А ведь он разрешен и более того, крайне удобен. Например user+habr@mail будет приходить к пользователю user, но он сможет выставить фильтр для адресата user+habr, чтобы распределять папки, настраивать уведомления.
merlin-vrn
13.01.2016 09:59+2Более того, не всегда надо выставлять фильтр. В Cyrus IMAP, если у вас есть пользователь user@domain.com (его INBOX будет user.user@domain.com) и у него есть ящик user.user.sub@domain.com (дочердний sub в INBOX), и у ящика sub стоит ACL anyone p (кому угодно можно постить) или хотя бы p для lmtp-аккаунта, под которым MTA осуществляет доставку почты в Cyrus, то письмо на user+sub@domain.com сразу попадёт в дочерний ящик, без каких-либо дополнительных правил. Если ящик глубже (user.user.sub.another@domain.com), то писать надо на user+sub.another@domain.com. А если создать «ничейный» общий ящик common@domain.com и дать ему ACL anyone p, то можно написать на +common@domain.com и письмо попадёт в этот ящик.
Раузмеется, если MTA настроен на обработку подобных случаев. Postfix умеет: у него есть отдельный конфигурационный параметр «разделитель», который по умолчанию (или рекомендуется, я не помню точно) имеет значение "+", и если это включено, то при проверке существования локального адреса user+sub@domain.com он будет делать запросы и для user@domain.com.
Visphord
13.01.2016 10:00+2Для себя настроил на своем домене сборку почты для всех неизвестных адресов в ящик spam@domain.ru и радостно регистрируюсь на всех сайтах с почтой вида habr@domain.ru, mailru@domain.ru, etc :)
Такую фичу имеют многие почтовые сервера, но в этом случае конечно нужно иметь свой домен.Pilat
13.01.2016 10:51Это оказывается очень неудобно, если Вы захотите перенести свою почту на какой-нибудь gmail.
mobilesfinks
13.01.2016 12:23Ну и переносите. У gmail так же можно подключить свой домен. В чём проблема? А можно почту на gmail сделать и сборщики настроить. Не ищите проблемы там где её нет.
Вообще я сначала на gmail почту поднял для организации, а потом оттуда переехал на яндекс. Ноу проблем. Проблема была только в размере архива писем (>50 гигов, а может и больше, уже точно не помню, даты писем с 2002 года и несколько писем ещё старше были)
mOlind
14.01.2016 11:36В ПдД от Яндекса так же можно настроить ящик по умолчанию и в него будут сыпаться все письма, которые не нашли явного получателя.
metis
22.01.2016 09:42+1Меня как-то опечалило, что много сайтов не принимают казалось бы валидный символ минуса/тире если его ставить дважды (user--name@domain.com).
bolk
13.01.2016 09:39+18Уже много было написано статей по этому поводу и лучшая из них была из одной строки: адрес почты проверять не надо.
alemiks
13.01.2016 11:48+10думаю, хотя бы на @ проверить надо, ибо это единственный символ в большинстве случаев, который вводится с зажатым шифтом, можно промахнуться
Albertum
13.01.2016 09:44+2Про валидацию email очень подробно описано описано в книге «Регулярные выражения» Джеффри Фридла www.ozon.ru/context/detail/id/4066500
В качестве приложения приводится выражение, которое проверяет соответствие email RFC, это выражение занимает несколько страниц :)
medvoodoo
13.01.2016 09:54-2Не знаю, как у вас, а у нас это не одно регулярное выражение, а много, т.к. у нас емейл активно используется в тех же рассылках, и периодически идут жалобы от людей, которые ввели и @ya.ru и yandex.ru или гмейл пяти видов с точками. Если у вас 1.5 землекопа на сайте, то имеет смысл использовать регулярки согласно rfc, а если сотни тысяч людей, при том многие ждут от вас писем, то вы потонете в bounce и жалобах от невнимательных пользователей.
merlin-vrn
13.01.2016 10:02+1Всё жду, когда кто-нибудь напишет такую же статью, но с учётом национальных TLD.
Хороший был бы емейл вася@пупкин.рф. Упс, а что делать с кодировкой локальной части?SelenIT2
13.01.2016 11:38AFAIK, национальные домены в адресах сначала преобразуются в punycode, а только затем валидируются на общих основаниях.
merlin-vrn
13.01.2016 11:42+1Домены — да. Я говорю про локальную часть адреса («вася» в данном случае). Она не кодируется никаким punycode, и правила обработки определяются исключительно сервером получателя (см. текст статьи).
sledopit
13.01.2016 12:09-2В локальной части нельзя использовать кириллицу. Только латиница. Поэтому вася@мыло.рф работать сейчас не будет.
Подробности можете в вики посмотреть.merlin-vrn
13.01.2016 12:18+3Прямо по вашей же ссылке:
In addition to the above ASCII characters, international characters above U+007F, encoded as UTF-8, are permitted by RFC 6531
Может это мне приснилось, но раньше (до RFC 6531) не было требования UTF-8. Просто не было. Было можно что угодно, за небольшим исключением, и всё. А как это понимать, решает получатель.sledopit
13.01.2016 12:40+1А, да. Действительно. Всё уже поменялось с тех пор, как я туда заглядывал в последний раз.
Спасибо за подсказку.Wesha
13.01.2016 18:31+4^^^^^^^^
И вот здесь, уважаемые коллеги, мы собственными глазами наблюдаем очередное подтверждние тезиса автора статьи о том, чточукчипрограммисты — не читатели.Dreyk
14.01.2016 14:10+4не совсем так. программисту ставят задачу -> он прочел RFC -> заимплеменил -> RFC поменяли -> программист не в курсе
merlin-vrn
15.01.2016 08:33+1Ой расскажите это Rit Labs. Наверное, ни один почтовый RFC ни в одной редакции не заимплементили правильно в The Bat.
Вот уж точно не читатели.
Visphord
13.01.2016 10:05+2P.S. Если автор использует .NET то зачем регулярные выражения? Почему бы не использовать встроенные средства для валидации? К примеру вот.
Einherjar
13.01.2016 11:05+5там кстати внутри этого атрибута регулярное выражение интересное очень. то что в статье по сравнению с этим выглядит как то бледновато:
^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?$
evocatus
13.01.2016 10:22+1Какое там!
У меня в адресе на GMail есть точка. Когда я её регистрировал там в качестве примера имени ящика был ящик вида name.surname@gmail.com.
Знаете, кто меня не пустил регистрироваться с таким адресом? Foursquare! Пришлось пользоваться тем фактом, что точка в имени e-mail игнорируется и писать тот же адрес без точки.merlin-vrn
13.01.2016 11:44+1Игнорирутся Gmail-ом. А другими, вообще говоря, не игнорируется. Я тут выше упоминал Cyrus IMAP, там в именах ящиков точка — недопустимый символ (в конфигурации по умолчанию).
Melkij
13.01.2016 11:02+6Я знаю: в адресе должен быть хотя бы один символ @ и хотя бы по одному байту до и после.
через которое они все прошли бы. Вот оно.
Коротенькое уж больно. Читайте RFC дальше. filter_var из PHP в исходнике регулярку за килобайт размером имеет.
github.com/php/php-src/blob/PHP-7.0.2/ext/filter/logical_filters.c#L575
Кстати, в комментариях в коду есть ответ на популярный вопрос «какого размера делать поле в БД»: 320 байт. согласно RFC 2821.alemiks
13.01.2016 11:54+2вы хотели сказать 254? stackoverflow.com/questions/386294/what-is-the-maximum-length-of-a-valid-email-address
Melkij
13.01.2016 12:07+3О как, спасибо. Да, я не нашёл времени прочитать все rfc посвящённые почте, только некоторые =)
tools.ietf.org/html/rfc5321#section-4.5.3
The maximum total length of a user name or other local-part is 64 octets.
The maximum total length of a domain name or number is 255 octets.
Вот они, исходные 320 байт
Но сверху ограничивает RFC 2821, который принимает только 254 октета.
outcoldman
13.01.2016 18:24На сколько мне известно, в interanet вы можете настроить email и без домена. Отправлять тупо по имени.
Wesha
13.01.2016 18:34Вы хотели сказать, в рамках одного хоста.
outcoldman
13.01.2016 18:39Не изменяет моего предложения. Я же не говорю как, говорю, что " в intranet можете настроить". С использованием default host/domain на mail server.
Wesha
13.01.2016 19:17В Вашем предложении использован несуществующий термин «interanet», поэтому я уточнил.
RolexStrider
13.01.2016 11:02+7RFC822? Регуляркой?
Есть такая(?:(?:\r\n)?[ \t])*(?:(?:(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:\r\n)?[ \t] )+|\Z|(?=[\["()<>@,;:\\".\[\]]))|"(?:[^\"\r\\]|\\.|(?:(?:\r\n)?[ \t]))*"(?:(?: \r\n)?[ \t])*)(?:\.(?:(?:\r\n)?[ \t])*(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:( ?:\r\n)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|"(?:[^\"\r\\]|\\.|(?:(?:\r\n)?[ \t]))*"(?:(?:\r\n)?[ \t])*))*@(?:(?:\r\n)?[ \t])*(?:[^()<>@,;:\\".\[\] \000-\0 31]+(?:(?:(?:\r\n)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|\[([^\[\]\r\\]|\\.)*](?:(?:\r\n)?[ \t])*)(?:\.(?:(?:\r\n)?[ \t])*(?:[^()<>@,;:\\".\[\] \000-\031]+ (?:(?:(?:\r\n)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|\[([^\[\]\r\\]|\\.)*\](?: (?:\r\n)?[ \t])*))*|(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:\r\n)?[ \t])+|\Z |(?=[\["()<>@,;:\\".\[\]]))|"(?:[^\"\r\\]|\\.|(?:(?:\r\n)?[ \t]))*"(?:(?:\r\n) ?[ \t])*)*\<(?:(?:\r\n)?[ \t])*(?:@(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:r\n)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|\[([^\[\]\r\\]|\\.)*\](?:(?:\r\n)?[ \t])*)(?:\.(?:(?:\r\n)?[ \t])*(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:\r\n) ?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|\[([^\[\]\r\\]|\\.)*\](?:(?:\r\n)?[ \t] )*))*(?:,@(?:(?:\r\n)?[ \t])*(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:\r\n)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|\[([^\[\]\r\\]|\\.)*\](?:(?:\r\n)?[ \t])* )(?:\.(?:(?:\r\n)?[ \t])*(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:\r\n)?[ \t] )+|\Z|(?=[\["()<>@,;:\\".\[\]]))|\[([^\[\]\r\\]|\\.)*\](?:(?:\r\n)?[ \t])*))*) *:(?:(?:\r\n)?[ \t])*)?(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:\r\n)?[ \t])+ |\Z|(?=[\["()<>@,;:\\".\[\]]))|"(?:[^\"\r\\]|\\.|(?:(?:\r\n)?[ \t]))*"(?:(?:\r \n)?[ \t])*)(?:\.(?:(?:\r\n)?[ \t])*(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?: \r\n)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|"(?:[^\"\r\\]|\\.|(?:(?:\r\n)?[ \t ]))*"(?:(?:\r\n)?[ \t])*))*@(?:(?:\r\n)?[ \t])*(?:[^()<>@,;:\\".\[\] \000-\031 ]+(?:(?:(?:\r\n)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|\[([^\[\]\r\\]|\\.)*\]( ?:(?:\r\n)?[ \t])*)(?:\.(?:(?:\r\n)?[ \t])*(?:[^()<>@,;:\\".\[\] \000-\031]+(? :(?:(?:\r\n)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|\[([^\[\]\r\\]|\\.)*\](?:(? :\r\n)?[ \t])*))*\>(?:(?:\r\n)?[ \t])*)|(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(? :(?:\r\n)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|"(?:[^\"\r\\]|\\.|(?:(?:\r\n)? [ \t]))*"(?:(?:\r\n)?[ \t])*)*:(?:(?:\r\n)?[ \t])*(?:(?:(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:\r\n)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|"(?:[^\"\r\\]| \\.|(?:(?:\r\n)?[ \t]))*"(?:(?:\r\n)?[ \t])*)(?:\.(?:(?:\r\n)?[ \t])*(?:[^()<> @,;:\\".\[\] \000-\031]+(?:(?:(?:\r\n)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|" (?:[^\"\r\\]|\\.|(?:(?:\r\n)?[ \t]))*"(?:(?:\r\n)?[ \t])*))*@(?:(?:\r\n)?[ \t] )*(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:\r\n)?[ \t])+|\Z|(?=[\["()<>@,;:\".\[\]]))|\[([^\[\]\r\\]|\\.)*\](?:(?:\r\n)?[ \t])*)(?:\.(?:(?:\r\n)?[ \t])*(? :[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:\r\n)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[ \]]))|\[([^\[\]\r\\]|\\.)*\](?:(?:\r\n)?[ \t])*))*|(?:[^()<>@,;:\\".\[\] \000- \031]+(?:(?:(?:\r\n)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|"(?:[^\"\r\\]|\\.|( ?:(?:\r\n)?[ \t]))*"(?:(?:\r\n)?[ \t])*)*\<(?:(?:\r\n)?[ \t])*(?:@(?:[^()<>@,; :\\".\[\] \000-\031]+(?:(?:(?:\r\n)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|\[([ ^\[\]\r\\]|\\.)*\](?:(?:\r\n)?[ \t])*)(?:\.(?:(?:\r\n)?[ \t])*(?:[^()<>@,;:\\" .\[\] \000-\031]+(?:(?:(?:\r\n)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|\[([^\[]\r\\]|\\.)*\](?:(?:\r\n)?[ \t])*))*(?:,@(?:(?:\r\n)?[ \t])*(?:[^()<>@,;:\\".[\] \000-\031]+(?:(?:(?:\r\n)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|\[([^\[\]r\\]|\\.)*\](?:(?:\r\n)?[ \t])*)(?:\.(?:(?:\r\n)?[ \t])*(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:\r\n)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|\[([^\[\]\r\\] |\\.)*\](?:(?:\r\n)?[ \t])*))*)*:(?:(?:\r\n)?[ \t])*)?(?:[^()<>@,;:\\".\[\] \0 00-\031]+(?:(?:(?:\r\n)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|"(?:[^\"\r\\]|\.|(?:(?:\r\n)?[ \t]))*"(?:(?:\r\n)?[ \t])*)(?:\.(?:(?:\r\n)?[ \t])*(?:[^()<>@, ;:\\".\[\] \000-\031]+(?:(?:(?:\r\n)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|"(? :[^\"\r\\]|\\.|(?:(?:\r\n)?[ \t]))*"(?:(?:\r\n)?[ \t])*))*@(?:(?:\r\n)?[ \t])* (?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:\r\n)?[ \t])+|\Z|(?=[\["()<>@,;:\\". \[\]]))|\[([^\[\]\r\\]|\\.)*\](?:(?:\r\n)?[ \t])*)(?:\.(?:(?:\r\n)?[ \t])*(?:[ ^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:\r\n)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\] ]))|\[([^\[\]\r\\]|\\.)*\](?:(?:\r\n)?[ \t])*))*\>(?:(?:\r\n)?[ \t])*)(?:,\s*( ?:(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:\r\n)?[ \t])+|\Z|(?=[\["()<>@,;:\".\[\]]))|"(?:[^\"\r\\]|\\.|(?:(?:\r\n)?[ \t]))*"(?:(?:\r\n)?[ \t])*)(?:\.(?:( ?:\r\n)?[ \t])*(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:\r\n)?[ \t])+|\Z|(?=[ \["()<>@,;:\\".\[\]]))|"(?:[^\"\r\\]|\\.|(?:(?:\r\n)?[ \t]))*"(?:(?:\r\n)?[ \t ])*))*@(?:(?:\r\n)?[ \t])*(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:\r\n)?[ \t ])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|\[([^\[\]\r\\]|\\.)*\](?:(?:\r\n)?[ \t])*)(? :\.(?:(?:\r\n)?[ \t])*(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:\r\n)?[ \t])+| \Z|(?=[\["()<>@,;:\\".\[\]]))|\[([^\[\]\r\\]|\\.)*\](?:(?:\r\n)?[ \t])*))*|(?: [^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:\r\n)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[]]))|"(?:[^\"\r\\]|\\.|(?:(?:\r\n)?[ \t]))*"(?:(?:\r\n)?[ \t])*)*\<(?:(?:\r\n) ?[ \t])*(?:@(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:\r\n)?[ \t])+|\Z|(?=[\[" ()<>@,;:\\".\[\]]))|\[([^\[\]\r\\]|\\.)*\](?:(?:\r\n)?[ \t])*)(?:\.(?:(?:\r\n) ?[ \t])*(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:\r\n)?[ \t])+|\Z|(?=[\["()<> @,;:\\".\[\]]))|\[([^\[\]\r\\]|\\.)*\](?:(?:\r\n)?[ \t])*))*(?:,@(?:(?:\r\n)?[ \t])*(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:\r\n)?[ \t])+|\Z|(?=[\["()<>@, ;:\\".\[\]]))|\[([^\[\]\r\\]|\\.)*\](?:(?:\r\n)?[ \t])*)(?:\.(?:(?:\r\n)?[ \t] )*(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:\r\n)?[ \t])+|\Z|(?=[\["()<>@,;:\".\[\]]))|\[([^\[\]\r\\]|\\.)*\](?:(?:\r\n)?[ \t])*))*)*:(?:(?:\r\n)?[ \t])*)? (?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:\r\n)?[ \t])+|\Z|(?=[\["()<>@,;:\\". \[\]]))|"(?:[^\"\r\\]|\\.|(?:(?:\r\n)?[ \t]))*"(?:(?:\r\n)?[ \t])*)(?:\.(?:(?: \r\n)?[ \t])*(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:\r\n)?[ \t])+|\Z|(?=[\[ "()<>@,;:\\".\[\]]))|"(?:[^\"\r\\]|\\.|(?:(?:\r\n)?[ \t]))*"(?:(?:\r\n)?[ \t]) *))*@(?:(?:\r\n)?[ \t])*(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:\r\n)?[ \t]) +|\Z|(?=[\["()<>@,;:\\".\[\]]))|\[([^\[\]\r\\]|\\.)*\](?:(?:\r\n)?[ \t])*)(?:.(?:(?:\r\n)?[ \t])*(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:\r\n)?[ \t])+|\Z |(?=[\["()<>@,;:\\".\[\]]))|\[([^\[\]\r\\]|\\.)*\](?:(?:\r\n)?[ \t])*))*\>(?:( ?:\r\n)?[ \t])*))*)?;\s*)
Fedorkov
13.01.2016 15:21Странно, что ни автор статьи, ни предыдущие комментаторы не стали гуглить готовые решения. Двухстрочечный регексп автора вызывает улыбку.
SelenIT2
13.01.2016 11:45+4Между тем, браузеры и HTML5 давно для себя всё определили.
A valid e-mail address is a string that matches the
email
production of the following ABNF, the character set for which is Unicode. This ABNF implements the
extensions described in RFC 1123. [ABNF] [RFC5322] [RFC1034] [RFC1123]
email = 1*( atext / "." ) "@" label *( "." label ) label = let-dig [ [ ldh-str ] let-dig ] ; limited to a length of 63 characters by <a href="https://tools.ietf.org/html/rfc1034#section-3.5">RFC 1034 section 3.5</a> atext = < as defined in <a href="https://tools.ietf.org/html/rfc5322#section-3.2.3">RFC 5322 section 3.2.3</a> > let-dig = < as defined in <a href="https://tools.ietf.org/html/rfc1034#section-3.5">RFC 1034 section 3.5</a> > ldh-str = < as defined in <a href="https://tools.ietf.org/html/rfc1034#section-3.5">RFC 1034 section 3.5</a> >
The following JavaScript- and Perl-compatible regular expression is an implementation of the above definition.
/^[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])?)*$/merlin-vrn
13.01.2016 11:48… и мы сразу получаем кучу сайтов, которые не позволяют ввести валидные email?
SelenIT2
13.01.2016 12:01Формально — да:
Примечание: это требование — намеренное нарушение RFC 5322, которое определяет синтаксис адресов e-mail, одновременно слишком строгий (до символа "@") и слишком мягкий (позволяющий комментарии, пробелы и строки в кавычках, что большинству пользователей незнакомо), чтобы подходить для практического использования здесь.
Практически — такие адреса в реальном мире надо еще очень поискать:). Национальные домены валидируются уже в виде punycode.
k12th
13.01.2016 11:51+2Где-то читал небезынтересную статью на тему этой проверки. Автор приходил к выводу, что проверить регуляркой на полное соответствие c RFC невозможно, и надо проверять конечным автоматом. Проверка конечным автоматом дает еще такой бонус, что можно пользователю выводить осмысленные ошибки.
Конечно, если речь о лендингах и конверсии, такие заморочки не нужны. Но ведь есть места, где нужна защита от дурака.merlin-vrn
13.01.2016 11:54+2А что, в принципе можно доказать теорему, что граматика адреса — сложнее, чем регулярная, определить место в иерархии (по Хомскому) и успокоиться с проверкой емейлов регулярками :)
sekrasoft
16.01.2016 15:50+1Нет, успокоиться не удастся.
- Конкретные реализации регулярных выражений охватывают больше, чем регулярные грамматики,
- На практике можно не рассматривать регулярку как самодостаточный парсер,
- Можно построить такое приближение грамматики, которое будет корректно работать в 99.9% случаев, скажем, реализовать вложенность скобок до какого-то уровня N
А вообще, раз есть ограничение на длину адреса, то количество возможных значений конечно, а значит это просто набор константных строк, которые записываются очень длинной регуляркой вида a@a|a@b|a@c|… :)
SirEdvin
13.01.2016 23:49+1Мне всегда казалось, что регулярные грамматики по мощности одинаковы с конечными автоматами.
Я не прав?k12th
14.01.2016 00:09Я в этом плаваю. Но вот HTML регулярками распарсить невозможно, а конечными автоматами — да.
SirEdvin
14.01.2016 00:47Возможно, это потому что регулярные грамматики мощнее, чем регулярные выражения. Но я тут тоже не уверен.
khim
14.01.2016 01:36+3Вообще-то регулярные грамматики в точности совпадают и с автоматами и с регулярными выражениями. И всё это вместе взятое так и не сможет сказать — у вас в последовательности из всего-навсего двух символов «(» и «)» баланс скобок соблюдён или нет. Какой тут, к бесу, разбор HTMLя или e-mail адресов?
SirEdvin
14.01.2016 01:54Буду знать.
Не вижу проблем с проверкой на e-mail адрес. Зачем там, например, контекстно-сводобная грамматика?khim
14.01.2016 02:19+1Вы не поверите :-) Комментарии. В скобочках. В которых, вы не поверите, могут быть комментарии. В скобочках. Дальше — смотри про разбор баланса скобок с помощью регулярок.
khim
14.01.2016 01:32+3На распарсите вы HTML ни регулярками, ни конечными автоматами. Теорема Клини, однако. Памяти нет. Проверьте хотя бы скобочный баланс регуляркой или конечным автоматом — а я над вами посмеюсь.
k12th
14.01.2016 10:17Очень может быть. Тем не менее, браузеры как-то парсят HTML. Как они это делают?
SelenIT2
14.01.2016 11:09Как-то так:) Запоминая стек открытых элементов и временами меняя правила «игры» по ходу дела в зависимости от его состояния…
k12th
14.01.2016 11:15Там написано «state machine».
khim
14.01.2016 16:28+1Конечный автомат плюс бесконечная лента — это «машина Тьюринга», вообще-то. При желании ленту можно заменить счётчиками (минимум двумя, если ничего не путаю). Главное — в системе должно быть что-то потенциально бесконечное. Без этого — никак.
Zenitchik
14.01.2016 17:33А если оно не потенциально бесконечное — то это линейно-ограниченный автомат. Тоже образует подкласс языков.
Zenitchik
14.01.2016 14:54+2Это только токенизация. Разумеется, токенизация доступна коенчному автомату.
Zenitchik
14.01.2016 14:55HTML парсится на МП-автомате.
khim
14.01.2016 16:30HTML парсится массой разных вещей. Но все эти вещи имеют имеют обну общую особенность: они все могут эмулировать машину Тьюринга, а значит могут реализовать все алгоритмы без исключения. МП-автомат — да, годится.
Zenitchik
14.01.2016 17:31Не обязательно. МП-автомат не может эмулировать машину Тьюринга, он образует свой подкласс языков (и почти все практически интересные языки в него входят).
khim
14.01.2016 18:50Oops. Да, простите, перепутал. Да, МП-автоматы «не умеют» машину Тьюринга. Редкий случай чего-то практически полезного между «конечными автоматами» и машиной Тьюринга…
Zenitchik
14.01.2016 19:25В смысле «редкий»? На практике есть КА, МПА и различная экзотика. А над всем этим стоит машина Тьюринга, которая, вообще говоря, тоже экзотика, но важная в теоретическом отношении.
khim
14.01.2016 20:34С смысле «практически редкий». Как правило всякие примочки (типа языка BPF) стартуют с чего-то, что может обработать конечный автомат, потом их делают «немного мощнее», потом ещё, и почти всегда когда происходит выход за пределы конечных автоматов выясняется что у нас в руках — уже нечто полное по Тьюрингу (с «проблемой остановки» и прочими прелестями).
Игра Жизнь полна по Тьюрингу, к примеру :-) Что как бы сходу ни разу не очевидно…Zenitchik
14.01.2016 22:17Как правило, язык токенизируется КА, а затем разбирается в структуру МПА. Крайне редко встречается такой язык, который невозможно было транслировать МПА. А учитывая, что МПА относительно легко разрабатывать — это очень популярный класс трансляторов, внутри которого есть уже много подклассов, отличающихся логикой работы со стеком.
sekrasoft
16.01.2016 15:34Проверьте хотя бы скобочный баланс регуляркой или конечным автоматом — а я над вами посмеюсь.
(([^()]|\((?1)\))*)
Смейтесь на здоровье. С PCRE нам даже КС не страшны:
boost::regex re("(?(DEFINE)" "(?<symbol>[^()])" "(?<expr>((?&symbol)|\\((?&expr)\\))*)" ")" "^(?&expr)$");
khim
16.01.2016 22:28+2Хотел упомянуть про то, кто perlовые регулярные выражения регулярными выражениями, строго говоря, не являются, но потом решил, что тот, кто про это знает понимает так же, о чём я.
А так-то да: PCRE'шные регулярные выражаения это, конечно, умеют, потому что у них есть память (хотя и ограниченное количество памяти, но для скобочек этого хватает, да).
Zenitchik
14.01.2016 14:56Регулярки мощнее. Вот если не использовать запоминание подстрок и заглядывание вперёд — тогда полностью эквивалентны.
unStaiL
13.01.2016 12:13Хорошо. Ну есть такая спецификация. Но вот регистрируюсь я в GMail или подобных системах. Они ведь не пропускают даже нижнее подчеркивание. Либо случай с habrahabr.ru/post/274985/#comment_8736743
Почему так происходит. Или что будет если я на своем домене таки сделаю нетипичный адрес и начну жаловаться на сервисы которые его не пропускают?Lopar
13.01.2016 12:37У гмыла есть интересный нюанс:
johndoe@gmail.com
john.doe@gmail.com
j.O.h.N.d.O.e@gmail.com
johndoe+sometext@gmail.com
Это всё ещё один ящик. Я часто пользовался комбинацией email+sometext@gmail.com чтобы регистрироваться на разных сервисах, с разными техническими именами ящиков, чтобы потом собирать всю почту в один и рулить фильтрами не по имени отправителя, а по ящику получателя, что ощутимо проще.
Но пару раз столкнулся с тем, что на некоторых сайтах email+sometext зарегистрировать можно, а вот при обращении к нему приходил или invalid email, или полный крах. Пришлось отказаться.
merlin-vrn
15.01.2016 09:14Не дают зарегистрировать такое имя или не дают проходить почте с такими адресами? Это очень разные вещи.
Error_403_Forbidden
13.01.2016 12:52+3Регулярка у автора смешная получилась, слишком маленькая. Ещё работать и работать над ней! :)
vsespb
13.01.2016 13:34+2Думаю, я создам email-адрес типа phil.h\@\@ck@haacked.com и начну жаловаться в техподдержку на сайтах, которые требуют ввода email-адреса, но не позволяют мне создать учётную запись с этим адресом. Люблю шалить!
был у меня адрес всецифры@домен — где-то не проходил проверку. жаловался. в ответ игнор. сомневаюсь что шалить будет весело.Wesha
13.01.2016 18:37У меня было веселее — с год назад в одном магазине не принимали емейл вида vasya@pupkin.name — считали, что TLD не может быть длиннее 3 символов.
(edit: не заметил: следующим комментом v v v коллега аналогичный случай уже отметил )
outcoldman
13.01.2016 18:25У меня главный email на домене .email. Встречаюсь достаточно часто с сайтами, которые его не понимают. Видимо ожидают 2 или 3 символа.
Dreyk
14.01.2016 14:13+2а их антиподы видимо ожидают до точки больше символов и ругаются на домен i.ua =)
prishelec
13.01.2016 20:04-6В свое время взял на заметку с некоего движка: ^([a-z0-9]([\-\_\.]*[a-z0-9])*)+@([a-z0-9]([\-]*[a-z0-9])*\.)+[a-z]{2,6}$
Пока не подводил ). Но, не смею претендовать на его незаменимость.
Ununtrium
19.01.2016 13:16-1Прочитал RFC — расскажи всем. Впрочем, по тому как пипл хавает, очевидно что читали его немногие.
Вдобавок, текст удобрен знатным количеством петросянства, что не способствует восприятию.
Renius
19.01.2016 14:28-1it { is_expected.to allow_value("customer\/department=shipping@example.com").for(:email) } it { is_expected.to allow_value("!def!xyz%abc@example.com").for(:email) } it { is_expected.to allow_value("_somename@example.com").for(:email) }
Тесты прошли
Мой валидатор, давно устаревший правда: http://habrahabr.ru/post/175399/
С остальными продолжаю возиться, спасибо за челлендж!
stychos
19.01.2016 17:07+1Нереально беситЧастенько в недоумении от того, что большинство таких «фильтров» не принимают адреса с однобуквенной локальной частью. Ещё немного непонятно, почему многие не разрешают в пароле использовать «слова» из локальной части имейла (этим даже майкрософт грешит), когда там одна буква, тоже не очень удобно.
captain_obvious
Лучший способ проверить email на валидность — отправить туда письмо.
Mendel
Валидность с точки зрения стандарта? С точки зрения большинства сервисов (иначе если почта будет ходить между двумя-тремя серверами, и примут такую почту на полутора сайтах, то она не то чтобы валидна)? С точки зрения своего мейлера?
Лучший способ валидации для каждого сценария разный.
занудаOFF:
Но вообще я с вами согласен, что для практических целей лучше просто отправить письмо.
Если это допустимо. Часто валидация почты письмом ухудшает конверсию. А проверить при регистрации надо.
Лично я проверяю штатным фильтром пхп, после чего проверяю существование МХ у домена почты. И прочтение статьи меня ничуть не сподвигло проверять насколько строгий фильтр встроен в пхп. Все им пользуются, значит для практических целей это можно условно считать стандартом.
captain_obvious
Про сценарии различные согласен, разумеется.
Вообще интересны какие-нибудь исследования про долю ошибок в поле емейла, которые отсекаются чем-то сложнее проверкой наличия коммерческого at и точки, отделяющей TLD.
stavinsky
По стандарту mx записи может и не быть, тогда почтовый сервер пытается отправить на сервер полученный из A записи…
inkvizitor68sl
Это вы старым sendmail теперь расскажите =)
stavinsky
O'Reilly's book 1997 год
Куда простите старше?)
inkvizitor68sl
В RFC это всегда было написано. А вот sendmail этого «не знал», года до 2005го.
stavinsky
книга по шлимылу как раз. А вот про rfc слукавил. Сейчас хотел найти и не нашел там такого.
Mendel
На самом деле я обычно проверяю SOA, но решил в подробности не вдаваться именно потому, что по стандарту МХ, а использование А это воркэраунд.
denis_g
Yup.
sebres
Так-то да, но можно жеж проверить и предупредить пользователя о возможной опечатке… Ну т.е. просто warning-ом «Вы возможно очепятались...», без запрещения дальнейшего действа.
Накидал для примера на коленке, кому будет интересно — [github]/.../tcl-test-valid-mail/tcl/check valid email.tcl
(результат исполнения и лог внизу файла).