ファイルのアップロード その1
はじめに
ローカル上のファイルをサーバー側にアップロードするCGIを作成する方法を紹介します。Perlの標準モジュールであるCGI.pmを使用すれば、簡単にこの機能が実現可能です。
このスクリプトの作動環境は次の通りです。Windows2000 SP4、Apache 1.3.31、ActivePerl 5.8.4 build 810。そしてUNIX環境でも確認しています。FreeBSD、Apache1.3.31、Perl5.6.1。CGI.pmはバージョン2.47以上を想定しています。
尚、CGI.pmは標準モジュールなので新しく用意する必要はありません。すぐに利用出来ます。以下に簡単な使用例を取り上げてみます。
HTMLのフォーム部分の作成
さて最初はHTMLのフォームを作成します。これを仮にupload.htmlとします。
<?xml version="1.0" encoding="Shift_JIS"?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="ja"> <head> <title>アップロード用フォーム</title> </head> <body> <h1>アップロード用フォーム</h1> <form action="upload.cgi" method="post" enctype="multipart/form-data"> <p><input type="file" name="filename" /></p> <p> <input type="submit" value="送信" /> <input type="reset" value="リセット" /> </p> </form> </body> </html>
ファイルのアップロードを行うには<form>タグ部分にenctype="multipart/form-data"を記述する必要があります。
<input type="file" name="filename">がアップロードするファイルを選択する部分で、ここではname値をfilenameとしています。
CGI側の作成
フォームから送信されるファイルを受け取るCGIを作成します。この例ではファイル名upload.cgiとして保存してください。
#!/usr/local/bin/perl -w # モジュール読み込み use strict; use CGI; my ($buffer); # オブジェクト作成 my $query = new CGI; # ファイル取得 my $fH = $query->upload('filename'); # MIMEタイプ取得 my $mimetype = $query->uploadInfo($fH)->{'Content-Type'}; # ファイル保存 open (OUT, ">Savefile") || die "Can't open Savefile!"; binmode (OUT); while(read($fH, $buffer, 1024)){ print OUT $buffer; } close (OUT); close ($fH) if ($CGI::OS ne 'UNIX'); # Windowsプラットフォーム用 chmod (0666, "Savefile"); # HTML出力 print $query->header(-charset=>'Shift_JIS'), $query->start_html(-lang=>'ja', -encoding=>'Shift_JIS', -title=>'upload.cgi'); print <<"HTML_VIEW"; <h1>ファイルアップロード</h1> <ul> <li>ファイル名:$fH</li> <li>MIMEタイプ:$mimetype</li> </ul> <p>ファイルのアップロードが完了しました。</p> HTML_VIEW print $query->end_html; exit;
ピックアップして説明します。
- use CGI;
- CGIモジュールを呼び出します。これでCGI.pmが利用できるようになります。
- $query = new CGI;
- new関数を使ってオブジェクトを作成する。これで$queryオブジェクトに送信されてきたデータが入ります。
- $fH = $query->upload('filename');
- フォーム側のアップロードフィールドで指定したname値であるfilenameを引数に与えます。 このupload()はCGI.pmバージョン2.47から新しく用意されたもので、正しくファイルが送信されてきたらそのファイルハンドルを返します。 反対に正しくなければundefが返ってきます。
- 従来のファイルの受け取りにはparam()が使用されましたが、このupload()を使った方が安全です。 param()では文字列をファイルハンドルとして使用しているし、ユーザーが入力欄にファイルパスでない文字列を入力するかもしれないからです。 (詳しくはCGI.podを参照)
- $mimetype = $query->uploadInfo($fH)->{'Content-Type'};
- uploadInfo関数によりファイルのMIMEタイプを取得します。これによりファイルの種類が判別できます。(例:image/jpeg、text/htmlなど)
- open (OUT, ">Savefile"); 〜中略〜 close(OUT);
- 送信されてきたファイルを、ファイル名Savefileとしてサーバー側に保存します。
- binmode (OUT);
- Windows環境のような改行コードが2byteのシステム下では、Perlの入出力時に1byteの改行コードを見つけては2byteに変換するようになっています。このbinmode()を宣言することにより改行コードを一切変更しないようにすることができます。これにより勝手にバイナリデータが書き変わるのを防ぐことが出来ます。Unix系の様に改行コードが1byteのシステム下では何も効果はありません。
- close ($fH);
- UNIX環境では問題ありませんが、WindowsサーバーでCGI.pmを使ったファイルのアップロードを行うスクリプトを実行するとテンポラリファイル(一時ファイル)が残ってしまいます。そこで明示的にファイルハンドルを閉じてやる必要があります。これによりゴミファイルが残らなくなります。
以上のHTMLとCGIによりローカル上のファイルをサーバーにアップロードすることが可能になります。アップロードを実行すればupload.cgiと同じ階層にSavefileという名前でファイルが保存されているはずです。
しかし簡単な例なので不完全な部分があります。保存されるファイル名はSavefileだけなので、正しい拡張子や任意のファイル名を付けてやる必要がありますし、無差別に多種多様なファイルがアップロードされないよう、アップを許可するファイルの種類(画像やテキストなど)を限定する必要があります。ここでMIMEのチェックが役立つでしょう。そして巨大なファイルを送信されないよう一定以上のファイルサイズで拒否する機能も必要です。またファイル名に日本語が含まれる場合、文字コードにも気を付けなければなりません。これはクライアントのOSに依存します。
upload.cgiの改良
不完全なupload.cgiを発展させて使い勝手を向上させてみます。上記のCGIから以下の点を改良してみました。
- 保存する際に重複しないファイル名を自動的に与えてやる。
- 適切な拡張子を付加する。
- MIMEを判断しアップロードされるファイルの種類を制限する。
- ファイルサイズを見て許容量かどうか判断する。
- 保存先のディレクトリを選択可能にする。
- エラー処理を入れ注意を促すようにする。
保存先のディレクトリはあらかじめ作っておいて下さい。初期設定の部分を見れば直感的に分かると思いますがMIMEの部分などお好みの値を記入して下さい。
大きいファイルを受け取る場合、$CGI::POST_MAX値を設定しておいた方がいいでしょう。
# POSTサイズの上限
$CGI::POST_MAX = 1024 * 1024; # 1MB
これでPOSTサイズの上限をバイト数で設定できます。もしこれより大きいPOSTを受け取った場合、直ちに転送は中断されparam()、upload()に空のパラメーターとcgi_error()に「413 Request entity too large」というエラーメッセージが返されます。またこれはサービス・アタックの回避にも使えます。useステートメントの直ぐ後に記述してください。
#!/usr/local/bin/perl -w # モジュール読み込み use strict; use CGI; # POSTサイズの上限 $CGI::POST_MAX = 1024 * 1024; # 1MB my $query = new CGI; # 初期設定 ------------------------------------- # 最大許容サイズ(KByte) my $maxsize = 300; # 保存先ディレクトリ my $logfiles = "./file"; # アップロードを許可するファイルの種類(MIMEと拡張子) my %hash_mime = ( 'text/html' => 'html', # HTMLファイル 'image/jpeg' => 'jpg', # JPEGファイル 'image/pjpeg' => 'jpg' # プログレッシブJPEGファイル ); # 送られてきたデータを処理する ----------------- # ファイル取得 my $fH = $query->upload('filename'); # エラーチェック if ($query->cgi_error) { my $err = $query->cgi_error; &error("$err") if ($err); } &error("File transfer error.") unless (defined($fH)); # MIMEタイプ取得 my $mimetype = $query->uploadInfo($fH)->{'Content-Type'}; # 保存するファイル名を取得 my $set = &set_name($mimetype); # ファイルサイズ取得 my $size = (stat($fH))[7]; # サイズ制限 &error("The filesize is too large. Max $maxsize KB") if ($size > $maxsize * 1024); # ファイル保存 --------------------------------- my ($buffer); open (OUT, ">$logfiles/$set") || &error("Can't open $set"); binmode (OUT); while(read($fH, $buffer, 1024)){ print OUT $buffer; } close (OUT); close ($fH) if ($CGI::OS ne 'UNIX'); # Windowsプラットフォーム用 chmod (0666, "$logfiles/$set"); # HTML出力 ------------------------------------- print $query->header(-charset=>'Shift_JIS'), $query->start_html(-lang=>'ja', -encoding=>'Shift_JIS', -title=>'upload.cgi'); print <<"HTML_VIEW"; <h1>ファイルアップロード</h1> <p>ファイルのアップロードが完了しました。</p> <ul> <li>ファイル名: <a href="$logfiles/$set">$set</a></li> <li>ファイルサイズ: $size byte</li> <li>MIMEタイプ: $mimetype</li> </ul> HTML_VIEW print $query->end_html; exit; # ファイル名を設定 ----------------------------- sub set_name { my ($mime) = @_; # 拡張子をセット my $ext = $hash_mime{$mime} ? $hash_mime{$mime} : &error("Can't permit this file."); # ファイル名のフォーマット my $set = time . "_" . $$ . "." . $ext; return $set; } # エラー出力 ----------------------------------- sub error { my ($mes) = @_; print $query->header(-charset=>'Shift_JIS'), $query->start_html(-lang=>'ja', -encoding=>'Shift_JIS', -title=>'upload.cgi'); print <<"HTML_VIEW"; <h1>ERROR</h1> <p>$mes</p> HTML_VIEW print $query->end_html; exit; } __END__
このようにCGI.pmを使用しロジックを上手く組み立てれば、十分実用可能なファイルアップロードCGIが作成可能です。
Last modified : 2004-08-31
Copyright © 1998-2006 Somali. All rights reserved.