📩

君はメールアドレスの正規表現を適当にググって使っていないか?

2025/02/25に公開
11

もう、そういうのは卒業しよう。

今日からメールアドレスのバリデーションの正規表現は

/^[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の標準仕様を定めるWHATWGの正規表現をそのまま使っている」ところ。
https://html.spec.whatwg.org/multipage/input.html#email-state-(type=email)

つまり、各ブラウザのデフォルトの<input type="email" />のバリデーションと一致するという大きなメリットを得られる。

これはMDNにも載っている列記とした「実用的な」正規表現だ。

ちなみにRFCオタクがRFC準拠のおおよそ実用に耐えないであろうメールアドレスの正規表現を推してくるかもしれないが無視して良い。
例えば、RFCに準拠している以下のようなメールアドレスを君のシステムで許容したいと思うだろうか?

"very.(),:;<>[]\".VERY.\"very@\\ \"very\".unusual"@strange.example.com

ちなみにRubyだとURI::MailTo::EMAIL_REGEXPで、WHATWGの正規表現とほぼ同じ正規表現にアクセスできる。

> ruby -r uri -e 'puts URI::MailTo::EMAIL_REGEXP'
(?-mix:\A[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])?)*\z)

言語側のライブラリーで用意されている正規表現を使うのが最も安全で楽だね。

まとめ

メールアドレスのバリデーションには、WHATWGの標準の正規表現を使うことをお勧めする。

/^[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. ブラウザの <input type="email" /> のバリデーションと一致する
  2. 実用的な範囲でメールアドレスを検証できる
  3. 信頼できるHTMLの標準に基づいている

適当にググって見つけた情報を使うだけでは、いつまで経っても初心者から抜け出せない。
標準に基づいた実装を選ぶことで、より確信を持ってシステムを作ることができる。

とにかく、メールアドレスのバリデーションという枯れた分野で適当にググった正規表現を使うのは卒業しよう。

Discussion

Na.17Na.17

私の見解としてメールアドレスは正規表現で検証するべきではないと考えています。

メールアドレスを最も確実に検証する方法は、実際にメールを送信することです。

メールアドレスの構文の問題よりも長さ制限のほうが重要ではないでしょうか?
RFCにはメールアドレスの長さ制限が記載されています。ローカル部は64オクテット、ドメイン部は255オクテットで、@を含めて320文字(ASCII文字)になるべきです。しかし、320文字のメールアドレスは送信できません。SMTP(RFC 2821)の仕様によれば、

The maximum total length of a reverse-path or forward-path is 256 characters.

とあり、ブラケットを含む関係上、実質的には254文字に制限されます。つまり長さ制約も検証する必要があるはずです。

これを超える以下のメールアドレスは、冒頭の正規表現を通過しますが、送信不可能な場合があります。少なくとも厳格なRFCに準拠していれば拒否されるはずです。
下に正規表現をバイパスするメールアドレスを示してみます。

abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrst@2pc.nexus

264文字あります。SMTP仕様に合致しないもののRFC準拠で正規表現に合致します。
上のアドレスはGmailとOutlookで送信こそ可能なものの ProtonMail は受信をメールアドレスが無効として拒否しました。Tutaも送信を試みましたが拒みました。

この例から、メールアドレスの正規表現がどれほど信頼できないかがわかると思います。あなたの正規表現よりも厳しい制約を課すプロバイダーを利用している場合送信できないケースがありあります。

なのでメールアドレスの検証は送信しない限りわかりません。

あいや - aiya000あいや - aiya000

本稿の言っていることはメールアドレスの形式のチェックであって、実在性のチェックの話ではないと思います💬

例えばフォームバリデーションにメールアドレスの実在性のチェックを使ってしまうと、少なからずUXは悪くなる気がします

Na.17Na.17

本稿とズレたかもしれません。
ただ、ほとんどのシステムではメールアドレスの実在性を気にしないことはあり得ないはずです。
メールアドレスの検証として正規表現は、保険的機能にすらなりません。@が1つだけ存在するか否かを求める正規表現で十分です。
主張を強めるなら、「メールアドレスの正規表現なんてどうでも良いです。」
RFC準拠でも、RFCより緩くても、厳しくても結局実際に送れるかは、分からないのです。
取りあえず受け入れて送信を試みて、無理なら使用を諦めてもらうが良いと私は考えています。
システムでは文字列にしかならないし、きちんとエスケープやプレースホルダで実装されていれば脆弱性はおきません。

debirudebiru

ちなみにRFCオタクがRFC準拠のおおよそ実用に耐えないであろうメールアドレスの正規表現を推してくるかもしれないが無視して良い。

なぜ無視して良いのでしょうか。RFC 準拠のメールアドレスの正規表現が実用的ではないからですか?

ちなみにこれの何がいいかというと

「HTMLの標準仕様を定めるWHATWGの正規表現をそのまま使っている」ところ。

それは WHATWG 信者であれば、の話ですよね。
WHATWG が定める <input type="email"> のバリデーションが気に食わない人は以前から一定数いるという認識です。https://github.com/whatwg/html/issues/1465

HTML 仕様のメールアドレス正規表現に最小限の修正を加えるという記事もあるようです。

これはMDNにも載っている列記とした「実用的な」正規表現だ。

MDN に載っていれば実用的なのかというのも疑問があります。

適当にググって見つけた情報を使うだけでは、いつまで経っても初心者から抜け出せない。
標準に基づいた実装を選ぶことで、より確信を持ってシステムを作ることができる。

とにかく、メールアドレスのバリデーションという枯れた分野で適当にググった正規表現を使うのは卒業しよう。

確かに、「適当」にググった二次情報のブログ記事の正規表現を何の考えもなしに流用するような人は初心者から抜け出せないでしょうね。
ただし、WHATWG が定めたバリデーションが絶対的に良いかというのも疑問です。

https://x.com/ockeghem/status/1894590881086542270

私は "><script>alert('or/**/1=1#')</script>"@example.jp のようなメールアドレスも受け付けるサービスの方が望ましいと思っています。これを弾く理由が合理的でないからです。

そのため、私はしばしば、次のような正規表現を用いてメールアドレスをバリデーションしています。

/** メールアドレスのバリデーション
 * 「@ 以外の文字が1文字以上」
 * 「@ が現れる」
 * 「@ . 空白類文字 以外の文字が続く」
 * 「. の後に @ . 空白類文字 以外の文字が続く」が1回以上繰り返される
 */
^[^@]+@[^@\.\s]+(\.[^@\.\s]+)+$

このシンプルな正規表現よりも、WHATWG の定める正規表現を使うべき理由が見当たりません。

そもそも、これってどのレイヤーでの話なのでしょうか。

JavaScript の文脈であれば、そもそも正規表現を使わずに <input type="email"> に対する checkValidity メソッドでバリデーションすべきという話もある気がしています。

総括として、独自のクソみたいな正規表現によるバリデーションを掛けるなという主張であれば同意です。

emkemk
^[^@]+@[^@\.\s]+(\.[^@\.\s]+)+$

この正規表現は"a@b"@example.jpのようなメールアドレスを弾きますけど、"><script>alert('or/**/1=1#')</script>"@example.jpを弾く理由は合理的でないのに"a@b"@example.jpを弾く合理的な理由があるということでしょうか。どんな理由ですか?

debirudebiru

確かに私の文章に対してそのような指摘は尤もですね。

合理的な理由なく正当なメールアドレスを弾きたくなければ、私が提示すべき正規表現は .*@.* で良かったはずです。

私が ^[^@]+@[^@\.\s]+(\.[^@\.\s]+)+$ を提示したのは過去に実際に使っていた正規表現を持ってきたためですが、私の主張とは矛盾していたかもしれません。

Naoki IwataNaoki Iwata

記事の正規表現だと example@gmailcom のような
ドット漏れのメールアドレスも true 判定になるので
実用的なバリデーションではないですね。

const email = 'example@gmailcom'
const reg = /^[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])?)*$/
console.log(reg.test(email)) // true

