2011年11月7日月曜日

PHP5.4のhtmlspecialcharsに非互換問題

PHP5.4.0から、htmlspecialchars関数のデフォルト文字エンコーディングがISO-8859-1(Latin-1)からUTF-8に変更されます。これに伴い、従来動いていたアプリケーションが動かなくなるケースが出てきます。典型的には、以下の両方の条件に該当するアプリケーションは、マルチバイト文字が表示されなくなります。

  • 内部文字エンコーディングとしてEUC-JPまたはShift_JISを用いている
  • htmlspecialcharsの第3引数を指定していない

htmlspecialchars関数の第3引数の変更内容
htmlspecialchars関数の第3引数には、処理対象文字列の文字エンコーディングを指定します。この指定をしない場合、従来(PHP5.3まで)はISO-8859-1とみなされていたのに対して、PHP5.4ではUTF-8とみなされるようになります。
また、第3引数として空文字列('')を指定した場合mbstring.internal_encodingで指定した文字エンコーディングが指定されることになっています。この仕様はPHP5.4でも変わりませんが、細かい挙動が変わります。これらを表としてまとめました。


PHP5.3まで
第3引数意味
何も指定しないISO-8859-1
空文字列''を指定mbstring.internal_encodingに従う
文字エンコーディングを明示明示された文字エンコーディング

PHP5.4以降
第3引数意味
何も指定しないUTF-8
空文字列''を指定mbstring.internal_encodingに従う (注意点を後述)
文字エンコーディングを明示明示された文字エンコーディング


第3引数を指定していない場合の影響

前述のように、htmlspecialchars関数の第3引数を指定していない場合、PHP5.3までは、文字エンコーディングがISO-8859-1が指定されたとみなされます。この場合、入力内容にかかわらず不正な文字エンコーディングと判定されることはありません。したがって、文字エンコーディングのチェックが働かない代わりに、エラーになることもありませんでした。
これに対して、PHP5.4の仕様により文字エンコーディングがUTF-8とみなされた場合に、Shift_JISやEUC-JPの2バイト文字が入力されると、高い確率で「UTF-8として不正」というエラーになり、htmlspecialchars関数の出力は空になります。つまり、プログラムが正常に動作しません。

  • htmlspecialchars関数の第3引数を指定しておらず、内部文字エンコーディングがShift_JISあるいはEUC-JPの場合、プログラムが正常に動かなくなる

これの簡単な対処法がないが探しましたが、簡単な方法はないようです。以下の3つを考えました。

  • プログラムを修正してhtmlspecialchars関数の第3引数を明示する
  • 内部文字エンコーディングをUTF-8に変更する
  • PHPのソースにパッチをあてて、アプリケーションの文字エンコーディングがデフォルトになるようにする

大規模なアプリケーションの場合、上の2つは一筋縄ではいかない感じですね(htmlspecialcharsの呼び出し箇所が多いため)。PHPにパッチをあてることを推奨するものではありませんが、現実的な選択肢として検討したくなるプロジェクトもありそうです。それ以前に、PHP5.4に移行しないという選択になりそうですが、PHP5.3のメンテナンスがいつまで続くかは不透明なので、PHP5.4への以降は計画しておかなければならないと思います。

まとめてhtmlspecialchars関数を修正する方法

htmlspecialchars関数を一括して修正する方法として、以下の方法があります。アプリケーション中のhtmlspecialcharsを全てhxなど別名に変換します。hxという名前がアプリケーション中で使われていれば、別の名前にします。
次に、関数hxを以下のように定義します。アプリケーションの文字エンコーディングはEUC-JPと仮定します。Shift_JIS等の場合は適宜変更して下さい。

function hx($str, $flags = ENT_COMPAT, $charset = 'EUC-JP') {
  return htmlspecialchars($str, $flags, $charset);
}

この方法により、比較的楽に、安全に文字エンコーディングを指定できることになります。

PHPそのものにパッチをあてる

あまりお勧めしませんが、PHPにパッチをあてて、htmlspecialcharsの第3引数を省略した際の挙動を、mbstring.internal_encodingに従うよう変更するというアイデアもあります。
パッチの案を以下に示します。

