発見した TMail-1.2.3.1 のバグ各種

TMail-1.2.3.1 のバグを色々直した。それぞれ詳しいことは以下。各修正を適用したTMailのgemをダウンロードに置いてあるので、手っ取り早く使いたい方は、これをダウンロードして、 gem install tmail-1.2.3.1.gem でインストールしてほしい。

ついでにこの gem には、leave a note [message] behind on Rails: RailsのMailer(TMail)のメールアドレスドット問題の修正も取り込ませていただいた。

正しく動作する保証は無いですが、不具合があればコメントしていただけると助かります。

Content-Typeで値がクォートを含んだまま取り込まれる

TMailプロジェクトに送信したパッチ: RubyForge: TMail: Modify: 23165 - ContentTypeHeader should unquote parameter's value on encode

例えば

Content-Type: image/jpeg; name="\e$B4A;z\e(B.jpg"

という生JISのファイル名がクォートされて指定されていると、なぜかエンコードした後に、

Content-Type: image/jpeg; name*=iso-2022-jp'ja'%22%1b$B4A%3bz%1b%28B.jpg%22

%22(")が前後にくっついてしまう。本当は、

Content-Type: image/jpeg; name*=iso-2022-jp'ja'%1b$B4A%3bz%1b%28B.jpg

でないといけない。

AppleMail 添付ファイルパートの Content-Type が解析エラー

TMailプロジェクトに送信したパッチ: RubyForge: TMail: Modify: 23681 - Fix parse error on AppleMail's bad Content-Type parameter value - unquoted but bencoded

AppleMailに日本語ファイル名を添付させてみよう。するとこんなパートが生成される。

--Apple-Mail-1-993553537
Content-Transfer-Encoding: base64
Content-Type: application/pdf;
	x-mac-type=50444620;
	x-unix-mode=0644;
	x-mac-creator=4341524F;
	name==?ISO-2022-JP?B?GyRCQXdFRTdPRX0/XhsoQi5wZGY=?=
Content-Disposition: inline;
	filename*=ISO-2022-JP''%1B%24BAwEE7OE%7D%3F%5E%1B%28B.pdf

これをパースし、このパートのcontent_typeを得ようとすると

part.content_type    => nil

nilが帰ってくる。さらに、encodedでエンコードすると、

--Apple-Mail-1-993553537
Content-Transfer-Encoding: base64
Content-Disposition: inline;
	filename*=ISO-2022-JP''%1B%24BAwEE7OE%7D%3F%5E%1B%28B.pdf

Content-Type そのものが消えてしまう。(メーラ上の動作では、本文として展開されてしまう)

実は、Content-Type をパースする際に構文エラーが発生しているのだ。 name==?ISO- の2つ目の = に出会った時点で、これは有効なトークンではないとエラーになってしまう。確かに、RFC 2045(対訳)多目的インターネットメール拡張 パート1 Content-Type ヘッダフィールドの文法 によれば、これはクォートしないといけない。 AppleMail が間違っているのだ。

Content-Type をパースする前に、このようなパターンを見つけてクォートさせてやることで解決した。

ちなみに、AppleMailに長いファイル名で添付をさせると

Content-Type: application/pdf;
	x-unix-mode=0644;
	name="=?ISO-2022-JP?B?GyRCRDkhPCQkRDkhPCQkRDkhPCQkRDkhPCQkGyhC?=
 =?ISO-2022-JP?B?GyRCRDkhPCQkRDkhPCQkRDkhPCQkRDkhPCQkGyhC?=
 =?ISO-2022-JP?B?GyRCRDkhPCQkRDkhPCQkRDkhPCQkRDkhPCQkGyhC?=
 =?ISO-2022-JP?B?GyRCRDkhPCQkRDkhPCQkRDkhPCQkGyhCLnBkZg==?="

ちゃんとクォートされる。1行で収まる場合のみ、クォートしてくれないらしい。

TMailで作成した添付ファイル名の文字化け

TMailで添付ファイル付きのメールを1から作成しようとすると、このようなスクリプトになるが、

filename = "任意の日本語ファイル名"   # JISにすること
filedata = "ファイルのデータ"

require "base64"
require "tmail"

mail = TMail::Mail.new()

part = TMail::Mail.new()
part.transfer_encoding = "7bit"
part.set_content_type('text', 'plain', 'charset'=>'iso-2022-jp')
part.body = "honbun."
mail.parts << part

part = TMail::Mail.new()
part.set_content_type('application', 'octet-stream', 'name' => filename)
part.set_disposition("attachment")
part.transfer_encoding = "base64"
part.body = Base64.encode64(filedata)
mail.parts << part

mail.encoded

filename によっては、途中でファイル名が化けたりする。

例えば、「何かチラシ.pdf」をこのスクリプトで作成すると、メーラで開いたときにはファイル名が「何か汁初酬.pdf」になってしまう。まあ笑えるからいいか、ってんなわけない。

このヘッダを見ると

Content-Type: application/octet-stream;
	name*=iso-2022-jp'ja'%1b$B2%3f$+%A%i%7%1b%28B.pdf

となっている。原因は、エンコード時にJIS文字列中の % をエスケープしてくれてないからである。正しくは、

Content-Type: application/octet-stream;
	name*=iso-2022-jp'ja'%1b$B2%3f$+%25A%25i%257%1b%28B.pdf

でないといけない。

encode.rb の encode_value の中で、 TOKEN_UNSAFE という正規表現を %xx に置き換えている。TOKEN_UNSAFEの宣言は utils.rb にある。それが使っている値である、 aspecial/tspecial に % を追加して修正した。

しかし、これだと他のメソッドにも影響が出そうだが…。ちょっと不安である。それに aspecial/tspecial の定義が、 RFC と違ってきてしまう。

このパッチはトラッカーにsubmitしていない。パッチは以下の通りである。

Index: lib/tmail/utils.rb
===================================================================
--- lib/tmail/utils.rb	(リビジョン 261)
+++ lib/tmail/utils.rb	(作業コピー)
@@ -109,8 +109,8 @@
   # It also provides methods you can call to determine if a string is safe
   module TextUtils
 
-    aspecial     = %Q|()<>[]:;.\\,"|
-    tspecial     = %Q|()<>[];:\\,"/?=|
+    aspecial     = %Q|()<>[]:;.\\,"%|
+    tspecial     = %Q|()<>[];:\\,"/?=%|
     lwsp         = %Q| \t\r\n|
     control      = %Q|\x00-\x1f\x7f-\xff|