UTF-7 エンコードについて
現在 FastGrepReplace の修正作業を行っているんだけど(そんなことをしている場合じゃないのに)、UTF-7 について、思うところをちらほら。
UTF-7 とは
UTF-7 というのは、もともと Unicode を 7bit スルーにして、主にインターネットやメールでメリットを得ようとして、そして失敗したエンコーディングです。
詳しくは RFC 2152 を見てもらいたいのですが、このエンコーディングが提案されたモチベーションとして、
- UTF-8 を Quoted-Printable でエンコードすると ASCII 以外の文字は 9 バイト必要になる
- だけど UTF-7 を使えば、ASCII 以外の文字は 3 バイトを 4 バイトで表せるよ
- 前後にシフト文字が入るから、平均すると 1 バイトを 1.5 バイトで表せるよ
- ASCII 部分は可読可能だよ、便利だよね!*1
というものなんです。
3 バイトを 4 バイトで表すのは Base64 の亜種なんですが、まったく同じではありません。詳しくはそれぞれの RFC を見てください。
UTF-7 の日陰っぷり
上記のようなモチベーションで提案されたエンコーディングですが、実際のところほとんど使用されていません。
というのも、エンコーディング後のテキストに / が + が含まれているためファイルシステムや、主にメールシステムのために開発されたエンコーディングなのに既存のメールシステムのメールボックス運用との相性が悪いこと、UTF-8 が 7bit スルーではないものの、圧倒的に優れているため、運用としては UTF-8 を Base64 エンコードした方が扱いやすいことなどが原因です。
また、エディタによる対応も難しいことが挙げられます。
7bit スルーであるが故、エディタで開く際に、「これは ASCII なの?それとも UTF-7 なの?」という問題が発生してしまうためです。
試していませんがサクラエディタはそれなりに判別してくれるようですが、我が秀丸はどのような UTF-7 テキストも自動識別の際には ASCII として開きます。
iconv の変換にバグがある
UTF-7 の非 ASCII 部分は、Base64 でエンコードされる際に + で始まり - で終わらなければならないのですが、iconv で UTF-7 に変換すると、最後の - が付与されない、という不具合が見つかりました*2。
iconv 1.12 と iconv 1.13 で確認しました。
仕様のあいまいさ
UTF-7 は、エンコードする対象が選択的になっているため、例えば僕の実装(iconv に不具合があったため自力で実装した)と秀丸の実装は、エンコードの結果が異なります。
元のテキスト:
OK_OK
+/y//KwBf-OK
僕のエンコード:
+/y//Kw-_OK
これは、アンダーバー(_)がエンコードしてもしなくてもよい扱いになっているためです(アンダーバー以外にもたくさんありますが)。
テキストの内容は違ってないのに、バイナリは異なる、というのがとても厄介です。UTF-8 は(BOM を除いて)そのようなことはありません。
grep でひっかけられない
たぶん一番の問題がこれかなと思います。
grep で任意の文字列を探す際に、対象のテキストすべてに対して、それが ASCII なのか UTF-7 なのかを判別しつつ、UTF-7 だったらデコードして検索文字列にマッチするかどうか、なんてことはやってられないということでしょう。
我が愛しの秀丸でも同様で、マルチバイト文字部分は grep でひっかけられないのです。
上記は間違いでした(2010/08/15)。秀丸の全般的な設定で、自動判別するエンコードの種類に UTF-7 がありました。僕の環境ではチェックが入ってなかったので、自動判別しないようになっていただけでした。
さて、FastGrepReplace は、grep 結果を元にまとめて置換を行うツールなわけですが、UTF-7 を対象にした場合、どのようなことが起こるか説明します。
まず、現在の公開版 v2.00 では、UTF-7 かどうかを判定する処理がそもそもないので、ファイルは ASCII として判定されます。そこにマルチバイト文字を置換で書き込もうとすると、ASCII を Shift_JIS に格上げして、Shift_JIS としてマルチバイト文字を書き込みます。
このようになってしまうと、UTF-7 の Base64 部分は、単なる ASCII の文字列になってしまうので、内容が失われてしまいます。
現在作成中の v2.50 では、ファイルが UTF-7 かどうかを判定する処理を入れてあり*3、マルチバイト文字を置換で書き込もうとすると、Base64 エンコードして書き込まれることになります。
ただし、副作用として Base64 のエンコーディング方法が、秀丸式から FastGrepReplace 式に代わってしまうため、置換してない場所もバイナリ的に変更が加わってしまう可能性があります。
上記は間違いでした(2010/08/05)。FastGrepReplace は、行単位でファイルの内容を管理していて、各行は変更されていない場合は出力時にそのままバイナリ的に同じ内容が書き出されるので、変更してない行の内容が変わるということはあり得ませんでした。
じゃあエンコード方式の実装を秀丸に合わせればいいじゃないか、という話になりますが、そもそもそのテキストは秀丸で作成されたものなのかどうかがわからないため、秀丸に合わせることで多少は問題が鎮静化しますが、根本的には UTF-7 の仕様があいまいであることがあり、どうしても変更場所以外がバイナリ的に変更されてしまう可能性は防げません。
とまあ、ここまで書いたのですが、UTF-7 なんて誰も使ってないエンコードなので、どうでもいいっちゃどうでもいいんですけどね。