#!/usr/local/bin/perl require 5.004; # このファイルは必ず EUCコードで保存すること! # This file must be saved as EUC-JP charset text. # 【注意】このスクリプトはβ版であり、実装されていない機能があります! # # <<< NY掲示板 >>> Version beta-0.9.7 [Apr.23,1999] # Scripted by Nagano Yutaka # # ・再利用・再配布・改造は自由に行って構いません。 # ・動作の保証、サポートは一切いたしません。 # ・flock() を使用しているので、Windows 95/98 では動作しません。 # ・ファイルを開いたままunlinkするので、Windows NT でも動作しません。 # ・HTTP-Daemon として、Apache を想定しています。 # # 設置方法: # ・このスクリプト単独で動作する。特別なライブラリは不要。 # ・"$filebase.dat" (下で設定するベースファイル名+拡張子.dat) # という名前の空のファイルを同じディレクトリに置く。 # これがデータベースファイルで、アクセス権はこのスクリプトが # 読み書き可能であるようにしておくこと。 # # 運用上の注意点: # ・削除された記事は、"$filename.deleted" というファイルに蓄積されて # ゆくので、適宜このファイルを削除しないと無制限に肥大化する。 # # このスクリプト作成にあたって、 # MiniBBS (http://www.rescue.ne.jp/) # のページデザインおよびユーザーインターフェースを参考にしました。 # 偉大なるフリーCGIスクリプトであるMiniBBSに感謝と賞賛を捧げます。 # $version = 'beta-0.9.7'; ## ユーザー設定変数 ################################################## # スクリプトファイルのベースファイル名(拡張子を除いた部分) $filebase = "./nybbs-0.9.4"; # 管理用パスワード - crypt() 関数で暗号化したものをここに記入 $password = ''; # 管理人プロフィール $webmaster_name = '名前の記入なし'; $webmaster_mail = 'yourname@yourdomain'; # Cookie の有効期限(〜日後) $cookie_keep_days = 30; # 1ページに表示する件数 $deflines = 20; # 最大記録保持数 $maxlines = 100; # 掲示板のタイトル $bbstitle = "NY掲示板 Version $version"; # タイトルに画像などを使用する場合はここに記述 # (次の行がコメントアウトされていると $bbstitle をタイトルとして表示) #$bbstitle_html = ""; # ページのタグ $bbsbody = ''; # タイトル下のエリアに表示するHTML $bbscomment = ""; # 記事のタイトルの色 $subject_color = '#FFFFFF'; $subject_bgcolor = '#FF69B4'; ## サブルーチンライブラリ ############################################ # スクリプトの作者表示 sub print_scripter { print < NY掲示板 Ver.$version
Scripted by Nagano Yutaka
HTML ; } # エラー出力:@_ に与えられたメッセージをリスト出力。$_[0]はタイトル sub error(@) { print < $bbstitle 【エラー】 HTML ; $_ = shift; print "

$_

