PHPの日本語変数名と文字コード

2011 年 10 月 5 日
冒頭で宣言しますが、今回のネタはあくまでPHPの仕様に対する実験であり、実際に使っちゃうと脆弱性を盛り込んでしまうネタなので、充分ご注意ください。

PHPの変数・配列のキーには2バイト文字が使えます。
もちろん仕様上「使えることになっている」ということではなく、結果的に使えることになったというのが正しい言い方かもしれません。
PHPリファレンス:基本的な事

使う意味があるか、と問われれば、ありません。
では、あえて使う意味が発生するとしたらどのようなシチュエーションか、考えてみます。

たとえば以下のような場合。

好きな果物: <input type="checkbox" name="data[fruits][]" value="1"/>りんご <input type="checkbox" name="data[fruits][]" value="2"/>バナナ <input type="checkbox" name="data[fruits][]" value="3"/>パイナップル
というようなパラメータをPOSTした場合、受け取るPHP側では
$_POST['data']['fruits']
という配列に入ります。
このようなFormのお約束として、「入力内容の確認画面」があったり、保存したデータを(人間の目にとってわかりやすく)見る、という用途があります。
どこかに「'fruits'は'好きな果物'」「data[fruits][]==1であれば'リンゴ'」というような対照表を持っておく)のが正しいお作法なわけですが、配列のキーに2バイト文字が使えるということであれば、たとえばこのような書き方ができるわけです。
<input type="checkbox" name="data[好きな果物][]" value="リンゴ"/>リンゴ <input type="checkbox" name="data[好きな果物][]" value="バナナ"/>バナナ <input type="checkbox" name="data[好きな果物][]" value="パイナップル"/>パイナップル
(こう書いちゃえOKということではありませんので重ねて念のため)

この方法で、HTML側とPHP側の文字コードがUTF-8で一致していれば、POST後、foreachなどでkeyとvalueの両方を2バイト文字列としていきなり使えます。
しかし逆に、入力される文字コードを変換する必要がある場合には、いちいちキーを取り出して変換する処理を書く必要があります。「変数の中身」の文字コードを変換するにはmb_convert_variablesなどを使えば良いですが、「配列のキーを含めて」文字コードを変換する方法が無いためです。
(力技は思いつかないこともないですけれども…)

さてここまで実験してみて、実はおもしろいことが判明しました。
配列のキーにSJISを使っていて、PHPがUTF-8である場合、特定の文字でキー文字列がブチ切られるのです。
どの時点でブチ切られているかというと、これはもう$_POSTに入った時点でそうなっています。まず間違いなく文字化けでしょう。
SJISで文字化けというと、毎度おなじみの0x5c問題です。「ソ能十表予暴貼構…」というアレですね。今回もそんなことだろうと元々の文字を確認してみました。ところが、キー内の文字列は、「希望」「頭脳」「講評」…。全て0x5cではないのです。
しかし根本的な原因としては変わらないだろう、ということで、SJISの2バイト目に注目してみます。
希望 … 0x8a 0xf3 0x96 0x5d 頭脳 … 0x93 0xaa 0x94 0x5d 講評 … 0x8d 0x75 0x95 0x5d
ビンゴ。全て2バイト目が「0x5d」でした。0x5dは何かというと、「]」です。つまり、配列キー文字列の終端だと認識されてしまっているわけですね。これで、リファレンスに書いてあったちょっと妙な説明が理解できます。
PHPリファレンス:基本的な事
注意: ここで言うところの文字とはa-z、A-Z、127から255まで (0x7f-0xff)のバイトを意味します。
つまり「何入れてもかまわんけど文字コードで扱うからね」ということになるかと思います。意図的に使用された場合はもちろん、今回のように意図せずして使用した場合でも、0x5dに限らず言語構造で使用する文字に合致したら使えないというわけです。

あたりまえだろ、と言われればそれまでなのですが、リファレンスを見ても、「予約語一覧」はあっても「予約文字一覧」は見当たらないので…。(あったらごめんなさい)

ということで、コード中の静的な記述ならまだしも、外部からの入力値などの2バイト文字を変数名や配列キーに使うのはあまり現実的ではない、という結論になるかと思います。
(まんま脆弱性になりますし)


ちなみに、じゃあこの種の文字化けは回避不可能なのかというと、策はあります。
mbstring.http_input=SJIS-win mbstring.http_output=SJIS-win mbstring.internal_encoding=UTF-8 mbstring.encoding_translation On
で$_POSTの中身は化けなくなります。ということはつまり、PHPの処理順序は、「POSTされたデータを受け取る→設定に従って文字コードを変換→変数として解釈→変数にセット」ということになっているんだなあ、と改めて思うわけです。よく考えるとそういう順序でないと困るわけですが。

コメントをどうぞ