От переводчика: прочитав статью, начал было отвечать в комментариях, но решил, что текст, на которую я собирался ссылаться, достоин отдельной публикации. Встречайте!
Если вы знаете, как валидировать 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)


  1. captain_obvious
    13.01.2016 02:34
    +38

    Лучший способ проверить email на валидность — отправить туда письмо.


    1. Mendel
      13.01.2016 02:55

      Валидность с точки зрения стандарта? С точки зрения большинства сервисов (иначе если почта будет ходить между двумя-тремя серверами, и примут такую почту на полутора сайтах, то она не то чтобы валидна)? С точки зрения своего мейлера?
      Лучший способ валидации для каждого сценария разный.
      занудаOFF:
      Но вообще я с вами согласен, что для практических целей лучше просто отправить письмо.
      Если это допустимо. Часто валидация почты письмом ухудшает конверсию. А проверить при регистрации надо.
      Лично я проверяю штатным фильтром пхп, после чего проверяю существование МХ у домена почты. И прочтение статьи меня ничуть не сподвигло проверять насколько строгий фильтр встроен в пхп. Все им пользуются, значит для практических целей это можно условно считать стандартом.


      1. captain_obvious
        13.01.2016 03:22

        Про сценарии различные согласен, разумеется.
        Вообще интересны какие-нибудь исследования про долю ошибок в поле емейла, которые отсекаются чем-то сложнее проверкой наличия коммерческого at и точки, отделяющей TLD.


      1. stavinsky
        13.01.2016 14:03

        По стандарту mx записи может и не быть, тогда почтовый сервер пытается отправить на сервер полученный из A записи…


        1. inkvizitor68sl
          13.01.2016 14:40

          Это вы старым sendmail теперь расскажите =)


          1. stavinsky
            13.01.2016 14:56
            +3

            If no MX records are found, sendmail tries to deliver the message to the single original host.

            O'Reilly's book 1997 год
            Куда простите старше?)


            1. inkvizitor68sl
              13.01.2016 15:07

              В RFC это всегда было написано. А вот sendmail этого «не знал», года до 2005го.


              1. stavinsky
                13.01.2016 15:21

                книга по шлимылу как раз. А вот про rfc слукавил. Сейчас хотел найти и не нашел там такого.


        1. Mendel
          13.01.2016 16:30

          На самом деле я обычно проверяю SOA, но решил в подробности не вдаваться именно потому, что по стандарту МХ, а использование А это воркэраунд.


    1. denis_g
      13.01.2016 10:20
      +5

    1. sebres
      13.01.2016 19:17

      Так-то да, но можно жеж проверить и предупредить пользователя о возможной опечатке… Ну т.е. просто warning-ом «Вы возможно очепятались...», без запрещения дальнейшего действа.

      Накидал для примера на коленке, кому будет интересно — [github]/.../tcl-test-valid-mail/tcl/check valid email.tcl
      (результат исполнения и лог внизу файла).


  1. lockywolf
    13.01.2016 04:37
    +17

    http://m.habrahabr.ru/post/175375/


  1. SergeyGrigorev
    13.01.2016 09:18
    +9

    Большое количество сайтов, где регистрировался, не понимают символа + в адресе, и это меня прям крайне печалит. А ведь он разрешен и более того, крайне удобен. Например user+habr@mail будет приходить к пользователю user, но он сможет выставить фильтр для адресата user+habr, чтобы распределять папки, настраивать уведомления.


    1. 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.


    1. Visphord
      13.01.2016 10:00
      +2

      Для себя настроил на своем домене сборку почты для всех неизвестных адресов в ящик spam@domain.ru и радостно регистрируюсь на всех сайтах с почтой вида habr@domain.ru, mailru@domain.ru, etc :)
      Такую фичу имеют многие почтовые сервера, но в этом случае конечно нужно иметь свой домен.


      1. Pilat
        13.01.2016 10:51

        Это оказывается очень неудобно, если Вы захотите перенести свою почту на какой-нибудь gmail.


        1. mobilesfinks
          13.01.2016 12:23

          Ну и переносите. У gmail так же можно подключить свой домен. В чём проблема? А можно почту на gmail сделать и сборщики настроить. Не ищите проблемы там где её нет.
          Вообще я сначала на gmail почту поднял для организации, а потом оттуда переехал на яндекс. Ноу проблем. Проблема была только в размере архива писем (>50 гигов, а может и больше, уже точно не помню, даты писем с 2002 года и несколько писем ещё старше были)


        1. mOlind
          14.01.2016 11:36

          В ПдД от Яндекса так же можно настроить ящик по умолчанию и в него будут сыпаться все письма, которые не нашли явного получателя.


    1. metis
      22.01.2016 09:42
      +1

      Меня как-то опечалило, что много сайтов не принимают казалось бы валидный символ минуса/тире если его ставить дважды (user--name@domain.com).


  1. bolk
    13.01.2016 09:39
    +18

    Уже много было написано статей по этому поводу и лучшая из них была из одной строки: адрес почты проверять не надо.


    1. alemiks
      13.01.2016 11:48
      +10

      думаю, хотя бы на @ проверить надо, ибо это единственный символ в большинстве случаев, который вводится с зажатым шифтом, можно промахнуться


  1. Albertum
    13.01.2016 09:44
    +2

    Про валидацию email очень подробно описано описано в книге «Регулярные выражения» Джеффри Фридла www.ozon.ru/context/detail/id/4066500
    В качестве приложения приводится выражение, которое проверяет соответствие email RFC, это выражение занимает несколько страниц :)


  1. medvoodoo
    13.01.2016 09:54
    -2

    Не знаю, как у вас, а у нас это не одно регулярное выражение, а много, т.к. у нас емейл активно используется в тех же рассылках, и периодически идут жалобы от людей, которые ввели и @ya.ru и yandex.ru или гмейл пяти видов с точками. Если у вас 1.5 землекопа на сайте, то имеет смысл использовать регулярки согласно rfc, а если сотни тысяч людей, при том многие ждут от вас писем, то вы потонете в bounce и жалобах от невнимательных пользователей.


  1. merlin-vrn
    13.01.2016 10:02
    +1

    Всё жду, когда кто-нибудь напишет такую же статью, но с учётом национальных TLD.

    Хороший был бы емейл вася@пупкин.рф. Упс, а что делать с кодировкой локальной части?


    1. SelenIT2
      13.01.2016 11:38

      AFAIK, национальные домены в адресах сначала преобразуются в punycode, а только затем валидируются на общих основаниях.


      1. merlin-vrn
        13.01.2016 11:42
        +1

        Домены — да. Я говорю про локальную часть адреса («вася» в данном случае). Она не кодируется никаким punycode, и правила обработки определяются исключительно сервером получателя (см. текст статьи).


        1. sledopit
          13.01.2016 12:09
          -2

          В локальной части нельзя использовать кириллицу. Только латиница. Поэтому вася@мыло.рф работать сейчас не будет.
          Подробности можете в вики посмотреть.


          1. 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. Просто не было. Было можно что угодно, за небольшим исключением, и всё. А как это понимать, решает получатель.


            1. sledopit
              13.01.2016 12:40
              +1

              А, да. Действительно. Всё уже поменялось с тех пор, как я туда заглядывал в последний раз.
              Спасибо за подсказку.


              1. Wesha
                13.01.2016 18:31
                +4

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


                1. Dreyk
                  14.01.2016 14:10
                  +4

                  не совсем так. программисту ставят задачу -> он прочел RFC -> заимплеменил -> RFC поменяли -> программист не в курсе


                  1. merlin-vrn
                    15.01.2016 08:33
                    +1

                    Ой расскажите это Rit Labs. Наверное, ни один почтовый RFC ни в одной редакции не заимплементили правильно в The Bat.

                    Вот уж точно не читатели.


  1. Visphord
    13.01.2016 10:05
    +2

    P.S. Если автор использует .NET то зачем регулярные выражения? Почему бы не использовать встроенные средства для валидации? К примеру вот.


    1. 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])))\.?$


  1. evocatus
    13.01.2016 10:22
    +1

    Какое там!
    У меня в адресе на GMail есть точка. Когда я её регистрировал там в качестве примера имени ящика был ящик вида name.surname@gmail.com.

    Знаете, кто меня не пустил регистрироваться с таким адресом? Foursquare! Пришлось пользоваться тем фактом, что точка в имени e-mail игнорируется и писать тот же адрес без точки.


    1. merlin-vrn
      13.01.2016 11:44
      +1

      Игнорирутся Gmail-ом. А другими, вообще говоря, не игнорируется. Я тут выше упоминал Cyrus IMAP, там в именах ящиков точка — недопустимый символ (в конфигурации по умолчанию).


  1. 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.


    1. alemiks
      13.01.2016 11:54
      +2

      1. 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 октета.


    1. kyrie
      13.01.2016 15:45
      -6

      В таком случае вы, уверен, будете меня рады видеть на своем проекте с е-мейл адресом — @@@

      Или, как вариант, с —

      <lets make some SQL inj magic>@<lets make some SQL inj magic>


      1. Melkij
        13.01.2016 16:07
        +9

        Конечно буду рад! Вы ведь сможете подтвердить адрес?


    1. outcoldman
      13.01.2016 18:24

      На сколько мне известно, в interanet вы можете настроить email и без домена. Отправлять тупо по имени.


      1. Wesha
        13.01.2016 18:34

        Вы хотели сказать, в рамках одного хоста.


        1. outcoldman
          13.01.2016 18:39

          Не изменяет моего предложения. Я же не говорю как, говорю, что " в intranet можете настроить". С использованием default host/domain на mail server.


          1. Wesha
            13.01.2016 19:17

            В Вашем предложении использован несуществующий термин «interanet», поэтому я уточнил.


            1. outcoldman
              13.01.2016 19:30

              Опечатка, да.


  1. RolexStrider
    13.01.2016 11:02
    +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*)


    1. Fedorkov
      13.01.2016 15:21

      Странно, что ни автор статьи, ни предыдущие комментаторы не стали гуглить готовые решения. Двухстрочечный регексп автора вызывает улыбку.


  1. 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])?)*$/


    1. merlin-vrn
      13.01.2016 11:48

      … и мы сразу получаем кучу сайтов, которые не позволяют ввести валидные email?


      1. SelenIT2
        13.01.2016 12:01

        Формально — да:

        Примечание: это требование — намеренное нарушение RFC 5322, которое определяет синтаксис адресов e-mail, одновременно слишком строгий (до символа "@") и слишком мягкий (позволяющий комментарии, пробелы и строки в кавычках, что большинству пользователей незнакомо), чтобы подходить для практического использования здесь.


        Практически — такие адреса в реальном мире надо еще очень поискать:). Национальные домены валидируются уже в виде punycode.


  1. k12th
    13.01.2016 11:51
    +2

    Где-то читал небезынтересную статью на тему этой проверки. Автор приходил к выводу, что проверить регуляркой на полное соответствие c RFC невозможно, и надо проверять конечным автоматом. Проверка конечным автоматом дает еще такой бонус, что можно пользователю выводить осмысленные ошибки.

    Конечно, если речь о лендингах и конверсии, такие заморочки не нужны. Но ведь есть места, где нужна защита от дурака.


    1. merlin-vrn
      13.01.2016 11:54
      +2

      А что, в принципе можно доказать теорему, что граматика адреса — сложнее, чем регулярная, определить место в иерархии (по Хомскому) и успокоиться с проверкой емейлов регулярками :)


      1. sekrasoft
        16.01.2016 15:50
        +1

        Нет, успокоиться не удастся.

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

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


    1. SirEdvin
      13.01.2016 23:49
      +1

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

      Я не прав?


      1. k12th
        14.01.2016 00:09

        Я в этом плаваю. Но вот HTML регулярками распарсить невозможно, а конечными автоматами — да.


        1. SirEdvin
          14.01.2016 00:47

          Возможно, это потому что регулярные грамматики мощнее, чем регулярные выражения. Но я тут тоже не уверен.


          1. khim
            14.01.2016 01:36
            +3

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


            1. SirEdvin
              14.01.2016 01:54

              Буду знать.

              Не вижу проблем с проверкой на e-mail адрес. Зачем там, например, контекстно-сводобная грамматика?


              1. khim
                14.01.2016 02:19
                +1

                Вы не поверите :-) Комментарии. В скобочках. В которых, вы не поверите, могут быть комментарии. В скобочках. Дальше — смотри про разбор баланса скобок с помощью регулярок.


        1. khim
          14.01.2016 01:32
          +3

          На распарсите вы HTML ни регулярками, ни конечными автоматами. Теорема Клини, однако. Памяти нет. Проверьте хотя бы скобочный баланс регуляркой или конечным автоматом — а я над вами посмеюсь.


          1. k12th
            14.01.2016 10:17

            Очень может быть. Тем не менее, браузеры как-то парсят HTML. Как они это делают?


            1. SelenIT2
              14.01.2016 11:09

              Как-то так:) Запоминая стек открытых элементов и временами меняя правила «игры» по ходу дела в зависимости от его состояния…


              1. k12th
                14.01.2016 11:15

                Там написано «state machine».


                1. khim
                  14.01.2016 16:28
                  +1

                  Конечный автомат плюс бесконечная лента — это «машина Тьюринга», вообще-то. При желании ленту можно заменить счётчиками (минимум двумя, если ничего не путаю). Главное — в системе должно быть что-то потенциально бесконечное. Без этого — никак.


                  1. Zenitchik
                    14.01.2016 17:33

                    А если оно не потенциально бесконечное — то это линейно-ограниченный автомат. Тоже образует подкласс языков.


              1. Zenitchik
                14.01.2016 14:54
                +2

                Это только токенизация. Разумеется, токенизация доступна коенчному автомату.


            1. Zenitchik
              14.01.2016 14:55

              HTML парсится на МП-автомате.


              1. khim
                14.01.2016 16:30

                HTML парсится массой разных вещей. Но все эти вещи имеют имеют обну общую особенность: они все могут эмулировать машину Тьюринга, а значит могут реализовать все алгоритмы без исключения. МП-автомат — да, годится.


                1. Zenitchik
                  14.01.2016 17:31

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


                  1. khim
                    14.01.2016 18:50

                    Oops. Да, простите, перепутал. Да, МП-автоматы «не умеют» машину Тьюринга. Редкий случай чего-то практически полезного между «конечными автоматами» и машиной Тьюринга…


                    1. Zenitchik
                      14.01.2016 19:25

                      В смысле «редкий»? На практике есть КА, МПА и различная экзотика. А над всем этим стоит машина Тьюринга, которая, вообще говоря, тоже экзотика, но важная в теоретическом отношении.


                      1. khim
                        14.01.2016 20:34

                        С смысле «практически редкий». Как правило всякие примочки (типа языка BPF) стартуют с чего-то, что может обработать конечный автомат, потом их делают «немного мощнее», потом ещё, и почти всегда когда происходит выход за пределы конечных автоматов выясняется что у нас в руках — уже нечто полное по Тьюрингу (с «проблемой остановки» и прочими прелестями).

                        Игра Жизнь полна по Тьюрингу, к примеру :-) Что как бы сходу ни разу не очевидно…


                        1. Zenitchik
                          14.01.2016 22:17

                          Как правило, язык токенизируется КА, а затем разбирается в структуру МПА. Крайне редко встречается такой язык, который невозможно было транслировать МПА. А учитывая, что МПА относительно легко разрабатывать — это очень популярный класс трансляторов, внутри которого есть уже много подклассов, отличающихся логикой работы со стеком.


          1. sekrasoft
            16.01.2016 15:34

            Проверьте хотя бы скобочный баланс регуляркой или конечным автоматом — а я над вами посмеюсь.

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

            Смейтесь на здоровье. С PCRE нам даже КС не страшны:
            boost::regex re("(?(DEFINE)"
              "(?<symbol>[^()])"
              "(?<expr>((?&symbol)|\\((?&expr)\\))*)"
            ")"
            "^(?&expr)$");
            


            1. khim
              16.01.2016 22:28
              +2

              Хотел упомянуть про то, кто perlовые регулярные выражения регулярными выражениями, строго говоря, не являются, но потом решил, что тот, кто про это знает понимает так же, о чём я.

              А так-то да: PCRE'шные регулярные выражаения это, конечно, умеют, потому что у них есть память (хотя и ограниченное количество памяти, но для скобочек этого хватает, да).


      1. Zenitchik
        14.01.2016 14:56

        Регулярки мощнее. Вот если не использовать запоминание подстрок и заглядывание вперёд — тогда полностью эквивалентны.


  1. unStaiL
    13.01.2016 12:13

    Хорошо. Ну есть такая спецификация. Но вот регистрируюсь я в GMail или подобных системах. Они ведь не пропускают даже нижнее подчеркивание. Либо случай с habrahabr.ru/post/274985/#comment_8736743
    Почему так происходит. Или что будет если я на своем домене таки сделаю нетипичный адрес и начну жаловаться на сервисы которые его не пропускают?


    1. 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, или полный крах. Пришлось отказаться.


    1. merlin-vrn
      15.01.2016 09:14

      Не дают зарегистрировать такое имя или не дают проходить почте с такими адресами? Это очень разные вещи.


  1. Error_403_Forbidden
    13.01.2016 12:52
    +3

    Регулярка у автора смешная получилась, слишком маленькая. Ещё работать и работать над ней! :)


  1. vsespb
    13.01.2016 13:34
    +2

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

    был у меня адрес всецифры@домен — где-то не проходил проверку. жаловался. в ответ игнор. сомневаюсь что шалить будет весело.


    1. Wesha
      13.01.2016 18:37

      У меня было веселее — с год назад в одном магазине не принимали емейл вида vasya@pupkin.name — считали, что TLD не может быть длиннее 3 символов.

      (edit: не заметил: следующим комментом v v v коллега аналогичный случай уже отметил )


  1. outcoldman
    13.01.2016 18:25

    У меня главный email на домене .email. Встречаюсь достаточно часто с сайтами, которые его не понимают. Видимо ожидают 2 или 3 символа.


    1. Dreyk
      14.01.2016 14:13
      +2

      а их антиподы видимо ожидают до точки больше символов и ругаются на домен i.ua =)


  1. prishelec
    13.01.2016 20:04
    -6

    В свое время взял на заметку с некоего движка: ^([a-z0-9]([\-\_\.]*[a-z0-9])*)+@([a-z0-9]([\-]*[a-z0-9])*\.)+[a-z]{2,6}$
    Пока не подводил ). Но, не смею претендовать на его незаменимость.


  1. veitmen
    19.01.2016 11:14

    Нужно больше символов.


  1. Ununtrium
    19.01.2016 13:16
    -1

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


  1. Renius
    19.01.2016 14:28
    -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. stychos
    19.01.2016 17:07
    +1

    Нереально бесит Частенько в недоумении от того, что большинство таких «фильтров» не принимают адреса с однобуквенной локальной частью. Ещё немного непонятно, почему многие не разрешают в пароле использовать «слова» из локальной части имейла (этим даже майкрософт грешит), когда там одна буква, тоже не очень удобно.