/*
 * Treeboard
 *
 * A hiearchical text message board.
 * The idea is to display a message tree drawn in SVG, embedded in HTML.
 * HTML is also embedded in the SVG, because not all browsers support textArea.
 *
 * alpha version
 * Just text, no formatting, no links, no pix, nothing fancy, not even flood control, and the text length is limited.
 * This version differs from previous ones in that messages are timestamped, which makes the database format incompatible.
 *
 * © 2013-2015 def@gmx.at
 * If you find a commercial use I'd like a slice of that pie.
 * Otherwise, have fun with it.
 *
 * clang tb.c -lfcgi -o tb.fcgi
 * (`gcc --std=c99 -lfcgi -o tb.fcgi` works as well.)
 *
 * Drop it in any directory where it has read-write-access, and have the server handle it as FCGI for a virtual directory.
 */

/*
 * The revoke feature is included for legal reasons. I hate it because it can be abused easily, as required by law.
 * If your jurisdiction doesn't require it, disable it.
 */
#define REVOKE

// includes

#include <fcgi_stdio.h>
#include <string.h> // strncpy

#include <stdlib.h> // atoi

#include <sys/types.h> // open
#include <sys/stat.h>
#include <fcntl.h>

#include <unistd.h> // mmap
#include <sys/mman.h>

#include <time.h>


// variable constants

#define MB_BUF_SIZE 65536 // 2**16
#define MB_MSG_LEN (160*14) // HTML-escaped; will change in a future version
#define MB_MAX_CONTENT_LEN (MB_MSG_LEN+26) // pass=this&msg=65535&text=160*14\0
/*
#define MB_MSG_LEN (1024) // 256 (16×16 or 8×32) characters are better than 160 (8×20) characters, but will they be enough?
#define MB_MAX_CONTENT_LEN (256*14+16)
*/

#if _BIG_ENDIAN
#define TREE_MAGIC 0x74726565
#else
#define TREE_MAGIC 0x65657274 // little endian is backwards
#endif

#define BUFFER "db.tb"

// structs

typedef struct leaf {
	unsigned short flags; // 0/1: deleted; 2/4: topic; and 14 bit flags to spare; little endian, no network byte order compatibility atm.
	unsigned short parent; // 0 .. 65535
	unsigned long timestamp; // UNIX epoch, y2k38-compliant, but not y2106; time_t is as yet incompatible between Linux and BSD
	char text[MB_MSG_LEN];
} leaf_t;

typedef struct tb_header {
	unsigned long magic; // tree 
	unsigned short head;
	unsigned short tail;
	struct leaf content[];
} tbheader_t;

// global vars

/* let's get serious. */
tbheader_t* tree;
leaf_t* message; // mmapped space
// recomputed node values: will need to be mmap-shared also once we parallelize.
unsigned short sib[MB_BUF_SIZE];
unsigned short heir[MB_BUF_SIZE]; // chronologically
unsigned short children[MB_BUF_SIZE]; // we only need to know if a node has children, but we may as well count them.
unsigned short b[MB_BUF_SIZE]; // breadth
unsigned short d[MB_BUF_SIZE]; // depth
short x[MB_BUF_SIZE];
//unsigned long t[MB_BUF_SIZE]; // the timestamp of the youngest descendant for any given branch
unsigned short youngest[MB_BUF_SIZE]; // id of the youngest leaf on the sub-tree
// when was the latest message? will also need to be mmapped once we parallelize, if ever.
//long last_time;
unsigned long now;

/* some string constants */
const char* HEAD="Content-type: application/xhtml+xml; charset-encoding=UTF-8\r\n\
Charset-encoding: UTF-8\r\
";
const char* FOOT="\t</p>\n\
</body>\n\
</html>";
const char* HEAD_HTML="\r\n\
<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\
<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.1//EN\"\
 \"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd\">\n\
<html xmlns=\"http://www.w3.org/1999/xhtml\">";
const char* HEAD_SVG="\r\n\
<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\
<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.1 plus MathML 2.0 plus SVG 1.2//EN\"\
 \"http://www.w3.org/2002/04/xhtml-math-svg/xhtml-math-svg.dtd\">\n\
<html xmlns=\"http://www.w3.org/1999/xhtml\" xmlns:svg=\"http://www.w3.org/2000/svg\">";
const char* BODY="<head>\n\
<title>treeboard</title>\n\
<link rel=\"stylesheet\" href=\"css\" type=\"text/css\"/>\n\
<!-- <link rel=\"shortcut icon\" type=\"image/png\" href=\"favicon.png\"> -->\n\
</head>\n\
<body>\n\
<div class=\"h\">\n\
<h1><a href=\".\">Treeboard</a></h1>\n\
<span><a href=\"latest\">latest</a> – <a href=\"tree\">all</a> – <a href=\"flip\">flipmode</a> – <a href=\"chan\">chanview</a> – <a href=\"bb\">bb-view</a></span>\n\
</div>\n\
	<p style=\"text-align: center; padding-top: 5em;\">";
const char* CSS=
"	body { background-color: #cccc00; font-family: sans-serif; }\n\
	foreignObject body { background-color: transparent; font-size: 12px; }\n\
	foreignObject body div { white-space: pre-wrap; text-shadow: 0 0 1px #006600; font-weight: bold; }\n\
	.h { text-align: center; position: fixed; box-shadow: 1ex 1ex 1ex 1ex rgba(255,255,255,0.5); background-color: rgba(255,255,255,0.5); border-radius: 1ex 1em; }\n\
	.h * { color: #000000; text-decoration: none; font-variant: small-caps; margin: 0.5ex; }\n\
	div { color: #ffffff; }\n\
	textArea { fill: #ffffff; }\n\
	line { stroke: #006600; }\n\
	.legal { color: #ff0000; text-decoration: none; font-size: small; }\n\
	.del { stroke: #660000; }\n\
	.leaf { stroke: #006600; }\n\
	a { color: #ffff00; fill: #ffff00; text-decoration: none; font-weight: bold; }\n\
	a:hover { /*stroke: #ffff00;*/ text-shadow: 0 0 0.2ex #ffff00; }\n\
	.thread { color: #ffffff; margin: 2em; padding: 1ex; border-style: solid; border-radius: 0.5em 1em; background-color: #66cc00; border-color: #006600; border-width: medium; white-space: pre-wrap; text-align: left; }\n\
	.inlink { color: #ffffff; text-decoration: none; }\n\
	.post { border-style: solid; border-width: thin; border-color: #006600; padding: 1em; background-color: #00cc00; }\n\
	.reply { color: #ffff00; }\n\
	table { border:1; white-space: pre-wrap; empty-cells: hide; }\n\
"; // should maybe read this from a file
/* In additon, referencing a @font-face src might allow adaptive leaf sizes. */

/* some colour constants */
//unsigned long faded=0xcccc00; // fades towards background
//unsigned long fresh=0x00cc00;
//unsigned long rotten=0xcc0000;
/* a note on colours:
 * the brightness difference between leaf and background needs to be less than between text colour and leaf, or the text will be hard to read.
 * counter-intuitively this can mean the background needs to be bright enough.
 * it also means the leafs may fade only halfway towards the background.
 */

const int m_w=200; // message width
const int m_h=185; // message height
const int sep_w=10; // padding
const int r_w=m_w-2*sep_w; // rect
const int r_h=m_h-2*sep_w;
const int t_w=r_w-2*sep_w; // text
const int t_h=r_h-2*sep_w;

/* auxilliary function: adjust positions to accomodate a new leaf */
void shift_sibs(unsigned short parent_id){
	short cp=b[parent_id]; // current position in group
	short prev_brev=0;
	for(unsigned short chid=heir[parent_id];chid!=parent_id;chid=sib[chid]){
		cp-=prev_brev; // half width of previous
		cp-=b[chid]; // half width of this
		x[chid]=cp;
		prev_brev=b[chid];
	}
}

/* auxilliary function: calculates the colour of a message */
/* explanation:
 * 
 * colour value goes from a minimum to a maximum.
 * the minimum is 0x00, the maximum is 0xcc/2=0x66
 * the factor is delta/limit, where delta is between 0 and limit,
 * so the factor is between 0 and 1, inclusively.
 * 0 means the leaf is brand new.
 * 1 means the leaf has faded to the maximum.
 * 
 * the colour value is therefore minimum+(delta/limit)*(maximum-minimum)
 * (if minimum and maximum are the same, the factor is always 0.)
 * in our case, the formula is 0x00+(delta/limit)*(0xcc/2-0x00)
 * or simplified: 0x66*delta/limit
 * 
 * because we are dealing with integer values, we have to switch the terms around,
 * to avoid overflows, and also to avoid divisors being larger than dividends,
 * or 0 and 1 would be the only factor values we ever get.
 * 
 * for maxima > 0x88, maximum*delta > max_int
 * in which case, do
 * delta/(limit/$maximum)
 * which works for any colour value except black.
 * with larger values it decreases in accuracy
 * 
 * therefore, even though a colour value fits into an int,
 * it is best to calculate each component separately.
 * 
 */
