あどけない話

Internet technologies

メールの符号化は悪だ

Subject: の日本語文字を符号化する方法(RFC 2047)とファイル名のそれ(RFC 2231)とが何故違うのか説明する機会がありましたので、ブログでも公開するとともに、僕の意見を述べさせてもらいます。

予備知識

Subject: の値に ASCII 文字以外を入れる場合は、RFC 2047 に従って符号化します。たとえば、

Subject: 日本語文字

は、以下のように変換されます。

Subject: =?iso-2022-jp?B?GyRCRnxLXDhsSjg7ehsoQg==?=

一方、「ファイル.pdf」というファイル名は、RFC 2231 に従って以下のように符号化します。

Content-Disposition: attachment;
 filename*=iso-2022-jp''%1B%24B%25U%25%21%25%24%25k%1B%28B%2Epdf

RFC 2047 方式でファイル名を符号化してみる

この記事は、なぜファイル名の符号化に RFC 2047 方式を使ってはいけないのかというお話です。

その理由を考えるために、「ファイル.pdf」というファイル名を RFC 2047 方式で符号化してみましょう。以下のようになるでしょう。

Content-Disposition: attachment;
 filename==?iso-2022-jp?B?GyRCJVUlISUkJWsbKEIucGRm?=

すぐに分るように、パラメータの名前と値を区切る "=" が、RFC 2047 方式でも使われているため、"==" となり不格好です。

不格好なのは気分の問題なので、それはそれでいいという人もいるでしょう。

本当の問題は、ファイル名が長くなったときです。

RFC 2822(旧 RFC 822)の制約

MIME は、RFC 2822 と互換性を保つように設計されました。そのポイントは2つあります。

  1. MIME で定義される文字列が、RFC 2822 字句解析器に悪影響を与えない
    • すなわち、MIME で定義される文字列は RFC 2822 の atom として扱われる。
  2. 1行は、行末の CRLF を入れて 80 文字にすべき(SHOULD)
    • それ以上の場合は、折り返す

1) をクリアするために、MIME では注意深く記号を選んでいます。"=" や "?" は、RFC 2822 では、区切り文字ではありません。たとえば、値の

=?iso-2022-jp?B?GyRCRnxLXDhsSjg7ehsoQg==?=

や、パラメータの

 filename="file.pdf"

は、これ以上分割できないもの、すなわち atom として扱われます。

2) の問題を解決するために RFC 2047 では、80 文字を越える場合、結果が分割できるようになっています。たとえば、こうです。

Subject: =?iso-2022-jp?B?GyRCJEokLCE8ITwhPCE8ITwhPCE8ITwhPCE8GyhC?=
 =?iso-2022-jp?B?GyRCITwhPCE8ITwhPCE8ITwkJCVVJSElJCVrTD4bKEI=?=

RFC 2047 方式で長いファイル名を符号化してみる

パラメータの値に、長い日本語文字列を入れたいとしましょう。この場合、分割する必要があります。

安直に符号化すると以下のようになるでしょう。

 filename==?iso-2022-jp?B?GyRCJEokLCE8ITwhPCE8ITwhPCE8ITwhPCE8GyhC?=
 =?iso-2022-jp?B?GyRCITwhPCE8ITwhPCE8ITwkJCVVJSElJCVrTD4bKEI=?=

これは、SPC で区切られていますので、2 つの atom です。*パラメータとして*1つに戻す方法は、定義されていません。

詳しくは述べませんが、RFC 2231 では以下のような書式で、パラメータを再構成できるようになっています。

 filename*0*=iso-2022-jp''%1B%24B%24J%24%2C%21%3C%21%3C%21%3C%21%3C%21
 filename*1*=%3C%21%3C%21%3C%21%3C%21%3C%24%24%25U%25%21%25%24%25k%1B
 filename*2*=%28B%2Epdf

蛇足

RFC 2231 で、パラメータに番号が付いているのは、パラメータの順番が変わるかもしれないと、策定者が思っているからです。

実際問題、ヘッダの行の順番が入れ替えられることは、極めて稀でしょう。(僕は、入れ替えられた経験はありません。) また、RFC 2047 は、ヘッダの行の順番が入れ替えられないという仮定の基に設計されていると思われるので、矛盾していると僕は思います。

RFC 2231 方式では、言語情報も格納できます。これは、RFC 2277 の影響だと思われます。しかし、言語情報が役に立った経験は、僕にはありません。(言語情報は、通常は OS から得るものだと思います。)

