私のパソコン雑記帖

ファイル添付フォームメール・テクニカルノート(1)

ファイルアップロードの仕様

カテゴリー: PHP
10May2005⇒2Aug2014更新

ファイル添付メールは、メールに画像を添付したり、文書ファイルを添付する応募フォームのようなニーズがあります。また、ファイル名に日本語が使える、件名に日本語が使える(コンテンツの日本語は当然)というような日本語処理も求められます。ファイル添付メールフォーム汎用スクリプトを制作する際に学んだ技術要素を整理して残します。その1:ファイルアップロードの仕様。


フォームの記述

フォーム記述のお作法は、

<form enctype="multipart/form-data" action="xxxxx.php" method="post">
<input type="hidden" name="max_file_size" size="65536">
<input type="file" name="file1" size="42">

ということで、特にまぎらわしいことはありません。


受信の仕方

if(!isset($file1)){ $file1 = $_FILES['file1']['tmp_name']; }
if(!isset($file1_name)){ $file1_name = $_FILES['file1']['name']; }
if(!isset($file1_type)){ $file1_type = $_FILES['file1']['type']; }

のように受信して次の処理に入っていく例がありました。一方PHP と Web アプリケーションのセキュリティについてのメモを見ると、

"基本的なファイルアップロード処理としては $_FILES の配列を使用して is_uploaded_file() でアップロードされた一時ファイルが正しいかどうかを確認し、move_uploaded_file() で任意のディレクトリに移動させることでファイルアップロードを行います。"

と書かれています。また、PHPマニュアル is-uploaded-fileには、

"この関数は、悪意のあるユー ザーがスクリプトをだまして動作しなくなるようなファイル、例えば、 /etc/passwd を指定することを防止したい場合に有用です。"

と書かれています。更に、PHPマニュアル move-uploaded-fileには、

"この種の確認は、アップロードされたファイルに関して何でもできる場合には、その内容をユーザ、または同じシステム上の他のユーザにさえ暴かれる可能性があるため、特に重要です。"

とも書かれています。isset のセキュリティー機能を確かめた訳ではありませんが、やはり is_uploaded_file() と move-uploaded-file で受信するのがより安全なのだろうと判断しました。実際のスクリプトは、

$save_dir="./test/";
if (is_uploaded_file($_FILES['upfile']['tmp_name'])) {
if (move_uploaded_file($_FILES['upfile']['tmp_name'],
$save_dir.$_FILES['upfile']['name'])){
//unlink($save_dir.$_FILES['upfile']['name']);
$upfile_name=$_FILES['upfile']['name'];
$upfile_type=$_FILES['upfile']['type'];
$upfile_size=$_FILES['upfile']['size']";
ファイルタイプのチェック
ファイルサイズのチェック }}

$save_dir にアップロードファイルを保存しても特にそれを使う必要がなければ削除します。この後のファイル情報取得はあくまで tmp ファイルから行われます。

またフォーム入力で max_file_size を規定していても、ファイルサイズのチェックは必要です。その理由としてファイルアップロード処理に次のように述べられてます。
「ブラウザ側でこの最大値を出し抜くのは簡単なことなので、この機能を信頼して、これより大きなサイズのファイルがブロックされることを前提にしてはいけません。しかし、PHP 側の最大サイズの設定を欺くことはできません。」
PHP 側の最大サイズとは php.ini の upload_max_filesize で指定されたファイルサイズを指しているようです。


入力チェックとファイル添付

フォーム入力画面で入力した後、チェック画面を表示するのが普通です。またそこでエラーがあって入力画面に戻る場合、最初の入力内容を保持するのがユーザーには便利です。手っ取り早くは、

<a href=javascript:history.back()>入力画面に戻る</a>

でも目的は達せられます。input type="text"、input type="checkbox"、input type="radio"、select、textarea 等に記入された内容は再表示されます。

ところが input type="file" の入力は一旦チェック画面に移って、再び入力画面に戻った時消えてしまいます。

Netscape7.1 や Firefox 1.5.0.7 ではそのようなことはないのですが、IE6 では消えてしまう。INPUT TYPE=FILEタグでの入力値保持についてを見ると、困っている人は結構いるようです。要はブラウザの仕様ということ。JavaScript でも解決できないようです。


フォームの設計

IE 独自の仕様とはいえ、ドミナントなブラウザですから、考慮に入れざるをえません。

ある有料 CGI (ファイル添付が可能なフォームメール)で、最初の入力画面からファイル入力できるものがありました。これだと、他の入力項目にエラーがあって戻る度にファイル入力を繰り返さなければなりません。なまじ他の入力項目が保持されているだけに、改めてファイル入力するのを忘れてしまうことも考えられます。

他の入力項目のチェックが終ってから、ファイル入力出来るように設計されている CGI もありました(最初にその旨ことわりが入っている)。このほうが使い易いのではないかと思いました。

ここは色々な工夫ができるところです。自作のスクリプトでは最初からファイル入力ができるようにし、再度入力画面に戻る時、ファイル名を表示するようにしています。

  1. 入力されたファイルは、ファイル・チェックがパスしていれば、サーバー側のフォールダに保存します。(その際ファイル名の頭に10桁のユニーク数を加えます。ファイル名の重複を避けるためです。)
  2. 他の項目がチェックパスできず元の入力画面に戻る時、入力されたファイル名を表示します。再入力は不要です。
  3. 送信画面に引き渡すのは、フォールダ名とファイル名のみ。送信画面ではそこからファイルを読込みます。

それにしても IE は何故このような仕様にしているのでしょうか。セキュリティーに関連した理由があるのでしょうか。


「戻る」ボタン

上に述べた「入力画面に戻る」ボタン、あるいはブラウザの「戻る」ボタンは結構便利ですが、入力チェック画面とはいえ、どこからアクセスされるかわからない、という心配は残ります。

また、半角カナの入力、半角であるべき英数字の全角入力等は、デコードすれば済む事なのでエラーとして返す必要もないこと。従って、入力データは然るべきデコード処理をして返す、その上でどうしても修正が必要な項目だけをエラーとして示す。これだとキッチリ submit ボタンで返すしかなく、「戻る」ボタンに依存する設計にはなりません。



コメント