unsigned char colour_channel(unsigned char kid,unsigned char geezer, unsigned long delta){ // because nested functions are not allowed in C
	const unsigned long limit=31556952; // a year
	unsigned char age=geezer-kid;
	//return kid+(age*delta/limit);
	return age?kid+(delta/(limit/age)):kid;
}
/* c is the colour of new messages. o is the colour of new deleted messages. delta is their age in seconds */
unsigned long colour_channels(unsigned long c, unsigned long o, unsigned long delta){
	unsigned long r,g,b,a;
	a=colour_channel( (c>>24)&0xff, (o>>24)&0xff , delta);
	r=colour_channel( (c>>16)&0xff, (o>>16)&0xff , delta);
	g=colour_channel( (c>> 8)&0xff, (o>> 8)&0xff , delta);
	b=colour_channel( (c    )&0xff, (o    )&0xff , delta);
	return (a<<24)+(r<<16)+(g<<8)+b;
}
unsigned long colour_original(unsigned short m){ // the original, recreated
	const unsigned long limit=31556952; // a year
	unsigned long delta=now-message[m].timestamp;if(delta>limit)delta=limit;
	
	unsigned char r,g,b;
	unsigned long c,o;
	if(message[m].flags&1){ // rotten
		c=0xcc0000;
		o=0xcc9900;
	}else{ // fresh
		c=0x00cc00;
		o=0x99cc00;
	}
	
	return colour_channels(c,o,delta);
}
unsigned long(*colour)(unsigned short m)=colour_original;

/* auxilliary function: adjust values for the ancestry */
void update_lineage(unsigned short m){
	// initialize properties of m
	heir[m]=m;
	children[m]=0;
	b[m]=1; // the message uses as much space as its heirs
	d[m]=1; // the message is at the lowest level in its subtree
	x[m]=0; // the message is centered initially
	//t[m]=message[m].timestamp; // the message is now
	youngest[m]=m;
	// paternal matters
	unsigned short par=message[m].parent;
	if(par!=m){
		sib[m]=heir[par];
		heir[par]=m;
		children[par]++;
		//t[par]=t[m];
		youngest[par]=m;
		if(children[par]!=1){ // first child does not alter width
			par=m; do{
				par=message[par].parent;
				b[par]++;
				shift_sibs(par);
				//t[par]=t[m];
				youngest[par]=m;
			}while(par!=message[par].parent);
		}else{ // first child does alter depth
			unsigned short nd=2; // new depth
			do{
				d[par]=nd++; // must be the same as nd=++d[par];
				//nd=++d[par];
				if(par==message[par].parent)break;
				par=message[par].parent;
				//t[par]=t[m];
				youngest[par]=m;
			}while(d[par]<=nd); // not the case when par==message[par].parent 
			//do{par=message[par].parent;t[par]=t[m];}while(message[par].parent!=par);
			do{par=message[par].parent;youngest[par]=m;}while(message[par].parent!=par);
		}
	}else{
		sib[m]=m; // possible loss of relationship
	/*	if(m==tree->head)sibling[m]=-1;
		else{
			// alternatively
			unsigned short cur=tree->head;
			while(sibling[cur]!=-1)cur=(unsigned short)(sibling[cur]);
			sibling[cur]=m;
			sibling[m]=-1;
			x[m]=x[cur]-b[cur]-b[m]; // left of visible pane, unaccessible
		}
	*/
	}
}

/* aux func: escape strings for json. rfc4627 */
void mdea(char* fleece){
	while(*fleece){
		switch(*fleece){
			case '\n':
				putchar('\\'); putchar('n');
				break;
			case '\r':
				putchar('\\'); putchar('r');
				break;
			case '\t':
				putchar('\\'); putchar('t');
				break;
			case '"':
			//case '\'':
			case '\\':
				putchar('\\');
			default:
				putchar(*fleece);
		}
		fleece++;
	}
}

/* aux func: recursively assemble JSON rep of tree */
void json_print(unsigned short n){
	printf("\"%hu\":{\"text\":\"",n);
	mdea(message[n].text);
	printf("\",\"timestamp\":%lu",message[n].timestamp);
	if(message[n].flags&0x01)fputs(",\"deleted\":true",stdout);
	if(heir[n]!=n){
		putchar(',');
		json_print(heir[n]);
	}
	putchar('}');
	if(sib[n]!=message[n].parent){
		putchar(',');
		json_print(sib[n]);
	}
}

/* aux func: traverse subtrees in search of the youngest */
int chan_rec(unsigned short list[5], int l, unsigned short cur, unsigned short pre){
	if(heir[cur]!=cur){ // all younger
		l=chan_rec(list, l, heir[cur], pre);
	}
	if(sib[cur]!=message[cur].parent){ // older siblings.
		l=chan_rec(list, l, sib[cur], pre);
	}
	int i=0;
	for(;i<l;i++){
		if(list[i]-pre<cur-pre){
			cur^=list[i];
			list[i]^=cur;
			cur^=list[i];
		}
	}
	if(i==l && l<5){
		list[i]=cur;
		l++;
	}
	return l;
}

/* aux: check if grandparent is topic */
void check_for_topic(unsigned short id){
	unsigned short parent=message[id].parent;
	if(parent==id)return;
	unsigned short gp=message[parent].parent;
	if(gp==parent)return;
	if(message[gp].flags&0x04)return;
	for(unsigned short ling=heir[gp];ling!=parent;ling=sib[ling]){
		if(ling==parent)continue;
		if(heir[ling]!=ling){
			//if(message[message[gp].parent].flags&0x04){return;}
			message[gp].flags|=0x04;
			return;
		}
	}
}

/* Add a message. This may overwrite the oldest message.
 * Messages are submitted via POST request.
 * The content is read from stdin.
 * Input needs to be sanitized.
 * (NOTE: input decoding should be overhauled for multi-layer decoding and sanitizing.)
 */
