[PHP-dev 1490] Re: mbstring の文字列変換、文字エンコーディング検出の関連の問題について

komura komura.db2r1e @ gmail.com
2009年 8月 20日 (木) 00:29:00 JST


komura です。

お忙しい中、確認どうもありがとうございます。

On Wed, 19 Aug 2009 00:37:22 +0900
Moriyoshi Koizumi <mozo @ mozo.jp> wrote:

> 基本的に送っていただいたパッチをそのままコミットしていいと思うのですが、ダブルチェックしたいのでちょっとお時間をください。

私は急いでいませんので、時間のあるときに検証いただけましたら幸いです。


> 1. mb_convert_encoding() で UTF-16(Little Endian) の文字列を変換すると結果が
>   壊れる
>
>   UTF-16(Little Endian) で作成した文字列を変換すると、1文字目が壊れます。
>   Shift_JIS や EUC-JP に変換した場合でも、1文字目が壊れます。
>   変換元文字列が UTF-16(Big Endian) の場合は問題ありません。
> 
> エンコーディング名 "UTF-16" は "UTF-16BE" や "UTF-16LE" と違い、BOM
> によってエンディアンを判定します。手元で試したところ、BOM が \xff \xfe でも \xfe \xff
> でも問題が発生することが確認できました。

以下のテストコードで、BOM 付きの Big Endian では、正しく変換されている
ように見えましたので、問題ないと書いたのですが、私の勘違いでしょうか?

 <?php
 // "テスト"(UTF-16 BOM 付き Little Endian)
 $str = "\xFF\xFE\xC6\x30\xB9\x30\xC8\x30";
 echo "* UTF-16(Little Endian) -> UTF-16: ";
 var_dump( bin2hex( mb_convert_encoding( $str, "UTF-16", "UTF-16" ) ) );

 // "テスト"(UTF-16 BOM 付き Big Endian)
 $str = "\xFE\xFF\x30\xC6\x30\xB9\x30\xC8";
 echo "* UTF-16(Big Endian)    -> UTF-16: ";
 var_dump( bin2hex( mb_convert_encoding( $str, "UTF-16", "UTF-16" ) ) );

 結果(PHP 5.3.0)
 ---------------
 * UTF-16(Little Endian) -> UTF-16: string(16) "feffffc630b930c8"  <- 壊れる
 * UTF-16(Big Endian)    -> UTF-16: string(16) "feff30c630b930c8"  <- 問題なし


> > 4. mb_check_encoding() で UTF-16(Little Endian) を判定すると、必ず false を
> >   返す
> 
> これは先にも書きましたが、BOM を見てエンディアンを判定することによるものなので問題はないように思えます。

すみません。これは、少し複雑な問題です。
実際には、1. の Patch を適用しても、以下の結果になります。

 <?php
 // "テスト"(UTF-16 BOM 付き Little Endian)
 $str = "\xFF\xFE\xC6\x30\xB9\x30\xC8\x30";
 echo "* UTF-16(Little Endian): ";
 var_dump( mb_check_encoding( $str, "UTF-16" ) );

 // "テスト"(UTF-16 BOM 付き Big Endian)
 $str = "\xFE\xFF\x30\xC6\x30\xB9\x30\xC8";
 echo "* UTF-16(Big Endian)   : ";
 var_dump( mb_check_encoding( $str, "UTF-16" ) );

 結果(PHP 5.3.0)
 ---------------
 * UTF-16(Little Endian): bool(false) <- bool(true) になって欲しい
 * UTF-16(Big Endian)   : bool(true)  <- 問題なし


補足しますと、mb_check_encoding( $str, "UTF-16" ) を実行した場合、
UTF-16 -> UTF-16 の変換を行い、その結果が、元の $str と同一かどうかで
結果を判定します。

この変換を行うと、UTF-16(BOM 付き Little Endian)の文字列は、
Big Endianに変換され、変換後は元の文字列に戻りません。
ただし、以下はどちらも UTF-16 としては正しい文字列です。

 変換前(UTF-16 BOM 付き Little Endian): "fffec630b930c830"
 変換後(UTF-16 BOM 付き Big Endian)   : "feff30c630b930c8"


このため、mb_check_encoding() では、UTF-16(BOM 付き Little Endian)の
文字列は、false になります。この問題を修正するには、UTF-16 -> UTF-16
の変換で、エンディアンを保持する必要があります。

ただ、UTF-16 の文字列を mb_check_encoding() で判定したいという状況は
あまり考えられません。今後、ICU ベースに移行する予定もありますし、
無理に修正する必要はないように思います。

-- 
komura <komura.db2r1e @ gmail.com


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