2018年4月22日日曜日

可能な限りRFCに準拠したEメールアドレス検証用正規表現

ウェブサービスを作っていると、入力されたメールアドレスが正しい形式か確かめたいということがあると思います。

今ならHTML5でinput要素にtype="email"が使えますし、そもそも検証せずとも実際に送ってみればいいのですが、やっぱり事前に検証したいというときもありますよね。

ただ、RFC5321RFC5322で規定されているメールアドレスの形式って結構複雑で、ほとんどのサービスでは簡易的に正規表現でチェックしてたりします

そこで、できるだけ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}))\])$/

なるほど、わかりやすい!
とりあえず使えればいいや!

…という人は何も考えずにコピペして使ってください。

わけわかんねーよ解説しやがれ

…という人は以下を読んでください。読んでも理解できる保証はありませんが。

わけわかんねー人のための解説

…解説していこうと思って途中まで書いたんですが、もうややこしくて仕方ない。

生成過程を公開するので、なんとか解析してください。
// 各パートで使用可能な文字セット(デリミタや特殊文字を除く)
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ではローカル部、ドメイン部、全体の長さに制限がありますが、チェックしていません。
  • ドメイン部の一部に、病理的な正規表現を使っています。
    • 正規表現のエンジンやマッチングする文字列によっては、まれにマッチング速度がとても遅くなることがあります。
    • …とはいってもめちゃくちゃヤバいものではなく、意識しないとみなさんも思わずやってしまいがちなレベルです。

0 件のコメント:

コメントを投稿