Регулярное выражение для email. Почему идеальной проверки не существует

1371
Регулярное выражение для email. Почему идеальной проверки не существует

У проверки email есть забавная ловушка. Адрес john..smith@example.com нарушает привычные правила RFC для локальной части, но стандартная HTML-проверка <input type="email"> может его принять. Адрес a@b тоже проходит встроенную проверку HTML, хотя большинство разработчиков назовут такую запись неправильной для обычной публичной регистрации.

Регулярное выражение для email

Причина не в плохих браузерах. Проверка электронной почты решает несколько разных задач, которые часто пытаются запихнуть в одну регулярку. Regex может найти строку, похожую на email, или проверить её по заранее выбранным синтаксическим правилам. Regex не способен доказать, что домен принимает почту, конкретный ящик существует и пользователь действительно имеет к нему доступ. Для последней задачи нужно отправить письмо с одноразовой ссылкой или кодом.

Простая регулярка для поиска email

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

[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+.[A-Za-z]{2,}

Регулярка найдёт привычные варианты.

ivan.petrov@example.com
 support@example.org
 alex+shop@example.net
 user_123@mail.example.ru
Фрагмент Что ищет
[A-Za-z0-9._%+-]+ Один или несколько разрешённых нами символов перед знаком @
@ Разделитель локальной и доменной частей
[A-Za-z0-9.-]+ Похожую на домен последовательность
. Обычную точку
[A-Za-z]{2,} Две или больше латинских букв в конце

Для поиска такой regex полезен именно как фильтр кандидатов. Ошибка начинается, когда найденное совпадение объявляют гарантированно корректным адресом.

Шаблон пропускает некоторые неправильные строки. Например, он не контролирует расположение дефисов и точек достаточно строго. Одновременно регулярка отклоняет часть синтаксически допустимых адресов. В локальной части стандарт разрешает гораздо больше знаков, включая !, #, $, &, ', *, +, /, =, ?, ^, _, `, {, |, } и ~. Правила подробно задаёт RFC 5322.

Есть и более экзотические конструкции. RFC допускает локальную часть в кавычках, поэтому некоторые адреса с пробелами или символами, запрещёнными в обычной форме, могут быть синтаксически допустимы.

"john smith"@example.com

Поддерживать подобные адреса в каждой форме регистрации необязательно. Сам SMTP рекомендует избегать ящиков, которым для работы требуется форма в кавычках, поскольку такая экзотика ухудшает совместимость. Здесь полезно разделять понятия «стандарт допускает» и «мой сервис обязан принимать».

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

john.smith@example.com    допустимая обычная форма
 .john@example.com         точка в начале
 john.@example.com         точка в конце
 john..smith@example.com   две точки подряд

Последние три строки не соответствуют форме dot-atom. Огромное количество коротких regex из интернета такого ограничения не проверяет.

Как проверять email в форме регистрации

Для веб-формы я сначала использую возможности HTML.

<input type="email" name="email" required>

Браузер проверяет значение перед отправкой формы и помечает неподходящую строку как ошибочную. Серверную проверку всё равно нужно выполнять заново. Клиентскую форму легко обойти, отправив HTTP-запрос другим способом.

Любопытнее другое. HTML Standard сознательно не копирует грамматику RFC 5322. Авторы стандарта прямо выбрали более практичное правило для пользовательских форм. Поэтому встроенный валидатор HTML не следует считать универсальной реализацией почтовых RFC.

Эквивалентная логика HTML выглядит примерно так.

/^[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])?)*$/

Отсюда появляется неожиданное поведение. HTML не требует точки в доменной части, поэтому a@b проходит базовую проверку. Такой подход нужен в том числе для локальных и корпоративных имён. Если публичному сервису нужны только адреса с обычными интернет-доменами, разработчик может добавить собственное бизнес-правило.

Я бы для типичной регистрации с ASCII-адресами использовал понятный ограниченный шаблон, а не пытался воспроизвести все RFC.

