#include <u.h>
#include <libc.h>
#include <bio.h>
#include <regexp.h>
#include "spam.h"

enum {
	Quanta	= 8192,
	Minbody = 6000,
	HdrMax	= 15,
};

typedef struct keyword Keyword;
typedef struct word Word;

struct word{
	char	*string;
	int	n;
};

struct	keyword{
	char	*string;
	int	value;
};

Word	htmlcmds[] =
{
	"html",		4,
	"!doctype html", 13,
	0,

};

Word	hrefs[] =
{
	"a href=",	7,
	"a title=",	8,
	"a target=",	9,
	"base href=",	10,
	"img src=",	8,
	"img border=",	11,
	"form action=", 12,
	"!--",		3,
	0,

};

/*
 *	RFC822 header keywords to look for for fractured header.
 *	all lengths must be less than HdrMax defined above.
 */
Word	hdrwords[] =
{
	"cc:",			3,
	"bcc:", 		4,
	"to:",			3,
	0,			0,

};

Keyword	keywords[] =
{
	"header",	HoldHeader,
	"line",		SaveLine,
	"hold",		Hold,
	"dump",		Dump,
	"loff",		Lineoff,
	0,		Nactions,
};

Patterns patterns[] = {
[Dump]		{ "DUMP:", 0, 0 },
[HoldHeader]	{ "HEADER:", 0, 0 },
[Hold]		{ "HOLD:", 0, 0 },
[SaveLine]	{ "LINE:", 0, 0 },
[Lineoff]	{ "LINEOFF:", 0, 0 },
[Nactions]	{ 0, 0, 0 },
};

static char*	endofhdr(char*, char*);
static	int	escape(char**);
static	int	extract(char*);
static	int	findkey(char*);
static	int	hash(int);
static	int	isword(Word*, char*, int);
static	void	parsealt(Biobuf*, char*, Spat**);

/*
 *	The canonicalizer: convert input to canonical representation
 */
char*
readmsg(Biobuf *bp, int *hsize, int *bufsize)
{
	char *p, *buf;
	int n, offset, eoh, bsize, delta;

	buf = 0;
	offset = 0;
	if(bufsize)
		*bufsize = 0;
	if(hsize)
		*hsize = 0;
	for(;;) {
		buf = Realloc(buf, offset+Quanta+1);
		n = Bread(bp, buf+offset, Quanta);
		if(n < 0){
			free(buf);
			return 0;
		}
		p = buf+offset;			/* start of this chunk */
		offset += n;			/* end of this chunk */
		buf[offset] = 0;
		if(n == 0){
			if(offset == 0)
				return 0;
			break;
		}

		if(hsize == 0)			/* don't process header */
			break;
		if(p != buf && p[-1] == '\n')	/* check for EOH across buffer split */
			p--;
		p = endofhdr(p, buf+offset);
		if(p)
			break;
		if(offset >= Maxread)		/* gargantuan header - just punt*/
		{
			if(hsize)
				*hsize = offset;
			if(bufsize)
				*bufsize = offset;
			return buf;
		}
	}
	eoh = p-buf;				/* End of header */
	bsize = offset - eoh;			/* amount of body already read */

		/* Read at least Minbody bytes of the body */
	if (bsize < Minbody){
		delta = Minbody-bsize;
		buf = Realloc(buf, offset+delta+1);
		n = Bread(bp, buf+offset, delta);
		if(n > 0) {
			offset += n;
			buf[offset] = 0;
		}
	}
	if(hsize)
		*hsize = eoh;
	if(bufsize)
		*bufsize = offset;
	return buf;
}

static	int
isword(Word *wp, char *text, int len)
{
	for(;wp->string; wp++)
		if(len >= wp->n && strncmp(text, wp->string, wp->n) == 0)
			return 1;
	return 0;
}

