#!/usr/bin/perl
#
#################################################################
# 簡易掲示板 T-Note Ver.3.XX (c)Takahiro Nishida 98/12/18       #
#                   http://www2s.biglobe.ne.jp/~tnishida/       #
#################################################################
#
### （変数設定部） 詳細は上記ＵＲＬをご覧下さい #################
# データファイルの入っているディレクトリ
$basedir = ".";
# 拡張子
$ext = "cgi";
# 文字コード
$code = "sjis";
# フォームの送信方法
$method = "post";
# 管理用パスワード
$password = "syo98";
# 管理者メールアドレス
$admin_email = "10083@contest.thinkquest.gr.jp";
# 文章の最大長（全角での文字数）
$max_words = 1000;
### （変数設定部） ここまで #####################################

require './jcode.pl';

$lockfile="$basedir/lockdir/tn.lock";

$verno = '3.10';

print "Content-type: text/html\n\n";

&main;

sub main{
	&init_variables;
	&check_input;
	&file_lock;
	&open_datafile;
	&check_password;
	&add_newdata;
	&update_datafile;
	&show_html;
	&unlock;
}



##### 変数の初期化
sub init_variables{
	$newflag = 0; # 更新があったかどうかのフラグ
	$from = 0; # 記事表示範囲
	$to = 0;
	$ano = 0; # 全発言数
	$page_link = ""; # 前後の記事へのリンク
	@configs = (); # コンフィグ行を保持
	$max_length = $max_words * 2; # 文章の最大長（byte）
	$default_color = "#000000,#F5F5DC,#000099,#009900,#FF0000,#0000FF"; # 標準の色設定
}