\n"; print "\n"; print "
\n"; print qq{$bbstitle  }; print qq{管理人:$webmaster_name\n}; &print_scripter& print "\n\n"; exit; } # Internal Error 出力 sub internal_error { &error( "エラー", "データの処理中に $! エラーが発生しました。", "掲示板システムに深刻なトラブルが発生しています。"); } # HTMLタグチェック # ・使用可能タグのみを通す。 # ・タグの閉じ忘れを修正する。 # ・URLとメールアドレスを自動的にリンクにする。 sub check_html($) { my($m,@stack); $_ = $_[0]; # タグ整合性チェック while ( /<([^<\n]*)/gm ) { $m = $1; next if $m =~ /^BR>/i; if ( $m =~ /^(FONT)(\s.*?>|>)/i || $m =~ /^(B)>/i || $m =~ /^(I)>/i || $m =~ /^(TT)>/i || $m =~ /^(SUP)>/i || $m =~ /^(SUB)>/i ) { unshift @stack,uc($1); next; } if ( $m =~ /^\/(FONT)>/i || $m =~ /^\/(B)>/i || $m =~ /^\/(I)>/i || $m =~ /^\/(TT)>/i || $m =~ /^\/(SUP)>/i || $m =~ /^\/(SUB)>/i ) { shift @stack if $stack[0] eq uc($1); next; } error("投稿できません","使用禁止HTMLタグが使われています。","処理の都合上、HTMLタグの中で改行することはできません。","半角の「<」や「>」を使いたいときは、それぞれ「&lt;」「&gt;」と記述してください。"); } # 閉じ忘れタグを閉じておく foreach $m ( @stack ) { $_ .= ""; } # 改行コードを
に s/\r\n/
/gs; s/\r/
/gs; s/\n/
/gs; # URLをリンクに s/((http|ftp):[\w\/\.\-~%\?=&]*)/$1<\/A>/g; # メールアドレスをリンクに s/([\w\.\-%_=]*@[\w\.\-%_=]*)/$+<\/A>/g; $_; } # timeを入力し、日本語の日付文字列を返す sub formatTime($) { my ($sec,$min,$hour,$mday,$mon,$year,$wday) = localtime $_[0]; sprintf("%d月%d日(%s) %d時%d分", $mon+1,$mday,('日','月','火','水','木','金','土')[$wday],$hour,$min); } # timeを入力し、HTTP-date形式(RFC1123-date)の文字列を返す sub getHTTPdate($) { my($s,$m,$h,$d,$mo,$y,$w) = gmtime $_[0]; sprintf( "%s, %02d %s %d %02d:%02d:%02d GMT", (Sun,Mon,Tue,Wed,Thu,Fri,Sat)[$w], $d, (Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec)[$mo], 1900 + $y, $h, $m, $s ); } # 英数字(と「_*-.@」)以外の記号を %XX 形式にエンコードする # スペースは + に置換する sub URLencode($) { $_ = $_[0]; s/([^\w\*\-\@\.])/'%'.sprintf"%02X",unpack('C',$1)/eg; s/%20/+/g; $_; } # URLencode によってエスケープされた文字列をデコードする sub URLdecode($) { $_ = $_[0]; tr/+/ /; s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg; $_; } ## ここからメインプログラム ########################################## # データベースのファイル名 $file = $filebase . '.dat'; # テンポラリファイル名 $tempfile = $filebase . '.temp'; # 削除ログファイル $deletedfile = $filebase . '.deleted'; # スクリプトのURLを取得 $scripturl = "http://$ENV{HTTP_HOST}$ENV{SCRIPT_NAME}"; # リモートホスト名を $host に取得 $host = $ENV{REMOTE_HOST}; $_ = $ENV{REMOTE_ADDR}; if ( $host eq $_ || $host eq "" ) { $host = gethostbyaddr( pack('C4',split/\./),2 ) || $_; } # 受信文字列を %form に格納 (POSTメソッド) if ($ENV{REQUEST_METHOD} eq "POST") { read(STDIN, $buffer, $ENV{CONTENT_LENGTH}); foreach ( split /&/,$buffer ) { ($name,$_) = split /=/; # デコード tr/+/ /; s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg; # 行頭・行末の空白を削除 s/^( |\xa1\xa1)*(.*?)( |\xa1\xa1)*$/$2/s if $name ne 'message'; # 空白行は無視 $form{$name} = $_ if !/\A[ \xa1]*\Z/; } } if ( $form{action} eq 'delete' ) { &delete_data; } elsif ( $form{action} eq 'post' ) { &post_data; } else { &main_view; } exit; ### アクション毎の処理ルーチン ####################################### # ■メインビュー:掲示板データをHTML出力 sub main_view { $page = $ENV{QUERY_STRING}; $page = 0 if $page < 1; # Cookie を @cookie に取得 # HTTP_COOKIE : ID="<名前>\t<メール>" # @cookie = ( <名前> , <メール> ) if ( $_ = $ENV{HTTP_COOKIE} ) { ($name,$_) = split /=/; @cookie = split /\t/, &URLdecode($_); } # $page にリクエストページ番号を指定してファイルを読み、以下の値を取得 # @list : 1ページ分のデータ # $totallines : 全記録件数 # $islast : 最後のページの場合、true # $current : 実際に表示されるページ番号 $totallines = 0; open FILE,"<$file" or &internal_error; READFILE: for ( $current=0; $current <= $page; $current++ ) { if ( eof FILE ) { $islast = 1; $current-- if $current; last READFILE; } @list = (); for ( $i=0; $i<$deflines; $i++ ) { if ( $_ = ) { chop; push @list, $_; $totallines++; } else { $islast = 1; last READFILE; } } } $current-- if !$islast; $totallines++ while ; close FILE; # HTML出力 print "Content-Type: text/html; charset=x-euc-jp\n"; print "Last-Modified: ",&getHTTPdate((stat $file)[9]),"\n"; # print "Pragma: no-cache\n"; if ( $ENV{HTTP_COOKIE} ) { print "Set-Cookie: $ENV{HTTP_COOKIE}; "; print "expires=",&getHTTPdate( time+3600*24*$cookie_keep_days ),"\n"; } else { print "Set-Cookie: ID=; expires=0\n"; } print "\n"; print < $bbstitle $bbsbody
HTML ; if ( $bbstitle_html ) { print $bbstitle_html; } else { print "$bbstitle
"; } print <
$bbscomment
投稿者
メール HTML ; print '  '; print '名前とメールアドレスの保存
"; print <題 名

本文 <FONT><B>などの一部のタグが使えます。URLやメールアドレスは自動的にリンクになります。


     (マークの項目は省略不可)

新しい記事から表示します。最高$maxlines件の記事が記録され、それを超えると古い記事から削除されます。
1ページにつき、$deflines件の記事を表示します。

HTML ; &pagenavi; print "
\n"; print "
<記事なし>


\n" if !scalar @list; foreach ( @list ) { &print_article( split /\t/ ); } &pagenavi; print <
パスワード  
このページの管理人:
$webmaster_name
HTML ; &print_scripter; print "
\n\n\n"; } sub pagenavi { $_ = int( ($totallines-1) / $deflines ) + 1; $_ = 1 if $_ < 1; print qq{}; print "【",$current+1,"/$_ ページ】"; if ( $current ) { print " ▲ 前頁"; } else { print " ▲ 前頁"; } if ( !$islast ) { print " ▼ 次頁"; } else { print " ▼ 次頁"; } print "
\n"; } sub print_article { my ($ID,$tm,$name,$mail,$subject,$message,$remote) = @_; print qq{
\n} if $subject_bgcolor; print qq{}; if ( $subject ) { print "$subject"; } else { print "<無題>"; } print " \n"; print "
\n" if $subject_bgcolor; print "投稿者:"; if ( $mail ) { print qq{$name}; } else { print qq{$name}; } print "さん ",&formatTime($tm); print " "; print qq{ 不適切}; print "
\n"; print "
\n$message\n
\n
\n"; } # ■投稿受理:投稿されたデータをデータベースに記録 sub post_data { &error("拒否されました","「$bbstitle」以外のページからの投稿は受け付けていません。") if $ENV{HTTP_REFERER} !~ /^$scripturl/; &error("データ不足","投稿者名と記事本文は省略できません。") if !$form{name} || !$form{message}; if ( $form{mail} ) { &error("不正なメールアドレス","入力されたメールアドレスが間違っている可能性があります。") if $form{mail} !~ /\A[\w\.\-%_=]*@[\w\.\-%_=]*\Z/; } if ( $form{cookie} ) { $ENV{HTTP_COOKIE} = "ID=".&URLencode("$form{name}\t$form{mail}"); } else { undef $ENV{HTTP_COOKIE}; } @postdata = ($ENV{UNIQUE_ID},time,$form{name},$form{mail},$form{subject},&check_html($form{message}),$host); if ( $form{preview} ) { # ■投稿プレビュー:投稿の確認 print "Content-Type: text/html; charset=x-euc-jp\n"; if ( $ENV{HTTP_COOKIE} ) { print "Set-Cookie: $ENV{HTTP_COOKIE}; "; print "expires=",&getHTTPdate( time+3600*24*$cookie_keep_days ),"\n"; } else { print "Set-Cookie: ID=; expires=0\n"; } print "\n\n\n"; print "\n"; print "$bbstitle 【投稿の確認】\n\n"; print "$bbsbody\n"; print "

