14←  home  →16
15 1999年7月28日

CGIでPerlのお勉強(その3)

さて、気合いを入れるぞ!

実際使っている、「アンケートかきかき」のcgiファイルの解説です。だいぶ解読できたでしょうか?ってもそれほど興味もたれてない感じがするが、まあ乗りかかった船だし・・・。
 

最初:

#!/usr/local/bin/perl

#はコメント行でした。よってperlは無視します。この行をUNIX系のサーバが読んでここに指定された場所にあるperlを動かすのだそうです。契約しているプロバイダの指示に従います。
 

# en.cgi
#
# Ver 1.0
#
# This cgi.file is based on
# guestadd.cgi Version 1.0.5
# Copyright (C) 1997 by Hiroshi Yuki.
# All rights reserved.
# 結城浩 <hyuki@st.rim.or.jp>
# http://www.st.rim.or.jp/~hyuki/
#

問題なし。次。

##########
# ↓保存されるアンケートのファイル名
$csvfile    = 'en.csv';

ここは「プログラム的」には意味のないところです。ここでの変数$csvfileは後で出てくる、
 

# データファイルのオープン
# if (!open(CSV, "+<en.csv")) {
if (!open(CSV, "+<$csvfile")) {
    &print_error("データを書き込むファイルが見つからない!");
}

の、$csvfile ってところに'en.csv'が代入されるだけの働きです。直前の#で始まるコメント行のen.csvを変数に置き換えただけです。この変数はメンテナンス用です。

ちょっと話変わって、アンケートの内容は非公開でした。ということはわたし以外の人に情報が流れたらわたしの信用問題になるということです。「隠しページの合い言葉」のとこで触れましたが、プロバイダによっては、index.htmlがディレクトリになかったら、ファイルリストを渡すものもありました。ファイルリストを渡されてしまったら「それらしいファイル」はすぐ見つかるので困ります。その場合は無意味な内容でもいいですから、index.htmlを置き、リストを渡さないようにします。
 これで、不埒なハッカーが「リストを見て」ということは無くなります。ではもう安全なんでしょうか?

ところで、アンケート関連のプログラムは

en.html
en.cgi
en.csv

の3つのファイルを使っています。拡張子以外は皆、en という同じ名前です。同じ名前で纏めた方が分かりやすいからです。さて、これも「隠しページの合い言葉」のところで触れましたが、フレームを使っている限り、http://ww3.tiki.ne,p/~kawa_s/のURLから変化しませんが、フレームを外すと、アンケート入力の画面ではen.htmlというファイル名が、書き込み完了の画面では、en.cgiというファイル名がURLに表示されます。

en.html, en.cgi ということは?データファイルは、en.txtかな? URLに直接 en.txt と入力してRETURN。ファイルが無いと言われる。じゃ、データベースでよく使うcsvかな? en.csv + RETURN。おお!表示されちゃった。

てなことが起こるわけです。ま、ウエッブの片隅に、こそっと存在する地味なページにハッキングにくる人もいないとは思いますが、en.csvなどと言う名前では、ちょっとの試行錯誤でばれてしまいそうです。最低限のセキュリティーぐらいは・・・・。

ということで、ハッキングを難しくする方法。

その1:ファイル名をパスワード風に無意味な羅列にする。例えば14eid9epois.csvとか・・・。
その2:他のディレクトリに置く。/data/kota/sota/en.csvとか・・・・。
その3:まめにファイルの情報を移して、万一の場合でも、漏れる情報を最小限にする。
その4:1・2・3を全部する。

とかあるみたいです。でまあ、ファイル名は変数使って、プログラムの最初の方に書いておき、自分のマシンでは分かり易い名前にしといて、実際にサーバに送る時には、、ここを修正れば楽に処理できるということです。また、プロバイダを変わったときなんかも修正が楽だそうです。
 

# ロック関連
$uselock    = 1; # ロックを使うなら1。うまく動かないとき0にする。

