マルチパートフォームの挙動に注意する
PHPの大御所「Do You PHP?」の中の人が困っていた問題に、同僚がずっぽりハマってしまった。
「multipart/form-data使ってアップロード」で助けて~ – Do You PHP はてな
http://d.hatena.ne.jp/shimooka/20080526/1211792488
PHP5.2.6で「multipart/form-data使ってアップロード」の続き – Do You PHP はてな
http://d.hatena.ne.jp/shimooka/20080527/1211872306
ファイルをアップロードするための「enctype=“multipart/form-data”」なフォームからPOSTされた内容が、PHPの文字コード変換(mbstring.http_input -> mbstring.internal_encoding)を通らない、という不具合だ。
検証ページを書いてみる。
<html> <head> <meta http-equiv="Content-Type" content="text/html; charset=Shift_JIS"> <body> <table border="1" cellspacing="0"> <?php // マルチバイト系の設定を表示 $mb_param = array( 'output_handler', 'mbstring.language', 'mbstring.internal_encoding', 'mbstring.http_input', 'mbstring.http_output', 'mbstring.encoding_translation', 'mbstring.detect_order', 'mbstring.func_overload', 'default_charset', ) ; foreach ($mb_param as $param) { echo "<tr>\n" ; echo "<th align=\"left\">{$param}</th>" ; echo "<td>" . ini_get($param) . "</td>\n" ; echo "</tr>\n" ; } ?> <hr> <table border="1" cellspacing="0"> <tr> <td> <form action="" method="POST"> <input type="text" name="text" value="フォームから送信される文字列" size="40"> <input type="submit" name="post_form" value="POSTフォームから送信"> </form> </td> <td> <?php // [A] 通常のPOSTフォームから送信された文字列とエンコーディングを表示 if (isset($_POST['post_form'])) { echo "<td>{$_POST['text']}</td>" ; echo "<td>". mb_detect_encoding($_POST['text']) . "</td>" ; } ?> </td> </tr> <tr> <td> <form action="" method="POST" enctype="multipart/form-data"> <input type="text" name="text" value="フォームから送信される文字列" size="40"> <input type="submit" name="multi_form" value="multipartフォームから送信"> </form> </td> <td> <?php // [B] マルチパートフォームから送信された文字列とエンコーディングを表示 if (isset($_POST['multi_form'])) { echo "<td>{$_POST['text']}</td>" ; echo "<td>". mb_detect_encoding($_POST['text']) . "</td>" ; } ?> </td> </tr> </table> </body> </html>
設定はこんな感じ。
output_handler |
---|
mbstring.language |
mbstring.internal_encoding |
mbstring.http_input |
mbstring.http_output |
mbstring.encoding_translation |
mbstring.detect_order |
mbstring.func_overload |
default_charset |
「POSTフォームから送信」すると、[A]に以下が出力される。
フォームから送信される文字列 | EUC-JP
変換された。
「multipartフォームから送信」すると、[B]に以下が出力される。
?t?H?[???????M????????/td> | SJIS
変換されてない!
ということで、mbstring.encoding_translation=onにしたい場合(すべてのエンコーディングを統一できないなど)は、
- PHP4.4.8ではmbstring拡張を組み込みでbuildする
- PHP5.2.xではパッチを当ててmbstring拡張を組み込みでbuildする
とする必要がありそうです。
PHP5.2.6で「multipart/form-data使ってアップロード」の続き – Do You PHP はてな
http://d.hatena.ne.jp/shimooka/20080527/1211872306
というまとめがあるにあるのだが、本番環境で動いているサーバのPHPをリビルドするのはなかなか難しい場合もあるだろう。同僚もそんな状況だったので、手作りのフィルタで自前変換することをアドバイスした。
●auto_prepend_fileを指定する。
.htaccess
php_value auto_prepend_file "encoding_filter.php"
可能ならhttpd.confで書くほうがいろいろと望ましい。
●フィルタの作成
encoding_filter.php
<?php $apache_headers = apache_request_headers() ; if (strpos($apache_headers['Content-Type'] , 'multipart/form-data') !== false) { $srcEnc = ini_get("mbstring.http_input") ; $dstEnc = ini_get("mbstring.internal_encoding") ; foreach ($_POST as &$param) { $param = mb_convert_encoding($param, $dstEnc, $srcEnc) ; } } unset($apache_headers) ; ?>
ハマった事例ではとりあえず困ってない(ファイルネームは動的に生成している)のでファイルネームの処理はしていないが、$_FILES[‘userfile‘][‘name’](アップロードもとのファイル名)を使用する場合は同様にフィルタでエンコーディングをコンバートしないと、ファイル名にマルチバイト文字を使われた場合困ったことになる。
PHP: apache_request_headers – Manual
http://www.php.net/manual/ja/function.apache-request-headers.php
PHP: ファイルアップロードの処理 – Manual
http://www.php.net/manual/ja/features.file-upload.php
ずいぶん前からあるバグっぽいのだが、いまだにこんなのが残ってるんだなぁ、と妙に感心してしまった。http_inputを固定してencoding_transrationを使うのは携帯っぽいが、携帯はmultipart/form-dataを使わない場合が大半だから、というのが気づかれにくい理由だろうか。