文字エンコーディングの妥当性確認(バリデーション)について

大垣さんからコメントをいただきましたので、最後に追記しました(2009.09.22)。
少し時間が経ってしまいましたが、以下のページを読んで、PHP に関連する部分について思ったことを書きたいと思います。

私の理解が間違っていなければ、「Web アプリケーションで文字エンコーディングに関連する問題を無くす」ことを目的として、全ての Web アプリケーション開発者が以下を実行しようという主張だと思います。

  1. 全ての入力文字列の文字エンコーディングの妥当性を確認する
  2. 文字エンコーディングを厳格に取り扱う
  3. データベースなどで「バイナリ」に近い文字エンコーディングは利用しない

個人的には、PHP は文字エンコーディング関連の処理について、問題が多いと考えています。PHP 利用者がこれらの問題やバグまで理解して扱うのはさすがに無理があると思います。以前、この日記でも取り上げましたが、mb_check_encoding() だけでも PHP のバージョンによって相当な違いがあります。最新の PHP ではかなり改善されていますが、まだ問題は残っています(mb_check_encoding() の内部処理)。このため、単純に入力データを mb_check_encoding() で妥当性の確認をするだけでは、十分でない場合もあります。

文字エンコーディングについては、mbstring のような PHP の1モジュールで対応するのではなく、PHP 自体が文字エンコーディングを厳格に取り扱うようにした方が問題が少なくなると思います。PHP6 をスナップショットからダウンロードして試してみたところ、文字列を変数に代入すると、文字エンコーディングの妥当性が確認されるようになっていました。PHP6 の完成度を上げて早期にリリースするようにした方がこの問題の解決には早道かもしれません。PHP6 のリリースにはまだまだ時間がかかりそうですが。


以下に、大垣さん文書の中で気になった、または、よく分からなかったことを挙げておきたいと思います。

入力時の自動変換

以下の記述で、対策可能と書いておられますが、注意すべき点が3つありますので、書いておきたいと思います。

追記:「PHPで手っ取り早く対策するには?」と聞かれたので書いておきます。エントリ中にも書いていますが、UTF-8の場合は

mbstrign.strict_detetion = on
mbstring.http_input = auto
mbstring.internal_encoding = utf-8
mbstring.http_output = pass

とします。(strict_detectionを入れ忘れていたので追加しています)

http://blog.ohgaki.net/is-char-encoding-problem-difficult

(1) 間違える人は少ないと思いますが、上記の設定だけでは、入力時の自動変換は有効になりません。mbstring.encoding_translation = On が必要です。
(2) mbstring.http_input に auto や複数の文字エンコーディングを指定すると、最悪の場合は、変換されずにそのまま入力変数($_GET, $_POST など)に渡ってしまうことがあるようです(PHP 5.2.11 で確認)。入力値の不正な文字列を排除することが目的の場合、mbstring.http_input に指定するのは1つだけにした方が良いと思います。UTF-8 の場合は、以下の通りです。この場合、不正な文字列になっている部分のみ削除されます。

mbstring.internal_encoding = UTF-8
mbstring.http_input = UTF-8
mbstring.encoding_translation = On
mbstring.strict_detection = On

(3) PHP のバージョンと設定によっては、mbstring.strict_detection = On の指定は危険です。正確には、PHP 5.1.2 〜 PHP 5.2.8 では、mbstring.http_input に複数の文字エンコーディングを設定(auto の場合を含む)して、不正な文字列を変換しようとすると、無限ループが発生します。以下のコードで無限ループが発生する場合は、mbstring.strict_detection を Off にするか、mbstring.http_input に複数の文字エンコーディングを指定しないでください。

<?php
ini_set( 'mbstring.strict_detection', 1 );
mb_convert_variables( 'UTF-8', 'auto', $a = array( "\xfc" ) );

詳しく知りたい方は、以下を参照してください。

enctype="multipart/form-data" を指定した場合の自動変換

以下の部分についてです。

PHPの場合はPerlよりもっと簡単で、コードを一切書き換えなくてもphp.iniの設定をするだけで"自動"バリデーションが行えます。
と自分自身で書いていながら何故、文字エンコーディングのバリデーションが必用なのか?と疑問に思った方も居ると思います。PHPはファイルアップロードを行う場合にフォームに
enctype="multipart/form-data"
を指定すると入力エンコーディングに自動変換は動作しない仕様になっています。enctypeが違っても普通のフィールドも送信できます。もし文字エンコーディング変換を行うなら、これらのフィールドには明示的な文字エンコーディング変換が必用です。つまり、明示的なバリデーションが必用になります。

http://blog.ohgaki.net/is-char-encoding-problem-difficult

「enctype="multipart/form-data" を指定すると、入力エンコーディングで自動変換が動作しない仕様」と書いておられますが、PHP マニュアルには、以下のように記述されています。

注意: PHP 4.3.2 およびそれ以前のバージョンの場合、HTML フォームのenctype が multipart/form-data に設定された場合、mbstring は、POST データの文字エンコーディングを変換しません。この場合、文字列を内部文字エンコーディングに変換してやる必要があります。
PHP 4.3.3 以降、HTML フォームの enctype が multipart/form-data に設定され、かつ、php.ini において mbstring.encoding_translation に On が指定されている場合、POST データの変数とアップロードされたファイルの名前の文字エンコーディングは、内部文字エンコーディングに変換されます。ただし、クエリキーに関しては、変換されません。

http://php.net/mbstring.http