Windowsマシンではロックはききません。その場合0です。サーバで動かすときには1です。これもプログラムの先頭部分に設定を纏めて置いた方が修正が楽、という変数の使い方です。出てきたときまた説明します。
 

##########

# 漢字ライブラリの読み込み
require "jcode.pl";

# 初期化
&init_form(sjis);

# ヘッダの送出
print "Content-type: text/html\n\n";

ここら辺は前に説明しましたよね。漢字ライブラリを外部から呼び出して、初期化で漢字をシフトJISで統一します。ヘッダでブラウザにHTMLが送られるよと教えます。

$name     = $form{'yourname'};
$mail     = $form{'mail'};
$sendem   = $form{'sendem'};
$sex      = $form{'sex'};
$age      = $form{'age'};
$howcome  = $form{'howcome'};
$homepage = $form{'homepage'};
$comment  = $form{'comment'};
 

とりあえず、連想配列の$formから、連想を一つずつ個別の変数に入れ直しています。ここは必ずしもこうしなくてもいいんですが、連想配列一つですますより、ざーっと変数に小分けしといた方が気分がすっきり、といった程度でしょうか。
 

# 名前が入力されているかどうかの確認
if ($name eq '') {
    &print_error("名前が入力されていません。<br>本名でなくてOKです。");
}

if文は前回出てきました。

if (A) {B}
もしAならばBをせよ。

でした。eq はイコールでした。''はシングルクオーテーションが2つです。ということは「空」emptyならということで「もし、$nameに何も記入されていなかったら」ということです。

もし、名前に何も記入してなかったらどうなるかというと
 
 

名前が入力されていません。
本名でなくてOKです。

エラー発生。 もう一度、試してみて下さい。
それでもこの画面が出たら、K's Kornerまでメール下さい。

と表示されます。その部分:

&print_error("名前が入力されていません。<br>本名でなくてOKです。");

&で始まっていたらサブルーチンでした。これです:

 エラー表示
# &print_error("メッセージ");
sub print_error {
    local($msg) = @_;
    &page_begin($msg);
    print "エラー発生。 もう一度、試してみて下さい。<br>それでもこの画面が出たら、K's Kornerまでメール下さい。\n";
    &page_end;
    exit(0);
}

localというのはローカル変数です。ローカルと言いながら、ローカル以外でも使えるそうですが・・・。
すみません。先走りました。
ローカル変数というのは、ローカル:地元で使う変数です。地元とはこのサブルーチンのことです。
perlの場合、このサブルーチンで定義した変数が他の場所でも使えるんだそうです。

さて

local($msg) = @_;

@_ って何?というと「とほほのWWW入門によると、特殊変数の一つで「サブルーチンへの引数」なんだそうです。「引数」は「ひきすう」と読むそうですが、どうもC言語というか、プログラム言語の世界の基本用語のようで説明がない!んで困るんですが、「ある処理をする際に一緒に渡す情報」ぐらいな理解でいいと思います。

つまり、次のの部分で「引数」がたらい回しされる訳です。
 

&print_error("名前が入力されていません。<br>本名でなくてOKです。");
 

# エラー表示
# &print_error("メッセージ");
sub print_error {
    local($msg) = @_;
    &page_begin($msg);
    print "エラー発生。 もう一度、試してみて下さい。<br>それでもこの画面が出たら、K's Kornerまでメール下さい。\n";
    &page_end;
    exit(0);
}

名前が入力されていません。<br>本名でなくてOKです。@_  $msg というように代入されていきます。

また、サブルーチンの中にサブルーチンが使われています。&page_begin($msg);&page_end;の2つです。煩雑なので引用しませんが、&page_beginがHTMLファイルの始まりを、&page_endが終わりを表しています。
このように、サブルーチンを駆使しています。なぜ駆使するかというとそれの方が楽だからです。

次の

# 更新希望なのに、メールアドレスが入力されていない
if ($sendem eq 'sem'){
      if ($mail eq '') {
    &print_error("メールアドレスが入力されていません。<br>更新連絡できませんよ〜。");
}
}