int add_message(unsigned short parent){
	// we don't know in advance whether the content really will fit.
	// we need to parse/sanitize it first before committing it to long-term memory.
	int i=0;
	char textbuffer[MB_MSG_LEN];
	//unsigned long code=0; // reject illegal characters. to that end, identify illegal characters.
	while(i<MB_MSG_LEN-1){
		int c=getchar();
		if(c==EOF || c=='&') break; // done
		if(c=='+')textbuffer[i++]=' ';
		else if(c=='%'){ // urldecode
			// WARNING:
			// this parser code is not strictly correct
			// on pathological input it will do the wrong thing
			int x=0;
			c=getchar();
			if(c<='9')x=c-'0';
			else if(c<='F')x=c-'A'+10;
			else if(c<='f')x=c-'a'+10; // does that ever happen?
			x*=16;
			c=getchar();
			if(c<='9')x+=c-'0';
			else if(c<='F')x+=c-'A'+10;
			else if(c<='f')x+=c-'a'+10;
			if(x=='<'){
				if(MB_MSG_LEN-i>4){
					textbuffer[i++]='&';
					textbuffer[i++]='l';
					textbuffer[i++]='t';
					textbuffer[i++]=';';
				}else{
					return -2;
				}
			}else if(x=='&'){ // alternatively we could unescape the character, but why bother?
				if(MB_MSG_LEN-i>5){
					textbuffer[i++]='&';
					textbuffer[i++]='a';
					textbuffer[i++]='m';
					textbuffer[i++]='p';
					textbuffer[i++]=';';
				}else{
					return -2;
				}
			}else if(x<0x20 && x!=0x09 && x!=0x0a && x!=0x0d) return -3; // some control sequences are rejected
			else textbuffer[i++]=x;
		}
		else if(c=='<'){ // should never happen, but better safe than sorry.
			if(MB_MSG_LEN-i>4){
				textbuffer[i++]='&';
				textbuffer[i++]='l';
				textbuffer[i++]='t';
				textbuffer[i++]=';';
			}else{
				return -2;
			}
		}
		else textbuffer[i++]=(char)c;
	}
	if(i==0)return -1; // no empty messages. TODO: should also check for non-whitespace.
	/* unicode spaces: 0009 - 000d, 0020, 0085, 00a0, 1680, 180e, 2000 - 200d, 2028, 2029, 202f, 205f, 3000, feff */
	textbuffer[i++]='\0';

	/*
	 * It would be convenient to parse for links and make them clickable.
	 * Doing it here means it only needs to be done once per message.
	 * (It also eats up message space.)
	 * However, Presto doesn't support clickable links in foreignObject,
	 * Links:
	 * <a href="…">…</a>  :  15+2m // for foreignObject only, doesn't work in Opera
	 * to open in a separate window, we'd also have to insert target="_blank", 16 bytes more
	 * Images:
	 * <img width="..." height="..." src="..."/> : 30+m+(w+h) ; malicious image files might crash the browser
	 * also note: embedded images can be SVG, and SVG can contain ECMAScript, possible XSS vector.
	 *
	 * ASIDE: if we do detect links, it might be SPAM.
	 * I believe spammers should pay for advertisement space. They use it commercially, so commerce.
	 */

	unsigned short mx=tree->tail++; // copy: message index; advance it to reserve this space.
	if(mx==tree->head){ // the oldest message has just been phased out.
		if(heir[mx]!=mx){ // process the orphans
			for(int i=heir[mx];i!=mx;i=sib[i]){
				message[i].parent=i; // condolences
			}
		}
		tree->head++; // moving on. will need to be moved down once we parallelize
	}

	// turn a new leaf
	message[mx].parent=parent;
	message[mx].flags=0; // original message
	message[mx].timestamp=now;
/*
	char* cursor=&(message[mx].text);
	if((char* h=strstr(textbuffer,"http"))!=NULL){
		char* end_of_url;
		// check if message buffer has space enough for link (first determine length of link. then if(15+m < MSG_BUF_LEN-i) )
		for(end_of_url=h+7;*end_of_url!=0;end_of_url++){
			if(*end_of_url<'#' && *end_of_url!='!')break;
			if(*end_of_url>'&' && *end_of_url<'+') break;
			if(*end_of_url>'=' && *end_of_url<'?')break; // if(*end_of_url=='>')break;
			if(*end_of_url>'Z' && *end_of_url<'_')break;
			if(*end_of_url>'_' && *end_of_url<'a')break;
			if(*end_of_url>'z') break;
		}
		// else return(-2)
		if(15+(end_of_url-h)<MSG_BUF_LEN-i)return -2;

		if((textbuffer[h+4]==':' && textbuffer[h+5]=='/' && textbuffer[h+6]=='/')
		|| (textbuffer[h+4]=='s' && textbuffer[h+5]==':' && textbuffer[h+6]=='/' && textbuffer[h+7]=='/'))
		{
				// simply foreinObject variant (Opera will catch on sooner or later)
				memcpy(cursor,textbuffer,h-textbuffer); cursor+=h-textbuffer;
				memcpy(cursor,"<a href=\",9); cursor+=9;
				memcpy(cursor,h,end_of_url-h); cursor+=end_of_url-h; // copy url into message buffer
				memcpy(cursor,"\">",2); cursor+=2;
				memcpy(cursor,h,end_of_url-h); cursor+=end_of_url-h; // copy url into message buffer again
				memcpy(cursor,"</a>",4); cursor+=4;
				// memcpy remainder of message, or look for next link
		}else // look for next link
	}else
*/	memcpy(&(message[mx].text),textbuffer,i);
	// msync(m,sizeof(leaf_t),MS_ASYNC|MS_INVALIDATE); // m needs to be aligned to page size, length set accordingly.

	update_lineage(mx);
	//last_time=now;
	//sort_by_activity(mx); // temporary test

	// msync(0,sizeof(tbheader_t),MS_ASYNC|MS_INVALIDATE);
	return mx;
}

#ifdef REVOKE
/* For legal reasons, it must be possible to delete any content.
 * We would like to contain this damage.
 * Logistically, it's easiest to just overwrite the content,
 * ideally with the reason why it was harmonized (and who to blame.)
 */
void redact_message(int id, char* reason){
	strncpy(message[id].text,reason,MB_MSG_LEN);
}
#else
/* In jurisdictions where deletion is not required, but covering up is sufficient,
 * or where destruction of evidence is even forbidden,
 * the original text should be preserved, but not displayed until further notice.
 */
void restore_message(int id){
	message[id].flags&=0xfffe;
}
#endif

/* this helper function prints a naked message in html */
void print_message_html(unsigned short id){
	if(message[id].parent!=id)
		printf("<strong><a class=\"inlink\" href=\"tree?%hu\">%hu</a> </strong><a href=\"chan?%hu\">&gt;&gt;%hu</a> %s <a class=\"reply\" href=\"reply?%hu\"> ⏎ </a>",id,id,message[id].parent,message[id].parent,message[id].text,id);
	else
		printf("<strong><a class=\"inlink\" href=\"tree?%hu\">%hu</a> </strong> %s <a class=\"reply\" href=\"reply?%hu\"> ⏎ </a>",id,id,message[id].text,id);
}
/* this function only prints the message in its container, without decorations or buttons. */
void print_message(unsigned short m){
/*	printf("<rect class=\"%s\" x=\"%d\" y=\"%d\" height=\"%d\" width=\"%d\" rx=\"0.5em\" ry=\"1em\" />\n\
		<!-- \n\
		Gecko does inline HTML, not TextFlow, and knows it. \n\
		Presto does TextFlow and inline HTML, and knows it. \n\
		WebKit does inline HTML, but thinks it cannot. \n\
		Explorer can neither. \n\
		KTHML, Dillo, and NetSurf can't even into SVG. \n\
		-->\n\
		<switch>\n\
		<g requiredFeatures=\"http://www.w3.org/Graphics/SVG/feature/1.2/#TextFlow\">\n\
		<textArea x=\"%d\" y=\"%d\" width=\"%d\" height=\"%d\">%s</textArea>\n\
		</g>\n\
		<!--<g requiredExtensions=\"http://www.w3.org/1999/xhtml\">-->\n\
		<foreignObject x=\"%d\" y=\"%d\" width=\"%d\" height=\"%d\">\n\
			<body xmlns=\"http://www.w3.org/1999/xhtml\">\n\
				<div>%s</div>\n\
			</body>\n\
		</foreignObject>\n\
		<!--</g>-->\n\
		</switch>\n\
	",(message[m].deleted?"del":"leaf"),sep_w,sep_w,r_h,r_w,sep_w*2,sep_w*2,t_w,t_h,message[m].text,sep_w,sep_w,t_w,t_h,message[m].text);
*//*	printf("<rect class=\"%s\" x=\"%d\" y=\"%d\" height=\"%d\" width=\"%d\" rx=\"0.5em\" ry=\"1em\" fill=\"#%02xcc00\"/>\n\
		<foreignObject x=\"%d\" y=\"%d\" width=\"%d\" height=\"%d\">\n\
			<body xmlns=\"http://www.w3.org/1999/xhtml\">\n\
				<div>%s</div>\n\
			</body>\n\
		</foreignObject>\n\
	",(message[m].flags&1?"del":"leaf"),sep_w,sep_w,r_h,r_w, 0x99*(tree->tail-m-1)/(tree->tail-tree->head) ,sep_w,sep_w,t_w,t_h,message[m].text);
	return;
*/	// colour by timestamp
	const unsigned long limit=31556952; // a year
	unsigned long delta=now-message[m].timestamp;if(delta>limit)delta=limit;
	printf("<rect class=\"%s\" x=\"%d\" y=\"%d\" height=\"%d\" width=\"%d\" rx=\"0.5em\" ry=\"1em\" fill=\"#%02x%02x00\"/>\n\
		<foreignObject x=\"%d\" y=\"%d\" width=\"%d\" height=\"%d\">\n\
			<body xmlns=\"http://www.w3.org/1999/xhtml\">\n\
				<div>%s</div>\n\
			</body>\n\
		</foreignObject>\n\
	",(message[m].flags&1?"del":"leaf"),sep_w,sep_w,r_h,r_w,
       		//0x99*delta/limit // linear // for sufficiently large delta, 0x99*delta > max_int ; limit/0x99==206255
		message[m].flags&1?0xcc:delta/(limit/0x99), // linear, red
		message[m].flags&1?delta/(limit/0x99):0xcc, // linear, green
		sep_w,sep_w,t_w,t_h,message[m].text);
}
/* this function prints the tree from any given root */
void print_tree(unsigned short m){
	printf("<g transform=\"translate(%d %d)\" >",x[m]*m_w/2,m_h);
	print_message(m);
	printf("<a xlink:href=\"reply?%d\" ><text x=\"%d\" y=\"%d\">reply</text></a>",m,sep_w*2,r_h);
	if(heir[m]!=m)print_tree(heir[m]);
	puts("</g>");
	printf("<line x1=\"%d\" y1=\"%d\" x2=\"%d\" y2=\"%d\"/>",m_w/2,m_h-sep_w,(x[m]+1)*m_w/2,m_h+sep_w);
	if(sib[m]!=message[m].parent)print_tree(sib[m]);
	else printf("<a xlink:href=\"tree?%d\"><circle fill=\"#006600\" r=\"0.5em\" cx=\"%d\" cy=\"%d\"/></a>",message[m].parent,m_w/2,m_h-sep_w);
}

