LoginSignup
12
6

More than 1 year has passed since last update.

HTML 仕様のメールアドレス正規表現に最小限の修正を加える

Last updated at Posted at 2020-10-24

TL;DR

以下の正規表現は、HTML 仕様のメールアドレス正規表現の末尾に、 TLD 必須のルールを加えたものです。

/^[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])?)*\.[a-zA-Z0-9][a-zA-Z0-9-]{0,61}[a-zA-Z0-9]$/

<input type="email">pattern 属性に指定する場合は、以下のように短く書けます。

<input type="email" pattern=".+\.[a-zA-Z0-9][a-zA-Z0-9-]{0,61}[a-zA-Z0-9]">

以下、詳細です。

HTML 仕様のメールアドレス正規表現について

HTML 仕様には、 <input type="email"> のバリデーションルールと同等の正規表現が含まれています。

以下がその正規表現です。

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

ブラウザが標準で提供する機能なので、ぜひこのルールを活用したいところです。

課題

しかしながら。。

この type="email" は、TLD が無いメールアドレスを許可しますa@a は、valid なアドレスだと判定されます。

上の正規表現の遷移図は以下のようになっています。

image.png
(Regexper)

このような緩いルールになっている理由は、pattern 属性を使って更に条件を狭めることができるからです。デフォルトでメールアドレスとして最も緩い条件を適用し、次に pattern で必要に応じた制限を加えます。

If you need the entered e-mail address to be restricted further than just "any string that looks like an e-mail address," you can use the pattern attribute to specify a regular expression the value must match for it to be valid.
(MDN)

デフォルトの正規表現と pattern の正規表現は、AND 条件です。両方が検証されます。

However, the browser runs both the standard e-mail address filter and our custom pattern against the specified text.
(MDN)

2つのルールは AND で検証されるので、pattern 属性で、例えば @ が含まれるかどうかをチェックする必要はありません。

pattern 属性を指定しない type="email" は、現実には使い物になりません。TLD があっても無くてもどちらでも構わないようなメールアドレスを input で入力させるのは、相当に特殊な場合だけでしょう。
image.png
JPNIC の「ドットなしドメイン名 (Dotless Domain Names) について」の記事も参考になります。

正規表現に TLD の制限を加える

そこで、HTML 仕様を極力変えずに TLD の制限を加えてみます。

(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*

がドメインの2番め以降のラベルで、0回以上の繰り返しなので、この後に、以下を付け加えます。

\.[a-zA-Z0-9][a-zA-Z0-9-]{0,61}[a-zA-Z0-9]

1文字の TLD を禁止する仕様は存在しないようですが、今の TLD に1文字のものはないので、(?: )? を外して「TLD 2 文字以上」の制限を加えています。上限 63 文字は、他のラベルと同様です。

修正を加えた正規表現は以下の通り。

/^[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])?)*\.[a-zA-Z0-9][a-zA-Z0-9-]{0,61}[a-zA-Z0-9]$/

遷移図は以下のようになります。

image.png
(Regexper)

TLD 部分だけを pattern に指定できるか

通常の正規表現であれば、部分一致で、末尾の TLD の正規表現だけを指定できそうなものです。

pattern="\.[a-zA-Z0-9][a-zA-Z0-9-]{0,61}[a-zA-Z0-9]$"

しかし、これはうまくいきません。HTML の pattern 属性は、^(?: )$ でラップされるからです。

5 . Let anchoredPattern be the string "^(?:", followed by pattern, followed by ")$".
(HTML Living Standard)

なので、pattern にはメールアドレス全体にマッチする正規表現を指定する必要があります。

.+ を先頭につけて、

pattern=".+\.[a-zA-Z0-9][a-zA-Z0-9-]{0,61}[a-zA-Z0-9]"

と書けます。デフォルトのルールと pattern は両方が検証されるので、前半部分は .+ と省略してしまっても問題ありません。

ローカルパートに記号を許すか

デフォルトの正規表現では、以下の記号をローカルパート (@ より前) に許可しています。

.!#$%&'*+/=?^_`{|}~-

この内、絶対に必要なのは以下でしょう。

.+_-

残りの記号は、場合によっては削ってもよいかもしれません。

!#$%&'*/=?^`{|}~

特にユーザ登録で、他のサービスと連携する可能性がある場合、最初から絞っておかないと後で困る可能性がありそうです。こちらで許可した記号が、連携先では許可されていないかもしれません。

メジャーなサービスのバリデーションはどうなっているか

いくつかのサイトで、ユーザ登録画面の挙動を見てみました。(実際には登録せず、クライアントサイドのバリデーションの挙動だけを見ています)

  • Facebook は、1 文字の TLD は許可。ローカルパートの記号も許可しているようでした
  • Twitter は、1文字の TLD は禁止。記号は基本の文字に加えて & を許可していました
  • Apple ID は、1文字の TLD は禁止。基本の記号だけを許可しているようでした

デモ

以下type="email"input 要素に pattern を指定したときの挙動を試せます。

See the Pen HTML email validation by koseki (@kkoseki) on CodePen.

また、様々なメールアドレスに正規表現を適用するテストページをこちらに作成しました。

image.png

Microsoft の以下のページに、いい感じのテストデータがあります。

12
6
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
12
6