の場合も同じ&print_error("コメント");で同様の処理がすんでしまいます。

なおここは

if ($sendem eq 'sem' && $mail eq '') {
    &print_error("メールアドレスが入力されていません。<br>更新連絡できませんよ〜。");
}

と書いても同じです。&&は「かつ」を意味します。「更新が希望するにチェックが入っていて、かつ、メールアドレスが無記入の場合」というとこです。
 

# タグを置換する
$comment =~ s/</&lt;/g;
$name =~ s/</&lt;/g;
 

 =~ s/</&lt;/g;

sはsubstituteで置換、gは何の略かしりませんが、処理を最後までするという意味です。省略すると1つ置換すると作業をやめます。<を&lt;に置換する処理です。HTMLのタグは<と>に挟まれました。それで<をタグと解釈されないように&lt;というHTMLで<を意味する記号に置換します。しかしこの処理は非公開のファイルには無意味な処理なので現在は削除しています。代わりにというわけでもないですが、今は次のような処理が入っています。
 

#  不正なメールをはじく
unless ($form{'mail'} =~ /@/) {
    &print_error("メールアドレスが不正です。<br>正しいメールアドレスを記入下さい。");
}

if ($form{'mail'} !~ /\./) {
    &print_error("メールアドレスが不正です。<br>正しいメールアドレスを記入下さい。");
}

=~ /A/ とは「Aを含む」、!~ /A/とは「Aを含まない」を意味します。

unlessは「もし〜でなかったら/〜でないかぎり」。ということで上の2つはともに、「もし・・が含まれていなかったら」という意味です。\.はピリオドだけならエラーが出ます。\をつけるとOK。

Eメールでは、必須と思える@とピリオドがないと不正だと決めつけます。逆に言えば@とピリオドだけあれば受け付けるという不完全なチェックです。別になくてもいいんですが、練習です。
 

# データファイルのオープン
if (!open(CSV, "+<$csvfile")) {
    &print_error("データを書き込むファイルが見つからない!");
}

この箇所にわたしは感動しました。「すっげえ」です。「ファイルを開いて、その内容をCSVに入れなさい。もし失敗すれば、&print_error、成功すれば次に進みなさい」というのを、if (! ){} と感嘆符をつけるだけですませている!!!!

いまさっきも「含んでいなかったら」で!~を使いましたが、感嘆符だけで「ちがう!」を表して・・・。いやはや簡素。禅の味わい・・・。(っていってもわたし禅は知らない。言ってみたかったのよ。許して・・・)
 

# データファイルのロック
if (!&lock_file(CSV)) {
    close(CSV);
    &print_error("書き込みの衝突が起きました。こんなこともあるんだ。");
}

&をつけているからサブルーチンです。openはperlにもともとあるので&はいりませんが、もとからあるものも、自分で作ったサブルーチンも、&をつけるつけないだけで他は同じように使える。う〜む。
あ、わたしが作ったんじゃなかったですが、ファイルをロックする、鍵をかける、というのはインターネットのようなマルチユーザーの場合、他のプログラムにファイルを使われないようにガキをかける訳です。

これは、後ろに纏めてある:

# ロック
# Copyright (C) 1997 by Hiroshi Yuki.
sub lock_file {
    local(*FILE) = @_;
    if ($uselock) {
        eval("flock(FILE, 2)"); # 2=LOCK_EX
        if ($@) {
            # flock が使えない場合、ここに来る。
            return 0;
        }
    }
    return 1;
}

というサブルーチンを使っていますが、この部分、わたしまだ解読していません。まあ、

# データファイルのロック
if (!&lock_file(CSV)) {
    close(CSV);
    &print_error("書き込みの衝突が起きました。こんなこともあるんだ。");
}
 
の部分は、ファイルをロックしろ、もしロックが失敗したら、ファイルをクローズして、エラーを伝えろということです。(もう分かります?)
 