IETF の罪、Microsoft の罪

IETF でファイル名の符号化方式を策定する作業は、遅れに遅れました。

IETF で策定を完了を待てないメールリーダや、そもそも背景を理解してないプログラマは、ファイル名の符号化方式に、RFC 2047 を(誤って)使用しました。

この代表選手は、Outlook Express です。以下のようなヘッダを作ります。

Content-Type: application/pdf;
        name="?iso-2022-jp?B?GyRCJEokLCE8ITwhPCE8ITwhPCE8ITwhPCE8GyhC?=
        =?iso-2022-jp?B?GyRCITwhPCE8ITwhPCE8ITwkJCVVJSElJCVrTD4bKEI=?="
Content-Transfer-Encoding: base64
Content-Disposition: attachment
        filename="?iso-2022-jp?B?GyRCJEokLCE8ITwhPCE8ITwhPCE8ITwhPCE8GyhC?=
        =?iso-2022-jp?B?GyRCITwhPCE8ITwhPCE8ITwkJCVVJSElJCVrTD4bKEI=?="

たくさん間違っていて、解説する気にもなれません。。。

大昔に「直して下さい」と MSKK のエンジニアに直接お願いしたときは、「RFC に準拠しているはずだ」と言われて、開いた口が塞がりませんでした。

まじめに RFC 2231 を実装しているメールリーダと Outlook Express の間では、ファイル名の情報がうまく交換できません。

策定が遅れたせいで、このような問題が発生しているにも関わらず、MIME の著者は事態を解決するための努力をまったくしていません。

僕は、この問題の解決方法を考えて、何年か前にもう一度 MSKK にお願いしました。その方法とは、以下の通りです。

  1. RFC 2231 の復号化を実装し、Content-Disposition: の filename が正しく RFC 2231 で符号化されている場合、ファイル名を復号化する
  2. そのバージョンの Outlook Express が広く普及した後で、RFC 2231 の符号化を実装し、filename を RFC 2231 方式で符号化するようにする

結果としては、この提案は無視されました。。。

解決方法

Thunderbird では、この問題を解決する方法が実装されました。その方法では、RFC では定義されていないが、Outlook Express が参照する name パラメータを使います。

すなわち、filename パラメータは RFC 2231 方式で、name パラメータは RFC 2047 方式で符号化するのです。こんな感じです。

Content-Type: application/pdf;
 name=?iso-2022-jp?B?GyRCJEokLCE8ITwhPCE8ITwhPCE8ITwhPCE8GyhC?=
 =?iso-2022-jp?B?GyRCITwhPCE8ITwhPCE8ITwkJCVVJSElJCVrTD4bKEI=?=
Content-Transfer-Encoding: base64
Content-Disposition: attachment
 filename*0*=iso-2022-jp''%1B%24B%24J%24%2C%21%3C%21%3C%21%3C%21%3C%21
 filename*1*=%3C%21%3C%21%3C%21%3C%21%3C%24%24%25U%25%21%25%24%25k%1B
 filename*2*=%28B%2Epdf

僕も随分前に同じ方法を考えて実験しましたが、うまくいきませんでした。

なんと、Outlook Express は本文(第一パート)が ASCII 文字のみの場合、name パラメータを復号化しないようなのです。僕は、本文に ASCII 文字だけを入れて実験したんですね。はぁ。

符号化は悪だ

メールのプロトコルを拡張する際には、2 つの方法があります。

積極的な方法
下位互換性はなくなるが、8bit クリーンにして、古い実装を置き換える
消極的な方法
下位互換性を保つように符号化を定義する

IETF は、消極的な方法を選択しました。おそらく、符号化ぐらい誰でも正しく実装できると思っていたのでしょう。しかし、まともな符号化のコードを書けるプログラマーは少数派であることは、歴史が証明しています。そして、メールリーダは相互互換性の問題で苦しんでいるのです。

メールの拡張には、積極的な方法を採用すべきでした。つまり、8bit クリーンな環境を作ればよかったのです(SMTP は番兵方式を採用しているのでバイナリクリーンにはなりません) 。一時的な混乱はあるでしょうが、長い目で見れば利点が勝ります。メールリーダの実装は簡単になりますし、相互接続性の問題は発生しにくくなりますし、メールのさらなる拡張も容易なのです。

メールに電子署名を施す際に、いまだに 7bit に変換しなければならない。それだけでも、消極的な方法の失敗は明らかでしょう。