TenForward

技術ブログ。はてなダイアリーから移転しました

PHP の mbstring で encoding_translation 有効の時,.htaccess の internal_encoding の値が反映されない事がある

というのを調べてました.[php-dev 1445], [php-dev 1446] で書いたのと同じ.

設定

php.ini で

mbstring.language = Japanese
mbstring.internal_encoding = EUC-JP
mbstring.http_input = auto
mbstring.http_output = pass
mbstring.encoding_translation = On
mbstring.detect_order = auto
mbstring.substitute_character = none;
mbstring.func_overload = 0
mbstring.strict_encoding = Off

という感じで設定して,.htaccess

php_value mbstring.language Japanese
php_value output_handler mb_output_handler
php_flag mbstring.encoding_translation 1
php_flag magic_quotes_gpc 0
php_value mbstring.internal_encoding UTF-8

という設定の環境でした.

UTF-8 で書いた以下のようなソース.

<html>
<body>
<?php
if($_REQUEST["message"] != ""){
    print "メッセージ<br>\n";
    print "encoding: " . mb_detect_encoding($_REQUEST["message"])  . "<br>\n";
    print htmlspecialchars($_REQUEST["message"]) . "<br>\n";
}
?>
<form action="test.php">
<input type="text" name="message" value="<?php htmlspecialchars($_REQUEST["message"])?>">
<input type="submit" value="送信">
</form>

<pre>
<?php
print_r(mb_get_info());
echo phpversion();
?>
<pre>
</body>
</html>

というようなプログラム.

再現その1

  1. まず上記ページにアクセスします.
  2. ブラウザに表示させたまま,一度 apache を落とします.
  3. httpd -X で apache を起動します.(かならず発現するようにシングルプロセスモード)
  4. フォームに日本語を入力し,submit します.
  5. 前画面で input に入力した文字列のみ文字化け.mb_detect_encoding は EUC-JP となります.mb_get_info で表示させた internal_encoding は UTF-8 で正常です.
  6. この後は (http -X で起動している限りは) 何度アクセスしても文字化けは起こらず,正常です.

再現その2

  1. (再現その1) の 6 でその後は何度アクセスしても正常と書きましたが,ここで一度,.htaccess のないディレクトリへ遷移して,その後,この .htaccess の存在するディレクトリへ戻ります.つまり.htaccess 有 -> 無 -> 有 と一度外に出て戻ります.
  2. 戻る際は http://server/dir/test.php?message=あいうえお みたいに直接パラメータ指定します.
  3. 文字化け.mb_detect_encoding は EUC-JP,mb_get_info は UTF-8

つまり

  • Apache 起動後の全ての変数が初期化された状態で問題が起きる
  • .htaccess 設定と違う internal_encoding が設定されたディレクトリを訪れた後は問題が起きる.

という動きで,internal_encoding の値が反映されてないことが予想されました.

調査

デバッガで追ってみました.全部追ってないので,かなりこう動くのだろう,という想像が入ってますが..

Apache 起動後,

  1. .htaccess が検出されると,mbstring.c の OnUpdate_mbstring_internal_encoding へ飛ぶ.ここで new_value の値をデバッガで見てみると,"UTF-8" となっており,確かに .htaccess は読み込まれているっぽいです.先の例で mb_get_info() で設定を取得すると,internal_encoding の値は "UTF-8" と正常になっていたのは,ちゃんと値が取得出来ているからだと思います.
  2. ここで encoding_translation が指定されていると,mb_gpc.c 内の mbstr_treat_data に処理が飛びます.ここで変換先のエンコーディングを決定しているっぽいですが,ここではグローバル変数 mbstring_globals の internal_encoding を見ているようで,この時点ではmbstring_globals.internal_encoding の値は更新されておらず,例だと "EUC-JP" のままなので,その後の変換処理で(?) 入力文字列が EUC-JP へ変換されてしまい,結果,出力が文字化けしてしまうようです.
  3. その後,グローバル変数は更新されるので,次にアクセスした際は,正常にエンコード変換される (or されない) のですが,一度,別設定が効いてくる他のディレクトリに移動すると,グローバル変数の値がそのディレクトリの値にリセットされるので,再度文字化けが発症します (= internal_encoding の値が .htaccess 以外の値に).

以上のように想像なのですが,エンコーディング変換が有効な際,.htaccess の値でグローバル変数が更新されていれば,問題なく動くのだと思います.とりあえず,きっと他の動きがめちゃめちゃになるのでしょうけど,以下のようにしてみると,文字化けは解消しています.ただし,これはグローバル変数に値を代入したら動くという確認のためのもので,これで問題がないわけではありません.mbstring の全体を見てませんので.(mbstring 難しい...)

--- mbstring.c.orig	2009-03-02 13:02:25.000000000 +0900
+++ mbstring.c	2009-03-02 13:03:03.000000000 +0900
@@ -745,6 +745,7 @@
 		 * 2. mbstring.language directive is processed in per-dir or runtime
 		 * context and 3. call to the handler for mbstring.language is done
 		 * after mbstring.internal_encoding is handled. */
+		_php_mb_ini_mbstring_internal_encoding_set(new_value, new_value_length TSRMLS_CC);
 		return SUCCESS;
 	}
 }