あとは、前回の簡略版とほぼ同じなので(ファイルのアンロックの部分が追加されるだけ)なので省略します。
 

お疲れさま。

ま、プログラムの解説は自分が作るつもりがなくては面白くもなんともないと思いますが・・・。
 

ついでに、蛇足。雑記の12で自慢げに載せた、「雑記原本自動作成プログラム」の解説。
 

#########
# 設定部分

# ↓漢字ライブラリ jcode.pl のファイル名
$jcodelib   = 'jcode.pl';
# ↓保存されるファイルの漢字コード('sjis' 'euc' 'jis' のいずれか)
$kanjicode  = 'sjis';
##########

# 漢字ライブラリの読み込み
require "$jcodelib";

# 初期化
&init_form($kanjicode);
 

ここまでは、定番。もっとも半角数字しか情報がないからわざわざinit_formなんてサブルーチン使わなくても出来そうなもんですが、わたしに能力がないのでこうなる訳です。

# 情報の取得
$number  = $form{'number'};
$number0  =$number -1;
$number2  =$number +1;
$number_z  = $form{'number'};
 

まず、送られてきた半角数字を、$numberという変数に入れちゃいます。それが一行目。
2行目は、送られてきた数字から1を引いた数字。これを1回前の雑記へのリンクで使います。
3行目は、1を足した数字。次回の雑記へのリンクで使います。(最新版の時点ではリンク先なしになる)
4行目zは全角のzです。雑記の見出しのロゴは全角数字を使いたいのでそうしました。もちろん代入しただけでは全角にはなりません。次の処理で全角にします。
 

# 半角を全角に置換する
$number_z =~ s/0/0/g;
$number_z =~ s/1/1/g;
$number_z =~ s/2/2/g;
$number_z =~ s/3/3/g;
$number_z =~ s/4/4/g;
$number_z =~ s/5/5/g;
$number_z =~ s/6/6/g;
$number_z =~ s/7/7/g;
$number_z =~ s/8/8/g;
$number_z =~ s/9/9/g;

もっとスマートな処理もあるのかも知れませんがわたしは知りません。

s=substitute gは何の略か知りませんが、gがないと1つ処理をすると終了します。gがあると最後まで処理します。0から9までこまめに全角に置換します。

# ファイルを開く
open (HTML, ">zakki_0$number.html");
 

ファイルを「書き込み」で開きます。15と送られきたら、zakki_015.htmlというファイルを開くわけです。
ここで賢明な読者は、「このプログラムは10から99まではいいが9以下および100以上ではトラブる不完全なプログラムである、と見抜いたと思います。100以上になると、zakki_1$number.htmlとプログラムを書き換えるからいいんです。(100以上になるとロゴも全角数字から半角数字に変える予定だからどうせ、プログラムは書き換える・・・おいおい100以上も雑記が続くの??)

次は

# ファイルに出力
print HTML <<"end_of_html";

これは、次に、end_of_htmlが出てくるまでの内容を、ファイルハンドルHTMLに出力せよという指示です。
その内容が、雑記の原本になるHTMLファイルの中身、です。赤の部分で変数を使っています。
 

# ファイルに出力
print HTML <<"end_of_html";
<!doctype html public "-//w3c//dtd html 4.0 transitional//en">
<html>
<head>
   <meta http-equiv="Content-Type" content="text/html; charset=Shift_JIS">
   <title>さみだれ雑記$number_z</title>
</head>
<body bgcolor="#F5F5DC">
<div align=right>
<font color="#3366FF"><a href=zakki_0$number0.html>$number0</a>←&nbsp;&nbsp;→<a href=zakki_0$number2.html>$number2</a></font></div>
<table BORDER=0 CELLSPACING=0 CELLPADDING=0 WIDTH="100%" >
<tr><td ALIGN=CENTER VALIGN=CENTER WIDTH="60%" HEIGHT="50" BGCOLOR="#8080FF">
<font size=+3><font color="#FF80FF">さ
</font><font color="#66FF99">み
</font><font color="#FF80FF">だ
</font><font color="#FFFF00">れ
</font><font color="#00FFFF">雑
</font><font color="#FF80FF">記</font></font></td>