^[A-Za-z0-9!#$%&'*+/=?^_`{|}~-]+(?:.[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])?)+$

Такой вариант запрещает точку в начале и конце локальной части, двойные точки, дефисы по краям компонента домена и требует хотя бы одну точку справа от @. Но универсальной регуляркой выражение не становится. Шаблон сознательно вводит политику конкретного сервиса.

Например, regex не принимает адреса с локальной частью в кавычках и не реализует международную электронную почту. Расширение SMTPUTF8 позволяет использовать UTF-8 в адресах, поэтому правило «email всегда состоит только из латиницы» давно нельзя считать общим стандартом.

Интернационализированные домены добавляют ещё один уровень. Пользователь может видеть Unicode-имя, тогда как программные компоненты способны работать с представлением Punycode. Простое условие вроде [A-Za-z]{2,} для последней части домена уже не описывает все возможные случаи.

Есть ещё одна ошибка, которую я встречаю в регистрационных системах. Разработчик приводит всю строку к нижнему регистру и считает преобразование безопасной нормализацией. С доменной частью такой подход нормален, но SMTP исторически требует сохранять регистр локальной части и допускает различие между, например, Smith@example.com и smith@example.com. Почтовым системам рекомендуют не строить реальные ящики на таком различии, однако сторонний сервис не должен сам придумывать правила чужого провайдера.

По той же причине нельзя глобально удалять точки или часть после +. Некоторые почтовые сервисы считают такие варианты алиасами одного ящика, другие обрабатывают адреса иначе. Правило конкретного провайдера нельзя превращать в универсальную нормализацию email.

Почему даже идеальный regex не проверит существование ящика

Представим адрес.

nonexistent-user-937251@example.com

Можно написать регулярное выражение на сотни символов и идеально проверить выбранную грамматику. Результат всё равно ничего не скажет о существовании пользователя nonexistent-user-937251.

Следующий очевидный шаг состоит в проверке DNS. Но здесь тоже легко сделать неверный вывод. Наличие MX-записи говорит о почтовой инфраструктуре домена, а не о существовании конкретного ящика.

Обратное утверждение тоже неверно. Отсутствие MX не всегда означает невозможность доставки. SMTP предусматривает случай, когда при отсутствии MX домен рассматривают как имеющий неявный почтовый маршрут через собственную адресную запись. Поэтому правило «нет MX, значит email неправильный» слишком грубое.

Можно пойти дальше и подключиться к SMTP-серверу. Протокол содержит команды для работы с получателями и даже команду VRFY для проверки имени пользователя. На практике такой тест не даёт гарантированного ответа. Серверы могут ограничивать проверку пользователей, чтобы не помогать сборщикам адресов. RFC по борьбе со спамом отдельно указывает на риск перечисления существующих ящиков через VRFY и EXPN.

Даже ответ сервера на RCPT TO не всегда равен окончательному результату доставки. Почтовая система может принять получателя на одном этапе, а затем вернуть недоставленное письмо. Возможны переадресация, списки рассылки, временные ошибки, антиспам-фильтры и архитектура, в которой принимающий сервер ещё не знает окончательного состояния ящика.

Поэтому сервисы «проверки существования email без отправки письма» могут оценивать адрес по нескольким сигналам, но не получают стопроцентного доказательства владения ящиком.

Отдельная ловушка связана с длиной. Часто встречается утверждение, будто максимальный email можно вычислить как 64 символа слева, знак @ и 255 символов справа. SMTP действительно задаёт предел локальной части в 64 октета и домена в 255 октетов, но одновременно ограничивает длину SMTP-пути. Простое сложение двух максимальных значений даёт неправильный результат. Для Unicode дополнительно приходится считать байты UTF-8, а не только видимые символы.

Рабочая схема вместо огромной регулярки

Для обычного сайта я разделяю проверку на несколько уровней.

  1. Убираю случайные пробелы по краям. Внутреннее содержимое адреса без необходимости не переписываю.
  2. Проверяю форму. В браузере использую type="email", на сервере применяю валидатор или небольшой regex, соответствующий правилам продукта.
  3. Проверяю ограничения приложения. Например, допустимую длину, поддержку международных адресов или требование публичного домена.
  4. Создаю одноразовый случайный токен. Ссылка или код должны иметь ограниченный срок жизни и переставать работать после использования.
  5. Отправляю письмо. Аккаунт получает статус подтверждённого только после перехода по ссылке или ввода кода.

Подтверждающее письмо проверяет то, чего regex принципиально не видит. Сервис убеждается, что сообщение дошло до указанного адреса и человек, проходящий регистрацию, получил доступ к содержимому письма. Такой тест не доказывает личность владельца и не превращает email в сильный фактор аутентификации, но для подтверждения доступности почтового адреса подходит намного лучше DNS-запросов и SMTP-зондирования.

В предыдущих материалах серии я разбирал базовый синтаксис регулярных выражений и показывал, как собирать regex по небольшим частям. Email хорошо продолжает эту мысль. Чем сложнее предметная область, тем хуже работает идея «запишем всю бизнес-логику одним шаблоном».

Мой рабочий выбор выглядит просто. Для поиска адресов в тексте беру короткую регулярку и принимаю небольшое число ложных совпадений. Для формы использую встроенную проверку HTML плюс серверный валидатор. Если продукт сознательно поддерживает только привычные ASCII-адреса, фиксирую такое ограничение как правило продукта. Если нужна международная почта, добавляю полноценную поддержку Unicode и SMTPUTF8, а не несколько кириллических диапазонов в regex.

Идеальная регулярка для email не нужна. Regex должен ответить на ограниченный вопрос, соответствует ли строка выбранному формату. Существование и доступность ящика подтверждает письмо. Попытка решить обе задачи одним выражением обычно заканчивается огромным regex, который сложно читать, сложно тестировать и всё равно невозможно назвать стопроцентной проверкой email.

Какое регулярное выражение для email использовать?

Для поиска в тексте подходит простой шаблон [A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+.[A-Za-z]{2,}. Для регистрации лучше сочетать серверную проверку, type="email" в HTML и подтверждающее письмо.

Можно ли полностью проверить email регулярным выражением?

Нет. Regex проверяет синтаксис по выбранным правилам, но не знает, существует ли почтовый ящик и имеет ли пользователь к нему доступ.

Почему сложная регулярка для email не гарантирует правильность адреса?

Даже идеальное совпадение с синтаксисом не проверяет DNS, конфигурацию почтового сервера, существование получателя и доступ человека к ящику.

Допустим ли знак плюс в адресе электронной почты?

Да. Знак + разрешён в обычной локальной части email. Некоторые провайдеры используют его для подадресации, но такое поведение нельзя автоматически переносить на все почтовые системы.

Можно ли использовать две точки подряд в email?

В обычной форме dot-atom локальной части две точки подряд запрещены. При этом базовая проверка HTML использует собственное упрощённое правило и может принять такой ввод.

Почему input type email принимает адрес без точки в домене?

HTML не требует обязательной точки в доменной части. Поэтому адрес вроде a@b соответствует встроенному правилу. Публичный сервис может ввести более строгое собственное требование.

Проверяет ли MX-запись существование email?

Нет. MX описывает почтовую маршрутизацию домена, но ничего не доказывает о конкретном почтовом ящике. Отсутствие MX тоже нельзя автоматически считать доказательством недействительного адреса.

Зачем отправлять письмо для подтверждения email?

Письмо с одноразовой ссылкой или кодом подтверждает практический факт доступа пользователя к указанному почтовому адресу. Регулярное выражение получить такую информацию не может.

Alt text
Обращаем внимание, что все материалы в этом блоге представляют личное мнение их авторов. Редакция SecurityLab.ru не несет ответственности за точность, полноту и достоверность опубликованных данных. Вся информация предоставлена «как есть» и может не соответствовать официальной позиции компании.
АНТИПОВ
0 : 1
ПРАВДА VS НАГЛОСТЬ
ПРАВ
Быть правым —
самый бесполезный актив
Умные проигрывают наглым. Логика сливается инстинктам толпы. Статусу и бюджетам плевать на ваши факты.
АНТИПОВ РЕЖЕТ БЕЗ НАРКОЗА
реклама

Рекламодатель
«Позитив Текнолоджиз»
ИНН: 7718668887
ptsecurity.com↗
Реклама «Позитив Текнолоджиз»