BLOGTIMES
2009/11/01

RubyのNKFのバグでSubjectのエンコードができなくて困った

  ruby  charset  rfc 
このエントリーをはてなブックマークに追加

結論から書くと、CentOS 5.3のyumで入るRubyにはNKFにバグがあって(というか、オリジナルのnkfのバグの影響で)、UTF-8のMIMEエンコードがうまく出来ない。このバグはあくまで入力がUTF-8の場合のみに起きる現象なので、文字列を一旦別の文字列(EUC-JP,SJIS,JIS)に変換してからエンコードすることで回避できます。

例えば、こんな感じ。

requrie 'nkf' subject = "ああああああああああああ" encoded = NKF.nkf("-JM", NKF.nkf("-jW", subject) )

このバグから抜け出すのに半日くらいかかってしまいました。

事の発端

NKFを使ってSubjectをMIMEエンコードしてメール送信するスクリプトを書いたら、なぜかSubjectの後半が文字化けする(というか、MIMEエンコードがきちんと終端されていない)という現象が発生。最初はメーラー依存の問題かと思っていたんですが、ログを確認すると送信時からどうもMIMEエンコードが壊れてるっぽい。

再現コードとしてはこんな感じ。Rubyのバージョンは1.8.5です。

$ ruby --version ruby 1.8.5 (2006-08-25) [i386-linux]
requrie 'nkf' NKF::VERSION # => "2.0.7 (2006-06-13)" subject = "あああああああああああああああああああああああああああああああああああああああああああああああ" # => "\343\201\202\343\201\202\343\201\202\343\201\202\343\201\202\343\201\202\343\201\202\343\201\202\343\201\202\343\201\202\343\201\202\343\201\202\343\201\202\343\201\202\343\201\202\343\201\202\343\201\202\343\201\202\343\201\202\343\201\202\343\201\202\343\201\202\343\201\202\343\201\202\343\201\202\343\201\202\343\201\202\343\201\202\343\201\202\343\201\202\343\201\202\343\201\202\343\201\202\343\201\202\343\201\202\343\201\202\343\201\202\343\201\202\343\201\202\343\201\202\343\201\202\343\201\202\343\201\202\343\201\202\343\201\202\343\201\202\343\201\202" encoded = NKF.nkf("-WM", subject) # => "=?ISO-2022-JP?B?GyRCJCIkIiQiJCIkIiQiJCIkIiQiJCIkIiQiJCIkIiQiJCIkIiQiGyhC?=\n =?ISO-2022-JP?B?GyRCJCIkIiQiJCIkIiQiJCIkIiQiJCIkIiQiJCIkIiQiJCIkIiQiGyhC?=\n =?ISO-2022-JP?B?GyRCJCIkIiQiJCIkIiQiJCIkIiQiJCIkI"

ここで、長いSubjectが勝手に複数の行に分割されているのはRFC2822の2.1.1 行の長さの制限に準拠するためなので、これを勝手にやってくれること自体はNKFの動作としては特に問題ありません。メーラーはこの分割されたSubjectをデコードして自動的につなぎあわせてくれるようになっています。が、よく見るとなぜか最後の行の行末にMIME Bエンコーディングで当然ついているべきの"?="がなく、ちょっとおしりが切れているようにも見えます。

どうやら元々のnkfのバグらしい。。。。

エンコードする文字列が短かかったりすると大丈夫なので、どう考えてもRubyもしくはnkfのバグのようなので、nkfの2.0.8以降のリリースノートをさらってみると、それらしきバグがnkf 2.0.8で修正されていることを発見。

nkf 2.0.8 released - はてなるせだいあり

* UTF-32, CP10001 (Microsoft's MacJapanese) のサポート。
* Unicode の BMP 外をサポート。
* CP932 系のユーザ定義文字をサポート。
* NTT DoCoMo と Softbank Mobile の Shift_JIS 絵文字の範囲をサポート。
* --guess にて改行コードを表示するようにした。
* --guess にて、文字コードを確定できなかった場合の推測が壊れていたのを修正。
* EUC モード時に SI/SO/ESC を捨てていたのを修正。
* UTF-8 の入力を MIME エンコードすると正しく出力されないのを修正。
* 複数ファイルを与えられた時、読めないファイルがあっても処理を続行するようにした。
* CP932 周りの各種修正

とりあえず、これだけのためにRubyやライブラリを入れ替えていられないのでなんとか抜け道を探します。このリリースノートを素直に読めば、入力がUTF-8でなければ良いように読めるので、今回の場合はどうせ最終的にはJIS(iso-2022-jp)にしてしまうので、JISに変換してからMIMEエンコードをかけるようにしてみたところ、うまく変換できるようになりました。

encoded = NKF.nkf("-WM", subject) # => "=?ISO-2022-JP?B?GyRCJCIkIiQiJCIkIiQiJCIkIiQiJCIkIiQiJCIkIiQiJCIkIiQiGyhC?=\n =?ISO-2022-JP?B?GyRCJCIkIiQiJCIkIiQiJCIkIiQiJCIkIiQiJCIkIiQiJCIkIiQiGyhC?=\n =?ISO-2022-JP?B?GyRCJCIkIiQiJCIkIiQiJCIkIiQiJCIkI" encoded = NKF.nkf("-JM", NKF.nkf("-jW", subject) ) # => "=?ISO-2022-JP?B?GyRCJCIkIiQiJCIkIiQiJCIkIiQiJCIkIiQiJCIkIiQiJCIkIiQiGyhC?=\n =?ISO-2022-JP?B?GyRCJCIkIiQiJCIkIiQiJCIkIiQiJCIkIiQiJCIkIiQiJCIkIiQiGyhC?=\n =?ISO-2022-JP?B?GyRCJCIkIiQiJCIkIiQiJCIkIiQiJCIkIhsoQg==?="

久々にこの手のバグでだいぶ時間をとられてしまいました。
このRubyはCentOSのデフォルトだから意外と困っている人多いんじゃないかなぁ。。。。

参考: [RFC2822] 2.1.1. Line Length Limits

http://www.puni.net/~mimori/rfc/rfc2822.txt

2.1.1. Line Length Limits

There are two limits that this standard places on the number of characters in a line. Each line of characters MUST be no more than 998 characters, and SHOULD be no more than 78 characters, excluding the CRLF.

行の長さの制限

この標準では1行中の文字数に2つの制限がある。それぞれの行の文字はCRLFを除いて、決して998文字以下でなければならず(MUST)、78文字以下であるべきである(SHOULD)。

    トラックバックについて
    Trackback URL:
    お気軽にどうぞ。トラックバック前にポリシーをお読みください。[policy]
    このエントリへのTrackbackにはこのURLが必要です→https://blog.cles.jp/item/3275
    Trackbacks
    このエントリにトラックバックはありません
    Comments
    愛のあるツッコミをお気軽にどうぞ。[policy]
    古いエントリについてはコメント制御しているため、即時に反映されないことがあります。
    コメントはありません
    Comments Form

    コメントは承認後の表示となります。
    OpenIDでログインすると、即時に公開されます。

    OpenID を使ってログインすることができます。

    Identity URL: Yahoo! JAPAN IDでログイン