preload
Rails勉強会(東京)に行ってきた(080217) ケータイサイトで機種情報を取得する Rails プラグイン mbterm_db
2月 19

Rails や TMail を使った Ruby アプリケーションで携帯メールをさばこうとするとブチ当たる、" 3つ以上連続するドットを含むメールアドレス問題 " の解決方法を再生産したので晒してみます。一応幾つかテストかけて使えてるけど、おかしかったらツッコミください。

まず、TMail::Mail.parse したときのエラーメッセージから:

NoMethodError: You have a nil object when you didn’t expect it!
You might have expected an instance of ActiveRecord::Base.
The error occurred while evaluating nil.[]

これを追っていくと、parser がパースに失敗しているところに行き着く。で、これに対して urekatのスカンク日記3 さんが紹介されているのは、parse 時の Syntax Error を rescue して、「あいまいパース」に落とすエレガントな方法。

プログラマ 福重 伸太朗 〜基本へ帰ろう〜 さんが紹介されているのは、先にアドレスをチェックして、必要に応じて ActionMailer を使わない方法へ回避する、これまたイカした方法。

で、ここで紹介するのは、TMail の parser を書き換えるという方法。
もうちょっと具体的に言うと TMail がメールアドレスのパースに使っている parser.rb – これが Racc というパーサジェネレータで生成されているのだが – の生成元: parser.y を改造してしまう、という方法です。つまり、これをやっちゃうと、同じ稼働環境で動いてるアプリ全体のメールアドレスの parse に影響するので、その辺覚悟してお試しを。

まず、TMail のアーカイブを拾ってきて、以下の通り tmail-1.2.1/lib/tmail/parser.y を書き換える。

$ diff parser.y.org parser.y
211c211,217
<        | local_head '.' { val[0].push ''; val[0] }
---
>        | local_head dots
>                 {
>                   val[1].times do
>                     val[0].push ''
>                   end
>                   val[0].push ''
>                 }
235,236c241,242
<   dots      : '.'     { 0 }
<             | '.' '.' { 1 }
---
>   dots      : '.'      { @dotnum = 0 }
>             | dots '.' { @dotnum = @dotnum + 1 }

ここで対応したのは、「3 つ以上連続したドット」への対応と、「@ (アットマーク) 直前で連続するドット」への対応の 2 点。(他にもケータイ特有な不可思議なフォーマットがあるかもですが、ここではスルー)

TMail の parser は、ドットふたつには対応していたが、ドット 3 つ以上の場合に対応する生成規則が無い ( dots ‘.’ から dots への還元ができない) ために parse error が起こっていたようだ。

アドレス中の文字列は逐次配列に push され、Address が new されるときに、配列要素の間にドットを入れる (空要素が入っていたら、ドットを一個追加する) という動きをしているようだったので、後半の書き換えで、連続するドットの数をカウントし、local_head への還元時に配列を組み立てるようにした。

前半の書き換えは、local 変数 ( @ の直前) として local_head ‘.’ と、ドットひとつしか許していないようだったので、これを同様に dots 変数を受け付けるようにしたもの。

あとは、こいつを make してやって( make には Racc が必要)、出来上がった parser.rb と parser.y を gem のパスでもどこでも適切なところに置いてやれば OK 。ActionMailer は内部 vendor ライブラリに TMail を包含しているので、必要であれば、そっちも書き換えてやる。(試しに消してみたら、gem の下の TMail を見に行ってくれたみたい。)

この方法のメリットは、サーバ環境を自由に使って良い人なら簡単にできちゃうってこと。デメリットは、これでいいかどうか自信が無いっていうのと、サーバ環境共有してる他のアプリにも影響がおよぶこと、あと、gem update tmail とかやったら元通りになっちゃうってこと。

TMail の過去の修正では、ドット 2 つに対応しているので、RFC 非準拠でもやむなく対応したのかと思っていたが、オリジナルの dots の生成規則を見るに、どうも不本意な実装になっているように見えた。が、この対応方法で合ってるかどうかもよくわからないので、作者さんに連絡する前に、とりあえずここで紹介してみましたです。



(2008-02-27 追記) なんと、この一ヶ月の間に(僕より先に)同様の問題で、以下のとおり同じような解決法を複数の方で生産していたことが判明(w しかも、僕のやつより、生成規則の書き方がうまい(インスタンス変数を使わないで、変数一個増やして解決してる)。しかも、postfix が受信時に RFC 違反アドレスをクォートする問題とか知らなかった。ということで、そっちの方が勉強になるので、皆さんそっちをご覧ください(ぉ



関連していそうなエントリ:

3 Responses to “TMail で携帯メールアドレスを parse する”

  1. urekat Says:

    いやいやこっちの方法のほうがエレガントですって!
    raccの書きかたがわからなくて逃げただけですから。
    TMailは日本人の作者さんから http://tmail.rubyforge.org/ に委譲されたので日本の携帯の都合に対応してもらいにくくなるかなあとおもってたところでした。
    その他のおかしな携帯アドレスも対応してTMail本体に反映したいですね。
    『gem update tmail とかやったら元通りになっちゃう』のはrailsプラグインにしたらよいかもですね。

  2. yosuke Says:

    うお、コメントはや!ありがとうございます!
    なるほど、TMail はそういう状況なんですね。そうなると、本家取り込みは難しいのかなあ。まあ、どうせまたハマることになりそうだし、もうちょい追ってみます :)
    んで、Rails プラグインにすれば良いのかな?parser.rb だけ lib に突っ込んでみたんですが、再定義の warning がべろべろ出てきて気持ち悪いんですよね。プラグインにすれば大丈夫なんでしょうか。この辺の読み込み順序がよくわかってない。。

  3. Persistence is Power Says:

    デコメールの3キャリア間変換などをおこなうRailsプラグインMbMail

    git とか GitHub とかいうのを使った事がなかったので、このたび初めて使ってみました。というわけで、標記の Rails プラグインを MIT License にて公開します。
    MbMail – GitHub
    MbMail は、Rails で日本の携帯向けサービスをつくる際、 メール扱い周りで発生するちょ…

Leave a Reply