2018年10月22日

ISO-2022-JPにねじ込まれた半角カナ

別に自分が使いたいわけではなくて、いわゆる半角カタカナ入りでメールを送ってくるシステムはどういう仕組みになっているのか気になった。

発端はクレジットカードを利用するたびに届く明細メールの一部が毎度文字化けしていたこと。

neomuttでテキストパートを表示するとこうなる。

■利用先: ^[(I7^V3Q=-^[(IJ_-

w3m経由でHTMLパートを読むと正しく表示でき、半角カナでギョウムスーパーと書かれているとわかった。

ISO-2022-JPの仕組み

文字コード関連は用語がややこしいので、誤用があったら優しく指摘してください。

どちらのメールパートも文字コードはISO-2022-JPで、文字集合に応じてエスケープシーケンスを挟みつつ文字を表現していくもの。RFC 1468を見るとエスケープシーケンスの値が明記されていたのは以下の4つだった。

   Esc Seq    Character Set                  ISOREG

   ESC ( B    ASCII                             6
   ESC ( J    JIS X 0201-1976 ("Roman" set)    14
   ESC $ @    JIS X 0208-1978                  42
   ESC $ B    JIS X 0208-1983                  87

それぞれ乱暴な言い方をすれば半角英数、半角ローマ字(ASCIIと微妙に違う)、全角文字、その改正版。

実際に対象の文字列のバイナリデータを見るとこんな具合。

$ od -t x1a  rakuten.dat
0000000  1b  24  42  22  23  4d  78  4d  51  40  68  1b  28  42  3a  20
        esc   $   B   "   #   M   x   M   Q   @   h esc   (   B   :  sp
0000020  1b  28  49  37  5e  56  33  51  3d  1b  28  42  2d  1b  28  49
        esc   (   I   7   ^   V   3   Q   = esc   (   B   - esc   (   I
0000040  4a  5f  1b  28  42  2d  0a
          J   _ esc   (   B   -  nl 

データ冒頭は日本語の直前ということでエスケープシーケンス ESC $ B 、続いて 0x22、 0x23 (= JISコード2223=■)。その後の「:」の直前にはASCIIを表す ESC ( B があるのがわかる。

半角カナの直前にあるのは ESC ( I 、しかしこれは規定されていない。半角カナも JIS X 0201 に含まれるのにローマ字集合だけ表れているのが不思議と思いつつ読み進めると

The Kana set of JIS X 0201 is not used in ISO-2022-JP messages.

結論: ISO-2022-JP では半角カナを表現できません。

エスケープシーケンスの決め方

メールでは半角カナはNGと言われていた理由がよくわかったところで、何に基づいて ESC ( I を使用していたのかが気になってきたので調べた。

まずこの複数の文字集合をエスケープシーケンスで切り替えて混在させるという考え方は別の規格ISO/IEC 2022で定められている。しかしここでは具体的な値は規定しておらず、ISO国際登録簿というさらに別のもので管理されている。

登録簿はRFCからもリンクされていて、先に引用したエスケープシーケンス表のISOREGの項目は実はこの登録簿内のID。
登録簿を見てみると、文字集合の表に「Final Byte」という項目がある。これがエスケープシーケンスの最終バイト。ISO版だったり旧称だったりで名前がそれぞれ違うが対応する文字集合を抜き出すと、

  • 2.1「ISO 646, USA Version X3.4 - 1968」=> 4/2 => 0x42 = B
  • 2.1「ISO 646, Japanese Version for Roman Characters JIS C6220-1969」=> 4/10 => 0x4A = J
  • 2.4「Japanese Character Set JISC C 6226-1978」 => 4/0 => 0x40 = @
  • 2.4「Japanese Character Set JIS C 6226-1983」=> 4/2 => 0x42 = B

そして1バイトの文字集合には ( 2バイトのには $ を中間に入れるので、前述の表の通りのエスケープシーケンスが得られる。(実際には中間バイトも登録簿に示されていてこんな括りではないけど、ISO-2022-JPではざっくりとこう示されている)

半角カナは登録簿では2.1「Katakana Character Set JIS C6220-1969」 => 4/9 => 0x49 = I、中間バイトは 4/0 = ( で確かに ESC ( I となった。

誰が始めたのか?

半角カナをISO-2022-JPに入れようと思いついたのは誰なのか。

このエスケープシーケンス自体はISO/IEC 2022的にはアリなので、ウェブを漁ると確かに半角カナを示すものとして挙げられている。ミケネコさんや杜甫々さんのページを見ると初心者に戻った気分になれる

その他のページでも独自拡張ですとか亜種で使われますといった記述がちらほら見つかるものの、具体的にどんな製品が導入し始めたのかは調べきれなかった。

せめて名前だけでもと調べると Windows で使われるという CP50221 が近そう。

この別名として ISO-2022-JP-MS があるらしい。ただこの名前、PHPがサポートしていたり(2007年2月リリースの5.2.1から)、libiconvに使われたり(2011年10月)しているが起源が不明。2006年に始まった「Legacy Encoding Project」が一番古そうでプロジェクトの参加者が解説も行なっているが、プロジェクトのwikiが死んでいるので確認ができなかった。

しかしこのエンコーディング名にMSが入っているからといってMicrosoftが半角カナ入りISO-2022-JPの発端と考えるのは早計だろう。
半角カナが入力できる→メールの作成画面でも打ち込める→(ユーザーから半角カナが送れないとクレームが来る→)メーラー側が変換を実装する、という流れがあるとすればMS製でないメーラーでも、ましてやWindows以外のOSでも当然起こりうる。
Outlook以外のメーラーで半角カナを含めていた例がこちら。 kanzakiさんのサイトもよくお世話になった

…半角カナを送り出すメールソフトもあります。たとえば、Outlook ExpressやEudoraで「1バイトカナ可」とか「JIS X 0201カナを通す」とした場合は、この方法を使っています。

インターネットでの日本語メール 「半角カナとJIS」セクション

ISO-2022-JPは日本の電子メールで用いられてきたJUNETコードを(略)RFC化したもの(Wikipedia)ということでRFCでも参考文献とされている「JUNET利用の手引(第1版)」を当たってみたが、こちらでは半角カナ禁止が明記されていた。

いわゆる半角カナは使わない。
半角カナとは、 JIS X0201 (C6220) の8単位符号のカナのことであり、8ビット目 が1の場合だけでなく、SI、SO によってシフトした場合の半角カナについても同様で ある。すなわち、半角のカナは使用しない。

6.3.1 JUNETにおける漢字利用の約束

この規定を引き継いだために半角カナはISO-2022-JPに入らなかったのかもしれない。一方で明確に禁止するということは巷では広く使われている証拠っぽくもある。

とりあえず流れをまとめてみる。

  • 1969年 JIS X 0201。半角カナにコードが与えられる。
  • 1973年 ISO/IEC 2022 初版。その後82年、86年と改正されている。
  • 1986年ごろ JUNETコードが使われるようになる
    • 「手引」6.3.2 に符号化に関する解説と提案が付属しているが、JUNETとfjの記念碑 によると1986年に投稿され、じきに合意が形成されたという。この中でエスケープシーケンスの誤用が指摘されているので、ISO/OEC 2022に基づいた符号化はかなり広まっているように思える
    • ただしこの時点では「ISO-2022-JP」という規格は存在していないためRFC違反ではない
  • 1993年
    • RFC 1468
    • Windows 3.1 日本語版発売。「Windows標準キャラクターセット」として半角カナを含んだ文字集合をサポート、実装はShift-JISベース(CP932)。 CP50221 が当初から導入されていたかは不明。
    • なお、このときすでにJUNETはほぼその役目を終えていた模様 (ニュースグループとしてのfjは続いている)
  • 1999年 あるいは98年 (ページ末尾の奥付による)
    • 上記のOutlookとEudoraに関する記述 = このときには半角カナ+ISO-2022-JPが広まってしまっている
  • 2006年 Legacy Encoding Project
  • 2007年 PHPがISO-2022-JP-MSをサポート

93年から99年の間にこのエンコーディングをISO-2022−JPと名乗り始めたメーラーがあるはず。Windows 95なんかもあるし。なにかわかったら追加していきます。

扱い方

そもそもISO-2022-JPを名乗るなって話だが、未知のエンコーディング扱いされて完全に文字化けするかもしれないから、とりあえずISO-2022-JPと名乗って送ろうって考え方らしい。独自規格と自覚してるなら外に出さないでくれよ……。

これを扱う方法はないかと調べていると、NKFならできるよという記述を見かけた。ruby経由で試す。

require "nkf"

data = File.binread("rakuten.dat")

puts NKF.nkf("-Jw", data) # ISO-2022-JPとして入力
puts NKF.nkf("-w", data)  # 自動判定に任せても大丈夫
puts NKF.nkf("-wx", data) # 半角カナを全角にする挙動を抑制

エンコーディング名がわかったので Ruby の String#encode もためしたらいけた。

puts data.encode(Encoding::UTF_8, Encoding::CP50221)
puts data.encode(Encoding::UTF_8, Encoding::ISO_2022_JP) # これはエラーになる

ついでに w3m について確認したらこちらも NKF を使っていた。HTMLパートではエンコードできたのはそういう理由だったんですね。

あとは mutt に NKF をどう噛ませるか(犬だけに)考えてみようとしたが、カードの明細は月1でダウンロードしてるし、そちらも半角混じりだけどエンコーディングはShift-JISで扱いには問題ないうえ、RFC違反のメールに骨を折るのは癪なのでやっぱり特になにもしないことにします。

この終わり方、SBI銀行のときと同じだな。
→libiconvを使えばいいようなのでやってみたら成功した



posted by かぷらす at 17:00| Comment(0) | 調査 | このブログの読者になる | 更新情報をチェックする
この記事へのコメント
コメントを書く
コチラをクリックしてください