はじめに
ウェブサービスを作っていると、入力されたメールアドレスが正しい形式か確かめたいということがあると思います。
今ならHTML5でinput
要素にtype="email"
が使えますし、そもそも検証せずとも実際に送ってみればいいのですが、やっぱり事前に検証したいというときもありますよね。
ただ、RFC5321やRFC5322で規定されているメールアドレスの形式って結構複雑で、ほとんどのサービスでは簡易的な正規表現でチェックしてたりします。
そこで、できるだけRFCに準拠した正規表現に挑戦してみました。
とりあえず結果教えろ
はい。
/^([\w!#$%&'*+\-\/=?^`{|}~]+(\.[\w!#$%&'*+\-\/=?^`{|}~]+)*|"([\w!#$%&'*+\-\/=?^`{|}~. ()<>\[\]:;@,]|\\[\\"])+")@(([a-zA-Z\d\-]+\.)+[a-zA-Z]+|\[(\d{1,3}(\.\d{1,3}){3}|IPv6:[\da-fA-F]{0,4}(:[\da-fA-F]{0,4}){1,5}(:\d{1,3}(\.\d{1,3}){3}|(:[\da-fA-F]{0,4}){0,2}))\])$/
わけわかんねーよ解説しろよ
そんなこといわれても解説できません。こっちだってわけわかんねーんだよヽ(`Д´#)ノウワァァァン
生成過程を公開しますので、がんばって解析してください。
(ECMAScriptで書いてます)
// 各パートで使用可能な文字セット(デリミタや特殊文字を除く)
const REGEXP_CHARSET_DOT = "[\\w!#$%&'*+\\-\\/=?^`{|}~]"; // dot-string
const REGEXP_CHARSET_QUOTED = "[\\w!#$%&'*+\\-\\/=?^`{|}~. ()<>\\[\\]:;@,]"; // quoted-string
const REGEXP_CHARSET_TLD = "[a-zA-Z]"; // トップレベルドメイン
const REGEXP_CHARSET_SLD = "[a-zA-Z\\d\\-]"; // サブレベルドメイン
const REGEXP_CHARSET_IPV4 = "\\d"; // IPv4
const REGEXP_CHARSET_IPV6 = "[\\da-fA-F]"; // IPv6
// 各パートの構成要素として受理可能なパターン(デリミタや特殊文字を除く)
const REGEXP_COMPONENT_DOT = `${REGEXP_CHARSET_DOT}+`;
const REGEXP_COMPONENT_QUOTED = `(${REGEXP_CHARSET_QUOTED}|\\\\[\\\\"])+`;
const REGEXP_COMPONENT_TLD = `${REGEXP_CHARSET_TLD}+`;
const REGEXP_COMPONENT_SLD = `${REGEXP_CHARSET_SLD}+`;
const REGEXP_COMPONENT_IPV4 = `${REGEXP_CHARSET_IPV4}{1,3}`;
const REGEXP_COMPONENT_IPV6 = `${REGEXP_CHARSET_IPV6}{0,4}`;
// ローカル部
const REGEXP_LOCAL_DOT = `${REGEXP_COMPONENT_DOT}(\\.${REGEXP_COMPONENT_DOT})*`;
const REGEXP_LOCAL_QUOTED = `"${REGEXP_COMPONENT_QUOTED}"`;
const REGEXP_LOCAL = `(${REGEXP_LOCAL_DOT}|${REGEXP_LOCAL_QUOTED})`;
// ドメイン部
const REGEXP_DOMAIN_GENERAL = `(${REGEXP_COMPONENT_SLD}\\.)+${REGEXP_COMPONENT_TLD}`;
const REGEXP_DOMAIN_IPV4 = `${REGEXP_COMPONENT_IPV4}(\\.${REGEXP_COMPONENT_IPV4}){3}`;
const REGEXP_DOMAIN_IPV6_COMMON = `${REGEXP_COMPONENT_IPV6}(:${REGEXP_COMPONENT_IPV6}){1,5}`;
const REGEXP_DOMAIN_IPV6_REST = `(:${REGEXP_DOMAIN_IPV4}|(:${REGEXP_COMPONENT_IPV6}){0,2})`;
const REGEXP_DOMAIN_IPV6 = `IPv6:${REGEXP_DOMAIN_IPV6_COMMON}${REGEXP_DOMAIN_IPV6_REST}`;
const REGEXP_DOMAIN_IP = `\\[(${REGEXP_DOMAIN_IPV4}|${REGEXP_DOMAIN_IPV6})\\]`;
const REGEXP_DOMAIN = `(${REGEXP_DOMAIN_GENERAL}|${REGEXP_DOMAIN_IP})`;
// メアド = ローカル部 + "@" + ドメイン部
const REGEXP_EMAIL = `^${REGEXP_LOCAL}@${REGEXP_DOMAIN}$`;
const PATTERN = new RegExp(REGEXP_EMAIL);
注意点
- **RFCに完全に準拠しているわけではありません。**だいたいのパターンでは正常に判定できますが、準拠していないものを通してしまう場合も一部あります。
- IPv6部分が正常に判定できない場合があります(不正な表記を受理する可能性)。
- IPv4部分も、255より大きな数値が指定されても受理してしまいます。
- RFCではローカル部、ドメイン部、全体の長さに制限がありますが、チェックしていません。
- ドメイン部の一部に、ごく軽いものですが病理的な正規表現を使っています。
- 正規表現のエンジンやマッチングする文字列によっては、まれにマッチング速度がとても遅くなることがあります。
- …とはいってもめちゃくちゃヤバいものではなく、意識しないとみなさんも思わずやってしまいがちなレベルです(「病理的な正規表現」という言葉を初めて聞いた方は、もしかすると今までやっていたかも?)
ここ違うんじゃね?とか、こうしたほうがいいんじゃね?みたいな部分があれば指摘おねがいします!