カテゴリー
Development

#PHP でもutf8_decodeは使ってはならない

(Last Updated On: 2009/09/22)

Twitterで書いた方が良いようなエントリですが、たまには良いでしょう。

#perl – utf8::decode()ではなくEncode::decode_utf8()を使うべき理由

という記事がありました。PHPにも似た関数、utf8_decodeがあります。PHPでも使ってはなりません。日本人というよりマルチバイト圏で使っている人はほとんどいないはずです。理由はコードを見れば一目瞭然です。

/* All the encoding functions are set to NULL right now, since all
* the encoding is currently done internally by expat/xmltok.
*/
xml_encoding xml_encodings[] = {
{ "ISO-8859-1", xml_decode_iso_8859_1, xml_encode_iso_8859_1 },
{ "US-ASCII",   xml_decode_us_ascii,   xml_encode_us_ascii   },
{ "UTF-8",      NULL,                  NULL                  },
{ NULL,         NULL,                  NULL                  }
};

ext/xml/xml.c – PHP 5.2.11より

ここで定義されているISO-8859-1, US-ASCII, UTF-8がPHPのXMLモジュールのutf8_decode/utf8_encodeでサポートしている文字エンコーディングです。それ以外はデコードもエンコードもされず、そのままの文字列が返ります。実装も文字エンコーディングチェックに使える様な実装ではありません。

Perlの方はどうなっているかソースを見てみました。以下のC関数がデコードする関数です。

bool
Perl_sv_utf8_decode(pTHX_ register SV *sv)
{
if (SvPOKp(sv)) {
const U8 *c;
const U8 *e;

/* The octets may have got themselves encoded - get them back as                                                           
* bytes                                                                                                                   
*/
if (!sv_utf8_downgrade(sv, TRUE))
return FALSE;

/* it is actually just a matter of turning the utf8 flag on, but                                                           
* we want to make sure everything inside is valid utf8 first.                                                             
*/
c = (const U8 *) SvPVX_const(sv);
if (!is_utf8_string((U8 *)c, SvCUR(sv)+1))
return FALSE;
e = (const U8 *) SvEND(sv);
while (c < e) {
const U8 ch = *c++;
if (!UTF8_IS_INVARIANT(ch)) {
SvUTF8_on(sv);
break;
}
}
}
return TRUE;
}

universal.c – perl 5.8.9より

これだけ見ても分からないと思いますが、マクロの定義を見ていくと文字エンコーディングのチェックに使えない事は直ぐに分かります。

シングルバイト圏の人が作った関数とはこんな感じで、どの言語でも同じ様なものなのかも知れません。と思ってRubyのコードを覗いてみました。1.8, 1.9共にlib/rexmlにutf8_decodeが定義されています。

module REXML
module Encoding
def encode_utf8 content
content
end

def decode_utf8(str)
str
end  

register(UTF_8) do |obj|
class << obj
alias decode decode_utf8
alias encode encode_utf8
end
end
end
end

ruby-1.9.1-p243/lib/rexml/encodings/UTF-8.rb より(1.8.7でも同じ)

詳しく調べませんでしたが、Rubyでは文字列をそのまま返しています。別の仕組みで文字エンコーディングチェックはできるとしても、decode_utf8では無理なようです。

全ての文字列系の関数で文字エンコーディングチェックが行われる(行える)と考えるない方が良いことが分かります。