[PHP-dev 1397] mb_check_encoding() の第1引数の最初に \0 が入力された場合の問題について

komura komura.db2r1e @ gmail.com
2008年 5月 18日 (日) 19:01:53 JST


komura です。

1つ前のメールにも関連することなのですが、mb_check_encoding() の第1引数
の最初の文字列に \0 を入力すると、条件によっては、必ず TRUE になる問題
があります。

例としては、以下の通りです。

1. mbstring.substitute_character に "none" が指定された場合
   <?php
       mb_substitute_character( 'none' );
       $result = mb_check_encoding( "\0\xFF\xFF\xFF", 'SJIS' );
       var_dump( $result );
   ?>
   -- 結果 --
   bool(true)

2. 第2引数のエンコーディングに UTF-8 が指定された場合
   <?php
       mb_substitute_character( 63 );
       $result = mb_check_encoding( "\0\xFF\xFF\xFF", 'UTF-8' );
       var_dump( $result );
   ?>
   -- 結果 --
   bool(true)


以上の問題が発生するのは、mb_check_encoding() の処理が以下のように
なっているためです。

1) 第2引数で指定されたエンコーディングで第1引数の文字列を変換
2) 不正文字カウント(illegal_chars) が 0 で、さらに、変換前と
   変換後の文字列が同一の場合、TRUE を返す(比較関数として
   strncmp() を使用)

不正文字カウント(illegal_chars) が正しくカウントされない理由は
以下の通りです。

1. substitute_character を "none" に設定した場合、illegal_chars の
   カウントが行われない(ext/mbstring/libmbfl/filters/mbfilter_*.c)

   各ファイルの mbfl_filt_conv_wchar_*() を見ると、
   filter->illegal_mode が MBFL_OUTPUTFILTER_ILLEGAL_MODE_NONE 
   (ubstitute_character を "none" にすると設定される) の場合は
   illegal_chars のカウントが行われないコードになっています。

2. UTF-8 は不正文字の判定が厳しくないため、ほとんどの場合、
   illegal_chars のカウントが行われない
   (ext/mbstring/libmbfl/filters/mbfilter_utf8.c)

   illegal_chars がカウントされる可能性が非常に低いコードになって
   います。

2) に関してですが、strncmp() は '\0' 以降の比較を行わないという
ことが原因です。不正文字カウントが 0 でも、変換後の文字列は破壊
されていますので、バイナリ比較を行えば、正しく判定できるように
思います。
この部分(strncmp() を使用するようにした)は以前私が Patch を書き
ましたが、本件の問題については考慮不足でした。すみません。


substitute_character の挙動を変更するのは非常に大変ですので、
mb_check_encoding() の変換後の文字列比較を行っている部分を
バイナリで行うようにする Patch を作成してみました。


diff -ru php-5.2.6.orig/ext/mbstring/mbstring.c php-5.2.6/ext/mbstring/mbstring.c
--- php-5.2.6.orig/ext/mbstring/mbstring.c  2008-02-17 11:06:56.000000000 +0900
+++ php-5.2.6/ext/mbstring/mbstring.c   2008-05-18 18:13:51.007778173 +0900
@@ -4088,7 +4088,7 @@

    if (ret != NULL) {
        MBSTRG(illegalchars) += illegalchars;
-       if (illegalchars == 0 && strncmp(string.val, ret->val, string.len) == 0) {
+       if (illegalchars == 0 && memcmp(string.val, ret->val, string.len) == 0) {
            efree(ret->val);
            RETURN_TRUE;
        } else {

-- 
komura <komura.db2r1e @ gmail.com>


PHP-dev メーリングリストの案内