FLYING

/* TODO: 気の利いた説明を書く */

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パラメータを受け取るための組み込み変数があるのでわざわざデコードのためのコードを書く必要すらないんだけどな。