encodeURIComponent()を使ってAjaxで送信した日本語文字(〜など)が,サーバ側で文字化けする場合への対処.

ブラウザからサーバに日本語文字列を送信する時の話だが, 普通にHTMLでサブミットする場合は,ブラウザが勝手にそのページの文字コードかつURLエンコーディングした結果を送信してくれるので, 特に何も考えなくて良いのだが,Ajaxの場合はエンコードを自前でやらねばならない.

この場合,JavaScriptのencodeURIComponent()関数を使うのが一般的のようだ. これを使うと,

  • ページのエンコーディングからUTF-8への変換
  • その変換結果の,URLエンコーディング

の両方を行ってくれる.

サーバ側では,受信した内容をURLデコードして, さらに必ずUTF-8で来ているはずなので,その前提で必要な文字コードへ簡単に変換して使うことができる.

...のはずだったのだが,文字化けが生じてはまってしまった.

このページでは,その顛末を書きます.

なお,Windowsのブラウザで発生した現象で,ページのエンコーディングはEUC-JPです.

現象

文字「〜」を,Windowsブラウザ上のJavaScriptのencodeURIComponent()でUTF-8に変換し, それをサーバに送信したが,サーバ側でPerlのEncode.pmを使ってデコードできなかった.

encuri_mjbk1.png

ブラウザでの変換

ブラウザのアドレスバーに,
 javascript:alert(encodeURIComponent('〜'))

と入力すれば判るように,encodeURIComponent()では以下の変換結果が得られる.

変換前変換後備考
%ef%bd%9eU+FF5E (FULLWIDTH TILDE)

これは,いわゆるMicrosoft仕様の変換だ.(MS932)

Unicode WAVE DASH - FULLWIDTH TILDE問題 (Wikipedia)
http://ja.wikipedia.org/wiki/Unicode#WAVE_DASH_-_FULLWIDTH_TILDE.E5.95.8F.E9.A1.8C

サーバでの変換

PerlのEncode.pm,Jcode.pmで「euc-jp」を使った場合, UTF-8からEUC-JPへの変換には,以下のようにJIS X 0221準拠の変換が適用される.
変換前変換後備考
%ef%bd%9e??化け文字

そのため,ブラウザから「%ef%bd%9e」として送信されてきた文字は, 本来の「〜」に変換されずに文字化けとなってしまう.

対策

理屈の上では,以下の3案が考えられる
  1. ブラウザでのEUCからUTF-8への変換を,MS932ではなくJIS X準拠の方式で行う
  2. ブラウザでの変換後に,MS932とJIS Xでマッピングが異なる文字をJIS Xのコードに置換する
  3. サーバでのUTF-8からEUCへの変換を,JIS X準拠ではなくMS932の方式で行う

このうち,1.はどうすればよいのかさっぱり判らないので却下.

3.については,こういうものがあるらしい

Encode-EUCJPMS モジュール
http://search.cpan.org/~naruse/Encode-EUCJPMS/

が,インストールするのが面倒なのでやめた.

ということで,2.のコード置換をやることにした.具体的には,「〜」のマッピングは以下になっているので,

エンコーディングMS932JIS X 0221
〜の位置ef bd 9e = U+FF5E(FULLWIDTH TILDE)e3 80 9c = U+301C(WAVE DASH)

Encode.pmのeuc-jpでデコードするためには,「ef bd 9e」→「e3 80 9c」に変換すればよい.

encuri_mjbk2.png

変換の具体的な内容

昔,JavaでWindows-31Jが使えなかった時代によく見ていたページ
Javaの日本語関連コンバータにおけるマッピングの違い
http://www.ingrid.org/java/i18n/encoding/ja-conv.html

を参照しつつ,実際の動きを調べたところ,Encode.pmで文字化けせずに変換するためには以下のコード置換が必要だとわかった.

$a =~ s/%ef%bd%9e/%e3%80%9c/gi; # 〜 U+FF5E(FULLWIDTH TILDE) → U+301C(WAVE DASH)
$a =~ s/%e2%88%a5/%e2%80%96/gi; # ‖ U+2225(PARALLEL TO) → U+2016(DOUBLE VERTICAL LINE)
$a =~ s/%ef%bc%8d/%e2%88%92/gi; # − U+FF0D(FULLWIDTH HYPHEN-MINUS) → U+2212(MINUS SIGN)
$a =~ s/%ef%bf%a0/%c2%a2/gi;    # ¢ U+FFE0(FULLWIDTH CENT SIGN) → U+00A2(CENT SIGN)
$a =~ s/%ef%bf%a1/%c2%a3/gi;    # £ U+FFE1(FULLWIDTH POUND SIGN) → U+00A3(POUND SIGN)
$a =~ s/%ef%bf%a2/%c2%ac/gi;    # ¬ U+FFE2(FULLWIDTH NOT SIGN) → U+00AC(NOT SIGN)
サーバ側のPerlでこの変換を実行することで,ひとまず文字化け問題は解決した.

この作業をやるために,UTF-8とUTF-16を相互変換するJavaScriptを書く羽目になった.

なお,上記の6文字とたいていセットになってくる

 ― U+2015(HORIZONTAL BAR)

については,encodeURIComponent()でJIS準拠の変換が行われていたので,特に変換する必要はなかった.

雑感

JavaでWindows-31Jが使えるようになってから,この問題のことはすっかり忘れていたが, UnicodeとWindowsを使う限り,逃れることはできないのか...

そういえば,Macのブラウザだとどうなるのかが判らないが, まぁこの変換を入れたところで,副作用は出ないだろう(多分).

参考資料

Unicode - Wikipedia
http://ja.wikipedia.org/wiki/Unicode
Javaの日本語関連コンバータにおけるマッピングの違い
http://www.ingrid.org/java/i18n/encoding/ja-conv.html
UCS-2とUTF-8
http://homepage1.nifty.com/nomenclator/unicode/ucs_utf.htm
UTF-8とUTF-16を相互変換するJavaScript
UTF-16とUTF-8のコード変換


© 2024 KMIソフトウェア