/* this function moves the root to provide space for its children */
void print_tree_top(short nx, unsigned short m){
	printf("<g transform=\"translate(%d %d)\">",nx*m_w+(b[m]-1)*(m_w/2), sep_w/2);
	if(message[m].parent!=m)
		printf("<a xlink:href=\"tree?%d\"><circle class=\"leaf\" r=\"%d\" cx=\"%d\" cy=\"%d\" fill=\"#00cc00\"/></a>\n",message[m].parent,sep_w,m_w/2,sep_w);
	print_message(m);
	printf("<a xlink:href=\"reply?%d\" ><text x=\"%d\" y=\"%d\">reply</text></a>\n",m,sep_w*2,m_h-2*sep_w);
	if(heir[m]!=m)print_tree(heir[m]);
	puts("</g>");
}

/* 
 * put the tree with the youngest descendant leftmost
 * traverse list from youngest,
 * pick only the freshest leafs,
 * find their ancestors,
 * and print their trees */
void woodcraft(unsigned short n){
/*	{
		unsigned short total=tree->tail-tree->head;
		if(total && n>total)n=total;
	}
*/	unsigned short btotal=0;
	unsigned short dtotal=0;
	const unsigned short begin=tree->tail-n;
	for(unsigned short i=begin;i!=tree->tail;i++){
		if(i==message[i].parent || i-message[i].parent>i-begin){
			btotal+=b[i];
			if(dtotal<d[i])dtotal=d[i];
		}
	}
	printf("<svg xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" width=\"%d\" height=\"%d\" version=\"1.2\">\n",btotal*m_w,dtotal*m_h);
	//puts("<rect stroke=\"#0000ff\" fill=\"none\" x=\"0\" y=\"0\" width=\"100%\" height=\"100%\"/>\n");
	unsigned short end=tree->tail-1;
	short nx=0;
	while(end!=begin){ // this will not work if buffer is full and entire tree is requested, but in that case tree?(tree->head) would be more efficient anyway.
		if(heir[end]==end){ // a leaf
			unsigned short my_a=message[end].parent-begin,my_b=end-begin; // circumvent a bug in clang 3.5.0
			//if(heir[message[end].parent]==end // a youngest
			if(youngest[message[end].parent]==end // a youngest
			//|| message[end].parent-begin>=end-begin // or an orphan
			|| my_a>=my_b // or an orphan // why do we need this?
			){
				unsigned short m=end;
				int skip=0;
				while(message[m].parent!=m && m-begin>=m-message[m].parent){
					// if any of the ancestors has siblings that we would have printed, skip the tree
					m=message[m].parent;
					//if(heir[message[m].parent]!=m){ // not the youngest
					if(youngest[m]!=end){ // not the youngest
						skip=1;
						break;
					}
				}
				if(!skip){
					print_tree_top(nx,m);
					nx+=b[m];
				}
			}
		}
		end--;
	}
	if(heir[begin]==begin)print_tree_top(nx,begin); // the oldest might not have been printed yet, especially if it is the only.
	puts("</svg>");
}

/* like print_tree, after an encounter of the lumberjack kind */
void print_tree_flipped(unsigned short m){
	printf("<g transform=\"translate(%d %d)\" >",m_w,x[m]*m_h/2);
	print_message(m);
	printf("<a xlink:href=\"reply?%d\" ><text x=\"%d\" y=\"%d\">reply</text></a>",m,sep_w*2,r_h);
	if(heir[m]!=m)print_tree_flipped(heir[m]);
	puts("</g>");
	//printf("<line x1=\"%d\" y1=\"%d\" x2=\"%d\" y2=\"%d\"/>",m_w/2,m_h-sep_w,(x[m]+1)*m_w/2,m_h+sep_w);
	printf("<line x1=\"%d\" y1=\"%d\" x2=\"%d\" y2=\"%d\"/>",m_w-sep_w,m_h/2,m_w+sep_w,(x[m]+1)*m_h/2);
	if(sib[m]!=message[m].parent)print_tree_flipped(sib[m]);
	else
		printf("<a xlink:href=\"tree?%d\"><circle fill=\"#006600\" r=\"0.5em\" cx=\"%d\" cy=\"%d\"/></a>",message[m].parent,m_w-sep_w,m_h/2);
		//printf("<a xlink:href=\"flip?%d\"><circle fill=\"#006600\" r=\"0.5em\" cx=\"%d\" cy=\"%d\"/></a>",message[m].parent,m_w/2,m_h-sep_w);
}
/* like print_tree_top, but growing sideways */
void print_tree_top_flipped(short nx, unsigned short m){
	printf("<g transform=\"translate(%d %d)\">",sep_w/2, nx*m_h+(b[m]-1)*(m_h/2));
	if(message[m].parent!=m)
		printf("<a xlink:href=\"tree?%d\"><circle class=\"leaf\" r=\"%d\" cx=\"%d\" cy=\"%d\" fill=\"#00cc00\"/></a>\n",message[m].parent,sep_w,sep_w,m_w/2);
		//printf("<a xlink:href=\"flip?%d\"><circle class=\"leaf\" r=\"%d\" cx=\"%d\" cy=\"%d\" fill=\"#00cc00\"/></a>\n",message[m].parent,sep_w,sep_w,m_w/2);
	print_message(m);
	printf("<a xlink:href=\"reply?%d\" ><text x=\"%d\" y=\"%d\">reply</text></a>\n",m,sep_w*2,m_h-2*sep_w);
	if(heir[m]!=m)print_tree_flipped(heir[m]);
	puts("</g>");
}

