#!/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";
foreach(@_) { 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タグの中で改行することはできません。","半角の「<」や「>」を使いたいときは、それぞれ「<」「>」と記述してください。");
}
# 閉じ忘れタグを閉じておく
foreach $m ( @stack ) {
$_ .= "$m>";
}
# 改行コードを
に
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
新しい記事から表示します。最高$maxlines件の記事が記録され、それを超えると古い記事から削除されます。
1ページにつき、$deflines件の記事を表示します。
\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\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;
}