Walrus,Digit. | 一覧 | 検索 | 更新履歴(RSS) | 新規作成
はてなブックマークに追加 はてなブックマークを表示 編集 | 編集(管理者用) | 差分

Perlモジュール/CGI::Lite

編集

フォームデコードなどの機能を持つ、CGI作成者のためのPerlモジュール。

CGIモジュールが肥大化し、動作が重くなっていったのを受け、フォームデコードなど一部の機能を切り出した、軽量なモジュールとしてPerlモジュール/CGI_Liteが作成されました。 これの名称を変更したものが、CGI::Liteモジュールです。

CGI::Liteの入手

編集

Perl4を使う場合

CGI_Liteはモジュールです。 このため、Perl4では使用できません。

かわりに、Perl4で使用では「cgi-lib.pl」ライブラリを使うことになります。 このライブラリは、「The cgi-lib.pl Homepage」から入手できます。

http://cgi-lib.berkeley.edu/

CGI_Liteによるファイルアップロードの受付け

CGI_Liteは「アスキー」モードでファイルを作成するので、画像などバイナリファイルを受け取ると壊れることがあります。

binmodeコマンドで入出力をバイナリモードで行うように指示してやると、これを避けられるようです。 実際の改修は、下記の数行で済みます。

 # "_parse_multipart_data" サブルーチン 985~987行
     $directory   = $self->{multipart_dir} || $self->{default_dir};
 
     binmode STDIN; # この行を挿入する
     while (1) {
 
 # "_parse_multipart_data" サブルーチン 1076~1077行
                     open (++$handle, ">$full_path") 
 	                || $self->_error ("Can't create file: $full_path!");
                     binmode $handle; # この行を挿入する
 
 # "_create_handles" サブルーチン 1168行
 	binmode $handle;	# この行を挿入する;
 	push (@{ $self->{all_handles} }, $handle);

なお、行番号はCGI_Lite v2.02に対応しています。 binmodeについては、perldocやPerl関数:binmodeなどで調べて下さい。

その他の問題

■ parse_form_dataを行ってもファイルが作成されない

次のようなケースが一般的です。

プラットフォームが指定されていない
Unix以外のOSでは、set_platformオプションでプラットフォーム(サーバーのOS)を指定する必要があります。CGI::Liteのドキュメントには「このデータは改行コードを判別するのに使われます」とありますが、実際にはファイルパスの区切り文字(Unixは/、Windowsは\、Macintoshは:)の判別にも使われます。このため、ファイルをバイナリモードで出力するとしても、set_platformは必須です。
ファイルを保存するディレクトリが存在しない
ファイルを保存するためのディレクトリは、あらかじめCGI実行ユーザーが書き込み権限を持つように、作成しておかなくてはなりません。多くの場合、set_directory時にエラーが起きます。
フォームでmulti-partを指定していない
フォームでは<FORM ACTION="..." ENCTYPE="multipart/form-data" METHOD="POST">のようにmultipartを指定しなくてはいけません。

ファイルアップロードCGI

CGI_Liteを使った極めて簡単な例です。

 #!/usr/local/bin/perl
 use CGI::Lite;
 # ファイル保存ディレクトリ関連
 my $dir = './file';
 unless (-d $dir) {
   mkdir $dir, 0777 or die $!;
 }
 # フォームデコード
 my $cgi=new CGI::Lite;
 $cgi->add_timestamp(0);
 $cgi->set_directory($dir) or die $!;
 $cgi->set_platform ('Unix');
 my %formdata = $cgi->parse_form_data();
 # 結果データ$result生成
 my $result = '';
 if (%formdata) {
   $result = "<hr>\n<table cellspacing=0 cellpadding=5 border=1>\n";
   foreach (sort(keys(%formdata))) {
   	$result .= "<tr><td>$_</td><td>$formdata{$_}</td></tr>\n";
   }
   $result .= "</table>\n";
 }
 # ファイルリスト$list生成
 my $list = '';
 my $size = 0;
 foreach my $path (sort(glob($dir.'/*'))) {
   next unless (-f $path);
   my $file = $& if ($path =~ /[^\/]+$/);
   $list .= "\t<dd><b><a href='$path'>$file</a></b> (".sprintf("%dKBytes", ((stat($path))[7] + 1023) / 1024).")</dd>\n";
   $size += (stat($path))[7];
 }
 $list = "<dl>\n\t<dt><B>ファイルリスト</B> (Total : ".sprintf("%dKBytes", ($size + 1023) / 1024).")\n".$list."</dl>\n";
 # 出力
 print <<EOD;
 Content-type: text/html
 
 <html>
 <head><title>ファイルアップロード</title></head>
 <body><h1>ファイルアップロード</h1>
   <!-- リスト -->
   <hr>$list<hr>
   <!-- アップロードフォーム -->
   <form action="Filer.cgi" ENCTYPE="multipart/form-data" method="post">
   File : <input type="file" name="file"> <input type="submit">
   </form>
   <!-- 結果 -->
   $result
   <hr><center><a href="http://digit.que.ne.jp">&copy;Walrus, Digit</a></center>
 </body>
 </html>
 EOD

安全なファイルアップロードCGIにするために

実際に設置する場合には、最低限下記を行ってサーバーの安全性を確保する必要があります。

  • ファイル名の文字コード統一
  • ファイル名の半角カナ禁止
  • ファイル名の禁則文字の排除
  • CGIなどの危険な拡張子の排除
  • ファイルサイズの制限

ほとんどはファイル名に関するものです。 ファイル名は、下のようにあらかじめ変換ルーチンを指定すると、パース時に自動的にサブルーチンでの変換が行われます。

 $cgi->filter_filename(sub {サブルーチン});
 my %form = $cgi->parse_form_data();

まず、日本語のファイル名については、文字コードに注意する必要があります。 MachintoshやWindowsでは、ファイル名はSJISにした方が良いでしょう。 これを行わないと、OS側ではうまくファイル名を処理できなくなる可能性があります。

この他、半角カナ、機種依存文字(丸の中に1、や罫線その他)なども排除した方が良いです。 こうした文字は、Webで表示、ダウンロードする際にトラブルの原因になりがちです。 また、OSごとに禁則文字が違い、ポピュラーなところではMachintoshではファイル名に使える文字で、Windowsでは使えないものがあります。 これらも排除した方が良いでしょう。

また、.cgi、.pl、.exeなどは、アップロードしたファイルが設定次第ではCGIだと認識され、サーバー上で実行されてしまう可能性があります。 こうした拡張子は拒否するか、拡張子を削除してしまう方が良いでしょう。

ファイル名の他に、ほとんどのケースではファイルサイズを考える必要があるでしょう。 ファイルサイズについては、$ENV{CONTENT_LENGTH}を見て、おおよそのサイズで受信の可否を決めてしまうのが最も簡単です。

 my $maxbytes = 51 * 1024; # 約50KBytesまで
 if ($ENV{CONTENT_LENGTH} < $maxbytes) {
   エラー処理
   exit;
 }
 my %form = $cgi->parse_form_data();

実際には、フィールド名やその他のフォームデータを含めて所定サイズ以下、となるので許されるファイルサイズは指定値より僅かに少なくなります。 このため、上のスクリプトでは1KBytesの遊びを持たせていますが、ほとんどの場合無視できる程度でしょう。

実用的なファイルアップロードCGIにするために

これを実用的なレベルにするには、以下のような機能が必要でしょう。

  • ユーザー管理
  • ユーザーごとのアクセス権管理
  • ファイルの移動と削除
  • ディレクトリによるファイル管理

サンプル

安全で、実用的なファイルアップロードCGIを作るためのヒントになるように、上の最小限のスクリプトにもう少し手を入れたものを作成しました。 以下のページから取得することができます。

  • WalRack2
  • WalRack ... CGI::Liteの前身、CGI-Liteを使ったバージョン

よくある質問

CGI_Liteの日本語ドキュメントは?

  • 私は英語が苦手です。CGI_Liteのドキュメントを日本語で読めるところはありませんか?

「Perl for Newbie (Perl初心者の部屋)」に「CGI_Lite1.62について」というページがあります。

http://www.harukaze.net/~mishima/perl/index.html

ただし、同サイトでも書かれているようにCGI_Liteの最新版は1.9ですし、さらにCGI::Liteは2.0版です。 これらを使う際には、上のサイトで概要をつかんだ上で、詳細はやはり英語版をあたられるべきかと思います。

ファイル名の前につく数字は何?

  • CGI_Liteでアップロードすると、ファイル名の前に変な数字がついてしまいます。なぜですか?

その数字はtimestampで、「$cgi->add_timestamp(n);」というメソッドで付け方を調整できます。nを0にするとつけない、1にするとつける、2にすると同名のファイルはタイムスタンプをファイル名にする、です。

CGI_Liteのドキュメントを確認して下さい。

CGI_LiteとCGI::Liteの違いは?

Version1.9まではCGI_Liteというモジュール(ファイル名はCGI_Lite.pm)でしたが、2.0からはCGI::Lite(ファイルはCGI/Lite.pm)になりました。 どちらもCPANから入手できます。

Version1.9と、CGI::Lite v2.0はほとんど同じものです。 詳細やCGI_Liteからの移行方法はCGI::Liteをご覧下さい。

ダウンロード時に、ファイル名の日本語が化けるのは?

Internet ExplorerがエンコードされたURLを元にファイル名を作成しようとして、文字化けになるようです。 Internet ExplorerにエンコードしていないURLをリクエストさせるのが回避策になります。 ただし、エンコードされていないURLでのリクエストは、処理できなくて当然です。この回避策の使用は推奨しません。

このためには、CGIから出力するURLを次のようにします。

  • URL中の日本語は、HTML文書自体の日本語文字コードにあわせる。
  • URLエンコードを行わない。

Windows上で動作しているAnHTTPdサーバと、同じくWindows上で動作しているInternet Explorer 5.5で上の回避策が動作することを確認しました。 この際、HTMLおよびURL中の日本語文字コードにはEUCを使用しました。 なお、前述の通り動作しなくて当然の方法ですので、他の環境で使う時は、その環境でつかわれるサーバと全てのクライアントに対して、問題がないか試験をして下さい。

CGI::LiteとCGI_Lite

CGI::LiteモジュールはCGI_Liteモジュールの後継になります。 CGI::Lite v2.0とCGI_Lite v1.9の違いは(ソースレベルで見ても)以下の3点だけです。

CGI_Lite]]に関する情報は、99%までそのままCGI::Liteにも適用できます。 なお、CGI::Lite 2.02版からは少しずつ修正が加わり始めています。