static char*
endofhdr(char *raw, char *end)
{
	int i;
	char *p, *q;
	char buf[HdrMax];

	/*
 	 * can't use strchr to search for newlines because
	 * there may be embedded NULL's.
	 */
	for(p = raw; p < end; p++){
		if(*p != '\n' || p[1] != '\n')
			continue;
		p++;
		for(i = 0, q = p+1; i < sizeof(buf) && *q; q++){
			buf[i++] = tolower(*q);
			if(*q == ':' || *q == '\n')
				break;
		}
		if(!isword(hdrwords, buf, i))
			return p+1;
	}
	return 0;
}

static	int
htmlmatch(Word *wp, char *text, char *end, int *n)
{
	char *cp;
	int i, c, lastc;
	char buf[MaxHtml];

	/*
	 * extract a string up to '>'
	 */

	i = lastc = 0;
	cp = text;
	while (cp < end && i < sizeof(buf)-1){
		c = *cp++;
		if(c == '=')
			c = escape(&cp);
		switch(c){
		case 0:
		case '\r':
			continue;
		case '>':
			goto out;
		case '\n':
		case ' ':
		case '\t':
			if(lastc == ' ')
				continue;
			c = ' ';
			break;
		default:
			c = tolower(c);
			break;
		}
		buf[i++] = lastc = c;
	}
out:
	buf[i] = 0;
	if(n)
		*n = cp-text;
	return isword(wp, buf, i);
}

static int
escape(char **msg)
{
	int c;
	char *p;

	p = *msg;
	c = *p;
	if(c == '\n'){
		p++;
		c = *p++;
	} else
	if(c == '2'){
		c = tolower(p[1]);
		if(c == 'e'){
			p += 2;
			c = '.';
		}else
		if(c == 'f'){
			p += 2;
			c = '/';
		}else
		if(c == '0'){
			p += 2;
			c = ' ';
		}
		else c = '=';
	} else {
		if(c == '3' && tolower(p[1]) == 'd')
			p += 2;
		c = '=';
	}
	*msg = p;
	return c;
}

static int
htmlchk(char **msg, char *end)
{
	int n;
	char *p;

	static int ishtml;

	p = *msg;
	if(ishtml == 0){
		ishtml = htmlmatch(htmlcmds, p, end, &n);
	
		/* If not an HTML keyword, check if it's
		 * an HTML comment (<!comment>).  if so,
		 * skip over it; otherwise copy it in.
		 */
		if(ishtml == 0 && *p != '!')	/* not comment */
			return '<';		/* copy it */

	} else if(htmlmatch(hrefs, p, end, &n))	/* if special HTML string  */
		return '<';			/* copy it */
	
	/*
	 * this is an uninteresting HTML command; skip over it.
	 */
	p += n;
	*msg = p+1;
	return *p;
}

/*
 * decode a base 64 encode body
 */
void
conv64(char *msg, char *end, char *buf, int bufsize)
{
	int len, i;
	char *cp;

	len = end - msg;
	i = (len*3)/4+1;	// room for max chars + null
	cp = Malloc(i);
	len = dec64((uchar*)cp, i, msg, len);
	convert(cp, cp+len, buf, bufsize, 1);
	free(cp);
}

int
convert(char *msg, char *end, char *buf, int bufsize, int isbody)
{

	char *p;
	int c, lastc, base64;

	lastc = 0;
	base64 = 0;
	while(msg < end && bufsize > 0){
		c = *msg++;

		/*
		 * In the body only, try to strip most HTML and
		 * replace certain MIME escape sequences with the character
		 */
		if(isbody) {
			do{
				p = msg;
				if(c == '<')
					c = htmlchk(&msg, end);
				if(c == '=')
					c = escape(&msg);
			} while(p != msg && p < end);
		}
		switch(c){
		case 0:
		case '\r':
			continue;
		case '\t':
		case ' ':
		case '\n':
			if(lastc == ' ')
				continue;
			c = ' ';
			break;
		case 'C':	/* check for MIME base 64 encoding in header */
		case 'c':
			if(isbody == 0)
			if(msg < end-32 && *msg == 'o' && msg[1] == 'n')
			if(cistrncmp(msg+2, "tent-transfer-encoding: base64", 30) == 0)
				base64 = 1;
			c = 'c';
			break;
		default:
			c = tolower(c);
			break;
		}
		*buf++ = c;
		lastc = c;
		bufsize--;
	}
	*buf = 0;
	return base64;
}