/* like woodcraft, but multiplied by the negative square root of -1 */
void flipmode(unsigned short n){
	unsigned short btotal=0;
	unsigned short dtotal=0;
	const unsigned short begin=tree->tail-n;
	for(unsigned short i=begin;i!=tree->tail;i++){
		if(i==message[i].parent || i-message[i].parent>i-begin){
			btotal+=b[i];
			if(dtotal<d[i])dtotal=d[i];
		}
	}
	printf("<svg xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" width=\"%d\" height=\"%d\" version=\"1.2\">\n",dtotal*m_w,btotal*m_h);
	//puts("<rect stroke=\"#0000ff\" fill=\"none\" x=\"0\" y=\"0\" width=\"100%\" height=\"100%\"/>\n");
	unsigned short end=tree->tail-1;
	short nx=0;
	while(end!=begin){ // this will not work if buffer is full and entire tree is requested
		if(heir[end]==end){ // a leaf
			unsigned short my_a=message[end].parent-begin,my_b=end-begin; // circumvent a bug in clang 3.5.0
			if(heir[message[end].parent]==end // a youngest
			//|| message[end].parent-begin>=end-begin // or an orphan
			|| my_a>=my_b // or an orphan
			){
				unsigned short m=end;
				int skip=0;
				while(message[m].parent!=m && m-begin>=m-message[m].parent){
					// if any of the ancestors has siblings that we would have printed, skip the tree
					m=message[m].parent;
					//if(heir[message[m].parent]!=m){ // not the youngest
					if(youngest[m]!=end){ // not the youngest
						skip=1;
						break;
					}
				}
				if(!skip){
					print_tree_top_flipped(nx,m);
					nx+=b[m];
				}
			}
		}
		end--;
	}
	if(heir[begin]==begin)print_tree_top_flipped(nx,begin); // the oldest might not have been printed yet, especially if it is the only.
	puts("</svg>");
}
/* chanview */
void print_chan(/*unsigned short dummy*/){
	puts(HEAD);
	puts(HEAD_HTML);
	puts(BODY);
	// get latest five tops, get latest 5 nodes in each of their subtrees
	unsigned short curt=tree->tail-1;
	int threadcount=0;
	unsigned short tclist[5]; // list of thread tops
	// gather tops (crude)
	while(threadcount<5 && curt!=tree->head){
		unsigned short parcurt;
		for(parcurt=curt;message[parcurt].parent!=tree->head && message[parcurt].parent!=parcurt;parcurt=message[parcurt].parent);
		int i=0;
		while(i<threadcount && parcurt!=tclist[i]) i++;
		if(i==threadcount){
			// sort
			for(int j=0;j<threadcount;j++){
				//if(t[tclist[j]]<t[parcurt]){ // youth first
				unsigned short t_j=tclist[j]-tree->head;
				unsigned short t_c=parcurt;while(heir[t_c]!=t_c)t_c=heir[t_c];t_c-=tree->head;
				if(t_j>t_c){ // youth first
					tclist[j]^=parcurt;
					parcurt^=tclist[j];
					tclist[j]^=parcurt;
				}
			}
			tclist[threadcount++]=parcurt;
		}
		curt--;
	}
	// print
	for(int i=0;i<threadcount;i++){
		printf("<div class=\"thread\"><p><strong><a class=\"inlink\" href=\"tree?%hu\">%hu</a> </strong>%s</p>",tclist[i],tclist[i],message[tclist[i]].text);
		if(heir[tclist[i]]!=tclist[i]){
			unsigned short list[5]; // to be filled with youngest descendants
			int l=chan_rec(list, 0, heir[tclist[i]], tclist[i]);
			for(int postcount=l-1;postcount>=0;postcount--){
				//puts("<div class=\"post\">"); // puts adds spaces in between posts
				fputs("<div class=\"post\">",stdout);
				print_message_html(list[postcount]);
				//puts("</div>");
				fputs("</div>",stdout);
			}
		}
		printf("<a class=\"reply\" href=\"reply?%hu\">Reply</a>",tclist[i]);
		//puts("</div>");
		fputs("</div>",stdout);
	}
/*	// -
	puts("<div class=\"thread\"><strong>New topic: </strong>");
	puts("<form action=\"reply\" method=\"post\" >");
	printf("<input type=\"hidden\" name=\"msg\" value=\"%hu\"/>",tree->head);
	puts("<textarea name=\"text\" cols=\"80\" rows=\"2\" maxlength=\"160\" ></textarea>");
	puts("<br/><input type=\"submit\" />");
	puts("</form></div>");
*/
#ifdef REVOKE
	puts("</p><p align=\"right\"><a class=\"legal\" href=\"report\">illegal content?</a>");
#endif
	puts(FOOT);
}

/* per request: output phpbb style */
void rec_bb(unsigned short n, unsigned short level){
/*	for(unsigned short hu=0;hu<level;hu++){
		fputs(stdout,"|&nbsp;");
	}
*/	//puts(message[n].text);
	print_message_html(n);
	for(long child=heir[n];child!=n;child=sib[child]){
		fputs("<div class=\"post\">",stdout);
		rec_bb(child,level+1);
		fputs("</div>",stdout);
	}
}
void print_bb(unsigned short n){
	puts(HEAD);
	puts(HEAD_HTML);
	puts(BODY);
	puts("<div class=\"thread\">");
	rec_bb(n,0);
	puts("</div>");
#ifdef REVOKE
	puts("</p><p align=\"right\"><a class=\"legal\" href=\"report\">illegal content?</a>");
#endif
	puts(FOOT);
}
void print_bb_latest(unsigned short n){
	if(tree->tail!=tree->head && tree->tail-tree->head<n)n=tree->tail-tree->head;
	unsigned short start=tree->tail-n;
	puts(HEAD);
	puts(HEAD_HTML);
	puts(BODY);
//	printf("<!--\ntree->head: %hu;\ntree->tail: %hu;\nn: %hu;\nstart: %hu;\n-->",tree->head,tree->tail,n,start);
	for(unsigned short cur=start;cur!=tree->tail;cur++){
		if(cur-message[cur].parent>cur-start || message[cur].parent==cur){
			puts("<div class=\"thread\">");
			rec_bb(cur,0);
			puts("</div>");
		}
	}
#ifdef REVOKE
	puts("</p><p align=\"right\"><a class=\"legal\" href=\"report\">illegal content?</a>");
#endif
	puts(FOOT);
}

/* another html output, equivalent to tree */
void print_table(unsigned short n){
	long q[MB_BUF_SIZE];
	unsigned short p=0;
	unsigned short s=1;
	unsigned short r=1;
	unsigned short z=d[n];
	q[0]=n;
	const unsigned long limit=31556952;
	//unsigned long now=(unsigned long)(time(NULL));
	puts("<table border=\"1\" style=\"white-space: pre-wrap; empty-cells: hide\" width=\"100%\">");
	puts("<tr>");
	while(r){
		//printf("<!-- p=%hu; s=%hu; r=%hu; z=%hu; -->",p,s,r,z);
		if(q[p]<0){
			puts("<td></td>");
			q[s++]=-1;
		}else{
			unsigned short m=q[p];
			unsigned long delta=now-message[m].timestamp;if(delta>limit)delta=limit;
			printf("<td colspan=\"%d\" bgcolor=\"#%02x%02x00\">",b[m],message[m].flags&1?0xcc:(delta/(limit/0x99)),message[m].flags&1?(delta/(limit/0x99)):0xcc);
			print_message_html(m);
			puts("</td>");
			if(heir[m]!=m){
				for(unsigned short l=heir[m];l!=m;l=sib[l]){
					q[s++]=l;
				}
			}else{
				q[s++]=-1;
			}
		}
		p++;
		r--;
		if(r<=0){
			puts("</tr>");
			if(--z){
				r=s-p;
				if(r)puts("<tr>");
			}
		}
	}
	puts("</table>");
}
/* table output almost equivalent to latest, but not sorted by descendants */
void print_table_latest(unsigned short n){
	if(tree->tail!=tree->head && tree->tail-tree->head<n)n=tree->tail-tree->head;
	unsigned short start=tree->tail-n;
	puts(HEAD);
	puts(HEAD_HTML);
	puts(BODY);
	for(unsigned short cur=start;cur!=tree->tail;cur++){
		if(cur-message[cur].parent>cur-start || message[cur].parent==cur){
			puts("<p>");
			print_table(cur);
			puts("</p>");
		}
	}
#ifdef REVOKE
	puts("</p><p align=\"right\"><a class=\"legal\" href=\"report\">illegal content?</a>");
#endif
	puts(FOOT);
}
/* print a thread */
void aux_thread(unsigned short leaf){
	// note that this is recursive, and might max out the stack
	// alternatively malloc a list the size of depth, or define a list the size of MB_BUF_SIZE
	if(message[leaf].parent!=leaf){ // alteratively, stop if leaf is a topic
		aux_thread(message[leaf].parent);
	}
	fputs("<div class=\"post\">",stdout); // if deleted, we should indicate that. also, colour code age maybe.
	print_message_html(leaf);
	fputs("</div>",stdout);
}
void print_thread(unsigned short leaf){
	puts(HEAD);
	puts(HEAD_HTML);
	puts(BODY);
	printf("<div class=\"thread\">");
	if(message[leaf].parent!=leaf)
		aux_thread(message[leaf].parent);
	// followed by tree, if any
	print_table(leaf);
	puts("</div>");
#ifdef REVOKE
	puts("</p><p align=\"right\"><a class=\"legal\" href=\"report\">illegal content?</a>");
#endif
	puts(FOOT);
}
/* print a discussion from any point */
void thread_svg(int leaf){
	// collect a list of ancestors instead of recursing?
	puts(HEAD);
	puts(HEAD_SVG);
	puts(BODY);
	// svg
	unsigned short height=1;for(unsigned short par=message[leaf].parent;par!=message[par].parent;par=message[par].parent)height++;
	printf("<svg xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" width=\"%d\" height=\"%d\" version=\"1.2\">",b[leaf]*m_w,(height+d[leaf])*m_h);
	// print all ancestors
	if(message[leaf].parent!=leaf){
		unsigned short trunk=leaf;
		unsigned short lift=height-1;
		do{
			trunk=message[trunk].parent;
			printf("<g transform=\"translate(%d %d)\">",(b[leaf]-1)*(m_w/2),lift*m_h);
			print_message(trunk);
			printf("<a xlink:href=\"reply?%d\"><text x=\"%d\" y=\"%d\">reply</text></a>",trunk,sep_w*2,r_h);
			printf("<line x1=\"%d\" y1=\"%d\" x2=\"%d\" y2=\"%d\"/>",m_w/2,m_h-sep_w,m_w/2,m_h+sep_w);
			printf("<a xlink:href=\"tree?%d\"><circle fill=\"#006600\" r=\"0.5em\" cx=\"%d\" cy=\"%d\"/></a>",message[trunk].parent,m_w/2,m_h-sep_w);
			puts("</g>");
			lift--;
		}while(message[trunk].parent!=trunk);
	}else height=0;
	// print node and all descendants
	printf("<g transform=\"translate(%d,%d)\">",0,height*m_h);
	print_tree_top(0,leaf);
	puts("</g></svg>");
#ifdef REVOKE
	puts("</p><p align=\"right\"><a class=\"legal\" href=\"report\">illegal content?</a>");
#endif
	printf("<script type=\"text/javascript\">window.scrollTo(%d-window.innerWidth/2,%d)</script>",(b[leaf])*m_w/2,height*m_h);
	puts(FOOT);
}


