URLエンコードとは

(日本語の要求パラメタのデコードに注意しよう)

 


メール(SMTP)HTTPなどのパケットは、ヘッダ部に宛先やその他メッセージの制御に関わる情報が載せられる。このヘッダは途中のゲートウエーを幾つか中継され、端から端のノードに伝達されるので、これらのノードに理解できるコードと文字で表現されなければならない。ヘッダ部に日本語のような2バイトの文字が入ると、ノードはこれを1バイトずつ解釈しようとする。そのような混乱を防止するためURLエンコードが考えられた。URLエンコードというのは、もともとヘッダ部のURL部分に2バイト文字や制御文字と紛らわしい文字が入るのを防止するために考えられたからである。しかし送られる情報をすべて「見える」文字列に変換するのは都合が良いことが多く、ボディ部分にもしばしば使われる。ボディ部分の変換にはもうひとつMIME(Multi-Purpose Internet Mail Extensions)エンコーディングがある。これは2バイトのバイナリ・データを3バイトの7ビットASCII(American Standard Code for Information Interchange)文字に変換するもので、マルチメディア情報の転送に使われる。

 

URLエンコードの手順は以下のようである。

 

-           日本語のように2バイトの文字は1バイト毎にとりだしてASCII文字とみなして以下の変換を行う。

-           名前と値にある「安全でない」文字は"%xx"に変換する。"xx"はその文字のASCII値を16進表示したものである。「安全でない」文字には=, &, %, +やプリントできない文字を含む。

-           全てのASCIIのスペース文字を+に変換する。

-           名前と値を=&でつないでひとつの文字列にする。例えばname1=value1&name2=value2&name3=value3

 

この文字列がPOSTのボディ部分、あるいはGETのクエリとしてはめ込まれるのである。詳細は公式仕様書であるRFC 2396の第2.4節を参照されたい。

 

 

ところで日本語の場合は歴史的な経過から文字エンコーディングが複数存在する。現在はMicrosoftが開発しWindows系で一般的に採用されているShift_JISと呼ばれるものと、Linux系で一般的に採用されているEUC-JP2つのエンコーディング双方が利用されている。WebSphereの場合はShift_JISがデフォルトとなっている。従ってWTEのサーブレット・エンジンもクライアントからURLエンコードで送られてきたパラメタ(バイト列)をShift_JISの文字コード列だと見なしてこれをJavaUnicodeに変換する。このことから、WTEの環境では、もしクライアントがEUC-JPで要求パケットを送ってくると問題が生じる。WebSphereのアプリケーション・サーバでは設定ファイルのデフォルトのクライアント・エンコーディングを変更してこれに対応することになる。これ以外にもPOST要求でHTTP要求のボディ部分にクライアントからのパラメタを書き込ませ、これをバイトごとに読み出してEUC-JPとしてUnicodeに変換する手段もあるが一般的ではない。

 

以上のことから、WTEの環境ではクライアントから必ずShift_JISでエンコードされた要求が到来するようにすることが推奨される。サーバに情報を送信するためのフォームを含むHTMLドキュメントが表示され、ユーザがその画面の送信ボタンをクリックして必要な情報をサーバに送信する際、IENetscapeのブラウザは現在その画面の表示に使っている文字エンコーディングを使ってバイト列を作成し、更にそれをURLエンコードする。従ってクライアントに渡すフォームを含むHTMLドキュメントには以下の要素を含めることが必要である。

<META HTTP-EQUIV="Content-Type" CONTENT="text/html;charset=Shift_JIS">

更にサーブレットからそのような画面をクライアントに返すときは、これに加えて次のようにエンジンに変換する文字エンコーディングを明示的に指示するだけでなく、クライアントにHTTP応答パケットのヘッダ行で使用文字エンコーディングをはっきり通知すべきである。

  response.setContentType("text/html; charset=Shift_JIS");  //応答ヘッダContent-Type行追加

 

WebsphereのエンジンはShift_JISでエンコードされた要求パラメタは正しくUnicodeに変換してくれている。しかしTomcatのエンジンはそれほど親切ではない。従ってこのサーブレットはそのままTomcatのコンテナで走らせると漢字は正しく表示されない。

 

Tomcatのエンジンは、URLをデコードしてこれをJavaの使用文字コードであるUnicodeに変換する。その際、変換する前の文字コードはISO8859_1(Latin-1)であると仮定して、8859_1からUnicodeへの変換テーブルを用いてString型のオブジェクトにしてしまう。従って、Shift_JISEUC-JPの文字列がURLエンコードされてきた場合には、正しくそれに対応したUnicodeの文字列が得られないことになる。

 

String correctName = new String(name.getBytes("8859_1"), "Shift_JIS");

 

なる操作は、この問題を解決する標準的な手法である。まずgetBytes(“8859_1”)なるメソッドで一旦Unicodeの文字列をISO8859_1(Latin-1)に変換したうえでバイト列にする。そうするとURLデコードしたときのバイト列が得られることになる。そのうえでそのバイト列をString(bytes,”Shift_JIS”)のコンストラクタでShift_JISのバイト列だと指定してString型のオブジェクト(Unicode)を生成する。

 

”Shift_JIS”の部分は、”EUC-JP””JISAutoDetect”などもともと期待していたエンコーディングを指定することになるが、”JISAutoDetect”は短い文字列の場合に正しく判定しないことがあるので使用しないほうが好ましい。

 

以下のコードはHttpRequestDumpの要求パラメタの出力部分をTomcat用に変更したものである。

 

  out.println();

  out.println("要求パラメタ:");

  Enumeration paramNames = request.getParameterNames();

  while (paramNames.hasMoreElements()) {

    String name = (String) paramNames.nextElement();

    String[] values = request.getParameterValues(name);

//  out.println("    " + name + ":");

//  Tomcatの場合は上の1行は以下の2行のように変更し、日本語の文字化けに対応すること

//  TomcatはURLエンコードされているパラメタのコードが標準のISO8859_1(Latin-1)であると仮定し

//  それを単にStringに(つまりUnicodeで)格納して各サーブレットに引き渡している。

//  したがってそのためパラメタ文字列を取得する際にはISO8859_1から自分が期待する

//  エンコードへの変換をしなければならない。短いデータではJISAutoDetectは正しく機能しないので

//  なるべく使わないこと。

    String correctName = new String(name.getBytes("8859_1"), "Shift_JIS");

    out.println("    " + correctName + ":");

    for (int i = 0; i < values.length; i++) {

        try{

//          out.println("      " + values[i]);

//  Tomcatの場合は上の1行も同様に以下の2行のように変更すること

            String correctValue = new String(values[i].getBytes("8859_1"), "Shift_JIS");

            out.println("      " + correctValue);

        }catch(Exception e){

            System.out.println("URL Decoding Error");

        }

    }

  }

 

読者のなかには「それならばWTEの環境でサーブレット・エンジンがShift_JISとして取り出した要求パラメタを同様にEUC-JPに以下のようにして復元できるのではないか」と考える人もいよう。

  String correctName = new String(name.getBytes("Shift_JIS"), "EUC-JP");

残念ながらこれは失敗する(各自試されたい)。8859_1という文字セットは8ビット単位なので、Unicode1文字は確実に8859_11バイト(null(%00)も含めた256文字)に11で対応しており、問題が生じなかったのである。WTESJISコンバータは、変換できないバイトは無視してしまうので、そのような逆操作をしても元には戻らない。

 

 

 

前節     目次     次節