CGI_LiteからCGI::Liteへの移行

CGI_Liteを使用しているスクリプトを、CGI::Liteを使用するように変更するのは簡単です。 次のようにして下さい。

  • CGI::Liteの設置
    • スクリプト本体と同じディレクトリに、「CGI」という名前でディレクトリを作成する。
    • CpanModule:CGI-Liteに含まれている「Lite.pm」を、CGIディレクトリ内に入れる。
  • CGI_LiteからCGI::Liteへの切り替え
    • スクリプト中の「use CGI_Lite;」を「use CGI::Lite」に変更する。
    • スクリプト中の「$cgi = new CGI_Lite;」などの部分を「$cgi = new CGI::Lite;」のように変更する。

CGI_LiteからCGI::Liteへの切り替えは、通常、スクリプト中のCGI_Liteの文字列をCGI::Liteに全置換するだけで問題ありません。

なお、CGI_LiteによるファイルアップロードのサンプルとしてWalRackというCGIを公開しています。 これを、CGI::Liteに移行したものを、WalRack2として公開します。 両者のファイル構成と、WalRack.cgiスクリプトの記述を比較してみてください。

このページへのご意見、ご質問

(こちらへお書きください)

1行コメント

コメントスパムがひどいためこのページのコメント欄を削除しました。コメントしたい方は暫定的に「掲示板」のページへお願いいたします。

  • 2006-04-09 (Sun) 08:51:55 いいだ? : お騒がせしました。自己解決しました。Safariのformの出力が他と、ほんの少し違っていて取得しそこなっていました^^;
  • 2006-04-07 (Fri) 17:42:58 いいだ? : なにかヒントでもいただけるとうれしいんですが…。
  • 2006-04-07 (Fri) 17:42:34 いいだ? : MaxOSXのsafariでファイルアップロードができなくて困ってます
  • 2006-04-07 (Fri) 17:41:40 いいだ? : multipart解析を自作のデコードルーチンでやっていてIEやFireFox?では問題なさそうなのですが、
  • 2006-01-05 (Thu) 05:11:06 Bansoukou? : CGI::Liteのドキュメントの邦訳と、CGI作成の解説を書いてみました。ご興味のある方はどうぞ。http://www.lanceweb.jp/perl
  • 2005-12-28 (Wed) 23:52:42 TTK? : CGI::Liteのデータ解析にバグがあるようなので、ご報告しておきます。multipart/form-dataデータを受け取る際に、バッファサイズによって分割される位置が悪いと正しく解析されないようです。具体的には、たとえば画像などを一度に複数アップロードするスクリプトで、ある条件が重なると必ずファイルが壊れます(もちろんバイナリ対応済みで)。その条件というのが、どうやらmultipartデータの各パートヘッダの途中、たとえばcontent-type行の途中あたりで切れるとヘッダ区切りを認識しないようです(ここまで特定するまで半日ぐらい費やした…)。しかしLite.pmのソースを見ると1002行あたりでつないで1029行あたりで識別してるように見えるのですが、ソース読むのを挫折しました…。とりあえずはset_buffer_size($ENV{CONTENT_LENGTH})などで回避できますが、どうにも釈然としないような。
  • 2004-10-05 (Tue) 16:27:15 wada? : 本ページで紹介されているサンプルを使用してファイルのアップロードを行いましたが、ファイルの先頭に改行コードのみの行があるとアップロードした結果、該当行が落ちるようです。回避する方法はありませんか?
  • 2004-04-06 (Tue) 16:38:17 勉強中? : ファイルを直接ディスクに書いてしまうのは何となく(^^; イヤなんですが、リファレンスで受け取ることはできませんか?DBに保存するにも便利だし。
  • 2004-03-23 (Tue) 21:41:24 塚本牧生 : 別にとめませんが...えーと、何か聞きたいことでも?
  • 2004-03-23 (Tue) 19:18:13 TAN? : データのEOLが不特定であり、アップロードしてからも元のデータのEOLでset_platformの処理をしたくない。
  • 2003-09-30 (Tue) 21:50:24 塚本牧生 : 修正しました。...あれ、最新は2.02版?更新されたんですね!
  • 2003-09-30 (Tue) 17:48:31 とおりすがり? : 「CpanModule?:CGI_Liteからダウンロード」するとCGI::Liteじゃないみたいなんですが
  • 2003-05-11 (Sun) 19:25:26 塚本牧生 : ...なんのでしょう?(^^;
  • 2003-05-11 (Sun) 18:05:33 guest? : テスト