<td ALIGN=CENTER WIDTH="10%" BGCOLOR="#FF80FF"><font color="#FFFFFF"><font size=+3>$number_z</font></font></td>

<td ALIGN=RIGHT WIDTH="30%" BGCOLOR="#408080"><font color="#FFFFFF"><font size=+1>1999年7月18日</font></font></td>
</tr>
</table>
<br>
</body>
</html>
end_of_html

HTML的には別に複雑なことしてません。(煩雑なことはしてますが)(もちろんこんなHTML手書きではありません。composer使って、それを修正)
 

# ファイルを閉じる
close (HTML);

# ヘッダの送出
print "Content-type: text/html\n\n";
print "<h1>OK</h1>";
print "<h1>zakki_0$number.html が生成されました。</h1><br>";

# 終了
exit(0);
 

ここは分かりますね。

ちょっと蛇足。print HTML <<"end_of_html";とする場合ではContent-type: text/htmlを書くと、それが文字として表示されます。1行ずつorint文を書く場合には "Content-type: text/html\n\n";は必須です。
 

おしまい。
 
 

残ったプログラムというと、「発言かきかき」がありますが、興味がある方は、ぜひ自分で作ってみたらいかかでしょう? リクエストがあれば解説しますが・・・。

でも手法がわからなければ作れない。ということでヒント。

基本的には「アンケート」と同じです。違う部分は、アンケートは非公開なので、ファイルに書き込めばおしまいですが、「発言」は公開なので、それを表示する必要があります。

つぎのhtmlが最初に表示されるものですが、cgiで出力してます。
 
<!doctype html public "-//w3c//dtd html 4.0 transitional//en">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=Shift_JIS">
<title>bbs</title>
</head>
<body bgcolor=beige>
<font color="#CC0000"><font size=+3>発言かきかき   </font></font>
<a href="http://localhost/prog/bbs.html">書き込む</a>
<p>これまでのみなさんの発言です。
<br>

<table BORDER WIDTH="100%" >
<tr VALIGN=TOP>
<td>K's Korner<br>1999/7/4/ 21:42</td>
<td>発言かきかきコーナーを設けました。 自由に感想、要望、励まし、苦言等お書き下さい。
この画面で改行しても、改行表示はされません。あしからず。</td>
</tr>
</table>

</body>
</html>
 

データファイルはbbs.txtという名前。空ファイルから始めましたが、中身があってもかまいません。

そこからデータを読み出すのですが、わたしの種本は:
 
#!/usr/local/bin/perl
#
# guestsee.cgi
#
# Version 1.0.2
#
# Copyright (C) 1997 by Hiroshi Yuki.
# All rights reserved.
# 結城浩 <hyuki@st.rim.or.jp>
# http://www.st.rim.or.jp/~hyuki/
#
##########
# 設定部分
# ↓(必ず修正)あなたの名前(画面の最下部に表示)
$modifier  = 'yourname';
# ↓(必ず修正)あなたのホームページのURL(「ホームページへ戻る」のリンク先)
$homepage   = 'http://yourdomain/yourpage/';
# ↓(必ず修正)あなたのメールアドレス(エラー時に表示)
$admin      = 'yourname@yourdomain';
# ↓保存されるゲストブックのファイル名
$txtfile    = 'guest.txt';
# ↓表示されるページの <BODY> タグ(背景色や背景イメージ指定で利用)
$bodytag    = '<BODY BGCOLOR="white">';
##########

# ヘッダの送出
print "Content-type: text/html\n\n";

# データファイルのオープン
if (!open(TXT, "$txtfile")) {
    &print_error("ゲストブックのデータファイルが読めません。");
} else {
    &page_begin("ゲストブック");

    print "<HR>\n";
    # データファイルをすべて表示
    while (<TXT>) {
        print;
    }
    &page_end;

    # ファイルをクローズ
    close(TXT);
}

