目次
典型的な文字化け
まずは上記からPHPスクリプトをダウンロードし、実行してみてほしい。ただし、セキュリティ対策が不十分なので、ローカルな環境で実行すること。

プログラムを実行すると、エンコード・タイプを3種類選べるようになっている。まず、一番上の UTF-8 を選択し、「Input Text」に何か日本語文字を入力し、[send] ボタンを押下してほしい。
「POST is passed」に表示される文字は化けていないだろうか。また、「Auto Converted」に表示される文字はどうだろうか。

次に、エンコード・タイプを、EUC-JP, Shift-JIS に切り換えて同様な操作をしてみてほしい。すべてのケースで文字化けが起きない環境というのは無いだろう。
また、もし複数のブラウザを利用できたり、異なるPHPサーバ環境が利用できるのであれば、それらでも試してみてほしい。環境が変わると、文字化けのケースも変化することを見ることができるだろう。

プログラムを実行すると、エンコード・タイプを3種類選べるようになっている。まず、一番上の UTF-8 を選択し、「Input Text」に何か日本語文字を入力し、[send] ボタンを押下してほしい。
「POST is passed」に表示される文字は化けていないだろうか。また、「Auto Converted」に表示される文字はどうだろうか。

次に、エンコード・タイプを、EUC-JP, Shift-JIS に切り換えて同様な操作をしてみてほしい。すべてのケースで文字化けが起きない環境というのは無いだろう。
また、もし複数のブラウザを利用できたり、異なるPHPサーバ環境が利用できるのであれば、それらでも試してみてほしい。環境が変わると、文字化けのケースも変化することを見ることができるだろう。
サンプル・プログラムの解説
53: $outstr1 = $_POST['param'];
54: $outstr2 = mb_convert_encoding($_POST['param'], $EncodingTable[$InternalEncoding][2], 'auto');
表示処理の関係で長くなっているが、このプログラムのコアは上記の部分である。
つまり、POSTで受け取った変数をそのまま表示($outstr1)と、関数 mb_convert_encoding で自動変換して表示($outstr2)するだけである。$outstr1 が「POST is passed」に、$outstr2が「Auto Converted」に表示される。

HTMLフォームからのデータをPOST変数で受けるようなPHPスクリプトを書く場合、$outstr1 を処理対象にするのはNGである。ここで見てきたように、文字化けする可能性が高いからだ。
では、$outstr2 なら安全かというと、これでも不十分である。たとえば「Input Text」に「十分な機能」という日本語を入力すると、円マーク「¥」が付加されてしまう環境がある。(⇒「バックスラッシュと円マーク」)
この場合、関数 stripslashes で円マークを取り除くのが定石なのだが、エンコードが Shift-JIS以外の場合には不具合が発生する。

なぜ、このような文字化けが発生するのか――じつは、複数の要因が絡んでいる。まずは要因を切り分けながら、各々の問題を明らかにしよう。
つまり、POSTで受け取った変数をそのまま表示($outstr1)と、関数 mb_convert_encoding で自動変換して表示($outstr2)するだけである。$outstr1 が「POST is passed」に、$outstr2が「Auto Converted」に表示される。

HTMLフォームからのデータをPOST変数で受けるようなPHPスクリプトを書く場合、$outstr1 を処理対象にするのはNGである。ここで見てきたように、文字化けする可能性が高いからだ。
では、$outstr2 なら安全かというと、これでも不十分である。たとえば「Input Text」に「十分な機能」という日本語を入力すると、円マーク「¥」が付加されてしまう環境がある。(⇒「バックスラッシュと円マーク」)
この場合、関数 stripslashes で円マークを取り除くのが定石なのだが、エンコードが Shift-JIS以外の場合には不具合が発生する。

なぜ、このような文字化けが発生するのか――じつは、複数の要因が絡んでいる。まずは要因を切り分けながら、各々の問題を明らかにしよう。
文字化けの原因と対策:PHPサーバ環境に依存するもの
まず、関数 phpinfo を実行し、自サーバの環境を見てほしい。その中に、mbstring directive の一覧表があるはずだ。
ここで注目してほしいのは、

POSTで渡されたデータは、まず mbstring.http_input に従ってデコードされる。
もし、mbstring.http_input の値が auto なら、自動でデコードされる。ただし、auto は万能ではないので、エンコード・タイプを間違えて文字化けを起こす場合がある。

mbstring.http_input の値が pass なら、デコードは行われない。この場合、POSTで渡されたエンコード・タイプと mbstring.internal_encoding で示されるエンコード・タイプが異なると、文字化けの原因となる。
mbstring.http_input の値が UTF-8, EUC-JP, SJIS などの具体的なエンコード・タイプであれば、明示的にこれらのタイプにデコードされる。しかし、mbstring.internal_encoding と一致していなければ、文字化けの原因となる。

