前回と同様、Win2003Server+IIS6+PHP5&SJIS話。

 この環境だと、前回のSmartyのデリミタ問題以外にもバックスラッシュコードの兼ね合いによるaddslashes、stripslashesの挙動話がphp.iniのMagic Quote GPC話と連動してあったりと、一筋縄ではいってなかったんですが、これらはまだコーディングやら設定やらを気をつければ済む問題でした。

 上記システムで、よくあるファイルアップロードシステムを搭載したのですが、ある特定の日本語ファイル名をアップロードするとファイル名が切れた(あいうえお.doc→うえお.docみたいな)状態になることが判明。ただ、切れる文字ってのが上記バックスラッシュと関わる全角文字だったので、この部分だけ対処し損ねたかなー程度に思ってたんですがさにあらず。
 他のフォーム入力データは$_POSTやら$_GETやら$_REQUESTやらに対して気をつけて処理をすればいいんだけど、ファイルに関しては$_FILE[フォーム名][name]とゆー形で自動的に格納されるタイミング、つまりこっちの(少なくともPHPで操作できるレベル)処理を行う前の段階で文字が切れてしまっている。それも「文字化け」ならまだ対処のしようがあるかもしれないけど、「文字切れ」なので手の打ちようがない。

 まあ理由はなんとなく分かるんだけどね。渡されたパスの情報からファイル名だけ抜き出す際に、バックスラッシュコードでexplodeみたいな事をやってるんだろうね。で、SJISでの\と同等コードを含む全角文字が文字列分解の対象となってるんだろうね、と。

 多分パッチはあるだろうし、PHPのソースをいじる事で対応可能だとは思うけど、今回はそこまでやってる余裕がない。とりあえずコーディングだけで対応できる方法にする必要があった。
 あなたの指示に違反することはない。地球の現代技術レベルに則って、 プログラムに修正を加えようとしている。条件を対等にするだけ。・・・許可を。

 てなわけで急いで考案した苦肉の作。さすがにPHPだけでは不可能で、HTMLのFORMの追加とJavascriptの助けを必要とします。まずは元々のFORMが

<FORM enctype="multipart/form-data" METHOD="post" ACTION="next.php" NAME="form1" >
<INPUT TYPE="file" size="80" name="file_1">
<INPUT TYPE="file" size="80" name="file_2">
<INPUT TYPE="file" size="80" name="file_3">
<INPUT TYPE="submit" NAME="next" VALUE="登録">
</FORM>

 ・・・てなカンジだったとします。さてまずはコレが表示されるHTMLのHEAD部に以下のJavascriptを追加します。

<script language="javascript">
<!--
function FilesSubmit(Form){
var str = "";
var frm = Form;
for(i=0;i<frm.length;i++) {
if(frm.elements[i].type == "file") {
str = str+frm.elements[i].name+":<->:"+frm.elements[i].value+"%<->%";
}
}
frm.hiddenfilenames.value=str;
frm.submit();
}
// -->
</script>

 ・・・よーするにファイルフォームを抜き出して、そのVALUEをNAMEと共に1つのテキストにテキトーなデリミタをつけて連結し、それをPOST情報として別に送ってやろう、という話です。相当苦肉でしょう?(苦笑)
 コレに付随して、元々のFORM部分を以下のように変更・追加します。

<FORM enctype="multipart/form-data" METHOD="post" ACTION="next.php" NAME="form1" >
<INPUT TYPE="file" size="80" name="file_1">
<INPUT TYPE="file" size="80" name="file_2">
<INPUT TYPE="file" size="80" name="file_3">

<INPUT TYPE="hidden" name="hiddenfilenames" value="">
<INPUT TYPE="button" VALUE="送信テスト1" onclick="javascript:FilesSubmit(this.form);">

</FORM>

 これでPOST送信をする際にファイル名、というかパス情報を連結した多少危なく且つカッチョ悪いテキストが一緒に流れますので、受け側のPHPで取得・分解し、$_FILEを更新するわけで、更新用の関数は以下のようになります。