以下のシンプルな正規表現ならドット漏れは false になるので
私もこちらの正規表現をメールアドレスの判定によく使用しています。

const email = 'example@gmailcom'
const reg = /^[^@]+@[^@\.\s]+(\.[^@\.\s]+)+$/
console.log(reg.test(email)) // false
owayoowayo

原理主義者も黙らせる正規表現はこちら
https://blog.tmtms.net/entry/2014/09/09/mailaddress-regexp
一般的な正規表現(PCRE)にすると、ちょっとゴチャっとするけどこんなかんじでしょうか

/^
  (?=.{1,256}$)
  (?=.{1,64}@.{1,255}$)
  (
    [0-9a-z!\#$%&'*+\-\/=?^_`{|}~]+
    (\.[0-9a-z!\#$%&'*+\-\/=?^_`{|}~]+)*
  |
    \"([\x09\x20\x21\x23-\x5b\x5d-\x7e]|\\[\x09\x20-\x7e])*\"
  )@
  (
    [0-9a-z]([0-9a-z-]{0,61}[0-9a-z])?
    (\.[0-9a-z]([0-9a-z-]{0,61}[0-9a-z])?)*
  )
$/i
yuuAnyuuAn

ユーザーがメールアドレスを入力するフォームにおけるバリデーション機能の要件って、システムが受け付けられないメールアドレスを受け付けないようにすることではなく、ユーザーの入力ミスを防ぐことであることが多いと感じています。
ユーザーは半角文字を全角で入力したり、見えないスペースが入っていることに気づかなかったり、メールアドレス欄にメールアドレスでないものを入力したりしがちです。
そのようなミスにユーザーが気づけるようにするために必要なのは、RFC 準拠の厳密な正規表現ではないと、私も思います。