こうしたことを考えると、mbstring.http_input の値は pass にしておき、スクリプト側でコード変換( mb_convert_encoding )を行うのが無難な設定だろう。この場合、送られてくる POST変数のエンコード・タイプが明らかでなければならない。関数 mb_convert_encoding で変換元に auto を指定することは、mbstring.http_input に auto を設定することと同義であり、前述のように auto は万能ではないからだ。

先のサンプル・プログラムでは、じつは、HTMLフォームとPHPスクリプトのエンコード・タイプは一致させてある。だから、関数 mb_convert_encoding で明示的にコード変換することは容易である。
しかし、POST変数に不正なデータを入れて攻撃を仕掛けてくるケースもあり、文字化けの原因になったり、セキュリティ・ホールになる可能性がある。そのため、冒頭で、このサンプル・プログラムを「ローカルな環境で実行すること」と注意したのである。

すなわち、PHPサーバ環境に依存する文字化け対策は、以下の通りとなる。
ここで注目してほしいのは、
- mbstring.http_input
- mbstring.internal_encoding

POSTで渡されたデータは、まず mbstring.http_input に従ってデコードされる。
もし、mbstring.http_input の値が auto なら、自動でデコードされる。ただし、auto は万能ではないので、エンコード・タイプを間違えて文字化けを起こす場合がある。

mbstring.http_input の値が pass なら、デコードは行われない。この場合、POSTで渡されたエンコード・タイプと mbstring.internal_encoding で示されるエンコード・タイプが異なると、文字化けの原因となる。
mbstring.http_input の値が UTF-8, EUC-JP, SJIS などの具体的なエンコード・タイプであれば、明示的にこれらのタイプにデコードされる。しかし、mbstring.internal_encoding と一致していなければ、文字化けの原因となる。

こうしたことを考えると、mbstring.http_input の値は pass にしておき、スクリプト側でコード変換( mb_convert_encoding )を行うのが無難な設定だろう。この場合、送られてくる POST変数のエンコード・タイプが明らかでなければならない。関数 mb_convert_encoding で変換元に auto を指定することは、mbstring.http_input に auto を設定することと同義であり、前述のように auto は万能ではないからだ。

先のサンプル・プログラムでは、じつは、HTMLフォームとPHPスクリプトのエンコード・タイプは一致させてある。だから、関数 mb_convert_encoding で明示的にコード変換することは容易である。
しかし、POST変数に不正なデータを入れて攻撃を仕掛けてくるケースもあり、文字化けの原因になったり、セキュリティ・ホールになる可能性がある。そのため、冒頭で、このサンプル・プログラムを「ローカルな環境で実行すること」と注意したのである。

すなわち、PHPサーバ環境に依存する文字化け対策は、以下の通りとなる。
- mbstring.http_input を pass にする
- mbstring.internal_encoding を明示する
- POST渡しする側のHTMLフォームをmbstring.internal_encodingに合わせる
- 受け取ったPHPスクリプト側では mb_convert_encoding でコード変換するとともに、不正なデータでないかどうか検証する。
文字化けの原因と対策:ブラウザ環境に依存するもの
HTML4では、formタグに属性として accept-charset を指定することで、サーバ側のエンコード・タイプに合わせてデータを送ってくれるという便利な機能がある。この機能を使えば、何らかの事情でHTMLフォームとPHPスクリプトのエンコード・タイプが異なっていたとしても、うまく文字化けしないでデータを渡すことができるはずである。
ところが、この仕様に対応しているブラウザとそうでないものがある。手元にある Windows用InternetExplorer 6/7 は対応していなかった。他の Windows/MacOS/Linux 用の FireFox, Safari, Opera は対応している。
したがって、HTMLフォームにaccept-charset属性を用いた場合、ブラウザによって文字化けが発生する可能性がある。
ところが、この仕様に対応しているブラウザとそうでないものがある。手元にある Windows用InternetExplorer 6/7 は対応していなかった。他の Windows/MacOS/Linux 用の FireFox, Safari, Opera は対応している。
したがって、HTMLフォームにaccept-charset属性を用いた場合、ブラウザによって文字化けが発生する可能性がある。
上記のプログラムを実行すると、「Select InternalEncode」と「Select InputEncode」の2つのエンコード・タイプを選ぶよう指示される。前者は、最初のサンプル・プログラム(httpparam1.php)と同様、HTMLフォーム及びPHPの内部処理エンコード・タイプを指定するものである。後者は、accept-charset属性を指定するものである。
「Input Text」に何か日本語文字を入力し、[send] ボタンを押下すると、「live POST parameter」「POST is passed」「Auto Converted」の3種類が表示される。「live POST parameter」は、POSTに渡されたデータをそのまま表示している。後2者は、最初のサンプル・プログラム(httpparam1.php)と同じ内容である。