投稿内容の確認

\n"; print "これは投稿確認用のプレビュー画面です。実際には記事は投稿されていません。
\n"; print "ブラウザの「戻る」をクリックして、修正または投稿処理を行ってください。
\n
\n
\n"; &print_article( @postdata ); &print_scripter; print "
\n\n\n"; } elsif ( $form{submit} ) { open FILE,"<$file" or &internal_error; open TEMP,">$tempfile" or &internal_error; flock TEMP,2; print TEMP join("\t",@postdata),"\n"; $i = 1; while ( ) { last if ++$i > $maxlines; print TEMP; } flock FILE,2; rename $tempfile,$file; flock FILE,8; close FILE; flock TEMP,8; close TEMP; &main_view; } else { &internal_error; } } # ■管理者削除:選択されたIDのメッセージを削除 sub delete_data { &error("拒否されました","「$bbstitle」以外のページからの削除処理は受け付けていません。") if $ENV{HTTP_REFERER} !~ /^$scripturl/; if ( crypt($form{password}, $password) ne $password ) { &error( "パスワードが違います", "記事の削除には管理者パスワードが必要です。"); } @ID = (); foreach( split /&/,$buffer ) { ($key, $value) = split /=/; push @ID, $value if $key eq 'ID'; } open FILE,"<$file" or &internal_error; open TEMP,">$tempfile" or &internal_error; flock TEMP,2; unless (-e $deletedfile) { open LOG,">$deletedfile"; } else { open LOG,">>$deletedfile"; } flock LOG,2; seek LOG, 0, 2; while( $value = ) { ($key) = split /\t/,$value; foreach ( @ID ) { if ( $key eq $_ ) { print LOG $value; $value = ''; } } print TEMP $value; } flock LOG,8; close LOG; flock FILE,2; rename $tempfile,$file; flock FILE,8; close FILE; flock TEMP,8; close TEMP; &main_view; }