// aux func for mmapping files
size_t load_file(const char* filename, void** pointer){
	int fd=open(filename,O_RDONLY);
	if(fd==-1){
		// an error occurred
		*pointer=NULL;
		return -1;
	}
	struct stat fs;
	if(fstat(fd,&fs)<0){
		// an error occurred
		close(fd);
		*pointer=NULL;
		return -2;
	}
	*pointer=mmap(NULL,fs.st_size,PROT_READ,MAP_PRIVATE,fd,0);
	close(fd);
	if(*pointer==MAP_FAILED){
		*pointer=NULL;
		return -3;
	}
	return fs.st_size;
}
void unload_file(void* pointer, size_t size){
	munmap(pointer,size);
}

/* handler for all your 404 needs */
void user_error(const char* text){
	FCGI_SetExitStatus(404);
	puts(HEAD);
	puts(HEAD_HTML);
	puts(BODY);
	puts(text);
	puts(FOOT);
}

void reply_form_proper(const unsigned int n){
	/*
	printf("<svg xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" width=\"%d\" height=\"%d\" version=\"1.2\">",m_w,m_h);
	if(message[n].parent!=-1)
		printf("<a xlink:href=\"tree?%d\"><circle class=\"%s\" r=\"0.5em\" cx=\"%d\" cy=\"10\" fill=\"#006600\"/></a>",message[n].parent,message[n].deleted&1?"del":"leaf",m_w/2);
	print_message(n);
	puts("</svg>");
	*/
	fputs("<div class=\"thread\"><div class=\"post\">",stdout);
	/* print_message_html(n); */ aux_thread(n);
	puts("</div><div style=\"text-align: center\">");
	puts("<form action=\"reply\" method=\"post\" >");
	// text field to check if poster has even seen the website
	puts("Please type leaf into this field: <input type=\"text\" name=\"spm\"/><br/>");
	printf("<input type=\"hidden\" name=\"msg\" value=\"%d\"/>",n);
	puts("<textarea name=\"text\" cols=\"20\" rows=\"8\" maxlength=\"160\" ></textarea>");
	puts("<br/><input type=\"submit\" />");
	puts("</form>");
	puts("</div></div>");
}

//char* last_visitor; // 32 bit (ulong) are enough for IPv4; IPv6 requires 128; if string, don't use after free
//char last_visitor[16];
//unsigned short number_of_visits=0; // but what should the limit be? number of leafs? better: rate limiter, timecheck.
//unsigned long last_post_timestamp;
// should we calculate the number of visitors during the last 24 hours?
// would be easy: just go from tail back until timestamp is older than that.
// better: keep an index, compare every time, adjust if necessary; deliver difference from tail.

