努力してみた日記

最新 追記
あまりためにはならない話しか書かないと思うよ。

努力したWiki

2012-04-13 いまさらメールアドレスのパースをやってみる

[mail] メールヘッダからメールアドレスを取り出す

私が管理しているあるメールのシステムでは、人間がメールアドレスを管理することをやめさせてます。具体的には、

  1. 送信されてきたメールは一度管理システムに取り込まれ、そのメールのFromとCcを記録します。
  2. Subject先頭に管理番号を付与し、Cc,Fromのメールアドレスを管理システム宛のメールアドレスに変更した後Toへ再配信します。
  3. 受信者は何も考えずそのメールに返信します。
  4. 返信メールを受信した管理システムはSubjectの管理番号にしたがって保存していたFromを送信対象メールのToへ、CcをCcへ、再設定して再送信します。

Subjectの番号を改ざんしちゃったりすればおかしなことになりますが、まぁほとんどの場合ここを直すような事はないですし、番号のおかげでソートされてスレッド機能のないメールシステムの相手先でも管理し易くなったとか(管理システムで先頭の「Re:」の連続を消しこんであげてます)それなりに成果は上がっている模様です。

また再送信するまで30分ほど時間をとるようにもしてあるので、間違った内容の送信でも時間内なら取り消しが可能です。

面倒なのは送信先の件数カウント

通常ならCcヘッダの送信先アドレスなんて個数なんか気にする必要はないです。

しかし、この管理システムが利用している上位のメールシステムでは、面倒な事にCcヘッダに指定できるメールアドレスの個数に制限があります。なんでも、やたらCcに送信先がある場合は事故の可能性ありとして送信をキャンセルするとか。遠回しに『CcとBccを間違えたメールだよね、それ?』と言いたいらしいです。誰がその個数を決めたか知りませんが。ものによっては複数回同じ内容のメールを送信せにゃいけないことになります。

すると、がんばってCcのメールアドレスを切り出し、送信先件数をカウントする必要が出てくるわけです。ちなみにこの管理システムではメールを分割送信せず、『メールサーバの制限でFromアドレスにしか返信していませんごめんなさい』と一言付け加えるようにしてます。転送は受信者様に再度やっていただくと。ま、大量のCcアドレス自体、あまり褒められたものではないと思いますし(MLにして流しちゃえば?とか思ってしまうのです)

さて、この送信先カウント、やってることは送られてきたメールのCcヘッダからメールアドレスと思われるものを切り出してその数を数えているのですがちょっと厄介です。

