CGIスクリプトの動作環境、環境変数、デコード方法について参考になると思われるスクリプトを作成してみました。詳細な説明は、スクリプトの中に書いてあるコメントを参照してください。
#!/usr/local/bin/perl # 上の1行の前には空行も空白文字もはいらないようにしてください。 # perlのパス名はプロバイダや環境に合わせて変更してください。 # # HTMLのメタ文字をエスケープする関数を用意します # sub html { $str = $_[0]; $str =~ s/&/&/g; $str =~ s/</</g; $str =~ s/>/>/g; $str =~ s/"/"/g; $str =~ s/'/'/g; return $str; } # # ヘッダ部を書き出します # print <<EOF; Content-Type: text/html <!doctype html> <html> <head> <meta charset="utf-8"> <title>CGI TEST</title> </head> <body> <h1>CGI TEST</h1> <pre> EOF # # コマンド引数を書き出します # print "=================================\n"; print "コマンド引数\n"; print "=================================\n"; for ($i = 0; $i <= $#ARGV; $i++) { print "ARGV[$i] = [ " . html($ARGV[$i]) . " ]\n"; } print "\n"; # # CGIスクリプトが参照可能な環境変数を書き出します。 # print "=================================\n"; print "環境変数\n"; print "=================================\n"; print "AUTH_TYPE = [ " . html($ENV{'AUTH_TYPE'}) . " ]\n"; print "CONTENT_LENGTH = [ " . html($ENV{'CONTENT_LENGTH'}) . " ]\n"; print "CONTENT_TYPE = [ " . html($ENV{'CONTENT_TYPE'}) . " ]\n"; print "GATEWAY_INTERFACE = [ " . html($ENV{'GATEWAY_INTERFACE'}) . " ]\n"; print "HTTP_ACCEPT = [ " . html($ENV{'HTTP_ACCEPT'}) . " ]\n"; print "HTTP_FORWARDED = [ " . html($ENV{'HTTP_FORWARDED'}) . " ]\n"; print "HTTP_REFERER = [ " . html($ENV{'HTTP_REFERER'}) . " ]\n"; print "HTTP_USER_AGENT = [ " . html($ENV{'HTTP_USER_AGENT'}) . " ]\n"; print "HTTP_X_FORWARDED_FOR = [ " . html($ENV{'HTTP_X_FORWARDED_FOR'}) . " ]\n"; print "PATH_INFO = [ " . html($ENV{'PATH_INFO'}) . " ]\n"; print "PATH_TRANSLATED = [ " . html($ENV{'PATH_TRANSLATED'}) . " ]\n"; print "QUERY_STRING = [ " . html($ENV{'QUERY_STRING'}) . " ]\n"; print "REMOTE_ADDR = [ " . html($ENV{'REMOTE_ADDR'}) . " ]\n"; print "REMOTE_HOST = [ " . html($ENV{'REMOTE_HOST'}) . " ]\n"; print "REMOTE_IDENT = [ " . html($ENV{'REMOTE_IDENT'}) . " ]\n"; print "REMOTE_USER = [ " . html($ENV{'REMOTE_USER'}) . " ]\n"; print "REQUEST_METHOD = [ " . html($ENV{'REQUEST_METHOD'}) . " ]\n"; print "SCRIPT_NAME = [ " . html($ENV{'SCRIPT_NAME'}) . " ]\n"; print "SERVER_NAME = [ " . html($ENV{'SERVER_NAME'}) . " ]\n"; print "SERVER_PORT = [ " . html($ENV{'SERVER_PORT'}) . " ]\n"; print "SERVER_PROTOCOL = [ " . html($ENV{'SERVER_PROTOCOL'}) . " ]\n"; print "SERVER_SOFTWARE = [ " . html($ENV{'SERVER_SOFTWARE'}) . " ]\n"; print "\n"; # # フォームに指定した値を書き出します # print "=================================\n"; print "フォーム変数\n"; print "=================================\n"; if ($ENV{'REQUEST_METHOD'} eq "POST") { # POSTであれば標準入力から読込みます read(STDIN, $query_string, $ENV{'CONTENT_LENGTH'}); } else { # GETであれば環境変数から読込みます $query_string = $ENV{'QUERY_STRING'}; } # 「変数名1=値1&変数名2=値2」の形式をアンパサンド( & )で分解します @a = split(/&/, $query_string); # それぞれの「変数名=値」について foreach $a (@a) { # イコール( = )で分解します ($name, $value) = split(/=/, $a); # + や %8A などをデコードします $value =~ tr/+/ /; $value =~ s/%([0-9a-fA-F][0-9a-fA-F])/pack("C", hex($1))/eg; # 変数名と値を書き出します print "$name = [ " . html($value) . " \n"; # 後で使用する場合は、$FORM{'変数名'} に代入しておきます $FORM{$name} = $value; } # # フッター部を書き出します # print <<EOF; </pre> </body> </html> EOF
このスクリプトを以下の場所に置いています。いろいろな方法で呼び出して、実行結果をみてください。
CGIスクリプトを記述する際に注意しなければならないことを、UNIXサーバーの場合、Windowsサーバーの場合に分けて説明します。(多くのプロバイダではUNIXサーバーを利用しています。たまにWindows NTサーバーもあるようです。)
セキュリティ確保のためCGIの使用を禁止していたり、CGIの設定を行っていなかったりするため、CGIを利用できない場合があります。
プロバイダによっては、CGIスクリプトを cgi-bin フォルダの下に置かなくてはならない、CGI用の別サーバに設置しなくてはならない、別途申請フォームから申請が必要など、プロバイダに依存した注意事項がある場合があります。ご利用のプロバイダの説明書をお読み下さい。
サーバが Apache などの場合、.htaccess というファイルの設定が必要な場合があります。プロバイダやサーバー管理者に問い合わせてください。また、.htaccess に記述しても有効にならない場合もありますので、サーバーの管理者の指示に従ってください。
CGIスクリプトの動作チェックは必ず、http:// で始まるアドレスでアクセスしなくてはなりません。自分のパソコンでチェックしたい場合は、「Windows でCGIを動かすには」を参照してください。
CGIスクリプトの1行目は、perlの絶対パス名(ディレクトリ上の位置)を正確に指定してください。絶対パス名がわからない場合は、プロバイダの説明を読んだり、サーバーの管理者に問い合わせてください。多くの場合、/usr/local/bin/perl や、/usr/bin/perl などに置かれています。
サーバーの設定によってはCGIスクリプトを置くディレクトリが制限されている場合があります。普段 HTMLファイルを置いているのとは別のサーバーに設置しなくてはならないプロバイダもあります。CGIスクリプトを置く場所がこのホームページで説明されている場所と合わない場合は、プロバイダの指示などに従って、適切にディレクトリを変更してください。
CGIスクリプトの拡張子が .cgi でなくてはならない場合があります。他にも .pl でなくてはならない場合もあります。これらも、サーバーの設定によって異なります。
Windows NTにはperlが標準では装備されていませんから、perl for Win32 や ActivePerl を入手してインストールする必要があります。また、MicrosoftのIISを用いる場合は、.cgi という拡張子に対して perl.exe が起動されるように、IISのヘルプを参照して設定を行う必要があります。(古いバージョンのIISではレジストリの追加が必要)
サーバーがUNIXの場合は、CGIスクリプトファイルのパーミッションを755にしてください。また、CGIスクリプトが書込むデータファイルは、666 にしてください。一部のプロバイダでは、700 や 600 でなければならないところもあるそうです。
MicrosoftのIISでは、IUSER_MachineName というユーザーの権限でコマンドが実行されます。このユーザーがCGIスクリプトを実行する権限を持っているか確認してください。
他のサーバーからデータファイルを移行させたときなど、ファイルの所有者が nobody でなくてはならないのに、自分自身の所有になっていて、パーミッションが 644 でもCGIがファイルに書き込めないことがあります。
CGIスクリプトファイルの改行コードは、サーバーのOSタイプに適したものでなくてはなりません。
.htaccessを設置する場合は、.htaccessの改行コードもまた、サーバーのOSタイプにあわせてやる必要があります。
.htaccessの最後の行が改行されていないと、その行は無視されてしまうようです。
スクリプト実行時、スクリプト内で使用するコマンドのパス(PATH)が通っていない場合があります。TELNETなどでログインした時のPATHと、CGIスクリプトが実行される時のPATHは異なるので注意してください。コマンドはなるべくフルパスで記述したほうが無難かもしれません。
CGIスクリプトの最初の1行は「#!」で始めるようにしてください。CGIスクリプトは「#!」で始めなくてはなりません。「#!」の前に空白文字や空行があってもいけません。
Content-type: text/html などのCGIヘッダの後には必ず、1行以上の空行を出力してください。これを怠るとCGIスクリプトは正常に動作しません。
UNIXというOSは通常、アルファベットの大文字と小文字を別の文字として扱いますので注意してください。たとえば、test1.cgi と TEST1.CGI はまったく別のファイルとして扱われます。
ブラウザがCGIスクリプトの結果をキャッシングするために、スクリプトを書き換えても古い情報が表示されたりします。CGIスクリプト変更時には十分に注意して、再読み込みを欠かさないようにしましょう。
意外に多いのがこれ。Context-type: になっていたり、test/html や type/html になっていたり....もう一度確認してみましょう。
CGIスクリプトから出力する漢字コードはJISコードにするのが無難です。通常はブラウザが自動判断するのですが、Windowsで使用されているシフトJISと、UNIXで使用されているEUCの場合は誤判断してしまうことがあります。
EUCのファイルを書き出しているのに、ブラウザの文字コード選択([表示]-[エンコード])がシフトJISのままになっていて、うまく表示できないケースがあるようです。
CGIスクリプトは複数のブラウザ(ひとつのブラウザからでも)同時に起動されます。CGIスクリプトからファイルを更新する際は、ロックファイルの生成などにより排他制御を行う必要があります。
CGIを<img>タグから呼び出す場合、Netscapeの場合[オプション]→[画像の自動読み込み]、Internet Explorerの場合[表示]→[オプション]→[情報]→[画像の表示]がオンになっていないと読み込まれません。
Content-type: text/plain を使用した場合、Internet Explorer 3.0以降ではうまく動かないことがあります。[表示]→[オプション]→[プログラム]→[ファイルタイプ]で、.cgiという拡張子のファイルにメモ帳などのプログラムを割り当てている場合、text/plain 形式の実行結果をダウンロードしてしまい、期待通りの動作をしないことがあります。
Windows NT + IISの場合は、CGIスクリプトが動作する際のカレントディレクトリ(作業フォルダ)の場所が異なる場合があります。スクリプトの最初の方に、chdir("C:/HomePage/cgi-bin"); などの1行(C:/~の部分にはCGIスクリプトを置いているフォルダを指定する)を追加することで回避できる場合があります。
すべて確認したのに、どうしても動かない・・・そういう時は、もう一度、すべてのスペルをチェックしてみましょう。大文字・小文字もあわせて見てください。l(小文字のエル)と I(大文字のアイ)と 1(数字の1)は間違えやすいので気を付けてください。
Perl5でダブルクォーテーション("...")の間にアットマーク(@)を記述する場合は @ を \@ と記述する必要があります。Perl4の場合や、Perl5であってもシングルクォーテーション('...')であればそのまま @ と記述できます。
広告付きのサイトを利用している場合が、カウンターなどの画像データにまで、広告画像が自動的に挿入されてしまい、画像データが壊れてしまうケースがあるようです。対策は利用サーバのマニュアルなどを参照ください。
サイトによっては perl のバージョンが 3 や 4 など古いバージョンだったり、perl 5 を利用するには /usr/bin/perl5 や /usr/local/bin/perl5 のように指定する必要があるサイトもあるようです。
print "Content-Type: text/html\n\n"; の前に、print "HTTP/1.0 200 OK\n"; の追記が必要など、サイトによっては CGI の仕様が一般のものと多少異なる場合があるようです。
それでも、どうしても、動かない時は、「CGIスクリプトをデバッグするには?」の方法でデバッグしてみてください。それでも動かなければ、質問メールを受け付けています。「とほほにメールを送る」の注意事項をよく読んで、ご質問ください。また、これらの他にも、「私はこういう原因でつまずいた!!」というのがありましたら、追記したいと思いますのでお知らせ下さい。
通常のCGIスクリプトの出力はWWWサーバー経由でWWWブラウザに渡されますが、NPHスクリプト形式にすると、CGIスクリプトから直接WWWブラウザに渡されるため、WWWサーバーの負荷が軽くなり、速度も若干速くなります。
CGIスクリプトをNPHスクリプトにするには次の2つの作業を行ってください。
次は、NPHスクリプトの例です。(nph-sample.cgi)
#!/usr/local/bin/perl binmode(STDOUT); print "HTTP/1.0 200 OK\r\n"; print "Content-type: text/html\r\n"; print "\r\n"; print "<html>\n"; print "<head>\n"; print "<title>NPHスクリプトのテスト</title>\n"; print "</head>\n"; print "<body>\n"; print "これは、NPHスクリプトのテストです。\n"; print "</body>\n"; print "</html>\n";
SSIやCGIは便利さの代償としてセキュリティ上非常に危険なものです。以下の文章はセキュリティを強化するために必要な情報ですが、反面、未防御のシステムに対してアタックをかけるための情報にもなってしまいます。この場で記載すべきかどうか悩んだのですが、このホームページの読者を信用して公開することにしました。
CGIスクリプトを置くディレクトリには index.html を置いておきましょう。index.html が存在しない場合、cgi-bin ディレクトリをブラウザでみるとCGIスクリプトの一覧が見えてしまいます。ファイル名からセキュリティホールのあるスクリプトを見つけてアタックされる可能性が高くなります。
パスワードファイルなど、見られるとまずいファイルは、public_htmlの外など、通常の閲覧者が見ることのできないディレクトリに置きましょう。(それでも、同じサーバーにログインできる人や、他の人のCGIのセキュリティホールのために、見られてしまうことはあります。)
perlはよく分からないのでCGIスクリプトをシェルで記述しよう・・・と思った時など、CGIスクリプトのパラメータを解釈するためによく、UNIX のシェルの eval を利用しますが、これは大変危険です。もし、変数名NAMEの値として「値;危険なコマンド」を指定されると・・・
「grep $KEY file」というコードも危険です。もし、KEYの値としてバッククォーテーション( ` )で囲まれた「`危険なコマンド`」を指定されると・・・
チャットや伝言板など利用者の書き込んだメッセージを表示するシステムではSSIに注意してください。もし、「<!--#exec cmd="危険なコマンド"-->」を書き込まれると・・・
人様のパスワードを扱う場合はとにかく厳重に扱ってください。多くの人は同じパスワードを使いまわします。あなたのサービスが不法使用されるだけでなく、同じパスワードを使いまわしていた他の有料サービスも不法使用される恐れがあります。あなたのCGIスクリプトは完璧でも、同じサーバー上の他の人の不備で情報が漏れる可能性もあります。パスワード情報ファイルに password.txt などといった安直な名前はつけないようにしましょう。
CGIを実行すると、http://.../.../xxx.cgi?aaa&bbb&ccc のように、CGIへの引数が表示されてしまうことがあります。この?以降の文字を表示させないようにするには、以下の手段があります。
<form method="GET" action="..."> の代わりに、<form method="POST" action="...">を用いると、?以降の文字がURLではなく標準入出力で渡されるため、表示されなくなります。ただし、CGI側もPOSTに対応する必要があります。
CGIで処理を行った後、Content-type:ヘッダの代わりに Location: ヘッダで一度別のHTMLやCGIに飛ばしてやると、表示されなくなります。
セレクトボックス(<select>)を複数選択可能(MULTIPLE)にした場合、CGI に渡される値は、SEL1=aaa&SEL1=bbb&SEL1=ccc のようになります。Perl で値を受け取る際に単純に &FORM{$name} = $value; としていると最後の値しか取得できないので、$FORM{$name} .= "$value "; とするなどの対応が必要となります。