FLYING

〈全日本・紀文豆乳飲料シリーズ「麦芽コーヒー」の500ミリリットルパックを扱う小売店が少ないことに遺憾の意を表明する会〉活動記録

gawkで掲示板CGI

誰得。

euc-jpで保存したあとawkCGIとして動かせるサーバーで実行してね。排他制御は一切行っていないので,同時に複数人が書き込みを行うとマスターファイルが壊れちゃう予感。

#!/usr/local/bin/gawk -f

BEGIN {
	header("SimpleBBS by gawk");
	RS = "&";
	FS = "=";
}

{
	key = decode($1);
	value = decode($2);
	params[key] = value;
}

END {
	RS = "\n";
	FS = " ";
	if (length(params["confirm"]) != 0) {
		confirm();
	} else if (length(params["post"]) != 0) {
		post();
	} else {
		view();
	}
	footer();
}

#################################################
# action functions
#################################################

# validate a message
function validate()
{
	# erase html elements
	gsub(/</, "\\&lt;", params["name"]);
	gsub(/>/, "\\&gt;", params["name"]);
	gsub(/</, "\\&lt;", params["text"]);
	gsub(/>/, "\\&gt;", params["text"]);
	
	# validate inputs
	is_valid = 1;
	if (length(params["name"]) == 0) {
		error["name"] = "名前を入力してください";
		is_valid = 0;
	}
	if (length(params["text"]) == 0) {
		error["text"] = "本文を入力してください";
		is_valid = 0;
	}
	if (length(params["text"]) > 256) {
		error["text"] = "本文が長すぎます";
		is_valid = 0;
	}
	return is_valid;
}

# confirm a message
function confirm()
{
	validate();
	confirm_template();
}

# post a message
function post(	filename, date)
{
	if (validate()) {
		filename = "bbs.dat";
		date = strftime("%c");
		print params["name"] "<>" params["text"] "<>" date >> filename;
		close(filename);
	}
	post_template();
}

# view messages
function view(	filename, i)
{
	n = 0;
	filename = "bbs.dat";
	while ((getline line < filename) > 0) {
		messages[n++] = line;
	}
	close(filename);
	view_template();
}

#################################################
# template functions
#################################################

# header template
function header(title)
{
	print "Content-Type: text/html";
	print "";
	print "<html>";
	print "<head>";
	print "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=euc-jp\" />";
	print "<title>" title "</title>";
	print "</head>";
	print "<body>";
	print "<h1>" title "</h1>";
}

# footer template
function footer()
{
	print "</body>";
	print "</html>";
}

# confirm template
function confirm_template()
{
	if (is_valid) {
		# confirm
		print "<h2>投稿確認</h2>";
		print "<form action=\"./bbs.awk\" method=\"post\">";
		print "<dl>";
		print "<dt>名前</dt>";
		print "<dd>" params["name"];
		print "<input type=\"hidden\" name=\"name\" value=\"" params["name"] "\" /></dd>";
		print "<dt>本文</dt>";
		print "<dd>" params["text"];
		print "<input type=\"hidden\" name=\"text\" value=\"" params["text"] "\" /></dd>";
		print "</dl>";
		print "<p><input type=\"submit\" name=\"post\" value=\"この内容で投稿する\" />";
		print "<input type=\"submit\" name=\"view\" value=\"投稿を中止する\" /></p>";
		print "</form>";
	} else {
		# error
		print "<h2>投稿フォーム</h2>";
		print "<form action=\"./bbs.awk\" method=\"post\">";
		print "<dl>";
		print "<dt>名前</dt>";
		print "<dd><input type=\"text\" name=\"name\" value=\"" params["name"] "\" /></dd>";
		print "<dd><strong>" error["name"] "</strong></dd>";
		print "<dt>本文</dt>";
		print "<dd><input type=\"text\" name=\"text\" value=\"" params["text"] "\" /></dd>";
		print "<dd><strong>" error["text"] "</strong></dd>";
		print "</dl>";
		print "<p><input type=\"submit\" name=\"confirm\" value=\"投稿内容を確認する\" />";
		print "<input type=\"submit\" name=\"view\" value=\"投稿を中止する\" /></p>";
		print "</form>";
	}
}

# view template
function view_template(	i, array)
{
	# form
	print "<h2>投稿フォーム</h2>";
	print "<form action=\"./bbs.awk\" method=\"post\">";
	print "<dl>";
	print "<dt>名前</dt>";
	print "<dd><input type=\"text\" name=\"name\" /></dd>";
	print "<dt>本文</dt>";
	print "<dd><input type=\"text\" name=\"text\" /></dd>";
	print "</dl>";
	print "<p><input type=\"submit\" name=\"confirm\" value=\"投稿内容を確認する\" /></p>";
	print "</form>";
	
	# messages
	print "<h2>メッセージ</h2>";
	print "<dl>";
	for (i=n-1;i>=0;i--) {
		if (split(messages[i], array, "<>") < 3) {
			continue;
		}
		print "<dt>" array[1] "</dt>";
		print "<dd>" array[2], "@", array[3] "</dd>";
	}
	if (n == 0) {
		print "<dt>SimpleBBS</dt>";
		print "<dd>メッセージがありません</dd>";
	}
	print "</dl>";
}

# post template
function post_template()
{
	if (is_valid) {
		# done
		print "<h2>投稿完了</h2>";
		print "<p><a href=\"./bbs.awk\">SimpleBBS</a>に戻る。</p>";
	} else {
		# error
		print "<h2>投稿エラー</h2>";
		print "<ul>";
		for (key in error) {
			print "<li><strong>" error[key] "</strong></li>";
		}
		print "</ul>";
		print "<p><a href=\"./bbs.awk\">SimpleBBS</a>に戻る。</p>";
	}
}

#################################################
# utility functions
#################################################

# urldecode
function decode(s,	filename, i, hex, ret)
{
	# init temporary file
	filename = "decode.dat"
	
	# init hex table
	for (i=0;i<10;i++) {
		hex[i] = i;
	}
	hex["A"] = hex["a"] = 10;
	hex["B"] = hex["b"] = 11;
	hex["C"] = hex["c"] = 12;
	hex["D"] = hex["d"] = 13;
	hex["E"] = hex["e"] = 14;
	hex["F"] = hex["f"] = 15;
	
	# plus2space
	gsub(/\+/, " ", s);
	
	# put decoded string to temporary file
	while (match(s, /%../)) {
		if (RSTART > 1) {
			printf "%s", substr(s, 1, RSTART-1) > filename;
		}
		printf "%c", hex[substr(s, RSTART+1, 1)] * 16 + hex[substr(s, RSTART+2, 1)] > filename;
		s = substr(s, RSTART + RLENGTH);
	}
	printf "%s", s > filename;
	close(filename);
	
	# get decoded string from temporary file
	while ((getline line < filename) > 0 && length(line) != 0) {
		ret = ret line;
	}
	close(filename);
	
	return ret;
}

URLデコードの実装はhttp://geni.ath.cx/unix.html#_awkを参考にした。モダンなスクリプト言語なら1行で済むっていうかそもそもPOSTパラメータを受け取るための組み込み変数があるのでわざわざデコードのためのコードを書く必要すらないんだけどな。