是非に及ばず

プログラミングに関する話題などを書いていきます(主にRailsやAndroidアプリ開発について)

Rails(TMail)で携帯のメールアドレスをパースするとエラーになる件の対応方法について

Ruby(Rails)で携帯の写メールを受け取るプログラムを作っていたら、ドット(.)が連続するメールアドレスでエラーになった。本来はRFC違反だから使ってはいけないんだけど、DoCoMoとかauでは使えてしまうので対応する必要がある。
対応方法をまとめてみた。
※ただし、この方法で対応できるのは"example...@ezweb.ne.jp"などの先頭がドットで始まらないメールアドレスのみ。先頭がドットで始まっているメールアドレスの場合は、対応出来ていない事に注意。それでもこの方法は実行する価値があると思う。

何が問題なのか

まず、これで何が問題になるのかについて軽く説明する。
以下のようにドットが3回連続するなどRFCに違反しているメールアドレスをTMailで処理しようとすると
メールアドレスがnilになってしまう。FromだけでなくToなどメールアドレスであればおそらく全て同様。
TMail(ActionMailer)を使ってメールを送信したり、メールを受信してFromアドレスへメールを返信する
などの処理が出来ない。

$ irb
irb> require 'rubygems'
irb> require 'action_mailer'
irb> mail = TMail::Mail.new
irb> mail.from = 'example...@exampl.com'
irb> mail.from
=> nil ← 設定したメールアドレスがnilになってしまう!

対応方法は?

対応方法は2通りある。

  • パターン1
    • 既存のparser.rbを新しいparser.rbで上書きする
  • パターン2
    • アプリケーションのディレクトリ内にparser.rbを別ファイル名で置く(tmail_parser.rbなど)
    • TMailを使用するソース内でParserを新しいparser.rbと置き換える

ここでは、既存の環境を壊したくないのでパターン2の方法を使う。

1. Raccをインストールする

parser.yをraccコマンドでコンパイル?してTMailが使用しているparser.rbを作成する事になるため、raccをダウンロードしてインストールしておく。

# wget http://i.loveruby.net/archive/racc/racc-1.4.5-all.tar.gz
# tar xvzf racc-1.4.5-all.tar.gz
# cd racc-1.4.5-all
# ruby setup.rb
2. TMailをrubygemsのサイトからダウンロードする

"rubygemsのサイト"から自分が使用しているバージョンのTMailをDLする。Railsを使っている場合、TMailを個別にgem installしていないはずなので、ActionMailerのディレクトリ内を確認する。

Rails2.1.0しか入っていない環境の場合
$RUBY_HOME/lib/ruby/gems/1.8/gems/actionmailer-2.1.0/lib/action_mailer/vendor/tmail-1.2.3
3. perser.yを編集してドットが連続しても対応できるようにする

ダウンロードしてきたtmail-{version}.tgzを展開して、tmail-{version}/lib/tmail/parser.yを編集する。
以下はtmail-1.2.3のparser.yと編集後のファイルのdiff。
他のバージョンでも同様に編集すれば良いはず。

-        | local_head '.' { val[0].push ''; val[0] }
+        | local_head dots { (val[1] + 1).times { val[0].push '' }; val[0] }

-   dots      : '.'     { 0 }
-             | '.' '.' { 1 }
+   dot_repeat : '.'
+              | dot_repeat '.' { val[0] + val[1] }
+
+   dots      : dot_repeat     { val[0].size - 1 }
4. Raccでparser.yからparser.rbを作成する
racc ./parser.y -E -o ./tmail_parser.rb 
5. 作成したtmail_parser.rbを{RAILS_ROOT}/lib/tmail_parser.rbに置く
6. TMailを使用するソース内でparserをtmail_parserに置き換える
TMail.instance_eval{remove_const 'Parser'}
require 'tmail_parser'
mail = TMail::Mail.parse(mail_data)
※mail_dataは受信したメールの内容