出力時の文字エンコーディング変換、妥当性確認について

大垣さんが以下のページで PHP について言及しておられますので、気になったことを書いておきたいと思います。

私は、現在、PHP で構築したサイト運用はしていませんので、出力時に文字エンコーディングを変換、妥当性確認する方法がどの程度有効で、どのような問題があるのかは十分把握していません。ある程度実施方法については書いておきますので、参考にされる方は、実用可能かどうかを十分検証してください。

出力時に文字エンコーディングを変換する方法

PHPで、似た様な動作にしたい場合(ブラウザ以外からの入力も怪しいなど)出力時に強制的に文字エンコーディング変換してしまえば良いです。例えば、

mbstring.internal_encoding = utf-8
mbstring.http_output = utf-8

と出力文字エンコーディングを指定すると不正な文字列はサニタイズされます。

http://blog.ohgaki.net/rails-ruby-1-9

これだけでは設定が不足しています。これだでは出力時に文字エンコーディングは変換されません。PHP マニュアルの以下に記述されている通り、出力時に文字エンコーディング変換をしたい場合は、以下の2つの設定が必要です。output_buffering は有効になっています(4096 などになっています)が、output_handler は設定する必要があります。

例2 php.ini の設定例

;; 全ての PHP ページで出力の文字エンコーディング変換を有効にする

;; 出力バッファリングを有効にする
output_buffering = On

;; mb_output_handler による出力変換を有効にする
output_handler = mb_output_handler

http://php.net/mbstring.http

mb_output_handler() では、Content-Type によって変換するかどうかを判定しています。全ての PHP スクリプトで文字エンコーディングの変換が行われるわけではありません。このため、以下の記述は誤りです。

ただし、http_outputをpass以外に設定すると副作用が発生するので注意が必要です。例えば、画像ファイルをUTF-8エンコーディングに変換すると確実に壊れます。

http://blog.ohgaki.net/rails-ruby-1-9

mb_output_handler() による、文字エンコーディング変換は、以下のようになっています。

出力時に文字エンコーディングを妥当性確認する方法

おそらく、元の記事だけでは、どうすれば良いのか分からないように思いますので、少し書いておきます。PHP スクリプト内で出力バッファリングを適用する場合は、以下のように、ob_start() を使用した後に出力します。

<?php
ob_start( 'mb_output_handler' );
// HTML の出力処理

mb_output_handler() は、mbstring が出力用に提供している関数で、mbstring.internal_encoding に設定した文字エンコーディングから mbstring.http_output に設定した文字エンコーディングに変換します。PHP が用意している出力用関数は、他にも ob_iconv_handler() や ob_gzhandler() などがあります。

ob_start() の引数は、コールバック関数で、この関数は PHP スクリプト内で定義することもできます。定義する関数には、第1引数に出力する値がそのまま渡ってきます。この関数内で、何か処理を行い、出力する文字列を返します。何も返さなかった場合、何も出力しないことになります。この関数内では、echo, print_r(), var_dump() などによる出力はできません。
今回の大垣さんの例では、以下のように、出力全体に対して mb_check_encoding() を適用し、不正な文字列が一文字でもあればエラー処理をする関数になっています。

<?php
function check_encoding_handler( $output )
{
    if ( ! mb_check_encoding( $output ) ) {
        // trigger_error('Invalid char encoding detected', E_USER_ERROR);
        return get_error_page(); // エラーページの HTML など
    }
    return $output;
}

ob_start( 'check_encoding_handler' );
// HTML の出力処理

注意点としては、ob_start() は複数回呼び出すことが可能です。このため、複数使用する場合、処理内容によっては、順番に注意してください。ob_start() は、入れ子構造で適用されますので、後で実行した方が先に適用されます。例えば、ob_gzhandler() を使用している場合は以下のようにします。

<?php
ob_start( 'ob_gzhandler' );
ob_start( 'check_encoding_handler' ); // 先に check_encoding_handler が適用されます。

この順番を反対にした場合、check_encoding_handler() では ob_gzhandler() で圧縮後のバイト列に適用することになります。このため、想定した結果にはなりません。

<?php
ob_start( 'check_encoding_handler' );
ob_start( 'ob_gzhandler' );           // 先に ob_gzhandler が適用されます。

現在の出力ハンドラ情報は、ob_list_handlers() や、ob_get_status() で取得できます。

詳しくは、PHP マニュアルの以下のページを参照してください