調べてみると判りますが、メールアドレスを表す正規表現ってかなりめんどくさいです。PHP使いが誤ったメールアドレス判定用の正規表現を紹介しまくったせいで切れてる方もいらっしゃったり(^^;

少し慣れてきて「正規表現で local@domain な部分の個数を数えればいいんだよね」とか単純にやってしまうとメールアドレスに一家言あるような人等がたくさん湧きます。

でも適用場所によっては全てを満たす必要はないの

さて対策用の言い訳を書いておきましょうか。

PHPでの例のほとんどは、入力されたメールアドレスの妥当性確認の例なので、ドットが連続していないか、とかローカル部分のトータル長が正しいかとか、使えない文字含んでないよね?とか、検証をいろいろやらにゃいけません。

しかし。メールのCcヘッダにあるメールアドレスは、送信されてきた時点でそのあたりの確認が済んでいるものとみなしてもさしつかえありません。だって、届かないメールアドレス使わないでしょ?それに携帯へ会社間で行き来するヘビーなメールを送るなんてまず考えられないし。なので携帯メールで見かけるような不正形式のアドレスは含まれないものと仮定しちゃいます。

Wikipediaのメールアドレスの項をみて、こんな正規表現を作りました。RFC読めって?ごめんなさい。

((?:[a-zA-Z0-9\.!#\$%&'*+-\/=\?\^_\`\{\|\}\~]+|"[a-zA-Z0-9\.!#\$%&'*+-\/=\?\^_\`\{\|\}\~\(\)<>\[\]:;@,\"\\]+")@[a-zA-Z0-9.-]+)

たぶんローカル部がダブルクォートで囲まれたよなメールアドレスは見ることはないと思うんですけど念のため。

これをPerlのスクリプトなんかで試してみると

今のところそれらしく動いてます。

my $Cc = 'ためしたいCcヘッダの内容';
my @addrs = $Cc =~ /((?:[a-zA-Z0-9\.!#\\$%&'*+-\/=\?\^_\`\{\|\}\~]+|"[a-zA-Z0-9\.!#\\$%&'*+-\/=\?\^_\`\{\|\}\~\(\)<>\[\]:;@,\"\\]+")@[a-zA-Z0-9.-]+)/g;
 
my $i=1;
foreach my $addr ( @addrs )
{
  print "$i: $addr\n";
  $i++;
}

そうそう

続きがあるので少々お待ちを。


2012-04-17 油断できないのぅ

[雑記] 解除

携帯通知解除っと

[mail] メールヘッダからメールアドレスを取り出す2

前回の続き。

まー、経験のある方々なら前回の正規表現の罠もすっかりお見通しですよねー。

my $Cc = 'abc@hoge.jp, "def@hoge.jp" <ghi@hoge.jp>';
my @addrs = $Cc =~ /((?:[a-zA-Z0-9\.!#\\$%&'*+-\/=\?\^_\`\{\|\}\~]+|"[a-zA-Z0-9\.!#\\$%&'*+-\/=\?\^_\`\{\|\}\~ \(\)<>\[\]:;@,\"\\]+")@[a-zA-Z0-9\.-]+)/g;
 
my $i=1;
foreach my $addr ( @addrs )
{
  print "$i: $addr\n";
  $i++;
}
EOF

結果はというと。

1: abc@hoge.jp
2: def@hoge.jp
3: ghi@hoge.jp

出て欲しいのは abc@hoge.jp と ghi@hoge.jp の二つなんですよ。

名前部分にメールアドレスらしきものがあると駄目になる例ですね。

これを避けるには、ダブルクォートではさまれた部分を無視するようにしなくちゃいけないんだけど、

 "this is test"@hoge.jp

みたいなメールアドレスもありうるので(実質使われていないとは思うんですけどね)判断が難しいんですよ。

名前部分の囲みなのかメールのローカル部の囲みなのか、これじゃあ判断できないですー、ってことに。

じゃあ名前とメールアドレスをパースしちゃえばいいじゃない!

メールアドレスだけをパースしようとするからいけないんだ。名前とメールアドレスをパースしてあとでメールアドレスだけ集めちゃえばいいじゃない!ってことで、無理に正規表現をこねくり回さないで素直に書いてみたのが次のコード。

my $Cc = 'abc@hoge.jp, "def@hoge.jp" <ghi@hoge.jp>, "are you \"ready\" ?" jkl@hoge.jp, ThisIsTest mno@hoge.jp ( other email pqr@hoge.jp ), "this is test"@hoge.jp';
my @addrs = $Cc =~/"(?:\\"|\\\\|[a-zA-Z0-9\.!#$%&'*+-\/=\?\^_\`\{\|\}~ \(\)<>\[\]:;,@])+"(?!@)|\((?:\\"|\\\\|[a-zA-Z0-9\.!#$%&'*+-\/=\?\^_\`\{\|\}~ \(\)<>\[\]:;,@])+\)|[a-zA-Z0-9\.!#$%&'*+-\/=\?\^_\`\{\|\}\~]+@[a-zA-Z0-9\.-]+|"(?:\\"|\\\\|[a-zA-Z0-9\.!#$%&'*+-\/=\?\^_\`\{\|\}\~ \(\)<>\[\]:;,@])+"@[a-zA-Z0-9\.-]+/g;
 
$i=1;
print "****\n$Cc\n****\n";
foreach my $ml ( @addrs )
{
  $a = ($ml =~ /^["\(].+["\)]$/) ? " " : "*";
  printf("%03i: %1s [%s]\n", $i, $a, $ml);
  $i++;
}

ええ、こそーっと一部正規表現をなおしてます。途中で気が付いちゃったもんで(^^; 実行するとこんな感じ。

****
abc@hoge.jp, "def@hoge.jp" <ghi@hoge.jp>, "are you \"ready\" ?" jkl@hoge.jp, ThisIsTest mno@hoge.jp ( other email pqr@hoge.jp ), "this is test"@hoge.jp
****
001: * [abc@hoge.jp]
002:   ["def@hoge.jp"]
003: * [ghi@hoge.jp]
004:   ["are you \"ready\" ?"]
005: * [jkl@hoge.jp]
006: * [mno@hoge.jp]
007:   [( other email pqr@hoge.jp )]
008: * ["this is test"@hoge.jp]

アスタリスクの付いた、001,003,005,006,008がお目当てのメールアドレスになります。つまり、切り出した要素の両脇がダブルクォートか括弧で囲まれてなければメールアドレスと判断すればよろし。

007は、メールアドレスのあとに書けるコメントです。こちらにもメールアドレスが書かれてしまう可能性があるので組み込んでおきました。

正規表現が長いのでちょっと説明

先の正規表現では4つの文字列にマッチングします。

  1. "(?:\\"|\\\\|[a-zA-Z0-9\.!#$%&'*+-\/=\?\^_\`\{\|\}~ \(\)<>\[\]:;,@])+"(?!@)
    ダブルクォートで囲まれ最後が『〜"@』で終わらない文字列にマッチングします。『〜"@』で終わるとメールアドレスのローカル部にもマッチングしてしまうためです。
  2. \((?:\\"|\\\\|[a-zA-Z0-9\.!#$%&'*+-\/=\?\^_\`\{\|\}~ \(\)<>\[\]:;,@])+\)
    ダブルクォートの替わりに括弧"()"で囲まれた文字列のマッチングします。
  3. [a-zA-Z0-9\.!#$%&'*+-\/=\?\^_\`\{\|\}\~]+@[a-zA-Z0-9\.-]+
    ローカル部がダブルクォートで囲まれていないメールアドレスにマッチングします。
  4. "(?:\\"|\\\\|[a-zA-Z0-9\.!#$%&'*+-\/=\?\^_\`\{\|\}\~ \(\)<>\[\]:;,@])+"@[a-zA-Z0-9\.-]+
    ローカル部がダブルクォートで囲まれているメールアドレスにマッチングします。

一行の正規表現でローカル部のダブルクォート囲みか名前のダブルクォート囲みか判断させるのは困難でした。なのでこの方法に落ち着くしかありませんでした。

もしうまい手があれば教えてください。いや本当に。

[mail] メールヘッダからメールアドレスを取り出す3

これは昨日会社でやっと気が付いたもの。お間抜けですわ(^^;

my $Cc = 'abc@hoge.jp,"def@hoge.jp",jkl@hoge.jp,ThisIsTest,mno@hoge.jp,"this is test"@hoge.jp';
my @addrs = $Cc =~/"(?:\\"|\\\\|[a-zA-Z0-9\.!#$%&'*+-\/=\?\^_\`\{\|\}~ \(\)<>\[\]:;,@])+"(?!@)|\((?:\\"|\\\\|[a-zA-Z0-9\.!#$%&'*+-\/=\?\^_\`\{\|\}~ \(\)<>\[\]:;,@])+\)|[a-zA-Z0-9\.!#$%&'*+-\/=\?\^_\`\{\|\}\~]+@[a-zA-Z0-9\.-]+|"(?:\\"|\\\\|[a-zA-Z0-9\.!#$%&'*+-\/=\?\^_\`\{\|\}\~ \(\)<>\[\]:;,@])+"@[a-zA-Z0-9\.-]+/g;
 
$i=1;
print "****\n$Cc\n****\n";
foreach my $ml ( @addrs )
{
  $a = ($ml =~ /^["\(].+["\)]$/) ? " " : "*";
  printf("%03i: %1s [%s]\n", $i, $a, $ml);
  $i++;
}

実行すると

****
abc@hoge.jp,"def@hoge.jp",jkl@hoge.jp,ThisIsTest,mno@hoge.jp,"this is test"@hoge.jp
****
001: * [abc@hoge.jp]
002:   ["def@hoge.jp"]
003: * [,jkl@hoge.jp]
004: * [,ThisIsTest,mno@hoge.jp]
005: * ["this is test"@hoge.jp]

何かおかしいですよね。カンマがメールアドレス構成文字として許されているように見えます。

...原因はここ。プラス記号とマイナス記号に注目。

[a-zA-Z0-9\.!#$%&'*+-\/=\?\^_\`\{\|\}\~]

a-zで英小文字a〜zを示します。じゃあ+-/は?

+-/の範囲

確かにカンマ入ってるよね(>_<)

早速この部分を修正して再度実行してみると

my $Cc = 'abc@hoge.jp,"def@hoge.jp",jkl@hoge.jp,ThisIsTest,mno@hoge.jp,"this is test"@hoge.jp';
my @addrs = $Cc =~/"(?:\\"|\\\\|[a-zA-Z0-9\.!#$%&'*+\-\/=\?\^_\`\{\|\}~ \(\)<>\[\]:;,@])+"(?!@)|\((?:\\"|\\\\|[a-zA-Z0-9\.!#$%&'*+\-\/=\?\^_\`\{\|\}~ \(\)<>\[\]:;,@])+\)|[a-zA-Z0-9\.!#$%&'*+\-\/=\?\^_\`\{\|\}\~]+@[a-zA-Z0-9\.-]+|"(?:\\"|\\\\|[a-zA-Z0-9\.!#$%&'*+\-\/=\?\^_\`\{\|\}\~ \(\)<>\[\]:;,@])+"@[a-zA-Z0-9\.-]+/g;
 
$i=1;
print "****\n$Cc\n****\n";
foreach my $ml ( @addrs )
{
  $a = ($ml =~ /^["\(].+["\)]$/) ? " " : "*";
  printf("%03i: %1s [%s]\n", $i, $a, $ml);
  $i++;
}

結果はこうなりました。

****
abc@hoge.jp,"def@hoge.jp",jkl@hoge.jp,ThisIsTest,mno@hoge.jp,"this is test"@hoge.jp
****
001: * [abc@hoge.jp]
002:   ["def@hoge.jp"]
003: * [jkl@hoge.jp]
004: * [mno@hoge.jp]
005: * ["this is test"@hoge.jp]

もう大丈夫でしょう。


2012-04-24 移行しちゃおうか....

[雑記] さくらVPSのプラン変更優待

今契約しているのが、さくらVPSの旧プラン512。2Core 512MB HDD20GBで980円/月のVPS。

これを期間内で移行するなら、旧プランの残り期間分の返金および移行先プランの1ヶ月無料をやってくれる(金額を減らすのではなく、無料使用期間が1ヶ月付く)。新プランの3Core 2GB HDD200GBだと1480円/月。

旧プランもそのうち512MB→1GBへのメモリ変更があるらしいけど、ディスク容量が足りないんだよな...将来的に置こうと思っているコンテンツが置けない。ただその将来がいつなのかが未定という(^^;;

500円上乗せでこれなら移行してもよいとも思うし。うーどうしよかね。


2012-04-28 移行しちゃった

[雑記] VPSもう一個

確保しちゃいました。乗り換えにするかどうかは新しい方の移行作業の進み具合による、かな。

Webサーバ乗っけてみないとどのくらいよくなったか分からないので、現在環境構築中。


2012-04-30 切り替え終了

[雑記] バーチャルホストなapacheサーバにIPアドレスでアクセスするとどうなるの?

VMイメージのコピーで済ませられたらいいのになぁ....まぁある程度は仕方ないか。

で、VPS移行作業中ちょっと分からない事が出てしまいました。マルチホストなapacheサーバにIPアドレス指定でアクセスされた場合どうなるのかって話です。

ひとつのIPアドレスにA,B二つのホストの構成の話

HTTP1.1はHostヘッダの内容でクライアントがどのホストにアクセスしているのか明示します。

apacheはこのヘッダを見て、DocumentRootを切り替えられます。Aなら /usr/local/www/A 、Bなら /usr/local/www/B 、に振り分けできます。

じゃあさ、ホスト名じゃなくてIPアドレスだったらどうなるの?

答え:「最初に記述されたブロックの設定が使われる」

→@IT - バーチャルホストによる複数サイトの同時運用(2/2)

邪悪になるな、でおなじみのGoogle先生を使い調べてたところ上記リンクのページに書いてありました。最初に記述された VirtualHostディレクティブの設定が適用されるとのこと。

実際に試して目視確認。ふーむ。するとIPアドレス指定の場合の振り分けは出来ないってことか。


単なる覚書以下の内容です。一度内容を全部消しました。
最新 追記
2010|11|12|
2011|01|02|03|04|05|06|07|08|09|10|11|12|
2012|01|03|04|05|06|07|08|09|11|12|
2013|01|02|03|04|06|08|
2014|02|04|06|07|09|10|11|12|
2015|01|02|03|04|06|08|09|10|11|12|
2016|01|02|04|05|10|
2017|02|03|04|05|06|09|10|
2018|04|