ほげノート(5)

はて前回の更新部分、コメントで気付いたんですが .htaccess とか好きにできちゃう。
http://d.hatena.ne.jp/hogelog/comment?date=20070404#c
とりあえずは .htaccessパーミッションを 404 にして対処しました。
さらに問題はそれだけでもなかった。page.cgiに直接アクセスすれば、「hoge<>」みたいな、普通にはアクセスできないファイルもつくれたり。

ここまできて「.htaccessにアクセスされたら困るからパーミッション変えておく」とか「変なファイル名がなあ」とかにいちいち対処するのではなく、そもそもデータファイルのファイル名を(encodeURI()でエスケープされない範囲では)利用者が好きに決められる仕組みがそもそも間違ってたのだ、と考えた。

タイトルの全部をエスケープすりゃいいかな、と思いそうしてみた。
http://konbu.s13.xrea.com/lib/ajax/page4/page.html
今回の更新にあたり、高度な JavaScript 技集の、UTF-8 , UTF16 変換ライブラリを参考にさせていただきました。
page.html

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="ja">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<link rel="stylesheet" type="text/css" href="page.css" media="screen" />
<title>
めもちょう。
</title>
<script type="text/javascript" src="jquery.js"></script>
<script type="text/javascript"><!--
function escapeUTF8(page_name){
	var escaped_name = String(), c;
	for(i=0;i<page_name.length;++i){
		c = page_name.charCodeAt(i);
		if ((c >= 0x0001) && (c <= 0x007F)) {
			escaped_name += "%"+c.toString(16);
		} else if (c > 0x07FF){
			escaped_name += "%"+(0xE0 | ((c >> 12) & 0x0F)).toString(16);
			escaped_name += "%"+(0x80 | ((c >> 6) & 0x3F)).toString(16);
			escaped_name += "%"+(0x80 | ((c >> 0) & 0x3F)).toString(16);
		} else {
			escaped_name += "%"+(0xC0 | ((c >> 6) & 0x1F)).toString(16);
			escaped_name += "%"+(0x80 | ((c >> 0) & 0x3F)).toString(16);
		}
	}
	return escaped_name;
}
function loadpage(page_name){
	if(!page_name) page_name = $("#page_name").text();
	$("#httpdump").load(datadir+encodeURI(escapeUTF8(page_name)),
		function(text, status){
			$("#page")[0].value = text;
			$("#page_name").text(page_name);
			document.title = page_name+" - メモ帳";
			print_flush("status", "loaded.");
		});
}
function savepage(page_name){
	var txtdata = $("#page")[0].value;
	$("#httpdump").load("page.cgi",
		{mode: "save", page_name: page_name, txtdata: txtdata},
		function(text, status){
			print_flush("status", "saved.");
		});
}
function editevent(event,tid){
	var page_name = $("#page_name").text();
	$("#status").text("text edited...");
	clearTimeout(tid);
	return window.setTimeout('savepage("'+page_name+'")',2000);
}
function openindexbox(){
	var indexbox;
	$("#httpdump").load("page.cgi",{mode: "index"},
		function(text, status){
			indexbox = decodeURIComponent(text);
			indexbox = indexbox.replace(/&/g, "&amp;");
			indexbox = indexbox.replace(/</g, "&lt;");
			indexbox = indexbox.replace(/>/g, "&gt;");
			indexbox = indexbox.replace(/"/g, "&quot;");
			indexbox = indexbox.replace(/(.+)\n/g,"<a onclick='selectpage(\"$1\");' href='#'>$1</a><br>");
			$("#indexlist").html(indexbox);
			$("#indexbox").show();
			$("#status").text("");
		}
	);
}
function selectpage(page_name){
	loadpage(page_name);
	$("#indexbox").hide();
}
function savepageas(){
	var page_name = $("#savepagename")[0].value;
	savepage(page_name);
	$("#page_name").text(page_name);
	document.title = page_name;
	$("#savepagename")[0].value = "";
	$("#savebox").hide();
}
function print_flush(id, str){
  document.getElementById(id).innerHTML = str;
  window.setTimeout('document.getElementById("'+id+'").innerHTML = ""', 2000);
}
var tid;
var datadir = "data/";
var xbegin, ybegin, xmove, ymove, moveflag;
var xpoint = 100, ypoint = 100, notex = 100, notey = 100;
$(function(){
	$("#openindexbox").click(function(){openindexbox()});
	$("#closeindexbox").click(function(){$("#indexbox").hide()});
	$("#opensavebox").click(function(){$("#savebox").show()});
	$("#closesavebox").click(function(){$("#savebox").hide()});
	$("#page").keypress(function(event){tid=editevent(event,tid)});
	$("#httpdump").ajaxStart(function(){$("#status").text("Wait...")});
	$(".closewindow").click(function(){
		$("#note").hide();
		$("#note_mini").show();
		notex = xpoint; notey = ypoint;
		xpoint = ypoint = 0;
		$("#note_mini")[0].style["left"] = "0px";
		$("#note_mini")[0].style["top"] = "0px";
	});
	$(".maxwindow").click(function(){
		$("#note").show();
		$("#note_mini").hide();
		xpoint = notex; ypoint = notey;
		$("#note")[0].style["left"] = xpoint+"px";
		$("#note")[0].style["top"] = ypoint+"px";
	});
	$(".titlebar").mousedown(function(event){
		xbegin=event.pageX;
		ybegin=event.pageY;
		movewindow=$(this).parent().parent();
		moveflag=true;
	});
	$().mousemove(function(event){if(moveflag){
		xmove = (event.pageX-xbegin);
		ymove = (event.pageY-ybegin);
		movewindow[0].style["left"] = xpoint+xmove+"px";
		movewindow[0].style["top"] = ypoint+ymove+"px";
	}});
	$().mouseup(function(event){if(moveflag){
		xmove = (event.pageX-xbegin);
		ymove = (event.pageY-ybegin);
		movewindow[0].style["left"] = xpoint+xmove+"px";
		movewindow[0].style["top"] = ypoint+ymove+"px";
		xpoint += xmove;
		ypoint += ymove;
		moveflag = false;
	}});
});
// -->
</script>
</head>
<body>
<table id="note">
<tr class="titlebar">
<td class="titlebar_title">
<span id="page_name">無題</span> - メモ帳
</td>
<td class="titlebar_button">
<input type="submit" value="□" class="maxwindow">
<input type="submit" value="×" class="closewindow">
</td>
</tr>
<tr class="note_menu"><td colspan="2">
<a class="menu_item" id="openindexbox" href="#">他のページ開く</a>
<div class="childbox" id="indexbox">
<div id="indexlist"></div>
<input type="submit" id="closeindexbox" value="やっぱやめる">
</div>
<a class="menu_item" id="opensavebox" href="#">別名で保存</a>
<div class="childbox" id="savebox">
<input type="text" id="savepagename">
<input type="submit" onclick="savepageas()" value="保存"><br>
<input type="submit" id="closesavebox" value="やっぱやめる">
</div>
</td></tr>
<tr class="note_txt"><td colspan="2">
<textarea id="page" rows="15" cols="80"></textarea>
</td></tr>
<tr class="note_status"><td colspan="2"><span id="status"></span></td></tr>
</table>
<table id="note_mini">
<tr class="titlebar">
<td class="minititlebar_title">メモ帳</td>
<td class="titlebar_button">
<input type="submit" value="□" class="maxwindow">
<input type="submit" value="×" class="closewindow">
</td>
</tr>
</table>
<textarea id="httpdump"></textarea>
<script type="text/javascript"><!--
$(document).ready(function(){
loadpage();
});
// -->
</script>
</body>
</html>

ちなみに、htmlな中身のテキストデータを読み込ませると、ちゃんと読めなかったのは

<pre id="httpdump"></pre>

なんて部分があったせい。textareaにしておいた。
page.cgi

#!/usr/bin/perl -w

use strict;
use CGI;

my $cgi = new CGI;

print "Content-Type: text/plain; charset=utf-8\n\n";

my $datadir = "data";
my $mode = $cgi->param('mode');
my $page_name = $cgi->param('page_name');
my $txtdata = $cgi->param('txtdata');

mkdir $datadir if(!(-e $datadir));
chdir $datadir;

if(!defined $mode){
	print "Bye.";
}
elsif($mode eq 'save' && defined $page_name && defined $txtdata){
	$page_name = escapeUTF8($page_name);
	open PAGE, ">$page_name";
	print PAGE $txtdata;
	close PAGE;
	print "saved.";
}
elsif($mode eq 'index'){
	opendir DIR, ".";
	for(readdir DIR){
		if(-T $_ && ($_ ne ".htaccess" and $_ ne ".htpasswd") != 0){
			print "$_\n" 
		}
	}
	closedir DIR;
}
sub escapeUTF8{
	my $page_name = shift;
	$page_name =~ s/(.)/"%" . unpack("H2", $1)/eg;
	return $page_name;
}

page.css

@charset "utf-8";

/*
  ほげほげ
*/

body{
    background-color:#efeff0;
	color:#004624;
}
a:link,a:visited{
	color:#004624;
}
a:active,a:hover{
	font-weight:bold;
}
#page{
}
#pageindex{
}
#status{
}
#httpdump{
	display:none;
}
#note{
	background:#c9c9c9;
	border:solid 1px;
	width:42em;
	position:absolute;
	left:100px;
	top:100px;
}
#note_mini{
	background:#c9c9c9;
	border:solid 1px;
	width:10em;
	display:none;
	position:absolute;
	left:0px;
	top:0px;
}
.childbox{
	position:absolute;
	z-index:1;
	border:solid 1px;
	background:#c9c9c9;
	display:none;
	padding:2px 2em 5px 2px;
}
.titlebar{
	background:#3939a0;
	color:white;
	text-align:left;
	border: 0px;
}
.titlebar_title{
	width:39em;
}
.minititlebar_title{
	width:6em;
}
.titlebar_button{
	color:black;
	font:bold;
	text-align:right;
	width:45px;
	background:#c9c9c9;
}
.titlebar_button input{
	width:20px;
	height:20px;
}
.note_status{
	height:1.5em;
}

test