.d8888b. .d8888b. 8888888
d88P Y88b d88P Y88b 888
888 888 888 888 888
888 888 888
888 888 88888 888
888 888 888 888 888
Y88b d88P Y88b d88P 888
"Y8888P" "Y8888P88 8888888
PCP => Perl CGI Program (ming)
Version 1.0
Q4.2: フォームメールスクリプトは複雑にみえます。 どうしてmailto:URLを使って、利用者が書き込んでくれた情報を私宛に単に メールするだけではいけないのでしょうか?
Q4.3: MacやMSーDOS、Windows、NTのような非UNIXプラットホームからPCPを行うには どうすればよいでしょうか? 私のPCPはこれら全ての環境の間で移植できるのでしょうか? それは透過的にできるのでしょうか ? 私はUNIXサーバにアカウントがあるのですが、Windows/Macシステムで仕事をして います。私自身のシステムでCGIスクリプトをテストするにはどうすれば いいのでしょうか?
Q4.4: PCPの中では、STDERRとSTDINとSTDOUTはどこに接続されるのですか?
Q4.5: アクセスカウンタスクリプトを書くにはどうしたらいいですか?
Q4.6: Perlの置換を使って文書からHTMLタグを全部取り去るにはどうしたら いいでしょうか?
Q4.7: どんなユーザ/ホスト/ブラウザが私のプログラムを呼び出しているのか 表示するにはどうしたらいいですか?
Q4.8: 人々は私のPCPが読めますか? そうならば、その人達が私のコードがどのように機能しているか知ってしまうという セキュリティの問題はありませんか? どうやってそれを隠せばよいでしょうか?
Q4.9: 全Perlライブラリを私のhtdocsディレクトリにコピーしなければならない のでしょうか?
Q4.10: どうして人々にパスワードや社会保証番号やクレジットカード番号を入力させる べきではないのでしょうか? TYPE="password"とはそのためにあるのでは ないのですか?
Q4.11: ネットスケープ対それ以外の世界用に別々のページを生成するには どうしたらいいでしょうか?
Q4.12: どうしてsystem ()の出力が正しい順序で出てこないのでしょうか?
Q4.13: ネットスケープはJavaをサポートしていると聞いています。 もうPerlの代わりにJavaを使わなければならないということなのですか? そうすべきなのでしょうか?
Q4.14: どうしたら私の環境変数にアクセスできますか? 時々異なっているのはどうしてですか?
Q4.15: 私の出力がむちゃくちゃになっているのはどうしてでしょうか? ("if b < a" のようなものがめちゃめちゃです)
Q4.16: 私のPCPはコマンドラインから実行するときは動くのですが、 ブラウザからだと動きません。どうしてでしょうか?
Q4.17: 私のPCPはうまく動いているのですが、出力ファイルに書き込めません。 どうしてでしょうか?
Q4.18: 状態を維持する、あるいはいくつかのエントリポイントのあるフォームを作るには どうしたらいいでしょうか?
Q4.19: webブラウザから走らせずにPCPをデバッグするにはどうしたらいいでしょうか?
Q4.20: <FORM>タグを使わないでPCPをcallするには どうしたらいいのでしょうか?
Q4.21: 何も記入しないでフォームをcallするのをやめてもらうにはどうしたら いいでしょうか? 人々はなぜこれをし続けるのでしょうか?
Q4.22: server response codesとは何ですか? またどんな意味があるのですか?
Q4.23: なぜ
print "Location: http://host/page.html\n"
はうまく機能しないのでしょうか?
1回目だけちゃんと動き、その後は間違ってリダイレクトされるのは
どうしてでしょうか?
Q4.24: 私のHTMLページの一番下の所に自動的に
"Last updated: ..."
行を加えたいのですが、どうすればいいのでしょうか?
あるいは、SSIページでしたこれはできないのでしょうか?
CGIスクリプトの日付はどうやったらわかりますか?
Q4.25: 簡単なタスクだけどPCPだとひどく複雑になってしまうのでシェルの方が いいというのはどんな時でしょうか? 難しい事に対して十分力を発揮できない場合はどんな時ですか? Cについてはどうですか?
実のところ、これを行うのは非常に簡単なプロセスです。 あなたのCGIスクリプトは2つのタスクを遂行できなくてはなりません。:
あなたがCGI::* モジュールを使っていると仮定しましょう。 以下にsendmailを取り扱う方法を示します。:
$cgi_form = new CGI::Form;
$from = $cgi_form->param('from');
$name = $cgi_form->param('name');
$to = $cgi_form->param('to');
$subject = $cgi_form->param('subject');
$message = $cgi_form->param('message');
open (SENDMAIL, "| /usr/bin/sendmail -t -n");
print SENDMAIL <<End_of_Mail;
From: $from <$name>
To: $to
Reply-To: $from
Subject: $subject
$message
End_of_Mail
あなたが留意すべき点は一つで、"Reply-To:"ヘッダを忘れないことです。
サーバはユーザ"nobody"で走っていますから、メールヘッダはグシャグシャに
なっているかもしれません(特に人々がそれに返事をしようとする時は)。
"Reply-To:"フィールドがそれを解決します。
次のようなフォーマットでメールを使用するメールゲートウェイが ずいぶんあちこちにあります。:
open (MAIL, "| mail -s 'Subject' $to");
^
|
+-- セキュリティホールの可能性!!!!
シェルのメタキャラクタ用の$to変数をチェックしなければ、 ひどい頭痛に悩まされることになるでしょう! 例えば、ある悪意のある利用者が次のように入力した場合、:
; rm -fr / ;
あなたは自分の両手に大きな問題を抱えることとなるでしょう。
残念なことに、mailto:が全てのブラウザでサポートされているわけではないのです。 もしこれがあなたの文書にあるのでしたら、それは制限要素となり、 この機能をサポートしていないブラウザを使う人々は、あなたにメールを送る ことができなくなってしまうでしょう。
Perlは上記に述べられたプラットホーム全てに移植されてきました。 その結果、あなたのPCPプログラムは問題なく移植できるはずです。 もしあなたがUNIX側にある様々な外部プログラムとのインターフェースを とっている場合は、おそらくうまく移植できないでしょう。 しかし、データを操作したり、ファイルをオープンして読み込んだりしている だけなら、何の問題もないはずです。
CGI環境では、STDERRはサーバのエラーログファイルを指し示します。 デバッグメッセージを出力するようにしておいて、後でログファイルを確認する ようにすれば、これをアドバンテージとすることができます。
STDIN と STDOUTはブラウザを指し示します。実際には、STDINは実はクライアント (あるいはブラウザ)のリクエストと情報を解釈するサーバを指し示しており、 スクリプトへリクエストと情報を送ります。
エラーを捕捉するために、スクリプトの頭の方(有効なHTTPヘッダを出力した後) でSTDERRをSTDOUTに"dupe"(結び付ける)ことができます。
これはSTDOUT(例:ブラウザ)へエラーメッセージを全部リダイレクトします。open (STDERR, ">&STDOUT");
カウンタスクリプトは大変な人気となってきました。 カウンタの背後にあるアイデアは非常に単純です。:
以下は簡単なカウンタスクリプトです。:
[訳注:Perl for Win32 build110ではflock()はサポートされていませんので 例外が発生します。]#!/usr/local/bin/perl $counter = "/home/shishir/counter.dat"; print "Content-type: text/plain", "\n\n"; open (FILE, $counter) || die "Cannot read from the counter file.\n"; flock (FILE, 2); $visitors = <FILE>; flock (FILE, 8); close (FILE); open (FILE, ">" . $counter) || die "Cannot write to counter file.\n"; flock (FILE, 2); print FILE $visitors; flock (FILE, 8); close (FILE);
これでSSI (Server Side Includes)を使って、あなたのHTML文書中に カウンタを表示することができます。
あなたは
番目の訪問者です。<!--#exec cgi="/cgi-bin/counter.pl-->
以下はHTMLタグを取り去る簡単な正規表現です。:
$line =~ s/<(([^ >]|\n)*)>//g;
あるいは、HTMLタグの中のある文字群を"回避させる"こともでき、 それは以下のように表されます。:
$line =~ s/<(([^>]|\n)*)>/<$1>/g;
さらに詳細な情報は、Tomの striphtml programを 参照して下さい。これは、彼の tour of perl5 regexps.にも含まれています。
ユーザのブラウザを決定するには環境変数 HTTP_USER_AGENT を使用すれば よいでしょう。
[ From WWW FAQ ]
5つの重要な環境変数があなたのCGIスクリプトで利用できるので、 エンドユーザを認証する際に助けとなるでしょう。
この変数は、サーバがクライアント上のIDENTDサーバとコンタクトした場合に 設定されます。 これは遅いオペレーションで、大半のサーバでは通常止められており、 クライアント機が返答した場合でも、問い合わせに正直に返答したことを 確かめる方法はありません。
この変数はユーザを特定するのではなく、ホスト名がサーバによって 取り出された時、ユーザがどのサイトから コンタクトしてきていたのかという情報を提供するものです。 ユーザの正確な特定に関して何も確実性がない時は、信用できるアドレス のリストに基づいた決定をすることが時々は十分な成果となります。 この変数は、サーバがホスト名をを検索するのに失敗したり、 スピードのために検索をとばした場合、設定されません。: 下記のREMOTE_ADDR 参照。また、一つのホスト名の下に記載された 特別なプロキシサーバの全ユーザを見る羽目になるかもしれないことに 留意して下さい。
この変数はユーザを特定するのではなく、ホスト名がサーバによって 取り出された時、ユーザがどのサイトから コンタクトしてきていたのかという情報を提供するものです。 REMOTE_ADDR は、クライアントのドット区分された10進数表現の IPアドレスを含んでいます。 ユーザの正確な特定に関して何も確実性がない時は、信用できるアドレス のリストに基づいた決定をすることが時々は十分な成果となります。 この変数は上記のREMOTE_HOST とは違い、常に設定されます。 また、一つのホスト名の下に記載された 特別なプロキシサーバの全ユーザを見る羽目になるかもしれないことに 留意して下さい。
あなたがサーバの設定をしているのであれば、特定のディレクトリ(例:"cgi-bin" ) にある全てのファイルやある拡張子(例: ".pl"、".tcl"、".sh" その他) を持つファイルがCGIプログラムであり、サーバがこれらのプログラムを 実行することはわかります。 ユーザがスクリプト自体を見る方法はありません。
一方、自分のスクリプトを(例えば、それを文書ルートディレクトリに 置いたりして)一般の人に見せることを許可する場合、 プログラムの中にセキュリティホールがないのであれば、たいていの場合 セキュリティ上問題にはなりません。 そのプログラムがセキュリティホールを含んでおり、ユーザにそれを見せた 場合は、その問題は食い物にされてしまうでしょう。
いいえ、サーバがchrootされた環境で実行されていない限り、CGIスクリプト
はサーバと文書のルートディレクトリの外側
(outside the server and documents root directory)のファイルに
アクセスすることが可能です。
[訳注:documents root directoryとはいわゆる/cgi-binを指すのではないかと..(^^;)
いけません! フォームのインターフェースは"password"フィールドを持つことを許していますが、 何か高い機密事項のために使用されるべきものではありません。 その主な理由は、("password"フィールドを含んでいる)すべてのフォームデータは ブラウザから Webサーバに暗号化されたデータとしてではなく、 平文として送られるからです。
セキュアな情報がどうしても欲しいということなら、 Netscape's Commerce Serverのようなセキュアサーバを使用する 必要があります。
環境変数HTTP_USER_AGENTを使うことで、あなたのスクリプトがネットスケープに アクセスされているかどうかをCGIスクリプトに確認させることができます。 以下はそのサンプルです。:
$browser = $ENV{'HTTP_USER_AGENT'};
if ($browser =~ /Mozilla/) {
#
# Netscape
#
} else {
#
# Non Netscape
#
}
標準出力がバッファされる方法を処理しなければなりません。 出力を正しい順序で表示するためには、$|変数を使ってバッファリングを オフにする必要があります。:
$| = 1;
いえいえ。JavaのコンセプトはCGIのそれとは全く異なっています。 CGIはサーバ側での実行に言及し、一方Javaはクライアント側の実行に 言及します。Javaを使うことによって改善することの出来る (アニメーションのような)ことは確かにあります。 しかしながら、サーバ側のアプリケーションを開発するため、 Perlを継続して使うことができます。
さらに詳細な情報については、下記で見ることが出来るでしょう。:
%ENV連想配列を通じて環境変数にアクセスすることが可能です。 以下は(ソートした)環境変数を全部ダンプアウトする簡単なスクリプトです。:
#!/usr/local/bin/perl
print "Content-type: text/plain", "\n\n";
foreach $key (sort keys %ENV) {
print $key, " = ", $ENV{$key}, "\n";
}
exit (0);
残念なことに、全てのブラウザが同じ環境変数を設定するわけではありません。 たとえば、HTTP_REFERERは全てのブラウザが設定しているわけではありません。
もしあなたがHTMLのMIME contentタイプを送っているなら、 "<"、"&" や ">"あるいはブラウザがそれはHTMLだと思うような 特定の文字を"エスケープ"しなければなりません。
次のようなコンストラクトを使って文字をエスケープしなければなりません。
&#ASCII Code;
以下は簡単な例ですが、コマンドラインから実行でき、 非英数字用にASCIIコードを与えるものです。:
#!/usr/local/bin/perl
print "Please enter a string: ";
chop ($string = <STDIN>);
$string =~ s/([^\w\s])/sprintf ("&#%d;", ord ($1))/ge;
print "The escaped string is: $string\n";
exit (0);
この大半は多分パーミッション(権限)問題によるものです。 おそらくあなたのサーバは"nobody"、"www"あるいは必要最小限の特権を持った プロセスとして実行されています。[訳注:このユーザ名は UNIX HTTPサーバに多い。] 結論としては、ちゃんとした権限[訳注:実行等]がない限り、あなたのスクリプトを 実行することはできないでしょう。
もう一度、これはパーミッション(権限)を処理しなければなりません! サーバはちゃんとした権限がなければ、特定のディレクトリにあるファイルへ の書込はできません。
openコマンドからのエラー状況を確認するのを忘れないことです。:
print "Content-type: text/plain\n\n";
.
.
.
open (FILE, ">" . "/some/dir/some.file") ||
print "Cannot write to the data file!"; #[訳注:標準エラーはファイル
#のため標準出力にprint]
.
.
.
複数のエントリポイント間の状態を保つ CGI::MiniSvr モジュールを使えばよいでしょう。
あるいは、クェリー、外部パス名として、または隠しフィールドとして、
お互いにユニークなセッションIDを受け渡す一連の動的文書を生成しても
よいでしょう。
[訳注:よくわかりませんでした。(^^;]
CGIスクリプトをデバッグするのは困難です。 マニュアルで環境変数を設定すればサーバをエミュレートすることは できるでしょう。:
ファイルにデータを置き、それをプログラムにpipeすることによって、 POSTリクエストをエミュレートすることができます。:setenv HTTP_USER_AGENT "Mozilla/2.0b6" (csh) あるいは export HTTP_USER_AGENT = "Mozilla/2.0b6" (ksh, bash)
cat data.file | some_program.pl
スクリプトをデバッグするのに CGI.pm (あるいは the CGI::* modules) を 使うこともできます。 次のような簡単なスクリプトがあり、受け渡すキーと値のペアを全部 printしているとします。
#!/usr/local/bin/perl5
use CGI;
$cgi = new CGI;
print $cgi->header;
print $cgi->start_html("Simple CGI.pm Program");
print "<H1>Simple CGI.pm Program</H1>\n";
print "<HR >";
print "Here is a list of the values you passed: ";
print $cgi->dump;
exit (0);
このスクリプトは、コマンドラインからか、あるいは標準出力、テキストファイル
からなのか、また、GET、POSTあるいはISINDEXとして値を受け渡しているのか
全く気にかけません。デバッグ目的のためには、コマンドラインからいくつか
値を渡してやりましょう。:
2番目の例の中で、あなたは文字全部を""で囲まなければなりません。 さもないと、シェルが"&"文字を見たとき混乱してしまうでしょう。 さて、以下は標準出力からのデバッグのやり方です。:% simple.cgi first=shishir last=gundavaram document='CGI\ FAQ' あるいは % simple.cgi "first=shishir&last=gundavaram&document='CGI\ FAQ'"
もちろん、以下のように、ファイルを使ってフォームデータを蓄積しておき、 入力をリダイレクトすることもできます。:% simple.cgi (標準入力を待つ) first=shishir last=gundavaram document=CGI\ FAQ ^D
% simple.cgi < form.data
また、まもなくリリース予定のCGI Lintを利用することもできます。これは 同じ事をやってくれるでしょう。それに、潜在的なセキュリティ問題や open ()におけるエラーや無効なHTTPヘッダのチェックもしてくれます。
単にそのCGIプログラムへのURLをオープンするだけで、 それをcallすることができます。:
http://some.machine/cgi-bin/your_program.pl
また、以下のように文書の中にリンクを持つこともできます。
<A HREF="http://some.machine/cgi-bin/your_program.pl">
Click here to access my CGI program</A>
何も記入しないでフォームをcallする理由は、彼らはフォームへのURLをセーブし、 それから再びフォームをcallするのですが、それはただの空のGET (and not a POST or a filled-out GET)となるのです。 The reason they call a form with nothing filled in is that they save the URL to the form, which they then call again, which is just an empty GET (and not a POST or a filled-out GET).
しかしながら、全フィールドからの情報を確認し、そのうちどれかが 空だった場合、"no response"を戻すことが可能です。 以下はその一例です。(あなたのフォームの情報が廉そう配列 %formに 含まれているとします。):
$error = 0;
foreach $value (values %form) {
$value =~ s/\s//g;
$error = 1 unless ($value);
}
if ($error) {
print "Content-type: text/plain\n";
print "Status: 204 No Response\n\n";
print "You should only see this message if your browser does";
print "not support the status code 204\n";
} else {
#
# Process Data Here
#
}
CGIプログラムはサーバに特定のレスポンスコードを送信でき、それは 順番にブラウザに送られてきます。 例えば、あなたが "No Response"(ブラウザが新しいページをロードしないという 意味)が欲しい場合、204 というレスポンスコード(上記参照)を送信することが 必要です。
print "Location: http://host/page.html\n"
はうまく機能しないのでしょうか?
1回目だけちゃんと動き、その後は間違ってリダイレクトされるのはどうしてでしょうか?
CGIプログラミングではロケーションヘッダは一つしか送ることが できません。 また、サーバにリダイレクションを実行させたい場合、MIME content タイプを 送ることはできません。 例えば、下記は有効ではありません。 まっ、いくつかのサーバでは機能するかもしれませんが。:
#!/usr/local/bin/perl . . . print "Content-type: text/plain\n" print "Location: http://some.machine/some.doc\n\n"";
"Last updated: ..."
行を加えたいのですが、どうすればいいのでしょうか?
あるいは、SSIページではこれはできないのでしょうか?
CGIスクリプトの日付はどうやったらわかりますか?
CGIを使って動的に文書を生成しているのなら、かなり簡単にタイム スタンプを挿入できます。以下は一例です。(Perl 5のみ)
あるいは:$last_updated = localtime (time); print "Last updated: $last_updated\n";
あるいは これでも:require "ctime.pl"; $last_updated = &ctime (time); print "Last updated: $last_updated\n";
以下のようにSSIを使っても完遂できます。:chop ($date = `/usr/local/bin/date`); print "Last updated: $last_updated\n";
<--#echo var="LAST_MODIFIED"-->
それぞれの言語はそれなりの優位性、非優位性を持っています。 やろうとしていることによるんです:という話は何回も耳にしたと思います。 1時間に数千回もアクセスされるようなCGIを書くつもりなら、CかC++を 使って書くべきでしょう。(実装と同じ位)早い解決を求めているのなら Perlが進むべき道です!
一般的には、どんなタイプのCGIプログラミングでも、シェルを使うのは よした方がいいです。セキュリティ問題に対する潜在的な脅威を秘めている からというのがその理由です。
This document, and all its parts, are Copyright (c) 1996, Shishir Gundavaram and Tom Christiansen. All rights reservered. Permisson to distribute this collection, in part or full, via electronic means (emailed, posted or archived) or printed copy are granted providing that no charges are involved, reasonable attempt is made to use the most current version, and all credits and copyright notices are retained. Requests for other distribution rights, including incorporation in commercial products, such as books, magazine articles, or CD-ROMs should be made to either of the authors.
ご意見、ご要望は、
電子メールまたは
投稿にお願い致します。ホームページへ戻る。