Senior Software Engineer; Software Archaeologist
0,6
рейтинг
13 января в 02:20

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

От переводчика: прочитав статью, начал было отвечать в комментариях, но решил, что текст, на которую я собирался ссылаться, достоин отдельной публикации. Встречайте!
Если вы знаете, как валидировать 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, я всё ещё не уверен в том, что же я, блин, делаю! Что ещё раз подтверждает тезис о том, что программисты — не читатели.
Перевод: Phil Haack
@Wesha
карма
9,5
рейтинг 0,6
Senior Software Engineer; Software Archaeologist
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Спецпроект

Самое читаемое Разработка

Комментарии (93)

  • +38
    Лучший способ проверить email на валидность — отправить туда письмо.
    • 0
      Валидность с точки зрения стандарта? С точки зрения большинства сервисов (иначе если почта будет ходить между двумя-тремя серверами, и примут такую почту на полутора сайтах, то она не то чтобы валидна)? С точки зрения своего мейлера?
      Лучший способ валидации для каждого сценария разный.
      занудаOFF:
      Но вообще я с вами согласен, что для практических целей лучше просто отправить письмо.
      Если это допустимо. Часто валидация почты письмом ухудшает конверсию. А проверить при регистрации надо.
      Лично я проверяю штатным фильтром пхп, после чего проверяю существование МХ у домена почты. И прочтение статьи меня ничуть не сподвигло проверять насколько строгий фильтр встроен в пхп. Все им пользуются, значит для практических целей это можно условно считать стандартом.
      • 0
        Про сценарии различные согласен, разумеется.
        Вообще интересны какие-нибудь исследования про долю ошибок в поле емейла, которые отсекаются чем-то сложнее проверкой наличия коммерческого at и точки, отделяющей TLD.
      • 0
        По стандарту mx записи может и не быть, тогда почтовый сервер пытается отправить на сервер полученный из A записи…
        • 0
          Это вы старым sendmail теперь расскажите =)
          • +3
            If no MX records are found, sendmail tries to deliver the message to the single original host.

            O'Reilly's book 1997 год
            Куда простите старше?)
            • 0
              В RFC это всегда было написано. А вот sendmail этого «не знал», года до 2005го.
              • 0
                книга по шлимылу как раз. А вот про rfc слукавил. Сейчас хотел найти и не нашел там такого.
              • 0
                И как я только пользовался почтой в 94-м на юникс-машинах для которых никто и никогда не стал-бы прописывать отдельные MX-ы?
                Я вам больше скажу, есть подозрение, что само понятие MX-ов появилось заметно позже, чем sendmail начал передавать письма по А-записям. Но лень искать подтверждения.
                • +2

                  Ну дык, так оно и есть. Сначала отправляли сразу серверу-адресату, а потом наткнулись на ситуацию "адресат в оффлайне, и чо делать бум?", добавили MXы — "если меня нет, оставляйте письма для меня Максу, я вернусь — он мне передаст."

        • 0
          На самом деле я обычно проверяю SOA, но решил в подробности не вдаваться именно потому, что по стандарту МХ, а использование А это воркэраунд.
    • +5
      Yup.
    • 0
      Так-то да, но можно жеж проверить и предупредить пользователя о возможной опечатке… Ну т.е. просто warning-ом «Вы возможно очепятались...», без запрещения дальнейшего действа.

      Накидал для примера на коленке, кому будет интересно — [github]/.../tcl-test-valid-mail/tcl/check valid email.tcl
      (результат исполнения и лог внизу файла).
  • +17
    http://m.habrahabr.ru/post/175375/
  • +9
    Большое количество сайтов, где регистрировался, не понимают символа + в адресе, и это меня прям крайне печалит. А ведь он разрешен и более того, крайне удобен. Например user+habr@mail будет приходить к пользователю user, но он сможет выставить фильтр для адресата user+habr, чтобы распределять папки, настраивать уведомления.
    • +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.
    • +2
      Для себя настроил на своем домене сборку почты для всех неизвестных адресов в ящик spam@domain.ru и радостно регистрируюсь на всех сайтах с почтой вида habr@domain.ru, mailru@domain.ru, etc :)
      Такую фичу имеют многие почтовые сервера, но в этом случае конечно нужно иметь свой домен.
      • 0
        Это оказывается очень неудобно, если Вы захотите перенести свою почту на какой-нибудь gmail.
        • 0
          Ну и переносите. У gmail так же можно подключить свой домен. В чём проблема? А можно почту на gmail сделать и сборщики настроить. Не ищите проблемы там где её нет.
          Вообще я сначала на gmail почту поднял для организации, а потом оттуда переехал на яндекс. Ноу проблем. Проблема была только в размере архива писем (>50 гигов, а может и больше, уже точно не помню, даты писем с 2002 года и несколько писем ещё старше были)
        • 0
          В ПдД от Яндекса так же можно настроить ящик по умолчанию и в него будут сыпаться все письма, которые не нашли явного получателя.
    • +1
      Меня как-то опечалило, что много сайтов не принимают казалось бы валидный символ минуса/тире если его ставить дважды (user--name@domain.com).
  • +18
    Уже много было написано статей по этому поводу и лучшая из них была из одной строки: адрес почты проверять не надо.
    • +10
      думаю, хотя бы на @ проверить надо, ибо это единственный символ в большинстве случаев, который вводится с зажатым шифтом, можно промахнуться
  • +2
    Про валидацию email очень подробно описано описано в книге «Регулярные выражения» Джеффри Фридла www.ozon.ru/context/detail/id/4066500
    В качестве приложения приводится выражение, которое проверяет соответствие email RFC, это выражение занимает несколько страниц :)
  • –2
    Не знаю, как у вас, а у нас это не одно регулярное выражение, а много, т.к. у нас емейл активно используется в тех же рассылках, и периодически идут жалобы от людей, которые ввели и @ya.ru и yandex.ru или гмейл пяти видов с точками. Если у вас 1.5 землекопа на сайте, то имеет смысл использовать регулярки согласно rfc, а если сотни тысяч людей, при том многие ждут от вас писем, то вы потонете в bounce и жалобах от невнимательных пользователей.
  • +1
    Всё жду, когда кто-нибудь напишет такую же статью, но с учётом национальных TLD.

    Хороший был бы емейл вася@пупкин.рф. Упс, а что делать с кодировкой локальной части?
    • НЛО прилетело и опубликовало эту надпись здесь
      • +1
        Домены — да. Я говорю про локальную часть адреса («вася» в данном случае). Она не кодируется никаким punycode, и правила обработки определяются исключительно сервером получателя (см. текст статьи).
        • –2
          В локальной части нельзя использовать кириллицу. Только латиница. Поэтому вася@мыло.рф работать сейчас не будет.
          Подробности можете в вики посмотреть.
          • +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. Просто не было. Было можно что угодно, за небольшим исключением, и всё. А как это понимать, решает получатель.
            • +1
              А, да. Действительно. Всё уже поменялось с тех пор, как я туда заглядывал в последний раз.
              Спасибо за подсказку.
              • +4
                ↑↑↑↑↑↑↑↑
                И вот здесь, уважаемые коллеги, мы собственными глазами наблюдаем очередное подтверждние тезиса автора статьи о том, что чукчи программисты — не читатели.
                • +4
                  не совсем так. программисту ставят задачу -> он прочел RFC -> заимплеменил -> RFC поменяли -> программист не в курсе
                  • +1
                    Ой расскажите это Rit Labs. Наверное, ни один почтовый RFC ни в одной редакции не заимплементили правильно в The Bat.

                    Вот уж точно не читатели.
  • +2
    P.S. Если автор использует .NET то зачем регулярные выражения? Почему бы не использовать встроенные средства для валидации? К примеру вот.
    • +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])))\.?$
  • +1
    Какое там!
    У меня в адресе на GMail есть точка. Когда я её регистрировал там в качестве примера имени ящика был ящик вида name.surname@gmail.com.

    Знаете, кто меня не пустил регистрироваться с таким адресом? Foursquare! Пришлось пользоваться тем фактом, что точка в имени e-mail игнорируется и писать тот же адрес без точки.
    • +1
      Игнорирутся Gmail-ом. А другими, вообще говоря, не игнорируется. Я тут выше упоминал Cyrus IMAP, там в именах ящиков точка — недопустимый символ (в конфигурации по умолчанию).
  • +6
    Я знаю: в адресе должен быть хотя бы один символ @ и хотя бы по одному байту до и после.

    через которое они все прошли бы. Вот оно.

    Коротенькое уж больно. Читайте RFC дальше. filter_var из PHP в исходнике регулярку за килобайт размером имеет.
    github.com/php/php-src/blob/PHP-7.0.2/ext/filter/logical_filters.c#L575
    Кстати, в комментариях в коду есть ответ на популярный вопрос «какого размера делать поле в БД»: 320 байт. согласно RFC 2821.
    • +2
      • +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 октета.
    • –6
      В таком случае вы, уверен, будете меня рады видеть на своем проекте с е-мейл адресом — @@@

      Или, как вариант, с —
      <lets make some SQL inj magic>@<lets make some SQL inj magic>
      • +9
        Конечно буду рад! Вы ведь сможете подтвердить адрес?
    • 0
      На сколько мне известно, в interanet вы можете настроить email и без домена. Отправлять тупо по имени.
      • 0
        Вы хотели сказать, в рамках одного хоста.
        • 0
          Не изменяет моего предложения. Я же не говорю как, говорю, что " в intranet можете настроить". С использованием default host/domain на mail server.
          • 0
            В Вашем предложении использован несуществующий термин «interanet», поэтому я уточнил.
            • 0
              Опечатка, да.
  • +7
    RFC822? Регуляркой?
    Есть такая
    (?:(?:\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*)
    (взято здесь)
    • 0
      Странно, что ни автор статьи, ни предыдущие комментаторы не стали гуглить готовые решения. Двухстрочечный регексп автора вызывает улыбку.
  • НЛО прилетело и опубликовало эту надпись здесь
    • 0
      … и мы сразу получаем кучу сайтов, которые не позволяют ввести валидные email?
      • НЛО прилетело и опубликовало эту надпись здесь
  • +2
    Где-то читал небезынтересную статью на тему этой проверки. Автор приходил к выводу, что проверить регуляркой на полное соответствие c RFC невозможно, и надо проверять конечным автоматом. Проверка конечным автоматом дает еще такой бонус, что можно пользователю выводить осмысленные ошибки.

    Конечно, если речь о лендингах и конверсии, такие заморочки не нужны. Но ведь есть места, где нужна защита от дурака.
    • +2
      А что, в принципе можно доказать теорему, что граматика адреса — сложнее, чем регулярная, определить место в иерархии (по Хомскому) и успокоиться с проверкой емейлов регулярками :)
      • +1
        Нет, успокоиться не удастся.
        1. Конкретные реализации регулярных выражений охватывают больше, чем регулярные грамматики,
        2. На практике можно не рассматривать регулярку как самодостаточный парсер,
        3. Можно построить такое приближение грамматики, которое будет корректно работать в 99.9% случаев, скажем, реализовать вложенность скобок до какого-то уровня N

        А вообще, раз есть ограничение на длину адреса, то количество возможных значений конечно, а значит это просто набор константных строк, которые записываются очень длинной регуляркой вида a@a|a@b|a@c|… :)
    • +1
      Мне всегда казалось, что регулярные грамматики по мощности одинаковы с конечными автоматами.

      Я не прав?
      • 0
        Я в этом плаваю. Но вот HTML регулярками распарсить невозможно, а конечными автоматами — да.
        • 0
          Возможно, это потому что регулярные грамматики мощнее, чем регулярные выражения. Но я тут тоже не уверен.
          • +3
            Вообще-то регулярные грамматики в точности совпадают и с автоматами и с регулярными выражениями. И всё это вместе взятое так и не сможет сказать — у вас в последовательности из всего-навсего двух символов «(» и «)» баланс скобок соблюдён или нет. Какой тут, к бесу, разбор HTMLя или e-mail адресов?
            • 0
              Буду знать.

              Не вижу проблем с проверкой на e-mail адрес. Зачем там, например, контекстно-сводобная грамматика?
              • +1
                Вы не поверите :-) Комментарии. В скобочках. В которых, вы не поверите, могут быть комментарии. В скобочках. Дальше — смотри про разбор баланса скобок с помощью регулярок.
        • +3
          На распарсите вы HTML ни регулярками, ни конечными автоматами. Теорема Клини, однако. Памяти нет. Проверьте хотя бы скобочный баланс регуляркой или конечным автоматом — а я над вами посмеюсь.
          • 0
            Очень может быть. Тем не менее, браузеры как-то парсят HTML. Как они это делают?
            • НЛО прилетело и опубликовало эту надпись здесь
              • 0
                Там написано «state machine».
                • +1
                  Конечный автомат плюс бесконечная лента — это «машина Тьюринга», вообще-то. При желании ленту можно заменить счётчиками (минимум двумя, если ничего не путаю). Главное — в системе должно быть что-то потенциально бесконечное. Без этого — никак.
                  • 0
                    А если оно не потенциально бесконечное — то это линейно-ограниченный автомат. Тоже образует подкласс языков.
              • +2
                Это только токенизация. Разумеется, токенизация доступна коенчному автомату.
            • 0
              HTML парсится на МП-автомате.
              • 0
                HTML парсится массой разных вещей. Но все эти вещи имеют имеют обну общую особенность: они все могут эмулировать машину Тьюринга, а значит могут реализовать все алгоритмы без исключения. МП-автомат — да, годится.
                • 0
                  Не обязательно. МП-автомат не может эмулировать машину Тьюринга, он образует свой подкласс языков (и почти все практически интересные языки в него входят).
                  • 0
                    Oops. Да, простите, перепутал. Да, МП-автоматы «не умеют» машину Тьюринга. Редкий случай чего-то практически полезного между «конечными автоматами» и машиной Тьюринга…
                    • 0
                      В смысле «редкий»? На практике есть КА, МПА и различная экзотика. А над всем этим стоит машина Тьюринга, которая, вообще говоря, тоже экзотика, но важная в теоретическом отношении.
                      • 0
                        С смысле «практически редкий». Как правило всякие примочки (типа языка BPF) стартуют с чего-то, что может обработать конечный автомат, потом их делают «немного мощнее», потом ещё, и почти всегда когда происходит выход за пределы конечных автоматов выясняется что у нас в руках — уже нечто полное по Тьюрингу (с «проблемой остановки» и прочими прелестями).

                        Игра Жизнь полна по Тьюрингу, к примеру :-) Что как бы сходу ни разу не очевидно…
                        • 0
                          Как правило, язык токенизируется КА, а затем разбирается в структуру МПА. Крайне редко встречается такой язык, который невозможно было транслировать МПА. А учитывая, что МПА относительно легко разрабатывать — это очень популярный класс трансляторов, внутри которого есть уже много подклассов, отличающихся логикой работы со стеком.
          • 0
            Проверьте хотя бы скобочный баланс регуляркой или конечным автоматом — а я над вами посмеюсь.

            (([^()]|\((?1)\))*)

            Смейтесь на здоровье. С PCRE нам даже КС не страшны:
            boost::regex re("(?(DEFINE)"
              "(?<symbol>[^()])"
              "(?<expr>((?&symbol)|\\((?&expr)\\))*)"
            ")"
            "^(?&expr)$");
            
            • +2
              Хотел упомянуть про то, кто perlовые регулярные выражения регулярными выражениями, строго говоря, не являются, но потом решил, что тот, кто про это знает понимает так же, о чём я.

              А так-то да: PCRE'шные регулярные выражаения это, конечно, умеют, потому что у них есть память (хотя и ограниченное количество памяти, но для скобочек этого хватает, да).
      • 0
        Регулярки мощнее. Вот если не использовать запоминание подстрок и заглядывание вперёд — тогда полностью эквивалентны.
  • 0
    Хорошо. Ну есть такая спецификация. Но вот регистрируюсь я в GMail или подобных системах. Они ведь не пропускают даже нижнее подчеркивание. Либо случай с habrahabr.ru/post/274985/#comment_8736743
    Почему так происходит. Или что будет если я на своем домене таки сделаю нетипичный адрес и начну жаловаться на сервисы которые его не пропускают?
    • 0
      У гмыла есть интересный нюанс:

      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, или полный крах. Пришлось отказаться.
    • 0
      Не дают зарегистрировать такое имя или не дают проходить почте с такими адресами? Это очень разные вещи.
  • +3
    Регулярка у автора смешная получилась, слишком маленькая. Ещё работать и работать над ней! :)
  • +2
    Думаю, я создам email-адрес типа phil.h\@\@ck@haacked.com и начну жаловаться в техподдержку на сайтах, которые требуют ввода email-адреса, но не позволяют мне создать учётную запись с этим адресом. Люблю шалить!

    был у меня адрес всецифры@домен — где-то не проходил проверку. жаловался. в ответ игнор. сомневаюсь что шалить будет весело.
    • 0
      У меня было веселее — с год назад в одном магазине не принимали емейл вида vasya@pupkin.name — считали, что TLD не может быть длиннее 3 символов.

      (edit: не заметил: следующим комментом ↓ ↓ ↓ коллега аналогичный случай уже отметил )
  • 0
    У меня главный email на домене .email. Встречаюсь достаточно часто с сайтами, которые его не понимают. Видимо ожидают 2 или 3 символа.
    • +2
      а их антиподы видимо ожидают до точки больше символов и ругаются на домен i.ua =)
  • –6
    В свое время взял на заметку с некоего движка: ^([a-z0-9]([\-\_\.]*[a-z0-9])*)+@([a-z0-9]([\-]*[a-z0-9])*\.)+[a-z]{2,6}$
    Пока не подводил ). Но, не смею претендовать на его незаменимость.
  • 0
    Нужно больше символов.
  • –1
    Прочитал RFC — расскажи всем. Впрочем, по тому как пипл хавает, очевидно что читали его немногие.
    Вдобавок, текст удобрен знатным количеством петросянства, что не способствует восприятию.
  • –1
    it { 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/
    С остальными продолжаю возиться, спасибо за челлендж!
  • +1
    Нереально бесит Частенько в недоумении от того, что большинство таких «фильтров» не принимают адреса с однобуквенной локальной частью. Ещё немного непонятно, почему многие не разрешают в пароле использовать «слова» из локальной части имейла (этим даже майкрософт грешит), когда там одна буква, тоже не очень удобно.

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.