DeleGateによる日本語文字コード変換

Yutaka Sato
2006年12月30日 [最終更新:2007年01月23日]

関連: DeleGateにおける日本語文字コードおよびMIMEコード変換機能

[2007年01月23日]
文字コード異常の例とDeleGateによる回避方法 を追加、 文字コード変換プロキシの問題点 加筆。

[2006年12月30日]
DeleGate 9.4.2 では、10年ばかり御無沙汰気味だった日本語文字コード変換機能 について見直しを行い、以下の拡張を行いました。

  • サーバ向けの文字コード変換の指定方法を拡充 (CHARCODE)
  • 指定した文字を別の文字にマップする機能を導入 (CHARMAP)
日本語コード変換関係のドキュメント類も、だいぶ古くなって内容が現状と 不一致だったり、散逸もしてしまった感じなので、この際にちょっとまとめて みることにしました。

■ 文字コード変換リバースプロキシとしてのDeleGate

クライアントとサーバの間の文字コードや文字セットの不整合によって、 いわゆる文字化け等の問題が発生します。 そのような問題を、サーバ側で回避するために、DeleGateを 「文字コード変換を行うリバースプロキシ」として使用することができます。

■ リバースプロキシの構成

いわゆる「リバースプロキシ」は、以下のように構成されます。
                               [http://www.com.dom:80]       [http://serv:8880]
    client -------------------> DeleGate -------------------> HTTP-server
この例では、DeleGateはクライアントから見て http://www.com.dom という サーバとして動作していて、 その実際のコンテンツは http://serv:8880 にあり、DeleGateにより ゲートウェイ(リバースプロキシ)されているという形になっています。 これは、DeleGateの「マウント」機能により、以下のようにして実現できます。
[ DeleGateの起動コマンド ]
delegated +=httpd.cnf

[ httpd.cnf ファイルの内容 ]
-P80
SERVER=http
MOUNT="/* http://serv:8880/* vhost=-www.com.dom"
URICONV=where:any
PERMIT="http:serv:*"
RELAY=no

この例では、設定が多少複雑ですので、必要なコマンド引数を "httpd.cnf" というファイルに書いて、"+=" で 参照しています(その一部あるいは全てをコマンド引数として直接書いても 構いません)。 それぞれの引数は、以下のような指定をしています。
  -P80 SERVER=http
このDeleGateは、80番ポートでHTTPサーバとして動作する
  MOUNT="/* http://serv:8880/*"
このDeleGateが受け取る全てのURL を http://serv:8880 に転送する
  vhost=-www.com.dom
このDeleGateが www.com.dom として参照された場合にのみ、 このMOUNTを適用する
(転送先のサーバが唯一である場合には省略可)
  URICONV=where:any
このDeleGateが認識できる全てのテキスト型応答データに、 このMOUNTを適用する
  PERMIT="http:serv:*"
任意のクライアントからのアクセスを許可、ただしアクセス先は http://serv に限定する
  RELAY=no
MOUNTされたサーバへの中継以外は拒否する

■ 文字コード変換プロキシ

DeleGateに文字コード変換を行わせるには、 CHARCODE という オプションで、変換先の文字コードを指定します。例えば、 CHARCODE=EUC-JP と指定すると、 サーバからのレスポンス中の日本語テキストの文字コードは EUC-JP に変換されて クライアントに転送されます。

    client <----- EUC-JP ------- DeleGate <===== Japanese ==== HTTP-server
           ------ EUC-JP ------>          ------ EUC-JP ----->

これが、DeleGateが当初から持っていた機能なのですが、これだけですと、逆に クライアント側からサーバへ、日本語テキストを送信する場合に問題が起こります。 典型的には検索エンジンへの検索語の送信や、掲示版への書き込みなどです。

まず、一般にクライアントは、サーバから返されたレスポンスの文字コードに 合わせてリクエストの文字コードを決定して送信します。 ここで、DeleGateによってレスポンスがサーバの文字コードと異なるコードに 変換され、クライアントがそのコードに合わせてリクエストを送信し、 サーバがその文字コードを認識してくれないと、文字化けが起こります。 また、一度もサーバからの応答を得ていない状態で、クライアントから日本語 を送信する場合には、文字コードはクライアントの一存で決められ、 サーバの期待する文字コードになっていない可能性があります。

このことから、DeleGate は 9.0.3以降、 上の例のように CHARCODE オプションによってサーバからのレスポンスに対して 文字コード変換を行った場合、サーバからのレスポンスの元の文字コードを Cookieに入れてクライアント送り、それを 次のリクエスト時に返送してもらうことで、サーバの文字コードを知り、 サーバ向けの文字コード変換を行うようになっています (ただし、サーバ向けの文字コード変換を明示的にCHARCODEオプションで 指定した場合(†1)には、そちらが優先します)。

ただこの方法には、サーバからの文字コードを一度も見ていない段階(あるいは それを記憶したCookieがクリアされた状態)でクライアントからのリクエストを サーバに中継する場合には、必要な文字コード変換が行えない、 という問題があります。

■ 文字コード変換リバースプロキシの構成

幸い、リバースプロキシの場合にはサーバが特定されていて、そのサーバが受理 できる文字コードが確定していることが普通です。このため、その文字コードに 固定的に変換してやるように、リバースプロキシを設定してやれば、より 確実に必要な文字コード変換を行うことができます。 上の例のように構成されたリバースプロキシ上で、文字 コード変換を行うとしましょう。例えば、 クライアントと http://www.com.dom のサーバとして動作している DeleGate との間では、テキストデータを UTF-8 でコード化して通信し、 DeleGate とhttp://serv:8880 サーバとの間では EUC-JP でコード化するものとします。
                               [http://www.com.dom:80]       [http://serv:8880]
    client <----- UTF-8 ------- DeleGate <=================== HTTP-server
           ===================> DeleGate ----- EUC-JP ------>

クライアント向けのレスポンス中の日本語コードの変換は、 CHARCODE=UTF-8 と指定します。 一方、サーバ向けのリクエスト中の文字コードの変換は CHARCODE=charcode:tosv のように指定します (†2)

[ httpd.cnfの内容 ]
-P80
SERVER=http
MOUNT="/* http://serv:8880/* vhost=-www.com.dom"
URICONV=where:any
PERMIT="http:serv:*"
RELAY=no
CHARCODE=UTF-8
CHARCODE=EUC-JP:tosv

最近のブラウザは、任意の文字コードに対応しているものが多いので、 サーバが受理するコードだけが限定されるという状況ならば、この例の クライアント向けの文字コード変換は不必要であるかも知れません (†3)

ただし、一方を Unicode に、他方を JIS の文字セットに固定することで、 Unicode と JIS の文字セットの変換を行う際に、変換表を限定することで、 変換表に無い文字がコード変換時に自動的に除外されるという効果があり、 これが有効である場合が考えられます (現状のDeleGateでは、この変換の際に JIS X 0213 文字セット中の文字 が自動的に除外されます)。 また、EUC-JPあるいはISO-2022-XX系から Shift_JIS への変換でも同様です (補助漢字が自動的に除外されます)。


(†1) CHARCODE=charcode:tosv という指定で、 サーバ向けのコード変換を指定できるようになったのは、DeleGate/9.4.2 からです。 それ以前の版では、 MOUNT="vURL rURL FTOSV=-cc-EUC-JP" のように、マウントのオプションとして指定する方法があります。
(†2) ちなみに、DeleGate.ORG の サイト内検索エンジン として使用している FreyaSX は、 受理できる文字コードが EUC-JP のみであるため、リバースプロキシとしての DeleGate において、このサーバ向けの文字コード変換(のみ)を行っています。
(†3) Windows VISTA上のクライアントの文字セットがサーバ側で問題を起こす可能性が あるという話を聞きますが、そもそもサーバからクライアント向けの文字コードを Shift_JISにしてやれば、普通のブラウザならばサーバ向けの文字コードも Shift_JISになり、Shift_JISで表現できない文字コードを送っては来ないと 考えられるので、それだけで問題を回避できるとも考えれるのですが、定かでは ありません。なにやら拡張されたシフトJISというのがあって、それをShift_JIS を名乗るサーバに送ってくる可能性も考えられますし、サーバの文字コードを 無視してUTF-8などで送ってくるかも知れません。 (使用する文字コード/セットで表現できない文字は、 &#ddddd;の形式で、Unicode値を送ってくるというのが、 昨今のブラウザのよくある振る舞いのようではあります)
私自身は、2年前からMacOSX上のFirefoxを使用していますが、MacOSXでは随分前 からJISX0213/UTF-8にも対応していますし、それで特段の問題があるとも聞きま せんので、 今回WindowsがVISTAでそれに対応したからといって新たに(サーバ側に)何か問題が 発生する可能性も低いのではないかと思います。 もし、リクエスト中の文字列でサーバ側に何か致命的な問題を発生させることが できるならば、それはDoS攻撃者にとっては美味しい話でしょうから、既にこれまで 発見され悪用されて来なかったとは考え難い、とも思います。 もっとも、そのような不具合を起こすサーバ側のプログラムを作ろうと思えば 簡単に作れますから、実際に存在しないとも言えませんが。


■ 文字コード変換プロキシの問題点

DeleGateによる(プロキシによる)文字コード変換は、規約の設計上原理的に、 あるいは DeleGateの実装上の不完全性から、完全ではありません。

日本語テキストの検出・文字コードの判別の問題

 サーバ向けの文字コード変換は、リクエストヘッダのURLの中、 およびPOSTリクエストのボディ(†p1)の中に、 %XX%XX (URLのエスケープ表記) あるいは &#ddddd; (Unicode文字のHTML表記) のようにエンコードされた日本語テキストに対して適用されます。 (†p2) ここで、%XX%XX の形式でエンコードされたものについては、 それが日本語テキストであるのか、またどの日本語コードで表現され ているのかは、どこにも指示されていません。このため一つの文字列が、例えば EUC-JP としても Shift_JIS としても解釈可能な場合、 変換結果が期待通りにならない場合があります (†p3)

サーバ固有の文字コード指示との不整合の問題

 サーバ固有の何らかの形式で、リクエスト中の非ASCII文字の文字コードが指示 されている場合があります。(†p4) DeleGateがそのようなリクエスト中の日本語テキストに文字コード変換を行った 場合、併せてそのような指示も書換えないと、サーバ側での文字コードの判別を 誤らせることになります。 残念ながらそのような指示の形式は標準化されたものでなく、個別に対処する しかない、一般的な解決が困難な問題です。

日本語文字コード間の文字セットの不整合の問題

 DeleGate/9.4.2 現在、DeleGate がサポートしている(と思われる)日本語の 文字セットは、JIS X 0201, JIS X 0208, JIS X 0212 です。 文字コードは、ISO-2022-JP, EUC-JP, Shift_JIS および UTF-8 です。 ここで、文字コードの間で、表現できる文字セットは、それぞれに異なります。 例えば「半角カナ」は、ISO-2022-JP には含まれていないので、例えば Shift_JIS から ISO-2022-JP に変換すると、半角から全角に変換されます。 例えば EUC-JP でコード化されている「補助漢字」は、Shift_JIS に変換すると 失われます。 変換先のコードが存在しない場合その文字は(デフォルトでは)ゲタ記号(〓) にしています (†p5)

Unicodeの問題

 UTF-8 (Unicode文字セット) と JIS文字セットのコードと間の変換では、 Unicode.Org から配布されている変換表を、DeleGate の起動時に (CHARSET オプションが指定されている場合)、 DeleGate.ORG 経由でダウンロードして、用いています。 DeleGate/9.4.2 現在、用いているのは、OBSOLETE と分類されている変換表で、 これは JIS X 0213 を含んでいません。 今後、必要性があると判断された段階で、実装したいと思っています。 (Unihan.txt の表から日本語の文字コードを抽出することになると思います) なお、Unicode については、よく言われているように、JISコードとの間の変換が 一意でないという問題もあります。


(†p1) POSTリクエストのボディに対する文字コード変換が適用されるのは、 デフォルトでは、そのデータ型(Content-Type)が x-www-form-urlencoded の場合のみです。 これ以外の型にも適用するには、 HTTPCONF=post-ccx-type で指定して下さい。 典型的には、FORMタグの中で ENCTYPE=multipart/form-data と 指定されて送り出される場合です。またこの場合、POSTのボディ中には、URLとして エスケープされていない生の形式で、日本語テキストが含まれることになります。
(†p2) サーバ側が明示的あるいは暗黙的に指定する受理可能な文字コード・セットで 表現できない文字をサーバに送信する際、最近のHTTPのクライアント(ブラウザ)は &#ddddd;という形式でそのUnicode文字のHTML表記を送ってくる、 というのが一般的なようです。URL中では "&" "#" ";" はそれぞれエスケープされて %26%23ddddd%3B となります。
(†p3) サーバからのレスポンスの場合にも同じような問題はありますが、レスポンス の場合、入力の文字コードが Content-Type や META タグで指定されていることが 多く、確実に推定するために十分な文字数がある場合が多いので、入力コードの 判別を誤ることは稀です。
(†p4) 例えば、何時の頃からか、search.yahoo.co.jp では、queryの中に、 "ei=UTF-8" とか "ei=euc-jp"のような形で入力の文字コードが指定されるように なっています。
(†p5) CHARMAP=jis:0000/2222 などとして、マップ先のない文字を、〓 (JISコードで0x222E) 以外の 文字にマップすることができます。

■ 文字コード異常の例とDeleGateによる回避方法

特定の文字コードにしか対応していないクライアント

 もともと DeleGateの日本語文字コード変換の機能は、黎明期のウェブブラウザ (日本語対応前の、Win/Mac 版 NCSA Mozaic)でとりあえず日本語が読めるよう、 追加されたものでした。 現在ではメジャーなHTTPのクライアントは、各種の日本語文字コードをサポート しているので、この問題はほとんど無いでしょう。 ただ、HTTP以外のプロトコルにおいては、このような変換が必要な状況は未だに あるようです。
 この問題は、サーバからクライアントにテキストを中継する際に、文字コードを クライアントがサポートしているものに変換してやることで回避できます。 例えばクライアントが EUC-JP に対応しているなら、 CHARCODE=euc-jp のように指定します。
(†b1)

サーバへ送られるメール・ニュースの文字コードの統一

 これもDeleGateの歴史的な用途として、ニュースリーダ(NNTPクライアント)から 投稿された記事の文字コードを、DeleGateでISO-2022-JP に強制変換してサーバに 送出するという機能が、しばらく使われていました。 この使われ方も、クライアント側の文字コード対応やネットニュースの衰退とともに、 現在ではほとんど見られなくなっていると思います。 現在では、メールを配送するSMTPの中継において、文字コードの強制や文字セットの 制限などを行う上で、必要性があるかも知れません。
 メール・ニュースのケースは、受信者(の使用クライアントプログラム)が不特定 多数であるため、受理可能な文字コードが特定できない場合といえます。 このため、日本語メール・ニュースについては、推奨されている(はずの) ISO-2022-JP に揃えるのが安全策(であるはず)です。 例えばサーバに送出(中継)するメッセージの文字コードを ISO-2022-JP に統一する には、 CHARCODE=iso-2022-jp:tosv のように指定します。 (†b2) (†b3)

文字コードが指示されていない日本語テキストによる字化け

 サーバから送られて来た応答テキストが、どのような文字コードによるものか、 どこにも(HTTPやMIMEのヘッダにも、HTMLのMETAタグにも)指示されていない場合 があります。 この時、その文字コードが何であるかはクライアント(ブラウザ)側で推定する ことになりますが、その自動判別に失敗した時や、デフォルトが非日本語の状態 に設定されている時に、字化けが発生します。 (†b4)
 このようなケースの多くは、リバースプロキシあるいはクライアント側のDeleGate において、応答の文字コードを推定し、HTTPヘッダ (Content-Type 中の charset パラメタ) に指示してやることで、回避することができます。 DeleGateにこれを行わせるには、 CHARCODE=guess のように指定します。

文字コードが混在した日本語テキストによる字化け

 サーバから送られて来た応答テキストが、複数の文字コードの混在したものである 場合があります。ほとんどのライアント(ブラウザ)はそれに対処してくれません ので、ここで字化けが発生します。 (†b5) これは、サーバ側で日本語文字コードを考慮せずに、複数のソースからのデータを マージしてレスポンスを合成した場合などに発生します。 そもそも、このようなテキストは通常の規格上許容されていないものですし、 文字コードの自動判別がより困難になりますが、DeleGateではいくつかの ケースについて、文字コードの混在した入力を判別して、対応しています。
 このようなケースでもやはり、リバースプロキシあるいはクライアント側のDeleGate において、特定の文字コードへのコード変換を行えば解消します。 例えば文字コードを EUC-JP に揃えるには、 CHARCODE=euc-jp のように指定します。

サーバ側のサポートしない文字コードでのリクエストで字化け

 検索サーバや掲示版への書き込みなど、クライアント側から送信した日本語 テキストの文字コードを、サーバ側で認識・受理できない場合に字化けが起こります。 この問題は、サーバ側がどの文字コードを受理できるかをクライアントが認識・考慮 していない場合や、そのコードに対処していない場合、そのコードで表現できない 文字を送ろうとする場合、などに発生します。
 この問題は、DeleGateによってサーバ向けのデータ中の文字コードを適切に 変換してやることで回避することができます。
 DeleGateをリバースプロキシとしての使用する場合など、対象サーバの数が 少なくその受理できる文字コードが特定できる場合には、 CHARCODE=euc-jp:tosv あるいは、 CHARCODE=euc-jp:tosv:http:server1,server2 などと指定することで対応できます。
(HTTPの)クライアント側のプロキシで、対象サーバが特定できない場合、 クライアント向けの文字コード変換、例えば CHARCODE=euc-jp などを指定すると、クライアント向けの変換の際にサーバの元の文字コードを Cookie中 (svcc) に記憶させ、 これに基づいてサーバ向けの文字コード変換を行います。


(†b1) 現在 DeleGateで、クライアント向けの(サーバからクライアントへ送られる テキストの)文字コード変換をサポートしているプロトコルは、 HTTP, FTP, POP, NNTP, Telnet, Tcprelay(任意のTCP上のデータ) です。
(†b2) MIMEメッセージに対するこの変換は、text型のメッセージ、および multipart メッセージに含まれるtext型の各パートに対して適用されます。
(†b3) 現在DeleGateで、サーバ向けの文字コード変換をサポートしているプロトコルは、 HTTP, FTP, SMTP, NNTP, Telent, Tcprelay(任意のTCP上のデータ) です。
(†b4) 私の場合、実験台になる意味もあって、ブラウザには「日本語版」を使わず、英語版 を使っていますので、デフォルトの文字セットが ASCII や ISO-8859-1 である ことが多く、このようなケースに遭遇しやすいといえます。
(†b5) 例えば9.4.1以前のDeleGateでは、SHTML (SSI) で #include したテキストの 文字コードが異なる場合に対処しておらず、文字コードの混在が発生して いました。

PageViews: 784 hits / 108 nets

以下は、DeleGate の参照マニュアル Manual.htm からの抜粋です。

DeleGate reference manual version 9.9 / CHARCODE

[CTX] [ALL]
CHARCODE parameter* ==  CHARCODE=[inputCode/]outputCode[:[tosv][:connMap]]
        outputCode  ==  charCode
          charCode  ==  iso-2022-jp | euc-jp | shift_jis | utf-8 | us-ascii |
                               JIS | EUC | SJIS | UTF8 | ASCII | guess
           connMap  ==  [ProtoList][:[dstHostList][:[srcHostList]]]
                    --  restriction: applicable to HTTP, FTP, SMTP, POP,
                                     NNTP, Telnet, Tcprelay
                    --  default: none
    If specified, DeleGate will convert the JIS code in text type response message into the specified character code. When UTF8 is specified, a mapping table necessary for conversion between Unicode and JIS code is downloaded automatically from the server at Unicode.Org via DeleGate.ORG.

    The pseudo code name "guess" means not doing conversion but supplement the "charset" attribute in "Content-Type" header in a message when it is lacking, guessing it from the body of the message. This is useful when a viewer program (ex. a web browser) of the message is not localized to Japanese thus non-ASCII codes like EUC-JP are guessed as European or so by the viewer.

    If "tosv" is specified, the conversion is applied to the request message (or a message toward a server). The conversion is also applied to fragments of Japanese text in a HTTP request message encoded in "%XX" in its request URL or in the body of a POST message (when it is encoded in Content-Type: application/x-www-form-urlencoded). The set of values of Content-Type to which the conversion is applied can be specified with HTTPCONF=post-ccx-type.

    The application of the conversion can be limited only to a specified set of protocols, servers and clients specified with connMap. In the connMap, the default value of ProtoList, dstHostList and srcHostList is "*" which matches any protocols or hosts.

    Example: send response in UTF8 to clients and send request in Shift_JIS to HTTP servers

    For the FTP protocol, the conversion is applied only to the data of the ASCII type relayed on the data-connections by default. It is applied also to binary data or data on the control-connection with FTPCONF="ccx:any".

    To enable this parameter for internet-mail/news protocols (SMTP, POP and NNTP), also MIMECONV parameter must be specified so that it enables character conversion (enabled by default).

    A HTTP client can override this specification by sending its choice in "Accept-Language" field in a request message, which may be configurable in each client (WWW browser). For example, if "Accept-Language: (charcode=EUC)" is sent in a request from client, the response text will be converted into EUC regardless of CHARCODE specification of the DeleGate. If "Accept-Language: (charcode=THRU)" is specified, any conversion specified by the administrator of this DeleGate is disabled.


DeleGate reference manual version 9.9 / CHARMAP
[CTX] [ALL]
CHARMAP parameter*  ==  CHARMAP=mapType:charMap[,charMap]*[:tosv]
           mapType  ==  ascii | ucs | jis | ucsjis | jisucs
           charMap  ==  inCharCode1[-inCharCode2]/outCharCode2[-[outCharCode2]]
          charCode  ==  hexa-decimal code | single ASCII character
                    --  default: none
    If specified, a character in texts relayed in a message of an application protocol is mapped to another character. By default, the mapping is applied to the response data to client. It can be applied to the request data to server specifying ":tosv".

    A character to be mapped is represented in a hexa-decimal value of it (represented in more than 2 columns), or a direct character in a single columns. The characters in the JIS X 0208 character set encoded in the variants of its encoding (ISO-2022-JP, EUC-JP, and Shift_JIS) are represented in its JIS code value without the most significant bits (8080) as "2121". The characters in the JIS X 0212 character set are represented in its JIS code value prefixed with "1" as "1222F".
    If inCharCode2 and outCharCode2 is specified, each character in the range is mapped to the corresponding character. If no -outCharCode2 is given, any input characters in the range is mapped to outCharCode1.

    The mapType is one of followings:

    Example: reverse lowercase and upper case

    Example: "rot13" encoding

      CHARMAP=ascii:a-m/n-z,n-z/a-m,A-M/N-Z,N-Z/A-M

    Example: replace any Japanese character in JIS X 0208 with "GETA MARK"

    Example: replace any Japanese character in JIS X 0212 with "GETA MARK"

    Example: represents unknown characters by "WHITE SQUARE" instead of "GETA MARK"