マニュアルでは、「PHP 4.3.3 以降では、php.ini で mbstring.encoding_translation = On に設定し、enctype="multipart/form-data" で HTML フォームを送ると、内部文字エンコーディングに変換する」と読めます。実際、試してみたところ、PHP 4.4.9 では、enctype="multipart/form-data" で HTML フォームを送っても、自動的に文字エンコーディングが内部エンコーディングに変換されました。仕様としては、マニュアルで正しいと思います。
ただし、PHP 5.2.11 や PHP 5.3.0 では、変換されませんでした。この件については、以下で報告されていましたが、結局修正されなかったようです。

mbstring がデフォルトモジュールになったのは、PHP 4.0.6 から

PHP 4.2からmbstringはデフォルトモジュールとなりました。デフォルトモジュールすべきと提案したのは私だったのでよく覚えています。 mbstringにはインプットを自動的に内部文字エンコーディング変換する機能とアウトプットに指定した文字エンコーディングを自動変換する機能を持っています。

http://blog.ohgaki.net/is-char-encoding-problem-difficult

mbstring は、4.0.6 で導入されたと記憶しています。PHP 4.2.0 では、mbregex(マルチバイト正規表現モジュール) が導入されました。

誤字・脱字について

大垣さんの文章に、以下のような記述がありました。

久しぶりにアマゾンのレビュー見ましたが、ほんと酷い評価(苦笑でもセキュリティ標準を読んだ事がある、せめて名前や内容を正しく解説できるWeb開発者はあたり前にいるとは思えません。誤字・脱字は自分でも見つけましたが、こういう本の評価に重要なのでしょうか?内容が正しいかどうかは技術者であれば判断できると思います。もしかしてコード監査で酷い目に合わせてしまった方なのかな?(汗

http://blog.ohgaki.net/char_encoding_must_be_validated

個人的には、本を買う際に誤字・脱字を結構気にする方なので、誤字・脱字が目立つ本はあまり買いません。これは、重要な部分にまで間違いがありそうで信用できなくなるためです。「内容が正しいかどうかは技術者であれば判断できる」とのことですが、勉強のために本を買う人に対してそれを期待するのは酷な気がします。
また、あまり人にえらそうに言えるた立場ではないのですが、大垣さんの文書は、誤字・脱字などの間違いが多いような気がします。誰でも、少しくらいは誤字・脱字があるものですし、あまり神経質になることはないと思うのですが、あまりに多いと文書全体が信用できなくなる気がします。それがどれだけ良い文書だったたとしても、読者から信用がないと、主張は伝わらないと思います。

コメントへの返信(2009.09.22)

大垣さんから以下のコメントをいただきましたが、少し長くなるのでこちらで返信します。

enctype="multipart/form-data" が指定された場合の自動変換について

mbstringにもいろいろ問題があるのは確かです。PHP4.4.9の動作はどうしてああなっているのか、単純な感違いで仕様を変えてしまっただけだと思います。mbstringは確かつかださんとおっしゃる方がphp3-i18n版をモジュール化して作った物です。その元のmbstringの仕様がmultipart/form-dataになっているとエンコーディング変換しなかったと記憶しています。PHP3がそのような仕様だったのかも知れません。ずいぶん前に変換しないことがバグとしてレポートされた場合に「この仕様どうする」という議論になってそのままにすることになったので、その経緯を知っている人にはバグです。(ご存知とは思いますがPHPにはこういう変更がちょくちょくあります)

過去の PHP-users や PHP-dev メーリングリストでの流れと、cvs.php.net のログを見ると、以下の2点が読み取れると思います。

  1. PHP 4.3.3 から 4.3.5 あたりで enctype="multipart/form-data" を指定した場合、内部エンコーディングに変換する機能を追加したこと
  2. php3-i18n 版でも enctype="multipart/form-data" が指定された場合、内部エンコーディングに自動変換されていたこと

今回、ファイルアップロードされたファイル名の文字コード
内部文字コードに変換するコードを追加していますので、
この辺をテストしていただけるとありがたいです。

http://ml.php.gr.jp/pipermail/php-users/2003-July/017633.html

multipart/form-dataの変数名を内部文字コードに変換するのは、
encoding_translation = On の時の動作として妥当なところだと思います。
また、PHP3-i18nの挙動と同等になったのではないかと思います。

http://ml.php.gr.jp/pipermail/php-users/2003-August/018109.html

後、PHP3-i18nで確認してみたところ、name属性に日本語を使った場合も
正常に変換しているようです。

http://ml.php.gr.jp/pipermail/php-users/2003-August/018123.html

cvs.php.net には、以下のようなログも残っています。

bug fixed: name parameter of multipart form was not converted into internal encoding when mbstring.encoding_translation is on.

http://cvs.php.net/viewvc.cgi/php-src/main/rfc1867.c?view=log#rev1.122.2.15

name/value in multipart/form-date will be converted into internal encoding when mbstring.encoding_translation is On.

http://cvs.php.net/viewvc.cgi/php-src/main/rfc1867.c?view=log#rev1.142

また、[PHP-dev 913]multipart/form-data時のencoding_translationの仕様についてから始まるスレッドも参考になります。
以上から、「enctype="multipart/form-data" が指定された場合、自動変換が行われるのは仕様である」という理解で正しいのではないかと思います。

mbstring が PHP に含まれるようになったバージョン

mbstringはPHP 4.0.xから使えたのですPHPプロジェクトのソース版に入ったのが4.2からです。

mbstring の実行時設定(PHP マニュアル) や、その他の関数でも確認できるように、PHP 4.0.6 から mbstring が使用できることが記述されています。また、museum.php.net から PHP 4.0.6 をダウンロードし、configure --help で確認したところ、--enable-mbstring オプションがありました。
cvs.php.net を確認しても PHP 4.0.6 のタグがありますので、mbstring が含まれるようになったのは PHP 4.0.6 からで間違いないと思います。

追記(2009.10.13)

誤字・脱字を修正しました。