「Select InternalEncode」と「Select InputEncode」が同じである場合は、どのブラウザでも「live POST parameter」の結果は同じになる。
ところが、たとえばSelect InternalEncodeにUTF-8を、Select InputEncodeにShift-JISを指定した場合は、InternetExplorerだけ結果が異なる。
結果的にAuto Convertedは文字化けしないで済んでいると思うが、PHPに渡されているデータが異なるというのは、あまりよろしくない状況である。

結局、HTMLフォームではaccept-charset属性は指定しない方がいいだろう。
「Input Text」に何か日本語文字を入力し、[send] ボタンを押下すると、「live POST parameter」「POST is passed」「Auto Converted」の3種類が表示される。「live POST parameter」は、POSTに渡されたデータをそのまま表示している。後2者は、最初のサンプル・プログラム(httpparam1.php)と同じ内容である。

「Select InternalEncode」と「Select InputEncode」が同じである場合は、どのブラウザでも「live POST parameter」の結果は同じになる。
ところが、たとえばSelect InternalEncodeにUTF-8を、Select InputEncodeにShift-JISを指定した場合は、InternetExplorerだけ結果が異なる。
結果的にAuto Convertedは文字化けしないで済んでいると思うが、PHPに渡されているデータが異なるというのは、あまりよろしくない状況である。

結局、HTMLフォームではaccept-charset属性は指定しない方がいいだろう。
サンプル・プログラムの解説
php://input からPOSTの生データを読み込むことができる。

そこで、変数 $_POST を利用せず、自力でPOST変数を解析して配列として返すようにしたのがユーザー関数 parsePOST である。

そこで、変数 $_POST を利用せず、自力でPOST変数を解析して配列として返すようにしたのがユーザー関数 parsePOST である。
41: /**
42: * original parsing POST parameter
43: * @return array : parameter
44: */
45: function parsePOST() {
46: $param = file_get_contents("php://input"); //get POST parameter
47: $arr1 = preg_split('/\&/i', $param);
48: foreach ($arr1 as $val) {
49: $arr2 = preg_split('/\=/i', $val);
50: $arr[$arr2[0]] = $arr2[1];
51: }
52:
53: return $arr;
54: }
enctypeを使った解決策
HTML4では、formタグに属性として enctype を指定することで、データを添付ファイルとして送信することができる。ここで enctype="multipart/form-data" を指定すると、エンコード・セットを含めてデータを送信することができるので、PHPスクリプト側で文字化けが発生することはない。

先のサンプル・プログラム(httpparam2.php)で用いたすべてのブラウザで enctype は有効に動作していることが確認できたので、この対策は効果的だといえる。

先のサンプル・プログラム(httpparam2.php)で用いたすべてのブラウザで enctype は有効に動作していることが確認できたので、この対策は効果的だといえる。
GET変数の場合
ここでは GET 変数による文字化け問題について触れてこなかったが、POST変数と同様、文字化けする場合がある。そこで、enctype="multipart/form-data" 指定で解決できるかというと、これが微妙なのである。
HTML4の仕様では enctype="multipart/form-data" が有効なのは method=POST の場合のみとしているが、ブラウザによって実装されていたりする。
したがって、GET変数まで含めて文字化け対策を考えようとすると、enctype="multipart/form-data" は上策とは言えない。
HTML4の仕様では enctype="multipart/form-data" が有効なのは method=POST の場合のみとしているが、ブラウザによって実装されていたりする。
したがって、GET変数まで含めて文字化け対策を考えようとすると、enctype="multipart/form-data" は上策とは言えない。
より効果的な対策法
原始的ではあるが、POST/GET で渡す変数にエンコード・タイプも持たせてやるというのが一番の解決策といえる。
実際、Yahoo!JAPAN の検索では、"ei=UTF-8" のようにしてエンコード・タイプを渡している。

ここまでの対策法を整理すると、下記のようになる。

【HTMLフォーム】
実際、Yahoo!JAPAN の検索では、"ei=UTF-8" のようにしてエンコード・タイプを渡している。

ここまでの対策法を整理すると、下記のようになる。

【HTMLフォーム】
- accept-charset属性を指定しない。
- enctype="multipart/form-data"を指定しない。
- エンコード・タイプを変数として渡す。(例:ei=UTF-8)
- mbstring.http_input を pass にする
- mbstring.internal_encoding を明示する
- POST渡しする側のHTMLフォームをmbstring.internal_encodingに合わせる
- 受け取ったPHPスクリプト側では、エンコード・タイプを元に、 mb_convert_encoding でコード変換するとともに、不正なデータでないかどうか検証する。
参考サイト
- 日本語のマルチバイト文字に関する基本事項(公式リファレンス)
- HTTP入出力(公式リファレンス)
- php://input(公式リファレンス)
- PHP 7 を Webサーバで利用できるようにする:ぱふぅ家のホームページ
- PHP 8:Windows版のイントール/Apacheと連携/特長:ぱふぅ家のホームページ
(この項おわり)
なぜそのようなことが起きるのか、原因と解決策をさぐる。
(2021年5月8日)PHP8対応