/*
 *	The pattern parser: build data structures from the pattern file
 */

static int
hash(int c)
{
	return c & 127;
}

static	int
findkey(char *val)
{
	Keyword *kp;

	for(kp = keywords; kp->string; kp++)
		if(strcmp(val, kp->string) == 0)
				break;
	return kp->value;
}

#define	whitespace(c)	((c) == ' ' || (c) == '\t')

void
parsepats(Biobuf *bp)
{
	Pattern *p, *new;
	char *cp, *qp;
	int type, action, n, h;
	Spat *spat;

	for(;;){
		cp = Brdline(bp, '\n');
		if(cp == 0)
			break;
		cp[Blinelen(bp)-1] = 0;
		while(*cp == ' ' || *cp == '\t')
			cp++;
		if(*cp == '#' || *cp == 0)
			continue;
		type = regexp;
		if(*cp == '*'){
			type = string;
			cp++;
		}
		qp = strchr(cp, ':');
		if(qp == 0)
			continue;
		*qp = 0;
		if(debug)
			fprint(2, "action = %s\n", cp);
		action = findkey(cp);
		if(action >= Nactions)
			continue;
		cp = qp+1;
		n = extract(cp);
		if(n <= 0 || *cp == 0)
			continue;

		qp = strstr(cp, "~~");
		if(qp){
			*qp = 0;
			n = strlen(cp);
		}
		if(debug)
			fprint(2, " Pattern: `%s'\n", cp);

			/* Hook regexps into a chain */
		if(type == regexp) {
			new = Malloc(sizeof(Pattern));
			new->action = action;
			new->pat = regcomp(cp);
			if(new->pat == 0){
				free(new);
				continue;
			}
			new->type = regexp;
			new->alt = 0;
			new->next = 0;

			if(qp)
				parsealt(bp, qp+2, &new->alt);

			new->next = patterns[action].regexps;
			patterns[action].regexps = new;
			continue;

		}
			/* not a Regexp - hook strings into Pattern hash chain */
		spat = Malloc(sizeof(*spat));
		spat->next = 0;
		spat->alt = 0;
		spat->len = n;
		spat->string = Malloc(n+1);
		spat->c1 = cp[1];
		strcpy(spat->string, cp);

		if(qp)
			parsealt(bp, qp+2, &spat->alt);

		p = patterns[action].strings;
		if(p == 0) {
			p = Malloc(sizeof(Pattern));
			memset(p, 0, sizeof(*p));
			p->action = action;
			p->type = string;
			patterns[action].strings = p;
		}
		h = hash(*spat->string);
		spat->next = p->spat[h];
		p->spat[h] = spat;
	}
}

static void
parsealt(Biobuf *bp, char *cp, Spat** head)
{
	char *p;
	Spat *alt;

	while(cp){
		if(*cp == 0){		/*escaped newline*/
			do{
				cp = Brdline(bp, '\n');
				if(cp == 0)
					return;
				cp[Blinelen(bp)-1] = 0;
			} while(extract(cp) <= 0 || *cp == 0);
		}

		p = cp;
		cp = strstr(p, "~~");
		if(cp){
			*cp = 0;
			cp += 2;
		}
		if(strlen(p)){
			alt = Malloc(sizeof(*alt));
			alt->string = strdup(p);
			alt->next = *head;
			*head = alt;
		}
	}
}

