掲示板

  1. 掲示板と機能
    1. 掲示板の目的
      掲示板は誰でも読み出し、書き込みができるのが特徴です。ホームページのFORMタグで送られた記事を、サーバーのファイルに保存しておきます。訪れた人は掲示板の内容を読むことができますし、特定の記事に返事や意見を書くことができます。

    2. 掲示板の手法
      掲示板のページはhtmlであらかじめ作成しておくことができますが、必要な項目や色などを設定できる掲示板も多いようです。
       このページの投稿欄に必要な記事を書き込み送信(投稿)ボタンを押します。このボタンで、CGIを呼び出し、記事をファイルに保存し、ホームページの表示を更新します。

    3. 掲示板で使用される機能
      クッキー: 名前やメールの入力を投稿の度に書き込むことは面倒です。クッキーを利用して、一度書き込んだ内容を保存しておけば、CGIの立ち上げ時に自動入力することができます。 

      パスワード:
      他人の記事を改竄されないよう、パスワードを付加することが多いようです。ただし、パスワードを付け忘れたり、パスワードを忘れると、本人でも変更できなくなります。

      クッキー:
      クッキーはブラウザ側で用意するファイル機能で、webサーバーからの求めで、ファイルに保存しておき、接続時にその内容を送ります。掲示板では、名前やメールアドレスなどを記録し、入力の手間を軽減します。

      メール機能:
      掲示板でメールアドレスを入力する場合、名前をクリックするとメールソフトを立ち上げたり、投稿記事がメールで送られてきます。この機能はここでは紹介しません。

      投稿拒否:
       落書き防止のため送信サイトを記録しておき、必要に応じて特定IPのサイトからの記事の受け取りを拒否することもあります。これは、管理人の仕事になります。この機能もここでは紹介していません。

    4. hiddenタイプのinputフォーム
      CGIの変数は呼び出される毎に初期化されます。そこで、前の状態を引き継ぐために、表示されないinputフォームを利用します。これが、CGIのパラメータとして重要な役割を果たします。
       たとえば、表示を開始する「記事番号」をこの hidden(隠し)タイプのinputフォームに設定しておきます。CGIを起動するときは この隠しタイプのFORMデータも送られますから、CGIでは、「次の記事」ボタンで続きの記事を表示することができます。「戻りボタン」のform記述の例を示します。このボタンを押すと、page=10の値をつけて、bbs.cgiを呼び出します。
      <form action=bbs.cgi method=POST>
       <input type=hidden name=page value=10>
       <input type=submit value="前のページ">
      </form>
      cgiを再起動すると、前に呼び出したときの値は利用できません。hidden(隠し)タイプのinputフォームは、異なるcgiで変数の値を共有するときのみ利用できます。

    5. クッキーの利用
      クッキーは、webのブラウザ側に作成されるファイルで、cgiから作成や参照が可能です。セキュリティに関する保護はありませんから、利用には注意が必要です。クッキーには有効期間の設定が可能で、有効期間が過ぎたクッキーは自動的に消去されます。ここではクッキーを、名前、住所、電子メールアドレス、URL、等の項目の記憶に利用します。クッキーの書き込みは次のように行います。

        print "Set-Cookie: nBBS=$cook; expires=$date_g\n";

      nBBSがクッキーの名前、$cook は記録する文字列です。expires=$date_g は、有効期間の指定で、$date_gに有効最終日(時刻)を設定します。

      クッキーの値に取得は環境変数から行います。

       $cookies = $ENV{'HTTP_COOKIE'};

      Cookieでは、=や;などの特殊記号や日本語文字は FORMのgetと同様な形式で英数字に変換(エンコード)して記録し、読み出し時にこれを逆変換(デコード)してやる必要があります。

    6. クッキーの保存場所
      クッキーは安全な保存方法ではありません。また、保存する場所はブラウザのファイルですから、同じ人が他のPCを利用すると、クッキーは利用できません。クッキーのファイルは、ブラウザやOSにより異なりますが、windows2k、IEブラウザでは、Documents and Setting に利用者毎に保存されます。

  2. 掲示板のスクリプト
    1. 原版
      この掲示板のスクリプトはKento氏作成の原版を元に、理解を容易にするため簡略化とモジュール分割を行いました。

    2. ログファイルの構造
      ログファイルは、投稿された記事を記録するファイルです。記事番号、日付、名前、メールアドレス、記事タイトル、記事、URL、送信サイトのIP、パスワードを記録します。各項目は<>で分離します。

      10<>2003/08/29(Fri) 21:10<>mako<>yt7m-itu@asahi-net.or.jp<>10<>メッセージ<>asahi-net.or.jp<>127.0.0.1<>key<>
      9<>2003/08/29(Fri) 21:09<>mako<>yt7m-itu@asahi-net.or.jp<>9<>message <>asahi-net.or.jp<>127.0.0.1<>yiZArxCOynoCg<>

    3. 記事の表示ページの作成(bbs.cgi)
       この bbs.cgi のホームページから接続します。これは、ログファイルから記事を読み込み、表示用のページを作成します。多くの掲示板では、投稿用のフォームを同じページに作成していますが、ここでは分離しています。

      1. 全体の流れ
        まず、変数を初期化し、set_file 関数でページの設定を行います。次に、decode()でFORMデータを解析し、連想配列 %in にセットします。$modeは、隠しフォームで設定される情報で、これが、regist のとき、記事の追加処理を、update のとき、記事の更新処理をします。
         html 関数でページを表示します。
         #設定ファイルで初期設定する
         &set_file();
         #FORM の解析
         &decode();
        
         if ($mode eq 'regist') { &regist; }
         if ($mode eq 'usrdel') { &usrdel; }
         if ($mode eq 'update') { &usrdel;&regist; }
        
         #掲示板のページを生成
         &html();
        ページの先頭に、「投稿」、「管理用」のフォームがあり、このボタンを押すと、bbsform.cgi、または、bbdmng.cgi のスクリプトに接続します。また、表示の最後に、必要に応じて、「次」または「前」のページを表示するボタンをフォームタグで生成します。このボタンを押すと、現在の記事番号を 隠しフォーム page に設定して、同じ bbs.cgi を呼び直します。
         各記事には編集ボタンがあります。このボタンを押すと、記事番号 no を隠しフォームに設定して、bbsform.cgi を呼び出します。ここで、記事の編集を行います。

      2. 記事の文字処理:docode
         
        フォームの解析を行うdecode処理で、Perl得意の文字変換処理をしています。&valueは各項目の文字列です。
         最初のFORM処理では、+に置き換えられている空白記号を元に戻し、16進文字に変換されている漢字などの特殊コードを元に戻します。&jcode'convert(*value,'sjis'); は文字コードをsjisに統一しています。
         次のタグ処理は、< > などのタグ記号を、&gtなどの文字に変換し、htmlで表示したときタグとして解釈されるのを防ぎます。
         改行処理は、改行記号を <br> に変換し、html として表示する場合、改行されるための処理です。改行の表現は、OSやシステムで異なるため、ここでは、\r\n、\r、\n、を改行として扱います。
        #FORM処理
            $value =~ tr/+/ /;
            $value =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg;
            
        # S-JISコード変換
            &jcode'convert(*value,'sjis');
        
        # タグ処理
            if ($tagkey) { $value =~ s/<>/&lt;&gt;/g; }
            else {
              $value =~ s/&/&amp;/g;
              $value =~ s/</&lt;/g;
              $value =~ s/>/&gt;/g;
            }
        
            # 改行処理
            if ($name eq "comment") {
              $value =~ s/\r\n/<br>/g;
              $value =~ s/\r/<br>/g;
              $value =~ s/\n/<br>/g;
            } else {
              $value =~ s/\r//g;
              $value =~ s/\n//g;
            }

      3. 記事の表示:html関数
        html関数で記事を表示します。ここで、$startと$endは表示開始と終了の番号です。next; は、次の繰り返しに進む制御文です。split(/<>/,$line) は $line から各項目を切り出します。$con が記事の内容になります。$mailの指定があれば、名前に mailto の参照をつけます。

        #startからendページまで表示
        open(IN,"$logfile") || &error("Open Error : $logfile");
        $i=0;
        while ($line=<IN>) {
        local($temp) = $xx;
        $i++;
        if ($i < $start) { next; }
        if ($i > $end) { next; }

        ($no,$date,$name,$mail,$sub,$com,$url,$host,$pw) = split(/<>/,$line);
        if ($mail) { $name = "<a href=\"mailto:$mail\">$name</a>"; }
        if ($url) { $url = "<a href=\"http://$url\" target='_blank'>http://$url</a>"; }
        if ($autolink) { &auto_link($com); }

        print "<hr>[<b>$no</b>] <font color=\"$sub_color\"><b>$sub</b></font>\n";
        print "投稿者:<b>$name</b> 投稿日:$date<br>\n";
        print "<blockquote>$com<P>$url</blockquote>\n";
        }
           #記事表示終了
        close(IN);
        以下のようなページが表示されます。



        投稿用のページは、このページの先頭の「投稿する」ボタンを押すと bbsform.cgi に接続し、表示されます。また、同じ位置の「管理用」ボタンから、管理人用の bbsmng.cgi にも接続します。
      4. 記事の削除:usrdel
         更新処理は、削除と追加機能で実行します。ここでは、指定記事を削除する処理を説明します。
        最初にログファイルを開き、配列@linesに記録します。次に、foreach文で、$in{'no'} と同じ記事番号の記事を探します。$in{'no'} は隠しフォームで指定される、削除する記事番号です。同じ番号でなければ、@newにコピーします。
         記事に記録されたパスワード:PWDと、フォームで指定されたパスワード $in{'pwd'} を比較し、同じでなければ エラーメッセージを表示しログファイルを更新せずに終了します。同じ場合、@newでログファイルを更新します。
        sub usrdel {
          #print "****usedel*****\n";
        
            open(IN,"$logfile") || &error("Open Error : $logfile");
            @lines = <IN>;
            close(IN);
        
            $flag=0;
            @new=();
            foreach (@lines) {
                 ($no,$date,$name,$mail,$sub,$com,$url,$host,$pw) = split(/<>/);
                  if ($in{'no'} eq "$no") { $flag=1; $PWD=$pw; }
                 else { push(@new,$_); }
            }
        
            if ($flag == 0) { &error("該当記事が見当たりません"); }
            # 削除キーを照合
            if ($in{'pwd'} ne "$PWD") { &error("パスワードが違います"); }
        
            # ログを更新
            open(OUT,">$logfile") || &error("Write Error : $logfile");
            print OUT @new;
            close(OUT);
        }

      5. 記事の追加:regist
        この関数は、投稿フォームを作成する cgi:bbsform.cgi より利用されます。記事の内容が不足する場合、追加処理をしないでエラーメッセージを表示し終了します。
         次に、ログファイルを@linesに読み出し、先頭記事と投稿された記事が同じかどうか調べます。同じ内容の場合、追加処理をしません。&get_host();で投稿したサイトのIP番号を調べ、記事番号とともにログファイルに書き出します。また、、クッキーへの書き出しを行います。

        sub regist {
          #print "*****redist******\n";
        
            if ($in{'name'} eq "" || $in{'comment'} eq "" ) { &error("名前または記事が入力されていません"); }
            if ($in{'pwd'} eq "") { &error("パスワードが入力されていません"); }
        
            open(IN,"$logfile") || &error("Open Error : $logfile");
            @lines = <IN>;
            close(IN);
        
            ($tno,$tdate,$tname,$tmail,$tsub,$tcom) = split(/<>/, $lines[0]);
            if ($in{'name'} eq "$tname" && $in{'comment'} eq "$tcom") {
                        &error("同じ記事です");
            }
            #投稿サイトのIPを取得する
            &get_host();
            # 記事番号を増加
            $no = $tno + 1;
            # 削除キーを設定
            if ($in{'pwd'} ne "") { $PW = $in{'pwd'}; }
            # 最大記事数以上なら最後の記事を削除後、記事を追加
            while ($max <= @lines) { pop(@lines); }
            unshift(@lines,"$no<>$date<>$in{'name'}<>$in{'email'}<>$in{'sub'}<>$in{'comment'}<>$in{'url'}<>$host<>$PW<>\n");
        
        #記事配列をファイルに書き出す
            open(OUT,">$logfile") || &error("Write Error : $logfile");
            print OUT @lines;
            close(OUT);
        
            #利用者情報をクッキーに記録する
            &set_cookie();
        }

      6. ソース
        全体のソースはこちらからご覧ください。

    4. 投稿記事を記録:bbsform.cgi
       
      次のようなFORMを作成します。クッキーを調べ、存在すれば、名前やメールアドレスを初期設定します。また、「編集ボタン」からの呼び出しであれな、同じ記事番号の記事を読み出し、フォームに書き込みます。
       



      ここで、一つ「はまってしまった」問題を紹介します。次の文は、textareaのフォームを生成し、その文字列を$c_comment に初期設定しています。
       <textarea name=comment cols="$f_cols" rows="6" wrap="soft" >$c_comment</textarea>
      input type の場合は、初期設定は value句で行います。
       <input type=text name=sub size="$f_sub" value="$c_sub">
      textarea の場合にも同じ、value句で設定すると、失敗します。注意してください。

      全体のソースはこちらからご覧ください。

    5. 管理者用ページの作成(bbsmng.cgi)
       管理者は独自の判断で、記事を削除できることが必要です。このcgiは bbsmng.cgi として別cgiとして作成しています。投稿記事を表の形で一覧し、削除ボタンをチェックした後、「削除する」ボタンで削除します。削除終了後、bbs.cgi に戻り、記事を表示します。



      全体のソースはこちらからご覧ください。

  3. エラー処理

    1. デバッグ
      実行して
       Internal Server Error
      が表示されると、お手上げです。身に覚えが内場合、Perlでコンパイルして、まず、文法エラーを探します。lこのとき、前にも紹介しましたが、
        #$ENV{'REQUEST_METHOD'}='GET';
       #$ENV{'QUERY_STRING'}='mode=17'
      などと、FORMパラメータを設定しておくと、FORMにしたがった実行ができますから便利です。
       Perlで実行できて、スクリプトで組み込むと異常の場合、先頭の
       #!/usr/bin/perl
      を確認してください。Perlで実行する場合、この文は無視されます。また、Perl実行で表示される文が正規のhtmlの文かどうかもチェックしてください。