int main(void){
	const int size=sizeof(tbheader_t)+sizeof(leaf_t)*MB_BUF_SIZE;
	printf("size: %d\n",size);
	int r=0;
	int buffd=open(BUFFER,O_RDWR);
	if(buffd==-1){
		// need to create the file, need to make it big enough, and need to map it.
		buffd=open(BUFFER,O_RDWR|O_CREAT|O_TRUNC,0664);
		if(buffd!=-1){
			if( lseek(buffd, size, SEEK_SET) != -1 ){
				if( write(buffd, "", 1) != -1 ){
					tree=mmap(NULL,size,PROT_READ|PROT_WRITE,MAP_SHARED,buffd,0);
					if(tree!=MAP_FAILED){
						tree->magic=TREE_MAGIC;
						tree->tail=1;
						// add message
						message=tree->content;
						message[0].timestamp=(unsigned long)time(NULL);
						heir[0]=0;
						children[0]=0;
						b[0]=1; // the message is as wide as its heirs combined
						d[0]=1; // the message is at the lowest level in its subtree
						x[0]=0; // the message is centered initially
						//t[0]=message[0].timestamp; // youngest descendent is itself
						strcpy(message[0].text,"Welcome to the treeboard. What would you like to talk about?");
					}else{
						perror("when mmapping new file");
						r=2;
					}
				}else{
					perror("when adjusting file size");
					r=6;
				}
			}else{
				perror("lseek failed:");
				r=5;
			}
		}else{
			perror("When opening content buffer file");
			r=1;
		}
	}else{ // file exists
		tree=mmap(NULL,size,PROT_READ|PROT_WRITE,MAP_SHARED,buffd,0);
		if(tree!=MAP_FAILED){
			if(tree->magic==TREE_MAGIC){
				message=tree->content;
				// recalculate variables here: traverse tree (chronologically)
				unsigned short cur=tree->head;
				do{ // assume tree contains at least one node
					update_lineage(cur);
					cur++;
				}while(cur!=tree->tail);
			}else{
				fprintf(stderr,"wrong file type. magic number was %04X\n",tree->magic);
				r=7;
			}
		}else{
			perror("When mmapping file");
			r=2;
		}
	}

	// favicons, if any
	void* favico=NULL,*favpng=NULL;
	size_t favico_size=0, favpng_size=0;
	favico_size=load_file("./favicon.ico",&favico);
	favpng_size=load_file("./favicon.png",&favpng);

	if(r==0){
		// let's get it started in here
		char* query_string;
		char* script_name;
		char* request_method;

		while(FCGI_Accept()>=0){
			script_name=getenv("SCRIPT_NAME");
			request_method=getenv("REQUEST_METHOD");

			if(script_name==NULL){
				FCGI_SetExitStatus(404);
				continue;
			}
			// script_name contains the path; we want to be path agnostic.
			char* script_path=script_name;
			script_name=strrchr(script_name,'/');
			*(script_name++)='\0';

			now=(unsigned long)time(NULL);
			// this cascade determines how we handle the request
			// should be sorted by likelihood of relevance
			// NOTE: a byte-by-byte parser would be more efficient
			//       than repeated strncmp()s, but less legible
			//       and thus less maintainable.
			//       may be implemented later.
			// 
			// the commands are:
			// GET:
			// "css" : for style
			// "report" : form for deleting a message
			// "reply" : form for adding a message
			// "tree" : print a tree
			// "latest" : newest n messages
			// "flip": latest sideways
			// "json" : json api
			// "chan" : kareha-style
			// "bb" : phpbb-style
			// "table" : sort of like tree, but pure HTML
			// "favicon.{png,ico}" : favicons, if they exist
			// TODO: replace tableview with flipmode, we already have chanview for html
			// TODO: javascript, for adding leafs in place
			// POST:
			// "reply" : add a new message
			// "revoke" : overwrite a message and change its class
			if(strncmp("GET",request_method,3)==0){
				query_string=getenv("QUERY_STRING");

				if((*script_name)=='\0' || strncmp("latest",script_name,7)==0){
					int n;
					if(query_string==NULL || (*query_string)=='\0')n=50;
					else n=atoi(query_string);
					{
						unsigned short total=tree->tail-tree->head;
						if(total && n>total)n=total;
					}
			//		unsigned long date=strtotime(getenv("HTTP_IF_MODIFIED_SINCE"));
			//		if(date<last_time){
						FCGI_SetExitStatus(200);
						puts(HEAD);
						puts(HEAD_SVG);
						puts(BODY);
						if(n+50<tree->tail-tree->head)printf("<a style=\"text-decoration: none; color: #006600;\" href=\"?%hu\">more?</a>\n",n+50);
						woodcraft(n);
#ifdef REVOKE
						puts("</p><p align=\"right\"><a class=\"legal\" href=\"report\">illegal content?</a>");
#endif
						printf("<script type=\"text/javascript\">window.scrollTo(%d-window.innerWidth/2,0)</script>",(b[(unsigned short)(tree->tail-n)])*m_w/2);
						puts(FOOT);
			//		}else{
			//			FCGI_SetExitStatus(304);
			//		}
				}
				else if(strncmp("flip",script_name,5)==0){
					int n;
					if(query_string==NULL || (*query_string)=='\0')n=50;
					else n=atoi(query_string);
					{
						unsigned short total=tree->tail-tree->head;
						if(total && n>total)n=total;
					}
			//		unsigned long date=strtotime(getenv("HTTP_IF_MODIFIED_SINCE"));
			//		if(date<last_time){
						FCGI_SetExitStatus(200);
						puts(HEAD);
						puts(HEAD_SVG);
						puts(BODY);
						if(n+50<tree->tail-tree->head)printf("<a style=\"text-decoration: none; color: #006600;\" href=\"?%hu\">more?</a>\n",n+50);
						flipmode(n);
#ifdef REVOKE
						puts("</p><p align=\"right\"><a class=\"legal\" href=\"report\">illegal content?</a>");
#endif
						printf("<script type=\"text/javascript\">window.scrollTo(0,%d-window.innerHeight/2)</script>",(b[(unsigned short)(tree->tail-n)])*m_w/2);
						puts(FOOT);
			//		}else{
			//			FCGI_SetExitStatus(304);
			//		}
				}
				else if(strncmp("tree",script_name,5)==0){
					int n;
					if(query_string==NULL)n=tree->head; // TODO: if buffer is full, there is most likely more than one root
					else{
						n=atoi(query_string);
						if(n<0 || (tree->head==0 && n>=tree->tail)){
							user_error("ID does not exist. <a href=\"tree\">Go back to top?</a>");
							continue;
						}
					}
					FCGI_SetExitStatus(200);
					thread_svg(n);
				}
				else if(strcmp("css",script_name)==0){
					FCGI_SetExitStatus(200);
					puts("Content-type: text/css\r\n\r");
					puts(CSS);
				}
				else if(strncmp("chan",script_name,4)==0){
					FCGI_SetExitStatus(200);
					int n;
					if(query_string==NULL || (*query_string)=='\0'){
						FCGI_SetExitStatus(200);
						print_chan();
						continue;
					}else{
						n=atoi(query_string);
						if(n<0||n>MB_BUF_SIZE||(tree->head==0 && tree->tail<n)){
							user_error("Thread does not exist. <a href=\"chan\">Go back?</a>");
							continue;
						}
					}
					FCGI_SetExitStatus(200);
					print_thread(n);
				}
				else if(strncmp("bb",script_name,4)==0){
					int n;
					if(query_string==NULL || (*query_string)=='\0')n=50;
					else{
						n=atoi(query_string);
						if(n<0 || (tree->head==0 && n>=tree->tail)){
							user_error("ID does not exist. <a href=\"bb\">Go back to top?</a>");
							continue;
						}
					}
					FCGI_SetExitStatus(200);
					print_bb_latest(n);
				}
				else if(strncmp("json",script_name,5)==0){
					FCGI_SetExitStatus(200);
					puts("Content-Type: application/json; charset-encoding=UTF-8\r\n\r");
					if(query_string==NULL || *query_string=='\0'){ // entire database as list
						unsigned short i=tree->head;
						printf("[{\"id\":%d,\"timestamp\":%lu,\"flags\":%d,\"parent\":%d,\"text\":\"",i,message[i].timestamp,message[i].flags,message[i].parent);
						mdea(message[i].text);
						putchar('"');putchar('}');
						for(i++;i!=tree->tail;i++){
							printf(",{\"id\":%d,\"timestamp\":%lu,\"flags\":%d,\"parent\":%d,\"text\":\"",i,message[i].timestamp,message[i].flags,message[i].parent);
							mdea(message[i].text);
							putchar('"');putchar('}');
						}
						puts("]");
					}
					else{ // subtree as s-exp
						int n=atoi(query_string);
						if(n<0||(tree->head==0 && n>=tree->tail)){
							puts("{}");
							FCGI_SetExitStatus(404);
							continue;
						}
						fputs("{\"text\":\"",stdout);
						mdea(message[n].text);
						//fputs("\"",stdout);
						printf("\",\"timestamp\":%lu",message[n].timestamp);
						if(message[n].flags&1)fputs(",\"deleted\"",stdout);
						if(heir[n]!=n){
							putchar(',');
							json_print(heir[n]);
						}
						puts("}");
					}
				}
				else if(strcmp("table",script_name)==0){
					int n;
					if(query_string==NULL || (*query_string)=='\0'){
						FCGI_SetExitStatus(200);
						print_table_latest(50);	
						continue;
					}else{
						n=atoi(query_string);
						if(n<0||n>MB_BUF_SIZE||(tree->head==0 && tree->tail<n)){
							user_error("");
							continue;
						}
					}
					FCGI_SetExitStatus(200);
					puts(HEAD);
					puts(HEAD_HTML);
					puts(BODY);
					print_table(n);
#ifdef REVOKE
					puts("</p><p align=\"right\"><a class=\"legal\" href=\"report\">illegal content?</a>");
#endif
					puts(FOOT);
				}
				else if(strncmp("reply",script_name,6)==0){
					int n;
					if(query_string==NULL) n=tree->head;
					else{
						n=atoi(query_string);
						if(n<0||n>MB_BUF_SIZE){
							user_error("");
							continue;
						}
					}
					FCGI_SetExitStatus(200);
					puts(HEAD);
					puts(HEAD_HTML);
					puts(BODY);
					reply_form_proper(n);
					puts(FOOT);
				}
				else if(strncmp("favicon.",script_name,8)==0){
					if(strcmp(script_name+8,"png")==0 && favpng!=NULL){
						// serve favpng
						FCGI_SetExitStatus(200);
						puts("Content-type: image/png\r");
						printf("Content-length: %d\r\n\r\n",favpng_size);
						for(size_t remaining=favpng_size;remaining>0;remaining-=fwrite(favpng,1,favpng_size,stdout));
						continue;
					}
					if(strcmp(script_name+8,"ico")==0 && favico!=NULL){
						// serve favico
						FCGI_SetExitStatus(200);
						puts("Content-type: image/x-icon\r");
						printf("Content-length: %d\r\n\r\n",favico_size);
						for(size_t remaining=favico_size;remaining>0;remaining-=fwrite(favico,1,favico_size,stdout));
						continue;
					}
					user_error("I don't have a favicon for you, sorry.");
				}
#ifdef REVOKE
				else if(strncmp("report",script_name,7)==0){
					FCGI_SetExitStatus(200);
					puts(HEAD);
					puts(HEAD_HTML);
					puts(BODY);
					/*
					puts("Dieses Formular existiert, weil nach geltendem Recht gewisse Inhalte unverzüglich und ohne Prüfung gelöscht werden müssen, sobald diese dem Server bekannt gemacht werden. Geben Sie hier die ID-Nummer des Beitrags ein, den Sie beschuldigen wollen, und wir lassen die Beweise verschwinden, wie vom Gesetz gefordert.</p><p lang=\"en\">Austrian law requires the immediate deletion of any content suspected of being of a certain nature as soon as the server receives notice, without delay. If you intend to accuse any post here, enter its ID below, and the evidence will be erased, as demanded by law.");
					*/
					puts("This form exists because some jurisdictions require the immediate deletion of any content accused of being of a certain nature as soon as the server receives notice, without delay. If you intend to accuse any post here, enter its ID below, and the evidence will be erased immediately, as demanded by law.");
					puts("<form action=\"revoke\" method=\"post\">");
					puts("<input type=\"text\" name=\"del\"/>");
					puts("</form>");
					puts(FOOT);
				}
#endif
				else { // what other names would this program even have?
					char* path=getenv("PATH_INFO");
					FCGI_SetExitStatus(404);
					puts(HEAD);
					puts(HEAD_HTML);
					puts(BODY);
					printf("You must have mistyped. Don't do that. Find your way <a href=\"");
					while(*path){if(*path=='/')printf("../");++path;}
					puts("latest\">here</a>.");
					puts(FOOT);
					//user_error();
				}
			}
			else if(strncmp("POST",request_method,4)==0){
				int content_length;
				char* cl_s=getenv("CONTENT_LENGTH"); // content length string
				if(cl_s!=NULL){
					content_length=atoi(cl_s);
					if(content_length>MB_MAX_CONTENT_LEN){
						FCGI_SetExitStatus(413);
						puts(HEAD);
						puts(HEAD_HTML);
						puts("Content length exceeds allotted space.<br/><a href=\"latest\">Go back?</a>");
						puts(FOOT);
						continue;
					}
				}else{
					// FCGI_SetExitStatus(411);
					content_length=0;
				}

				if(strncmp("reply",script_name,6)==0){
					/* we might want to extract the requesting IP.
					 * if the same IP posts too often in a row, it should receive a 402: Free advertising if you pay me enough!
					 *
					 * getenv("REMOTE_ADDR");
					 */
					int c;
					// insert spam field, check its content (leaf)
					if((c=getchar())=='s' &&
					   (c=getchar())=='p' &&
					   (c=getchar())=='m' &&
					   (c=getchar())=='=' &&
					   (c=getchar())=='l' &&
					   (c=getchar())=='e' &&
					   (c=getchar())=='a' &&
					   (c=getchar())=='f' &&
					   (c=getchar())=='&'){
						if(
						   (c=getchar())=='m' &&
						   (c=getchar())=='s' &&
						   (c=getchar())=='g' &&
						   (c=getchar())=='='){
							unsigned short msg=0;
							while((c=getchar())>='0' && c<='9'){
								msg*=10;
								msg+=c-'0';
							}
							if(c!='&'){
								FCGI_SetExitStatus(400);
								puts(HEAD);
								puts(HEAD_HTML);
								puts(BODY);
								puts("You must be tired. Get some sleep.");
								puts(FOOT);
								continue;
							}
							if((c=getchar())=='t' &&
							   (c=getchar())=='e' &&
							   (c=getchar())=='x' &&
							   (c=getchar())=='t' &&
							   (c=getchar())=='='){
								if(msg==tree->tail || (tree->head!=tree->tail && msg>tree->tail)){
									FCGI_SetExitStatus(403);
									puts(HEAD);
									puts(HEAD_HTML);
									puts(BODY);
									puts("The message you attempt to reply to should exist first.");
									puts(FOOT);
									continue;
								}
								int nid=add_message(msg);
								if(nid>=0){
									// cobble together the location string
									char* host;
									if((host=getenv("HTTP_HOST"))==NULL){ // only if HTTP<1.1
										if((host=getenv("SEVER_NAME"))==NULL){
											// other fallbacks? referer? listen addr?
											FCGI_SetExitStatus(200);
											puts(HEAD);
											puts(HEAD_HTML);
											puts(BODY);
											printf("Message has been added, not to worry. Find it <a href=\"tree?%s\">here</a>.<br/>I would redirect you there automatically, but your browser is too old. And for some reason I am not sure of my name.\n",query_string);
											puts(FOOT);
											continue;
										}
									}
									FCGI_SetExitStatus(201);
									// location
									printf("Location: http");
									if(getenv("HTTPS"))putchar('s');
									printf("://%s",host);
									if(*script_path)printf("%s",script_path);
									printf("/tree?%d\r\n",nid);
									// refresh: all browsers seem to ignore this in favour of location
									printf("Refresh: 0;url=");
									if(getenv("HTTPS"))printf("https");else printf("http");
									printf("://%s",host);
									if(*script_path)printf("%s",script_path);
									printf("/tree?%d\r\n\r\n",msg);
								/*	FCGI_SetExitStatus(202);
									printf("Refresh: 0;url=");
									if(getenv("HTTPS"))printf("https");else printf("http");
									printf("://%s",host);
									if(*script_path)printf("%s",script_path);
									printf("/tree?%d\r\n",msg);
								*/	puts(HEAD);
								}else if(nid==-1){
									FCGI_SetExitStatus(404);
									puts(HEAD);
									puts(HEAD_HTML);
									puts(BODY);
									puts("No empty messages, please.<br/>If you are using Internet Explorer, you will not be able to see message content in the tree view. Use <a href=\"table\">tableview</a> or <a href=\"chan\">chanview</a> instead. Also consider switching to any other web browser.");
									puts(FOOT);
								}else if(nid==-2){
									FCGI_SetExitStatus(413);
									puts(HEAD);
									puts(HEAD_HTML);
									puts(BODY);
									printf("Message length exceeds allotted space.<br/><a href=\"reply?%s\">Try again</a> or <a href=\"tree?%s\">Go back</a>.",msg,msg);
									puts(FOOT);
								}else if(nid==-3){
									FCGI_SetExitStatus(404);
									puts(HEAD);
									puts(HEAD_HTML);
									puts(BODY);
									puts("Message contains at least one illegal character. For shame!");
									puts(FOOT);
								}else if(nid==-4){
									FCGI_SetExitStatus(402);
									puts(HEAD);
									puts(HEAD_HTML);
									puts(BODY);
									puts("Looks like you found a commercial use for my board. Care to cut me in?");
									puts(FOOT);
								}else{
									FCGI_SetExitStatus(500);
									puts(HEAD);
									puts(HEAD_HTML);
									puts(BODY);
									puts("I have no idea what just happened.");
									puts(FOOT);
								}
							}else{
								FCGI_SetExitStatus(400);
								puts(HEAD);
								puts(HEAD_HTML);
								puts(BODY);
								puts("It's just two parameters, it's not that complicated.");
								puts(FOOT);
							}
						}else{
							FCGI_SetExitStatus(400);
							puts(HEAD);
							puts(HEAD_HTML);
							puts(BODY);
							puts("You botched the first variable. Keep trying.");
							puts(FOOT);
							continue;
						}
					}else{
						puts(HEAD);
						puts(HEAD_HTML);
						puts(BODY);
						puts("<p>Please read the instructions carefully.<br/>We apologize for the inconvenience.<br/>Brought to you by the initiative for a greener and healthier network environment.</p>");
						// attempt to extract message id from parameters
						// unfortunately we duplicate code here
						while(c!='&')c=getchar(); // skip towards next parameter if we are not there already
						if(
						   (c=getchar())=='m' &&
						   (c=getchar())=='s' &&
						   (c=getchar())=='g' &&
						   (c=getchar())=='='){
							unsigned short msg=0;
							while((c=getchar())>='0' && c<='9'){
								msg*=10;
								msg+=c-'0';
							}
							reply_form_proper(msg); // NOTE: we could insert the message text into the form, but the user agent probably retained it anyway
						}else{ // it failed
							printf("<p align=\"center\"><a href=\"%s\">back</a></p>",getenv("HTTP_REFERER"));
						}
						puts(FOOT);
						FCGI_SetExitStatus(400);
					}
				}
#ifdef REVOKE
				else if(strncmp("revoke",script_name,3)==0){
					int c;
					int msg_id=0;
					if((c=getchar())=='d' &&
					   (c=getchar())=='e' &&
					   (c=getchar())=='l' &&
					   (c=getchar())=='='){
						while((c=getchar())>='0'&&c<='9'){
							msg_id*=10;
							msg_id+=c-'0';
						}
						if(c!=EOF){
							/* get angry */
						}
					}else{
						FCGI_SetExitStatus(400);
						continue;
					}
					// TBI: also check referer
					if(!message[msg_id].flags&1){
						sprintf(message[msg_id].text,"Content erased by: %s",getenv("REMOTE_ADDR"));
						message[msg_id].flags|=1;
						FCGI_SetExitStatus(303);
						puts(HEAD);
						printf("Location: %s://%s%s/tree?%d\r\n\r\n",getenv("HTTPS")?"https":"http",getenv("HTTP_HOST"),script_path,msg_id);
						puts(HEAD_HTML);
						puts(BODY);
						puts("You should be ashamed of yourself.");
						puts(FOOT);
					}else{
						FCGI_SetExitStatus(410);
						puts(HEAD);
						puts(HEAD_HTML);
						puts(BODY);
						puts("That message had been deleted already.");
						puts(FOOT);
					}
				}
#endif
				else{ // handle a POST invalid_url
					FCGI_SetExitStatus(418);
					//FCGI_SetExitStatus(404);
					puts(HEAD); puts(HEAD_HTML); puts(BODY);
					puts("Wrong server?");
					puts(FOOT);
				}
			}else{ // neither GET nor POST
				FCGI_SetExitStatus(501);
				puts(HEAD);
				puts(HEAD_HTML);
				puts(BODY);
				puts("Method not implemented.<br/>Method was: ");
				puts(request_method);
				puts(FOOT);
			}
		} // wend
	}
	munmap(tree,0);
	close(buffd);
	unload_file(favico,favico_size);
	unload_file(favpng,favpng_size);

	return r;
}

