私自身驚いたのだが、'test@[127.0.0.1' . "\\\x1f]"
はRFC2822に準拠している。
おかげで上記のコードもvalidだ。なんてこった
なぜそうなのか、というのは、RFC2822のdomain-literal
の仕様による。
domain-literal = [CFWS] "[" *([FWS] dcontent) [FWS] "]" [CFWS]
「[]
で囲まれたdcontent
」っていったいなんだ?
dcontent = dtext / quoted-pair
「dtext
またはquoted-pair
」?
dtext = NO-WS-CTL / ; Non white space controls %d33-90 / ; The rest of the US-ASCII %d94-126 ; characters not including "[", ; "]", or "\"
quoted-pair = ("\" text) / obs-qp
で、text
とは何かにたどり着く。
text = %d1-9 / ; Characters excluding CR and LF %d11 / %d12 / %d14-127 / obs-text
見ての通り、\x1F
はtext
であり、\\\x1F
はquoted-pair
であり、よってdcontent
の正当な一部となり、domain-literal
として正当なのである。
少なくとも、RFC2822に従えばそういうことになる。私自身我が目を疑ったが、Email::Valid->rfc822()
も同様の結果を返す。ただしEmail::Valid->address()
の方は、domain-lit
はすべて無視する。
余談だが、quoted-pair
が、RFC2822とRFC822では異なっている。
quoted-pair = "\" CHAR ; may quote any char
端的な違いは、CRとLFを含むか含まないかである。
話を元に戻す。以上をふまえると、私が元にしたperlfaq9も不正確だということになる。たまたま\x1F
は正しく解釈したものの、\\\S
は明らかに手抜きであり、正しくは\\[\x01-\x09\x0B-\x0c\x0e-\x7f]
ということになる。この点を修正した正規表現を、以下に掲載する。
ところでこの正規表現には他にも問題が残っている。domain-literalで\\\Sにマッチするようになっているがこれはなんなのだろう。
実にするどい意見である。そもそもの問題は、domain-lit
を無批判に使っていたことにある。MTAなどはとにかく、Webフォームやメーラーの設定フィールドなど、およそ人間が入力する「メールアドレス」にdomain-lit
は不要である。RFC2822から、domain-lit
を抜いた正規表現は、以下のとおりとなる。
最後に、これらをデモする perl script をEntryの最後につけておく。
正規表現を正規にやろうとするとこれほど難しいものだとは。
Dan the Regexp Monger
use strict; use warnings; use Email::Valid; my $rfc2822 = qr<(?:(?:(?:(?:[a-zA-Z0-9_!#\$\%&'*+/=?\^`{}~|\-]+) (?:\.(?:[a-zA-Z0-9_!#\$\%&'*+/=?\^`{}~|\-]+))*)|(?:"(?:\\[^\r\n]| [^\\"])*")))\@(?:(?:(?:(?:[a-zA-Z0-9_!#\$\%&'*+/=?\^`{}~|\-]+) (?:\.(?:[a-zA-Z0-9_!#\$\%&'*+/=?\^`{}~|\-]+))*)| (?:\[(?:\\[\x01-\x09\x0B-\x0c\x0e-\x7f]|[\x21-\x5a\x5e-\x7e])*\])))>x; my $rfc2822_ndl = qr<(?:(?:(?:(?:[a-zA-Z0-9_!#\$\%&'*+/=?\^`{}~|\-]+) (?:\.(?:[a-zA-Z0-9_!#\$\%&'*+/=?\^`{}~|\-]+))*)|(?:"(?:\\[^\r\n]| [^\\"])*")))\@(?:(?:(?:[a-zA-Z0-9_!#\$\%&'*+/=?\^`{}~|\-]+) (?:\.(?:[a-zA-Z0-9_!#\$\%&'*+/=?\^`{}~|\-]+))*))>x; my $perlfaq9 = qr<(?:(?:(?:(?:[a-zA-Z0-9_!#\$\%&'*+/=?\^`{}~|\-]+) (?:\.(?:[a-zA-Z0-9_!#\$\%&'*+/=?\^`{}~|\-]+))*)|(?:"(?:\\[^\r\n]| [^\\"])*")))\@(?:(?:(?:(?:[a-zA-Z0-9_!#\$\%&'*+/=?\^`{}~|\-]+) (?:\.(?:[a-zA-Z0-9_!#\$\%&'*+/=?\^`{}~|\-]+))*)|(?:\[(?:\\\S| [\x21-\x5a\x5e-\x7e])*\])))>x; my $regexp_info = qr<(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\. [a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|" (?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]| \\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[a-z0-9](?:[a-z0-9-]* [a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:25[0-5]| 2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]? [0-9][0-9]?|[a-z0-9-]*[a-z0-9]: (?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]| \\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])>x; sub match { $_[0] =~ /\A$_[1]\z/ } for my $addr ( 'da.me..@docomo.ne.jp', '"da.me.."@docomo.ne.jp', 'dankogai+regexp@gmail.com', 'test@[192.168.0.1]', 'test@[127.0.0.1' . "\\\x1f]", 'test@[127.0.0.1' . "\\\x0a]", 'test@[127.0.0.1' . "\\\x20]", ) { my $eaddr = $addr; $eaddr =~ s/([\x00-\x1f])/sprintf("\\x%02x",ord $1)/eg; print "$eaddr", "\n"; print " rfc2822: ", 0 + !!match( $addr, $rfc2822 ), "\n"; print " rfc2822_ndl: ", 0 + !!match( $addr, $rfc2822_ndl ), "\n"; print " perlfaq9: ", 0 + !!match( $addr, $perlfaq9 ), "\n"; print " regexp_info: ", 0 + !!match( $addr, $regexp_info ), "\n"; print " E::V->rfc822: ", 0 + !!Email::Valid->rfc822($addr), "\n"; print " E::V->address:", 0 + !!Email::Valid->address($addr), "\n"; }
同じレベルの間違いですね。
正規表現を書き慣れていれば、メールアドレスを表す正規表現に
"\S" が使われることはありえないと直感的に気付くはずです。
もし "\S" と同じような意味で使うなら "(?!\s)" を使うべきなので "\S" を使う機会はありません。
ちなみにまだ指摘するべき点が2点あります。
このまま使うと恥をかきますよ >読者のみなさん