--- html.c.org  2011-11-06 16:29:18.438601385 +0900
+++ html.c      2011-11-06 16:29:52.608727050 +0900
@@ -368,11 +368,7 @@
        int len = 0;
        const zend_encoding *zenc;

-       /* Default is now UTF-8 */
-       if (charset_hint == NULL)
-               return cs_utf_8;
-
-       if ((len = strlen(charset_hint)) != 0) {
+       if (charset_hint != NULL && (len = strlen(charset_hint)) != 0) {
                goto det_charset;
        }

ご覧のように、元のソースは文字エンコーディングが指定されていない場合、UTF-8を返すよう力強くハードコーディングされていますが、パッチではこの処理を削除し、文字エンコーディングが空文字列の場合と同じ処理になるようにしています。あまりテストはしていないので、もし採用される場合は、十分なテストをしてから使って下さい。


PHPの教科書はどうか

従来、PHPの教科書には、「htmlspecialcharsの第3引数は指定しなくてもよい」という説明が多かったようです。試みに、手元のPHPの教科書を調べたところ、以下の5つは、htmlspecialcharsの第3引数を指定せずに説明しています。

これらのうち、逆引きレシピについては、htmlspecialchars関数の第3引数を指定するように修正記事が出ています。立派な態度ですね。
一方、以下の書籍は、htmlspecialchars関数の第3引数を指定するように説明しています。

やはり、最近の書籍はhtmlspecialchars関数の第3引数を指定するように説明する傾向が伺えます。古い書籍でPHPを勉強された方や、古くからhtmlspecialchars関数の第3引数を指定しない方法で通している方は注意が必要です。

文字エンコーディングとして空文字列が指定されている場合の挙動の変化

次に、htmlspecialchars関数の第3引数に空文字列を指定している場合の挙動の変化について報告します。htmlspecialcharsはmbstring関数に比べて、指定できる文字エンコーディングが限られます。たとえば、Shift_JIS系の文字エンコーディングはShift_JISという指定のみが許され、SJIS-winやcp932は受け付けられません(参考:htmlspecialcharsのリファレンス)。しかし、PHP5.3までのhtmlspecialchars関数は、第3引数に空文字列が指定されていた場合、mbstring.internal_encodingにSJIS-winと指定していても、htmlspecialchars関数側ではShift_JISという指定に読み替えられていました。
一方、PHP5.4では、この読み替えが行われず、第3引数に空文字列を指定、かつmbstring.internal_encodingにSJIS-winと指定した場合、以下のエラーとなります。
PHP Warning:  htmlspecialchars(): charset `SJIS-win' not supported, assuming utf-8 in /home/wasbook/test/test2.php on line 12
htmlspecialcharsの第3引数に空文字列をしている例は見たことがないので、現実には問題にならないかもしれませんが、これはPHP5.4.0beta2のバグではないでしょうか。PHP5.3に比べて厳格にする理由が見あたらないからです。PHP5.4.0beta2のバグとして報告してもよいかもしれません。

まとめ

PHP5.4のhtmlspecialchars関数の挙動の変化について報告しました。既存のアプリケーションが動かなくなる可能性があり、PHP5.4移行にあたり注意が必要です。


追記
廣川さんの日記にて、htmlspecialcharsの第3引数に空文字列を指定した場合の挙動について報告がありました。PSとして、『SJIS-win、CP932、eucJP-winについてPHP 5.3と動作が同じとなるようにパッチをコミットしておきました。』ということですので、PHP5.4.0の正式版までには修正されると思われます。やはり、これはバグという認識で良かったようですね。廣川さんありがとうございました。


[PR]
安全なWebアプリケーションの作り方」電子書籍版販売しています。電子版はこちら

1 件のコメント:

  1. 最後の件については、既に修正されているようです。


    http://d.hatena.ne.jp/rui_hi/20111105/1320455951
    http://news.php.net/php.cvs/66894
    http://news.php.net/php.cvs/66895

    返信削除

フォロワー