##### 入力のチェック
sub check_input{
	if ($ENV{'REQUEST_METHOD'} eq "POST") {
		read(STDIN, $buffer, $ENV{'CONTENT_LENGTH'});
	}
	else {
		$buffer = $ENV{'QUERY_STRING'};
	}
	@pairs = split(/&/, $buffer);
	foreach $pair (@pairs) {
		($vn, $value) = split(/=/, $pair);
		$FORM{$vn} = &decode($value);
		(length($FORM{$vn}) <= $max_length) || &error(9);
	}
	
	$book = $FORM{'book'} || &error(3);
	$who  = &replace($FORM{'who'});
	$mail = &replace($FORM{'mail'});
	$url  = &replace($FORM{'url'});
	$head = &replace($FORM{'head'});
	$com  = &replace($FORM{'com'});
	$from = $FORM{'from'};
	$to   = $FORM{'to'};
	$rm   = $FORM{'rm'};
	$pwd  = $FORM{'pwd'};
	
	($book =~ /^\w+$/) || &error(8);
	(!$mail) || ($mail =~ /(.+)\@(.+)\.(.+)/) || &error(4);
	(!$url) || ($url =~ /^http:\/\//) || &error(5);
}



##### データファイルのオープン
sub open_datafile{
	$datafile = "$basedir/$book.txt";
	
	(-f $datafile) || &error(7, $book);
	open(FILE,"$datafile") || &error(1, $datafile);
	@datas = <FILE>;
	close(FILE);
	
	# 初期化
	unless(@datas){
		$datas[0] = "見出し\tタイトル\t\n";
		$datas[1] = "コメント\n";
		$datas[2] = "http://戻り先ＵＲＬ\n";
		$newflag = 1;
	}
	# （V2->3 にも対応）
	if($datas[3] !~ /^\d+,/){
		splice(@datas, 3, 0, "20,$default_color,,1,0,\n");
		$newflag = 1;
	}
	
	# コンフィグ行を取り出す
	@configs = splice(@datas, 0, 4);
}



##### 管理モード
sub check_password{
	return unless ($pwd eq $password);
	
	# 発言削除
	if($rm > 0 && $rm <= @datas){
		$rm--;
		$datas[$rm] = "\n";
		$newflag = 1;
	}
	
	# 管理フォーム表示
	$admform = 1 if ($FORM{'admmode'});
	# 修正ボタンが押されてなければ終了
	$FORM{'fix'} || return;
	
	# タイトル等変更
	$title    = &replace($FORM{'atitle'});
	$headline = &replace($FORM{'ahead'}, 1);
	$comment  = &replace($FORM{'acom'}, 1);
	$back     = &replace($FORM{'aback'});
	$sdef     = ($FORM{'asdef'} > 0) ? $FORM{'asdef'} : 20;
	$color    = &replace($FORM{'acolor'}) || $default_color;
	$bgimg    = &replace($FORM{'abgimg'});
	$shead    = ($FORM{'ashead'}) ? 1 : 0;
	$pform    = ($FORM{'apform'}) ? 1 : 0;
	
	$configs[0] = "$headline\t$title\t\n";
	$configs[1] = "$comment\n";
	$configs[2] = "$back\n";
	$configs[3] = "$sdef,$color,$bgimg,$shead,$pform,\n";
	
	$newflag = 1;
}



##### 新規書き込みを追加する
sub add_newdata{
	return unless ($who && $com);
	
	local ($remote_host) = $ENV{'REMOTE_HOST'};
	local (@ts) = localtime(time);
	$ts[4]++;
	$ts[5] += 1900;
	$date_now = sprintf("%02d/%02d/%02d %02d:%02d", $ts[5], $ts[4], $ts[3], $ts[2], $ts[1]);
	push(@datas, "$date_now\t$who\t$mail\t$url\t$com\t$remote_host\t$head\t\n");
	$newflag = 1;
}



##### データファイルの更新
sub update_datafile{
	return unless ($newflag);
	
	open(FILE,">$datafile") || die &error(2, $datafile);
	print FILE @configs;
	print FILE @datas;
	close(FILE);
}



##### 表示
sub show_html{
	local($htmlbuf);
	
	# デザインの取得
	chop(@configs);
	($headline, $title) = split("\t", $configs[0]);
	$comment = $configs[1];
	$back = $configs[2];
	($sdef, $cfont, $cbody, $clink, $cname, $cnumb, $chead, $bgimg, $shead, $pform) = split(",", $configs[3]);
	
	#「配列番号＝発言番号」ダミー行を挿入
	unshift(@datas, "dmyline");
	$ano = $#datas; # 総発言数
	
	&get_show_scope;
	
	$htmlbuf  = &html_header;
	$htmlbuf .= &html_form_admin if ($admform); 
	$htmlbuf .= &html_form_new if ($pform); # 発言欄が上の場合
	$htmlbuf .= &html_form_articles;
	$htmlbuf .= &html_articles;
	$htmlbuf .= &html_form_new if (!$pform); # 発言欄が下の場合
	$htmlbuf .= &html_form_delete;
	$htmlbuf .= &html_footer;
	
	print $htmlbuf;
}



##### 表示範囲を取得
sub get_show_scope{
	local($i, $pfrom, $pto, $nfrom, $nto);
	
	### デフォルトの表示範囲
	unless($from > 0 && $from <= $ano){
		$from = ($ano > $sdef) ? ($ano - $sdef + 1) : 1;
		$from = 0 if ($ano == 0);
	}
	unless($to > 0 && $to <= $ano){
		$to = $ano;
	}
	&error (6) if ($from > $to);

	### 前の...件
	if($from > 1){
		$pto = $from - 1;
		$pfrom = ($from <= $sdef) ? 1 : ($from - $sdef);
		$i = $pto - $pfrom + 1;
		$page_link .= "<B>[<A HREF=\"tnote.$ext?book=$book&from=$pfrom&to=$pto\">←No.$pfrom〜$pto</A>]</B>\n";
	}
	
	$page_link .= "<B>[<FONT COLOR=\"$cnumb\">No.$from〜$to</FONT>]</B>\n";

	### 次の...件
	if($to < $ano){
		$nfrom = $to + 1;
		$nto = ($to + $sdef > $ano) ? $ano : ($to + $sdef);
		$i = $nto - $nfrom + 1;
		$page_link .= "<B>[<A HREF=\"tnote.$ext?book=$book&from=$nfrom&to=$nto\">No.$nfrom〜$nto→</A>]</B>\n";
	}
}



##### ヘッダ
sub html_header{
	"
<HTML>
<HEAD>
<TITLE>$title</TITLE>
</HEAD>
<BODY BACKGROUND=\"$bgimg\" BGCOLOR=\"$cbody\" LINK=\"$clink\" VLINK=\"$clink\">
<FONT COLOR=\"$cfont\">
[<A HREF=\"$back\">戻る</A>]
<P>
$headline
<P>
$comment
<HR>
	";
}



##### フッタ
sub html_footer{
	"
<TABLE ALIGN=\"right\" BORDER=\"2\" CELLPADDING=\"0\" CELLSPACING=\"0\">
<TR><TD BGCOLOR=\"\#FFFF99\">
<A HREF=\"http://www2s.biglobe.ne.jp/~tnishida/\" TARGET=\"_top\">
<FONT SIZE=\"2\" COLOR=\"#000099\">
<B>Powered by T-Note Ver.$verno</B>
</FONT></A></FONT>
</TD></TR></TABLE>
</FONT></BODY></HTML>
	";
}



##### 発言用フォーム
sub html_form_new{
	local($form_head) = "発言タイトル：<BR><INPUT TYPE=\"text\" NAME=\"head\" SIZE=\"60\"><BR>
\n" if ($shead);
	"
<A NAME=\"new\">
<FORM METHOD=\"$method\" ACTION=\"tnote.$ext\">
<INPUT TYPE=\"hidden\" NAME=\"book\" VALUE=\"$book\">
名前：<BR><INPUT TYPE=\"text\" NAME=\"who\" VALUE=\"$who\" SIZE=\"20\"><BR>
メールアドレス：<BR><INPUT TYPE=\"text\" NAME=\"mail\" VALUE=\"$mail\" SIZE=\"40\"><BR>
URL：<BR><INPUT TYPE=\"text\" NAME=\"url\" VALUE=\"$url\" SIZE=\"60\"><BR>
$form_head
内容：<BR><textarea NAME=\"com\" rows=\"7\" cols=\"60\"></textarea><BR>
<P>
<INPUT TYPE=\"submit\" VALUE=\"書き込み\">
<INPUT TYPE=\"reset\" VALUE=\"やりなおし\">
　<FONT SIZE=\"2\"><I>※ 名前と内容は必須です。文章は全角$max_words文字まで。HTMLタグは使えません。</I></FONT>
</FORM>
<HR>
	";
}



##### 記事表示用フォーム
sub html_form_articles{
	"
<CENTER>
<FORM METHOD=\"$method\" ACTION=\"tnote.$ext\">
<INPUT TYPE=\"hidden\" NAME=\"book\" VALUE=\"$book\">
<TABLE BORDER=\"0\" CELLPADDING=\"2\" CELLSPACING=\"0\">

<TR>
<TH COLSPAN=\"3\"><FONT COLOR=\"$cfont\">
総発言数：$ano件　
[<A HREF=\"#new\">新規書込</A>]
</FONT></TH>
</TR>

<TR>
<TH><FONT COLOR=\"$cfont\">[<A HREF=\"tnote.$ext?book=$book&from=1&to=$ano\">全発言</A>]</FONT></TH>
<TH><FONT COLOR=\"$cfont\">[<A HREF=\"tnote.$ext?book=$book\">最新$sdef発言</A>]</FONT></TH>
<TH>
<FONT COLOR=\"$cfont\">
No.<INPUT TYPE=\"text\" NAME=\"from\" SIZE=\"3\" VALUE=\"$from\">
〜
No.<INPUT TYPE=\"text\" NAME=\"to\" SIZE=\"3\" VALUE=\"$to\">
を
<INPUT TYPE=\"submit\" VALUE=\"表\示\">
</FONT></TH>
</TR>

</TABLE>
</FORM>
</CENTER>
<HR>
	";
}



##### 記事表示
sub html_articles{
	return unless ($ano);
	local($no, $htmlbuf);

	$htmlbuf .= "<CENTER>$page_link</CENTER><HR>\n";
	for($no = $to; $no >= $from; $no--){
		chop($datas[$no]);
		# 削除されていた場合
		unless($datas[$no]){
			$htmlbuf .= "<FONT SIZE=\"2\" COLOR=\"$cnumb\"><B>No.$no</B> (削除済)</FONT><HR>";
			next;
		}
		
		@sps = split("\t", $datas[$no]);
		
		# 発言番号、日時
		$htmlbuf .= "<FONT SIZE=\"2\" COLOR=\"$cnumb\"><B>No.$no</B> ($sps[0])</FONT>\n";
		
		# 発言タイトル
		if($shead && $sps[6]){
			$htmlbuf .= "<B><FONT SIZE=\"3\">title:<FONT COLOR=\"$chead\">$sps[6]</FONT></FONT></B>\n";
		}
		
		# 名前
		$htmlbuf .= "<BR><B>Name:<FONT SIZE=\"4\" COLOR=\"$cname\">$sps[1]</FONT></B>\n";
		
		# ホスト名
		$htmlbuf .= "(<FONT SIZE=\"2\"><I>$sps[5]</I></FONT>)\n";
		
		# メールアドレス
		if($sps[2]){
			($htmlbuf .= "<BR><FONT SIZE=\"2\"><B>Email:</B><A HREF=\"mailto:$sps[2]\">$sps[2]</A></FONT>\n");
		}
		
		# ＵＲＬ
		if($sps[3]){
			($htmlbuf .= "<BR><FONT SIZE=\"2\"><B>URL:</B><A HREF=\"$sps[3]\">$sps[3]</A></FONT>\n");
		}
		
		# 内容
		$htmlbuf .= "<P>$sps[4]<P><HR>\n";
	}
	
	$htmlbuf .= "<CENTER>$page_link</CENTER><HR>\n";
}



##### 発言削除用フォーム
sub html_form_delete{
	"
<CENTER>
<FORM METHOD=\"$method\" ACTION=\"tnote.$ext\">
<INPUT TYPE=\"hidden\" NAME=\"book\" VALUE=\"$book\">
<INPUT TYPE=\"hidden\" NAME=\"from\" VALUE=\"$from\">
<INPUT TYPE=\"hidden\" NAME=\"to\" VALUE=\"$to\">
<B>発言削除</B>：No.<INPUT TYPE=\"text\" NAME=\"rm\" SIZE=\"3\">　
管理パスワード：<INPUT TYPE=\"password\" NAME=\"pwd\" SIZE=\"10\" VALUE=\"$pwd\">
<INPUT TYPE=\"submit\" VALUE=\"削除実行\">
<INPUT TYPE=\"submit\" NAME=\"admmode\" VALUE=\"デザイン変更\">
</FORM>
</CENTER>
<HR>
	";
}



##### 管理用フォーム
sub html_form_admin{
	local($pf1, $pf0, $sh1, $sh0);
	
	$headline = &replace($headline);
	$comment = &replace($comment);
	
	($pform) ? ($pf1 = "CHECKED") : ($pf0 = "CHECKED");
	($shead) ? ($sh1 = "CHECKED") : ($sh0 = "CHECKED");
	
	"
<FORM METHOD=\"$method\" ACTIONT=\"tnote.$ext\">
<INPUT TYPE=\"hidden\" NAME=\"book\" VALUE=\"$book\">
<INPUT TYPE=\"hidden\" NAME=\"pwd\" VALUE=\"$pwd\">
<INPUT TYPE=\"hidden\" NAME=\"admmode\" VALUE=\"1\">
<TABLE BORDER=\"2\" WIDTH=\"100%\">
<TR><TD BGCOLOR=\"#FFFFCC\" NOWRAP>
<FONT SIZE=\"3\">
<CENTER><B><U>■ T-Note Ver.$verno デザイン変更用フォーム ■</U></B></CENTER>
<P>
<CENTER><FONT COLOR=\"#FF0000\">※ 古いブラウザだとタグ関連がうまく修正されないことがあります。※</FONT></CENTER>
<P>
【タイトル】 <INPUT TYPE=\"text\" NAME=\"atitle\" SIZE=\"70\" VALUE=\"$title\"><BR>
【見出し】 <INPUT TYPE=\"text\" NAME=\"ahead\" SIZE=\"70\" VALUE=\"$headline\"><BR>
【説明文】 <INPUT TYPE=\"text\" NAME=\"acom\" SIZE=\"70\" VALUE=\"$comment\"><BR>
【戻り先】 <INPUT TYPE=\"text\" NAME=\"aback\" SIZE=\"40\" VALUE=\"$back\"><BR>
【表\示単位】 <INPUT TYPE=\"text\" NAME=\"asdef\" SIZE=\"5\" VALUE=\"$sdef\">発言ずつ表\示<BR>
【配色】 <INPUT TYPE=\"text\" NAME=\"acolor\" SIZE=\"70\" VALUE=\"$cfont,$cbody,$clink,$cname,$cnumb,$chead\"><BR>
<FONT SIZE=\"2\">（※ フォント、背景、リンク、名前、発言番号、タイトル、の順に<B>半角カンマ区切り</B>で入力）</FONT><BR>
【背景画像】 <INPUT TYPE=\"text\" NAME=\"abgimg\" SIZE=\"15\" VALUE=\"$bgimg\"><BR>
【発言フォーム】 
<INPUT TYPE=\"radio\" NAME=\"apform\" VALUE=\"1\" $pf1>記事の上　　
<INPUT TYPE=\"radio\" NAME=\"apform\" VALUE=\"0\" $pf0>記事の下<BR>
【発言タイトル】 
<INPUT TYPE=\"radio\" NAME=\"ashead\" VALUE=\"1\" $sh1>要　　
<INPUT TYPE=\"radio\" NAME=\"ashead\" VALUE=\"0\" $sh0>不要<BR>
<P>

<INPUT TYPE=\"submit\" NAME=\"fix\" VALUE=\"作業実行\">
[<A HREF=\"tnote.$ext?book=$book\">通常モードに戻る</A>]
[<A HREF=\"http://www2s.biglobe.ne.jp/~tnishida/cgitools/note.html#design\" TARGET=\"_blank\">ヘルプ</A>]
</FONT>
</TD></TR>
</TABLE>
</FORM>
<HR>
	";
}



##### デコード
sub decode{
	local($org) = @_;
	$org =~ tr/+/ /;
	$org =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg;
	&jcode'convert(*org, $code);
	$org;
}



##### HTMLに影響を与える文字の変換
sub replace{
	local($org, $frag) = @_;
	unless($frag){
		$org =~ s/</&LT;/g;
		$org =~ s/>/&GT;/g;
		$org =~ s/"/&#34;/g;
		$org =~ s/'/&#39;/g;
	}
	$org =~ s/\t//g;
	$org =~ s/\cM//g;
	$org =~ s/\n{2,}/<P>/g;
	$org =~ s/\n/<BR>/g;
	$org;
}



##### ロック
sub file_lock{
	$try=0;
	while(!(symlink("$$", $lockfile))){
		(++$try>3) && &error(0);
		sleep(1);
	}
}



##############################################
# symlink が使えないサーバー用のファイルロック
##############################################
#sub file_lock{
#	$try = 0;
#	while(-f $lockfile){
#		(++$try > 3) && &error(0);
#		sleep(1);
#	}
#	open(FILE,">$lockfile") || &error($lockfile,0);
#	close(FILE);
#}
##############################################



##### ロック解除
sub unlock{
	unlink($lockfile);
}



##### エラーメッセージ
sub error{
	local ($id, $file) = @_;
	local (@sts) = lstat($lockfile);
	local ($tn) = time();
	
	$fmid[0] = 0; $msg[0] = 'ロック中です';
	$fmid[1] = 1; $msg[1] = 'を開くことができません';
	$fmid[2] = 1; $msg[2] = 'に書き込めません';
	$fmid[3] = 0; $msg[3] = 'book が指定されていません';
	$fmid[4] = 0; $msg[4] = 'メールアドレスが不正です';
	$fmid[5] = 0; $msg[5] = 'ＵＲＬが不正です';
	$fmid[6] = 0; $msg[6] = '発言番号の指定が不正です';
	$fmid[7] = 0; $msg[7] = 'という book は存在しません';
	$fmid[8] = 0; $msg[8] = 'book の指定が不正です';
	$fmid[9] = 0; $msg[9] = "文章が長すぎます。全角で$max_words文字以下にして下さい";
	
	$fmsg[0] = "Backを押して戻ってください";
	$fmsg[1] = "管理者<A HREF=\"mailto:$admin_email\">$admin_email</A>に連絡してください";
	
	$fid = $fmid[$id];
	
	print "<HTML><HEAD><TITLE>Error!!</TITLE></HEAD>\n";
	print "<BODY BGCOLOR=\"#CCCCFF\">\n";
	print "<FONT SIZE=4><B>エラー</B></FONT><P>\n";
	print "<FONT SIZE=3><B>$file $msg[$id]</B></FONT><P>\n";
	print "<FONT SIZE=3>$fmsg[$fid]</FONT><HR>\n";
	
	print "</BODY></HTML>";
	
	&unlock if ($id); # ID が 0 以外の場合はロック解除
	&unlock if ($tn - $sts[9] > 15); # 約15秒以上ロックが続いてたら自動解除
	exit;
}