static int
extract(char *cp)
{
	int c;
	char *p, *q, *r;

	p = q = r = cp;
	while(whitespace(*p))
		p++;
	while(c = *p++){
		if (c == '#')
			break;
		if(c == '"'){
			while(*p && *p != '"'){
				if(*p == '\\' && p[1] == '"')
					p++;
				if('A' <= *p && *p <= 'Z')
					*q++ = *p++ + ('a'-'A');
				else
					*q++ = *p++;
			}
			if(*p)
				p++;
			r = q;		/* never back up over a quoted string */
		} else {
			if('A' <= c && c <= 'Z')
				c += ('a'-'A');
			*q++ = c;
		}
	}
	while(q > r && whitespace(q[-1]))
		q--;
	*q = 0;
	return q-cp;
}

/*
 *	The matching engine: compare canonical input to pattern structures
 */

static Spat*
isalt(char *message, Spat *alt)
{
	while(alt) {
		if(*cmd)
		if(message != cmd && strstr(cmd, alt->string))
			break;
		if(message != header+1 && strstr(header+1, alt->string))
			break;
		if(strstr(message, alt->string))
			break;
		alt = alt->next;
	}
	return alt;
}

int
matchpat(Pattern *p, char *message, Resub *m)
{
	Spat *spat;
	char *s;
	int c, c1;

	if(p->type == string){
		c1 = *message;
		for(s=message; c=c1; s++){
			c1 = s[1];
			for(spat=p->spat[hash(c)]; spat; spat=spat->next){
				if(c1 == spat->c1)
				if(memcmp(s, spat->string, spat->len) == 0)
				if(!isalt(message, spat->alt)){
					m->sp = s;
					m->ep = s + spat->len;
					return 1;
				}
			}
		}
		return 0;
	}
	m->sp = m->ep = 0;
	if(regexec(p->pat, message, m, 1) == 0)
		return 0;
	if(isalt(message, p->alt))
		return 0;
	return 1;
}


void
xprint(int fd, char *type, Resub *m)
{
	char *p, *q;
	int i;

	if(m->sp == 0 || m->ep == 0)
		return;

		/* back up approx 30 characters to whitespace */
	for(p = m->sp, i = 0; *p && i < 30; i++, p--)
			;
	while(*p && *p != ' ')
		p--;
	p++;

		/* grab about 30 more chars beyond the end of the match */
	for(q = m->ep, i = 0; *q && i < 30; i++, q++)
			;
	while(*q && *q != ' ')
		q++;

	fprint(fd, "%s %.*s~%.*s~%.*s\n", type, 
		utfnlen(p, m->sp-p), p,
		utfnlen(m->sp, m->ep-m->sp), m->sp,
		utfnlen(m->ep, q-m->ep), m->ep);
}

enum {
	INVAL=	255
};

static uchar t64d[256] = {
/*00 */	INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL,
	INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL,
/*10*/	INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL,
	INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL,
/*20*/	INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL,
	INVAL, INVAL, INVAL,    62, INVAL, INVAL, INVAL,    63,
/*30*/	   52,	  53,	 54,	55,    56,    57,    58,    59,
	   60,	  61, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL,
/*40*/	INVAL,    0,      1,     2,     3,     4,     5,     6,
	    7,    8,      9,    10,    11,    12,    13,    14,
/*50*/	   15,   16,     17,    18,    19,    20,    21,    22,
	   23,   24,     25, INVAL, INVAL, INVAL, INVAL, INVAL,
/*60*/	INVAL,   26,     27,    28,    29,    30,    31,    32,
	   33,   34,     35,    36,    37,    38,    39,    40,
/*70*/	   41,   42,     43,    44,    45,    46,    47,    48,
	   49,   50,     51, INVAL, INVAL, INVAL, INVAL, INVAL,
/*80*/	INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL,
	INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL,
/*90*/	INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL,
	INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL,
/*A0*/	INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL,
	INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL,
/*B0*/	INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL,
	INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL,
/*C0*/	INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL,
	INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL,
/*D0*/	INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL,
	INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL,
/*E0*/	INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL,
	INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL,
/*F0*/	INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL,
	INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL,
};
