最近の mbstring 動向について(PHP 5.4〜)

PHP 5.4 に向けて、久しぶりに PHP の mbstring に対して機能追加と修正がありましたので、メモしておきます。
PHP 5.4.0 の正式リリースまでに「十分なテストが必要」とのことですので、気になる方はテストに参加した方が良いと思います。
変更点は以下の通りです。

  1. 携帯絵文字のサポート
  2. 正しい UTF-8 チェックの強化

詳しくは以下のページを参照してください。

携帯絵文字のサポート

開発中の PHP-5.4.0alpha3 で新たに追加された文字エンコーディングは以下の通りです。

各絵文字の相互変換、Unicode への変換や妥当性チェックが可能になりました。

また、絵文字ではありませんが、以下の文字エンコーディングも追加されています

上記の文字エンコーディングについては後日少し詳しく調査してみたいと思います。

正しい UTF-8 チェックの強化

以下の通り、PHP 5.4 以降では文字列の妥当性チェックが強化されるそうです。


libmbfl 1.3.1 からは、UTF-8の変換や検出時に行われる文字コード範囲検出において整形式であることを確認するチェックを導入します。上記の不正なバイト列は無効な文字として判定され、指定した処理が行われます。libmbfl 1.3.1 は PHP 5.5dev, PHP 5.4beta1 に適用予定です。
libmbflPHP の mbstring 内部で使用しているマルチバイト文字列処理エンジンです。

実際に試してみたところ、結果が以下のように変わりました(PHP 5.3.6 と PHP 5.4.0alpha3 を使用)。
mb_substitute_character() に不正文字列の出力方法を指定しています。

<?php
// 不正文字列を含む文字列
$s = "\x41\xc0\xaf\x41\xf4\x80\x80\x41";

// 不正文字列を"?"(63)に変換(デフォルト)
mb_substitute_character( 63 );
$u = mb_convert_encoding( $s, 'UTF-16BE', 'UTF-8' );
echo format( $u ) ."\n";

// 不正文字列を"U+FFFD"(0xFFFD)に変換
mb_substitute_character( 0xFFFD );
$u = mb_convert_encoding( $s, 'UTF-16BE', 'UTF-8' );
echo format( $u ) ."\n";

// 不正文字列を"BAD+??"(long)に変換
mb_substitute_character( 'long' );
$u = mb_convert_encoding( $s, 'UTF-16BE', 'UTF-8' );
echo format( $u ) ."\n";

// 整形用の関数
function format( $s )
{
    return mb_convert_encoding( $s, 'UTF-8', 'UTF-16BE' ) . '|' .
        preg_replace_callback(
            '/(?:\0B\0A\0D\0\+([\x00-\xff]{4})|[\x00-\xff]{2})/',
            function( $m ) {
                return ( strlen( $m[0] ) > 2 )
                    ? ( 'BAD+' . preg_replace( '/\x00([\x00-\xff])/', '$1', $m[1] ) . ' ' )
                    : ( 'U+'   . strtoupper( bin2hex( $m[0] ) ) . ' '   );
            },
            $s );
}
  • PHP 5.3.6 の結果

以下のように、不正文字列は削除されています。

AAA|U+0041 U+0041 U+0041 
AAA|U+0041 U+0041 U+0041 
AAA|U+0041 U+0041 U+0041 
  • PHP 5.4.0alpha3 の結果

以下のように、不正文字列が表示されるようになりました(見やすさのため、整形しました)。

A??AA          |U+0041 U+003F U+003F U+0041 U+0041 
A・・ AA       |U+0041 U+FFFD U+FFFD U+0041 U+0041 
ABAD+C0BAD+AFAA|U+0041 BAD+C0 BAD+AF U+0041 U+0041 

また、UTF-8 の5バイト、6バイト表現は排除されるようになりました。例えば、以下は5バイト表現や6バイト表現の UTF-8 をチェックするスクリプトです。

<?php
// U+3FFFFFF  (UTF-8 では 5バイトで表現)
var_dump( mb_check_encoding( "\xf8\xbf\xbf\xbf\xbf",     "UTF-8" ) );
// U+7FFFFFFF (UTF-8 では 6バイトで表現)
var_dump( mb_check_encoding( "\xfc\xbf\xbf\xbf\xbf\xbf", "UTF-8" ) );

以下のように、UTF-8 の 5バイト、6バイトの表現は PHP 5.4.0alpha3 では FALSE を返します。

  • PHP 5.3.6 の結果
bool(true)
bool(true)
  • PHP 5.4.0alpha3 の結果
bool(false)
bool(false)