能書き
前エントリを書いてからいろいろと調べていて驚いたんだけど、日本語のwebsiteで、それなりにまともにRFC822(RFC2822,RFC5322)に準拠した(もしくはきちんと意図的に準拠していない部分を選択している)正規表現はPerlだろうがPHPだろうがRubyだろうが軽くぐぐった程度では見当たらない。PerlのモジュールのEmail::AddressもEmail::Validも程度の差はあれ問題を抱えている。そこらへんの既存の出回ってる正規表現にどういった問題があるかなんてことは次回エントリにて。
というわけで、Perl、PHP、RubyでRFC5322準拠なメールアドレス(addr-spec)の正規表現を以下に示します。尚、addr-specの最終的な正規表現のみならずそれを作成するに至る部分も併記してあります。これは、最終的な正規表現だけでは難解すぎてとても理解できないからです。内容を理解せずにそのままコピペすることを否定はしませんが理解しようとしたときの助けとなるよう、コメントアウトでもよいのでコード中に併記しておくことをお勧めします。
方針
- RFC5322準拠が基本
- addr_spec_looseは..や.@を許容した正規表現(日本のモバイルキャリア用)
- ただしobsoleteなsyntaxは無視
- ただしdomain_literalは無視
- ただしCFWSは無視
- ただしFWSも無視
- 上記のように無視してるのが多いのは用途をweb入力のチェックやテキストからの抜き出し用途を想定しているため
- BNFのsymbolの変数移植はできるだけRFCに即しつつ-を_に
- 文字種の記述はできるだけRFCの順番にあわせる
- ASCIIをコードポイントで指定する場合は16進数で
- perlではflagged utf8でも処理できるように
- できるだけテストはしてありますが、完璧だとは思っていないのでミスを指摘してくださる方は大歓迎です。
無視多すぎて準拠じゃないじゃんという突っ込みがありそうですが、自分で宣言している場合はありという俺ルールで。
Perl
動作確認: 5.10.0
my $wsp = '[\x20\x09]'; my $vchar = '[\x21-\x7e]'; my $quoted_pair = "\\\\(?:$vchar|$wsp)"; my $qtext = '[\x21\x23-\x5b\x5d-\x7e]'; my $qcontent = "(?:$qtext|$quoted_pair)"; my $quoted_string = "\"$qcontent*\""; my $atext = '[a-zA-Z0-9!#$%&\'*+\-\/\=?^_`{|}~]'; my $dot_atom_text = "$atext+(?:[.]$atext+)*"; my $dot_atom = $dot_atom_text; my $local_part = "(?:$dot_atom|$quoted_string)"; my $domain = $dot_atom; my $addr_spec = qr{${local_part}[@]$domain}; my $dot_atom_loose = "$atext+(?:[.]|$atext)*"; my $local_part_loose = "(?:$dot_atom_loose|$quoted_string)"; my $addr_spec_loose = qr{${local_part_loose}[@]$domain}; my $input_addr_spec = 'foo@example.com'; if ( $input_addr_spec =~ /\A$addr_spec\z/ ) { print "valid addr-spec\n"; } use utf8; my $input_text = 'ぼくの@メールアドレスはfoo@example.comです'; if ( $input_text =~ /($addr_spec)/ ) { print "My addr-spec is <$1>\n"; }
PHP
動作確認: 5.2.6
<?php $wsp = '[\x20\x09]'; $vchar = '[\x21-\x7e]'; $quoted_pair = "\\\\(?:$vchar|$wsp)"; $qtext = '[\x21\x23-\x5b\x5d-\x7e]'; $qcontent = "(?:$qtext|$quoted_pair)"; $quoted_string = "\"$qcontent*\""; $atext = '[a-zA-Z0-9!#$%&\'*+\-\/\=?^_`{|}~]'; $dot_atom_text = "$atext+(?:[.]$atext+)*"; $dot_atom = $dot_atom_text; $local_part = "(?:$dot_atom|$quoted_string)"; $domain = $dot_atom; $addr_spec = "${local_part}[@]$domain"; $dot_atom_loose = "$atext+(?:[.]|$atext)*"; $local_part_loose = "(?:$dot_atom_loose|$quoted_string)"; $addr_spec_loose = "${local_part_loose}[@]$domain"; $input_addr_spec = 'foo@example.com'; if ( preg_match("/\A$addr_spec\z/", $input_addr_spec) ) { print "valid addr-spec\n"; } $input_text = 'ぼくの@メールアドレスはfoo@example.comです'; if ( preg_match("/($addr_spec)/", $input_text, $matches) ) { print "My addr-spec is <$matches[0]>\n"; } ?>
Ruby
動作確認: 1.8.7
wsp = '[\x20\x09]' vchar = '[\x21-\x7e]' quoted_pair = "\\\\(?:#{vchar}|#{wsp})" qtext = '[\x21\x23-\x5b\x5d-\x7e]' qcontent = "(?:#{qtext}|#{quoted_pair})" quoted_string = "\"#{qcontent}*\"" atext = '[a-zA-Z0-9!#$%&\'*+\-\/\=?^_`{|}~]' dot_atom_text = "#{atext}+(?:[.]#{atext}+)*" dot_atom = dot_atom_text local_part = "(?:#{dot_atom}|#{quoted_string})" domain = dot_atom addr_spec = "#{local_part}[@]#{domain}" dot_atom_loose = "#{atext}+(?:[.]|#{atext})*" local_part_loose = "(?:#{dot_atom_loose}|#{quoted_string})" addr_spec_loose = "#{local_part_loose}[@]#{domain}" input_addr_spec = 'foo@example.com' if /\A#{addr_spec}\z/ =~ input_addr_spec then puts "valid addr-spec" end input_text = 'ぼくの@メールアドレスはfoo@example.comです' if /(#{addr_spec})/ =~ input_text then puts "My addr-spec is <#{$1}>"; end
テストコードこみのコード
以下にテストコード付きのものを貼り付けます。検証したいかたはどうぞ。テストケースは全言語共通になってます。
Perl
#!/usr/bin/perl use strict; use Test::More; my $wsp = '[\x20\x09]'; my $vchar = '[\x21-\x7e]'; my $quoted_pair = "\\\\(?:$vchar|$wsp)"; my $qtext = '[\x21\x23-\x5b\x5d-\x7e]'; my $qcontent = "(?:$qtext|$quoted_pair)"; my $quoted_string = "\"$qcontent*\""; my $atext = '[a-zA-Z0-9!#$%&\'*+\-\/\=?^_`{|}~]'; my $dot_atom_text = "$atext+(?:[.]$atext+)*"; my $dot_atom = $dot_atom_text; my $local_part = "(?:$dot_atom|$quoted_string)"; my $domain = $dot_atom; my $addr_spec = qr{${local_part}[@]$domain}; print 'addr_spec: ' . $addr_spec, "\n"; my $dot_atom_loose = "$atext+(?:[.]|$atext)*"; my $local_part_loose = "(?:$dot_atom_loose|$quoted_string)"; my $addr_spec_loose = qr{${local_part_loose}[@]$domain}; print 'addr_spec_loose' . $addr_spec_loose, "\n"; my @valid = ( 'foo@example.com', # normal # local-part # dot-atom 'foo.hoge@example.com', 'foo.bar.baz@example.com', # quoted-string '"foo"@example.com', '"!"@example.com', # \x21 '"#"@example.com', # \x23 '"["@example.com', # \x5b '"]"@example.com', # \x5d '"["@example.com', # \x7e # quoted-pair '"\\ "@example.com', # \x20 "\"\\\x09\"\@example.com", # \x09 # php @ '"\\!"@example.com', # \x21 '"\\["@example.com', # \x7e # domain 'foo.hoge@localhost', 'foo.hoge@sub.example.com', ); my @valid_loose = ( 'foo.@docomo.ne.jp', 'foo.foo.@docomo.ne.jp', 'foo..@docomo.ne.jp', 'foo..foo@docomo.ne.jp', 'foo..foo.@docomo.ne.jp', ); my @invalid = ( '', 'foo', 'foo@', '@foo', # local-part # dot-atom '.foo@example.com', '..foo@example.com', 'foo@@example.com', 'foo[@example.com', 'foo @example.com', # quoted-string "\"\x00\"\@example.com", # \x00 # php @ '" "@example.com', # \x20 '"""@example.com', # \x22 '"\\"@example.com', # \x5c "\"\x7f\"\@example.com", # \x7f # php @ # quoted-pair "\"\\\x1f\"\@example.com", # \x1f # php @ "\"\\\x7f\"\@example.com", # \x7f # php @ # \z check "foo\@example.com\n", # php @ "foo\@example.com\nfoo\@example.com", # php @ # non-ascii "\x80\@example.com", "\"\x80\"\@example.com", "\"\\\x80\"\@example.com", # utf8 "\x100\@example.com", "\"\x100\"\@example.com", "\"\\\x100\"\@example.com", ); plan tests => (@valid + @invalid + @valid_loose) * 2; { # normal for (@valid) { ok( m{\A$addr_spec\z}o , 'normal-valid - ' . $_ ); } for (@invalid, @valid_loose) { ok( !m{\A$addr_spec\z}o, 'normal-invalid - ' . $_ ); } } { # loose for (@valid, @valid_loose) { ok( m{\A$addr_spec_loose\z}o , 'loose-valid - ' . $_ ); } for (@invalid) { ok( !m{\A$addr_spec_loose\z}o, 'loose-invalid - ' . $_ ); } }
PHP
<?php $count = 0; $wsp = '[\x20\x09]'; $vchar = '[\x21-\x7e]'; $quoted_pair = "\\\\(?:$vchar|$wsp)"; $qtext = '[\x21\x23-\x5b\x5d-\x7e]'; $qcontent = "(?:$qtext|$quoted_pair)"; $quoted_string = "\"$qcontent*\""; $atext = '[a-zA-Z0-9!#$%&\'*+\-\/\=?^_`{|}~]'; $dot_atom_text = "$atext+(?:[.]$atext+)*"; $dot_atom = $dot_atom_text; $local_part = "(?:$dot_atom|$quoted_string)"; $domain = $dot_atom; $addr_spec = "${local_part}[@]$domain"; echo 'addr_spec: ' . $addr_spec, "\n"; $dot_atom_loose = "$atext+(?:[.]|$atext)*"; $local_part_loose = "(?:$dot_atom_loose|$quoted_string)"; $addr_spec_loose = "${local_part_loose}[@]$domain"; echo 'addr_spec_loose: ' . $addr_spec_loose, "\n"; $valid = array( 'foo@example.com', # normal # local-part # dot-atom 'foo.hoge@example.com', 'foo.bar.baz@example.com', # quoted-string '"foo"@example.com', '"!"@example.com', # \x21 '"#"@example.com', # \x23 '"["@example.com', # \x5b '"]"@example.com', # \x5d '"["@example.com', # \x7e # quoted-pair '"foo\\ "@example.com', # \x20 "\"foo\\\x09\"@example.com", # \x09 # php @ '"\\!"@example.com', # \x21 '"\\["@example.com', # \x7e # domain 'foo.hoge@localhost', 'foo.hoge@sub.example.com', ); $valid_loose = array( 'foo.@docomo.ne.jp', 'foo.foo.@docomo.ne.jp', 'foo..@docomo.ne.jp', 'foo..foo@docomo.ne.jp', 'foo..foo.@docomo.ne.jp', ); $invalid = array( '', 'foo', 'foo@', '@foo', # local-part # dot-atom '.foo@example.com', '..foo@example.com', 'foo@@example.com', 'foo[@example.com', 'foo @example.com', # quoted-string "\"\x00\"@example.com", # \x00 # php @ '" "@example.com', # \x20 '"""@example.com', # \x22 '"\\"@example.com', # \x5c "\"\x7f\"@example.com", # \x7f # php @ # quoted-pair "\"\\\x1f\"@example.com", # \x1f # php @ "\"\\\x7f\"@example.com", # \x7f # php @ # \z check "foo@example.com\n", "foo@example.com\nfoo@example.com", # non-ascii "\x80@example.com", "\"\x80\"@example.com", "\"\\\x80\"@example.com", # utf8 "\x100@example.com", "\"\x100\"@example.com", "\"\\\x100\"@example.com", ); { # normal foreach ($valid as $addr) { ok($addr_spec, $addr, 'normal-valid - ' . $addr); } foreach (array_merge($invalid, $valid_loose) as $addr) { not_ok($addr_spec, $addr, 'normal-invalid - ' . $addr); } } { # loose foreach (array_merge($valid, $valid_loose) as $addr) { ok($addr_spec_loose, $addr, 'loose-valid - ' . $addr); } foreach ($invalid as $addr) { not_ok($addr_spec_loose, $addr, 'loose-invalid - ' . $addr); } } function ok($regexp, $addr, $desc, $xor = 0) { global $count; $count++; if ( preg_match("/\A$regexp\z/", $addr) ^ $xor ) { echo "ok $count - $desc\n"; } else { echo "not ok $count - $desc\n"; } } function not_ok($regexp, $addr, $desc) { ok($regexp, $addr, $desc, 1); } ?>
Ruby
#!/usr/bin/ruby wsp = '[\x20\x09]' vchar = '[\x21-\x7e]' quoted_pair = "\\\\(?:#{vchar}|#{wsp})" qtext = '[\x21\x23-\x5b\x5d-\x7e]' qcontent = "(?:#{qtext}|#{quoted_pair})" quoted_string = "\"#{qcontent}*\"" atext = '[a-zA-Z0-9!#$%&\'*+\-\/\=?^_`{|}~]' dot_atom_text = "#{atext}+(?:[.]#{atext}+)*" dot_atom = dot_atom_text local_part = "(?:#{dot_atom}|#{quoted_string})" domain = dot_atom addr_spec = "#{local_part}[@]#{domain}" puts 'addr_spec: ' + addr_spec dot_atom_loose = "#{atext}+(?:[.]|#{atext})*" local_part_loose = "(?:#{dot_atom_loose}|#{quoted_string})" addr_spec_loose = "#{local_part_loose}[@]#{domain}" puts 'addr_spec_loose: ' + addr_spec_loose valid = [ 'foo@example.com', # normal # local-part # dot-atom 'foo.hoge@example.com', 'foo.bar.baz@example.com', # quoted-string '"foo"@example.com', '"!"@example.com', # \x21 '"#"@example.com', # \x23 '"["@example.com', # \x5b '"]"@example.com', # \x5d '"["@example.com', # \x7e # quoted-pair '"foo\\ "@example.com', # \x20 "\"foo\\\x09\"\@example.com", # \x09 # php @ '"\\!"@example.com', # \x21 '"\\["@example.com', # \x7e # domain 'foo.hoge@localhost', 'foo.hoge@sub.example.com', ] valid_loose = [ 'foo.@docomo.ne.jp', 'foo.foo.@docomo.ne.jp', 'foo..@docomo.ne.jp', 'foo..foo@docomo.ne.jp', 'foo..foo.@docomo.ne.jp', ] invalid = [ '', 'foo', 'foo@', '@foo', # local-part # dot-atom '.foo@example.com', '..foo@example.com', 'foo@@example.com', 'foo[@example.com', 'foo @example.com', # quoted-string "\"\x00\"\@example.com", # \x00 # php @ '" "@example.com', # \x20 '"""@example.com', # \x22 '"\\"@example.com', # \x5c "\"\x7f\"\@example.com", # \x7f # php @ # quoted-pair "\"\\\x1f\"\@example.com", # \x1f # php @ "\"\\\x7f\"\@example.com", # \x7f # php @ # \z check "foo@example.com\n", "foo@example.com\nfoo@example.com", # non-ascii "\x80\@example.com", "\"\x80\"\@example.com", "\"\\\x80\"\@example.com", # utf8 "\x100\@example.com", "\"\x100\"\@example.com", "\"\\\x100\"\@example.com", ] $count = 0 def ok(regexp, addr, desc, xor = 0) $count = $count + 1 if (/\A#{regexp}\z/ =~ addr) ^ xor then puts "ok #{$count} - #{desc}"; else puts "not ok #{$count} - #{desc}"; end end def not_ok(regexp, addr, desc) ok(regexp, addr, desc, 1) end # normal valid.each do |addr| ok(addr_spec, addr, 'normal-valid - ' + addr); end (invalid + valid_loose).each do |addr| not_ok(addr_spec, addr, 'normal-invalid - ' + addr); end # loose (valid + valid_loose).each do |addr| ok(addr_spec_loose, addr, 'loose-valid - ' + addr); end invalid.each do |addr| not_ok(addr_spec_loose, addr, 'loose-invalid - ' + addr); end
コメント:0
トラックバック:0
- この記事のトラックバック URL
- https://blog.everqueue.com/chiba/2009/03/22/163/trackback/
- トラックバックの送信元リスト
- メールアドレス(addr-spec)の正規表現 - へぼい日記 より