mb_detect_encoding() は文字コード判定として使用できるか(その1)

最近、mb_detect_encoding() について調べていたので、そのメモです。mb_detect_encoding() は文字コード検出を行う関数です(mb_detect_encoding() - PHP マニュアル)。
結論としては、以下の問題があるため、mb_detect_encoding() を文字コード判定には向いていないように思います。

  1. 判定が甘い
    1. 例えば、("SJIS" === mb_detect_encoding( "\x81", "SJIS" )) は SJIS の上位バイトのみの判定になり、FALSE を返してほしいところですが、TRUE を返します。
    2. 第3引数に strict (厳格なエンコーディング検出)を指定しても同様です。
  2. UTF-8 の判定に問題がある
    1. 一部の文字列(\xc0\x00, \xd0\x00, ..., \xfd\x00)の判定では、TRUE を返します。
    2. UTF-8 の冗長表現や、サロゲートペアの領域でも TRUE を返します。
  3. UTF-16 などの判定には使えない
    1. PHP マニュアルに書いている通りです。

UTF-16, UTF-32, UCS2, UCS4 の場合、 エンコーディング検出は常に失敗します。

http://php.net/mb_detect_order

これらの問題をもう少し改善できるように、PHP 5.2.9 に対する Patch を作ってみました(上記の1.と 2-1)。

  • mb_detect_encoding() の第3引数に TRUE を渡すと文字コード判定をもう少し厳格にする Patch です。
diff -ru php-5.2.9.orig/ext/mbstring/libmbfl/mbfl/mbfilter.c php-5.2.9/ext/mbstring/libmbfl/mbfl/mbfilter.c
--- php-5.2.9.orig/ext/mbstring/libmbfl/mbfl/mbfilter.c	2009-02-15 16:11:23.000000000 +0900
+++ php-5.2.9/ext/mbstring/libmbfl/mbfl/mbfilter.c	2009-06-14 17:03:10.601591122 +0900
@@ -622,7 +622,7 @@
 	if (!encoding) {
 		for (i = 0; i < num; i++) {
 			filter = &flist[i];
-			if (!filter->flag) {
+			if (!filter->flag && (!strict || !filter->status)) {
 				encoding = filter->encoding;
 				break;
 			}
  • UTF-8 の判定で不正だと思われる文字列(\xc0\x00, \xd0\x00, ..., \xfd\x00)を通さないようにする Patch です。
diff -ru php-5.2.9.orig/ext/mbstring/libmbfl/filters/mbfilter_utf8.c php-5.2.9/ext/mbstring/libmbfl/filters/mbfilter_utf8.c
--- php-5.2.9.orig/ext/mbstring/libmbfl/filters/mbfilter_utf8.c	2009-02-25 00:09:42.000000000 +0900
+++ php-5.2.9/ext/mbstring/libmbfl/filters/mbfilter_utf8.c	2009-06-14 20:24:31.088574372 +0900
@@ -215,7 +215,7 @@
 	if (c < 0x80) {
 		if (c < 0) { 
 			filter->flag = 1;	/* bad */
-		} else if (c != 0 && filter->status) {
+		} else if (filter->status) {
 			filter->flag = 1;	/* bad */
 		}
 		filter->status = 0;

PHP 5.2.9 に上記の Patch を適用すると、mb_detect_encoding() は以下のような正規表現とほぼ同じになります(ISO-2022-JPISO-2022-JP-MS は怪しいですが)。

ASCII\A[\x00\x09\x0a\x0d\x20-\x7f]*\z
SJIS\A([\x00-\x7f]|[\xa1-\xdf]|[\x81-\x9f\xe0-\xef][\x40-\x7e\x80-\xfc])*\z
SJIS-win\A([\x00-\x7f]|[\xa1-\xdf]|[\x81-\x9f\xe0-\xfc][\x40-\x7e\x80-\xfc])*\z
EUC-JP\A([\x00-\x7f]|[\xa1-\xfe][\xa1-\xfe]|\x8e[\xa1-\xdf]|\x8f[\xa1-\xfe][\xa1-\xfe])*\z
eucJP-win\A([\x00-\x7f]|[\xa1-\xfe][\xa1-\xfe]|\x8e[\xa1-\xdf]|\x8f[\xa1-\xfe][\xa1-\xfe])*\z
CP51932\A([\x00-\x7f]|[\xa1-\xfe][\xa1-\xfe]|\x8e[\xa1-\xdf])*\z
ISO-2022-JP\A([\x00-\x1a\x1c-\x7f]|\x1b\x24[\x40\x42](?:[\x21-\x7e][\x21-\x7e])+|\x1b\x24\x28[\x40\x42\x44](?:[\x21-\x7e][\x21-\x7e])+|\x1b\x28\x42)*\z
ISO-2022-JP-MS\A([\x00-\x1a\x1c-\x7f]|\x1b\x24[\x40\x42](?:[\x21-\x7e][\x21-\x7e])+|\x1b\x24\x28[\x40\x42\x44](?:[\x21-\x7e][\x21-\x7e])+|\x1b\x28\x42|\x1b\x28\x4a[\x00-\x1a\x1c-\x7f]+|\x1b\x28\x49[\x00-\x1a\x1c-\x7f]+\x1b\x28\x42)*\z
UTF-8\A([\x00-\x7f]|[\xc0-\xdf][\x80-\xbf]|[\xe0-\xef][\x80-\xbf]{2}|[\xf0-\xf7][\x80-\xbf]{3}|[\xf8-\xfb][\x80-\xbf]{4}|[\xfc-\xfd][\x80-\xbf]{5})*\z
UTF-16判定不可(常に FALSE)
UTF-16BE判定不可(常に FALSE)
UTF-16LE判定不可(常に FALSE)
2009.06.21 修正: 上記の UTF-8正規表現について、BOM は一番前ではなくても良いみたいなので、修正しました。

以下は続きです。