# 終了
exit(0);

##########

# エラー表示
# &print_error("メッセージ");
sub print_error {
    local($msg) = @_;
    &page_begin($msg);
    print "恐れ入りますが、再度試していただくか、";
    print "<A HREF=mailto:$admin>$admin</A>";
    print "までお知らせください。\n";
    &page_end($msg);
    exit(0);
}

# タイトル部分
# &page_begin("メッセージ");
sub page_begin {
    local ($msg) = @_;
    print "<HTML>\n";
    print "<HEAD>\n";
    print "<TITLE>$msg</TITLE>\n";
    print "</HEAD>\n";
    print "$bodytag\n";
    print "<H1>$msg</H1>\n";
}

# ページの終わり
sub page_end {
    print "<HR>\n";
    print "<A HREF=$homepage>ホームページへ戻る</A>\n";
    print "<HR>\n";
    print "<H4>Copyright (C) 1997 by <A HREF=http://www.st.rim.or.jp/~hyuki/cgibook/>Hiroshi Yuki</A>.</H4>\n";
    print "<H4>Modified by $modifier</H4>\n";
    print "</BODY>\n";
    print "</HTML>\n";
}
 

というものでした。参考にして下さい。

このファイルの根幹は

# データファイルをすべて表示
while (<TXT>) {
      print;
}

という箇所です。

何やってんだろ? なんですが、「とほほのWWW入門」のperl編によると:
 

     ファイルから各行を読み込むには次のようにします。 

         open(IN, "datafile.txt");
         while ($xx = <IN>) { print $xx; }
         close(IN);

     変数 $xx を省略した場合は、省略時の暗黙の変数 $_ が使用されます。前記の例は次の
     ように記述することもできます。 

         open(IN, "datafile.txt");
         while (<IN>) { print; }
         close(IN);

     これは、次の記述と同じ意味を持ちます。 

         open(IN, "datafile.txt");
         while ($_ = <IN>) { print $_; }
         close(IN);

     もっと簡単には、次のようにも記述することができます。 

         open(IN, "datafile.txt");
         print while (<IN>);
         close(IN);
 

う〜む。次も「とほほ」からの引用ですが
 
省略時の変数($_)

     perlには省略の美学というものがあり、いろいろな箇所で変数名を省略することができま
     す。省略した場合は、$_ を指定したものとみなされます。 

         <IN>;         # $_ = <IN>; と同じ意味
         print;        # print $_; と同じ意味
         /^From:/      # $_ =~ /^From:/ と同じ意味
 

なんだそうです。
 

while ($xx = <IN>) { print $xx; }
while ($_ = <IN>) { print $_; }
while (<IN>) { print; }
print while (<IN>);

の4つは同じ、と。

 1行目が一番オーソドックスで、「変数$xxに<IN>が代入できる間は、$xxを出力せよ」、と読める、ということは「INの内容をすべて出力せよ」、ということ。

Aさん: けど、変数$xxって、ここでしか使わないんだよ、名前考えるの面倒くさいじゃん。
Bさん: では、$_って変数を、とりあえず使うための変数ってことにして、2行目でいかかでしょう?
Aさん: まだ、面倒だなあ。どうにかなんないの?
Bさん: それではですね。3行目みたいに、変数を省略したら、変数$_を使っているとしましょう。
Aさん: ありがとさん。ついでに4行目のような書き方も認めてくれないかな?
Bさん: もうAさんたら、ほんとに面倒くさいこと嫌いなんですねえ。
 

ということらしい。で
 

# データファイルをすべて表示

while (<TXT>) {
      print;
}

は、「ファイルハンドルTXTの内容をすべて出力する」という意味で、

print while(<IN>);

とも書けるそうな。こっちの方が素人には分かり易い。
 

ま、これで「発言かきかき」のプログラムを作る技術はすべて説明したことになりますので、あとは暇と手間さえかければ出来るはずです。
 
 

  


14←  home  →16