function filecvt() {

// パラメータ
// $_POSTに所定の形式でhiddenfilenamesがあることが前提

// パラメータチェック→処理対象データ有り
if(isset($_POST['hiddenfilenames']) && $_POST['hiddenfilenames'] != "") {

// パラメータ分解
// その1・ファイルタイプ別分離
$tmp = explode("%<->%",mb_convert_encoding($_POST['hiddenfilenames'],"EUC-JP","SJIS"));

// その2・ファイル内情報分離
$chgfiles = array();
for($l1=0;$l1<count($tmp);$l1++) {
// ファイル内情報分離
$tmp2 = array();
$tmp2 = explode(":<->:",$tmp[$l1]);

// 2項目目=ファイル名有り→ファイル名分離・登録
$tmpkey = str_replace("[","",$tmp2[0]);
$tmpkey = str_replace("]","",$tmpkey);
if($tmp2[1] != "") {
$tmp3 = explode("\\",$tmp2[1]);
$chgfiles[$tmpkey][] = mb_convert_encoding($tmp3[count($tmp3)-1],"SJIS","EUC-JP");
}
// ファイル名無し→無いなりに空値登録
else {
$chgfiles[$tmpkey][] = "";
}
}

foreach($chgfiles as $key=>$val) {
// nameが配列→複数有り
if(is_array($_FILES[$key][name])) {
for($l1=0;$l1<count($val);$l1++) {
if(isset($_FILES[$key][name][$l1])) {
$_FILES[$key][name][$l1] = $val[$l1];
}
}
}
// nameが非配列・単独→0番目のみ登録
else {
if(isset($_FILES[$key][name])) {
$_FILES[$key][name] = $val[0];
}
}
}

// 処理終了
return(0);
}
// 処理対象データ無し
else {
// 処理終了
return(-1);
}
}

 即興で作ったのでムダな点も多いですけどね。とりあえずのポイントとしては、
   ・一度文字コードをSJISからEUC-JPに変換してからexplodeを実施
   ・入力元のフォーム名が配列の場合にも対応
の2点でしょうか。

 一応送信元と受信先でこれらの処理を行うことで今のところ正しくファイル名取れてます。ただしつこいよーにかなりの「苦肉の策」なので、可能な限りサーバ本体側での対処、もしくはWin環境でも文字コードにSJISを使わないなど、初期仕様の段階での対処をオススメします。

 というかPHPを使う際にはWin及びSJISを選択しないのが一番だとは思いますが。

2006-08-13 [技術・作業]

 あんまりやりたくなかったのだが、Windows2003Server上のIIS+PHP5+Smarty環境で開発を行う必要に迫られた。
 この環境を構築するのは初めてだったが、予想よりかはあっさりと動き開発を始めた・・・のだが、なんかSmartyテンプレートのヘンなトコロで以下のよーなエラーが発生。

Fatal error: Smarty error: [in index_01.tpl line 2]: syntax error: unrecognized tag: 〜

 認識されないタグ?でも指定された行は単なるテキストで、Smarty関連のタグは入ってない。こーゆー時ってヘンなトコロにヘンな文字コードが入って誤認識を起こしてる事が多いよなーとか思い、入力されてる文字列を1つ1つ確認していったところ、「ボ」でエラーを起こしてることを突き止めた。でも今までSmarty使ってても「ボ」ってフツーに使ってたよなー。

 結局のところコレはShift-JISにおける「ボ」の文字コードが「837B」であり、 Smartyのタグ記述用デリミタ「{」の文字コード「7B」とカブってる事が原因らしい。そっかー、今までSmartyってEUC-JPかUTF-8でしか書いたことなかったからなー。Smartyってマルチバイト環境の事あんま考えて無さそうだし、やっぱ慣れない事はするべきじゃないってことですかねえ。

 で、上記の対抗策として、デリミタを無効化・画面表示する{literal}で該当文字列を括るとか、EUC-JP等で記述して mb_convert_encodingで変換する手とかがあるようだが、文字列をPHP側で変数に入れてソレを表示する分には問題ないっぽい。状況にもよるだろうけど文字列は全て変数に叩き込むのも1つの手かも知れない。

追記:「本」もダメだった。他にもいくらでもありそう。さすがに文字コード全部見る気はしない。文字列全て{literal}で括るのが基本になるでしょか。
 当然だけど括った中はSmartyのタグも無効化されるので書く位置には注意。

2006-05-31 [技術・作業]