Commit Diff


commit - b6afd33e2f23953f00c6fac6b5d45946a9113654
commit + 9df487d720a59bf8cb0dc4ccffc30ad8eb48256a
blob - /dev/null
blob + 1fdd46c6694508803452c7d9d2e3a58b6d5f50d7 (mode 644)
--- /dev/null
+++ src/libhttpd/alloc.c
@@ -0,0 +1,35 @@
+#include <u.h>
+#include <libc.h>
+#include <bin.h>
+#include <httpd.h>
+
+/*
+ * memory allocators:
+ * h routines call canalloc; they should be used by everything else
+ * note this memory is wiped out at the start of each new request
+ * note: these routines probably shouldn't fatal.
+ */
+char*
+hstrdup(HConnect *c, char *s)
+{
+	char *t;
+	int n;
+
+	n = strlen(s) + 1;
+	t = binalloc(&c->bin, n, 0);
+	if(t == nil)
+		sysfatal("out of memory");
+	memmove(t, s, n);
+	return t;
+}
+
+void*
+halloc(HConnect *c, ulong n)
+{
+	void *p;
+
+	p = binalloc(&c->bin, n, 1);
+	if(p == nil)
+		sysfatal("out of memory");
+	return p;
+}
blob - /dev/null
blob + 1b535ec917373f8a754f89230aa9a9be2bf1af3c (mode 644)
--- /dev/null
+++ src/libhttpd/checkcontent.c
@@ -0,0 +1,33 @@
+#include <u.h>
+#include <libc.h>
+#include <bin.h>
+#include <httpd.h>
+
+int
+hcheckcontent(HContent *me, HContent *oks, char *list, int size)
+{
+	HContent *ok;
+
+	if(oks == nil || me == nil)
+		return 1;
+	for(ok = oks; ok != nil; ok = ok->next){
+		if((cistrcmp(ok->generic, me->generic) == 0 || strcmp(ok->generic, "*") == 0)
+		&& (me->specific == nil || cistrcmp(ok->specific, me->specific) == 0 || strcmp(ok->specific, "*") == 0)){
+			if(ok->mxb > 0 && size > ok->mxb)
+				return 0;
+			return 1;
+		}
+	}
+
+	USED(list);
+	if(0){
+		fprint(2, "list: %s/%s not found\n", me->generic, me->specific);
+		for(; oks != nil; oks = oks->next){
+			if(oks->specific)
+				fprint(2, "\t%s/%s\n", oks->generic, oks->specific);
+			else
+				fprint(2, "\t%s\n", oks->generic);
+		}
+	}
+	return 0;
+}
blob - /dev/null
blob + 450f60f7e9474bf28abda30f351c9a18f48f34ff (mode 644)
--- /dev/null
+++ src/libhttpd/date.c
@@ -0,0 +1,212 @@
+#include <u.h>
+#include <libc.h>
+#include <httpd.h>
+
+/*
+ * print dates in the format
+ * Wkd, DD Mon YYYY HH:MM:SS GMT
+ * parse dates of formats
+ * Wkd, DD Mon YYYY HH:MM:SS GMT
+ * Weekday, DD-Mon-YY HH:MM:SS GMT
+ * Wkd Mon ( D|DD) HH:MM:SS YYYY
+ * plus anything similar
+ */
+static char *
+weekdayname[7] =
+{
+	"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"
+};
+static char *
+wdayname[7] =
+{
+	"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
+};
+
+static char *
+monname[12] =
+{
+	"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
+};
+
+static	int	dateindex(char*, char**, int);
+
+static int
+dtolower(int c)
+{
+	if(c >= 'A' && c <= 'Z')
+		return c - 'A' + 'a';
+	return c;
+}
+
+static int
+disalpha(int c)
+{
+	return c >= 'A' && c <= 'Z' || c >= 'a' && c <= 'z';
+}
+
+static int
+disdig(int c)
+{
+	return c >= '0' && c <= '9';
+}
+
+int
+hdatefmt(Fmt *f)
+{
+	Tm *tm;
+	ulong t;
+
+	t = va_arg(f->args, ulong);
+	tm = gmtime(t);
+	return fmtprint(f, "%s, %.2d %s %.4d %.2d:%.2d:%.2d GMT",
+		wdayname[tm->wday], tm->mday, monname[tm->mon], tm->year+1900,
+		tm->hour, tm->min, tm->sec);
+}
+
+static char*
+dateword(char *date, char *buf)
+{
+	char *p;
+	int c;
+
+	p = buf;
+	while(!disalpha(c = *date) && !disdig(c) && c)
+		date++;
+	while(disalpha(c = *date)){
+		if(p - buf < 30)
+			*p++ = dtolower(c);
+		date++;
+	}
+	*p = 0;
+	return date;
+}
+
+static int
+datenum(char **d)
+{
+	char *date;
+	int c, n;
+
+	date = *d;
+	while(!disdig(c = *date) && c)
+		date++;
+	if(c == 0){
+		*d = date;
+		return -1;
+	}
+	n = 0;
+	while(disdig(c = *date)){
+		n = n * 10 + c - '0';
+		date++;
+	}
+	*d = date;
+	return n;
+}
+
+/*
+ * parse a date and return the seconds since the epoch
+ * return 0 for a failure
+ */
+ulong
+hdate2sec(char *date)
+{
+	Tm tm;
+	char buf[32];
+
+	/*
+	 * Weekday|Wday
+	 */
+	date = dateword(date, buf);
+	tm.wday = dateindex(buf, wdayname, 7);
+	if(tm.wday < 0)
+		tm.wday = dateindex(buf, weekdayname, 7);
+	if(tm.wday < 0)
+		return 0;
+
+	/*
+	 * check for the two major formats
+	 */
+	date = dateword(date, buf);
+	tm.mon = dateindex(buf, monname, 12);
+	if(tm.mon >= 0){
+		/*
+		 * MM
+		 */
+		tm.mday = datenum(&date);
+		if(tm.mday < 1 || tm.mday > 31)
+			return 0;
+
+		/*
+		 * HH:MM:SS
+		 */
+		tm.hour = datenum(&date);
+		if(tm.hour < 0 || tm.hour >= 24)
+			return 0;
+		tm.min = datenum(&date);
+		if(tm.min < 0 || tm.min >= 60)
+			return 0;
+		tm.sec = datenum(&date);
+		if(tm.sec < 0 || tm.sec >= 60)
+			return 0;
+
+		/*
+		 * YYYY
+		 */
+		tm.year = datenum(&date);
+		if(tm.year < 70 || tm.year > 99 && tm.year < 1970)
+			return 0;
+		if(tm.year >= 1970)
+			tm.year -= 1900;
+	}else{
+		/*
+		 * MM-Mon-(YY|YYYY)
+		 */
+		tm.mday = datenum(&date);
+		if(tm.mday < 1 || tm.mday > 31)
+			return 0;
+		date = dateword(date, buf);
+		tm.mon = dateindex(buf, monname, 12);
+		if(tm.mon < 0 || tm.mon >= 12)
+			return 0;
+		tm.year = datenum(&date);
+		if(tm.year < 70 || tm.year > 99 && tm.year < 1970)
+			return 0;
+		if(tm.year >= 1970)
+			tm.year -= 1900;
+
+		/*
+		 * HH:MM:SS
+		 */
+		tm.hour = datenum(&date);
+		if(tm.hour < 0 || tm.hour >= 24)
+			return 0;
+		tm.min = datenum(&date);
+		if(tm.min < 0 || tm.min >= 60)
+			return 0;
+		tm.sec = datenum(&date);
+		if(tm.sec < 0 || tm.sec >= 60)
+			return 0;
+
+		/*
+		 * timezone
+		 */
+		dateword(date, buf);
+		if(strncmp(buf, "gmt", 3) != 0)
+			return 0;
+	}
+
+	strcpy(tm.zone, "GMT");
+	tm.tzoff = 0;
+	return tm2sec(&tm);
+}
+
+static int
+dateindex(char *d, char **tab, int n)
+{
+	int i;
+
+	for(i = 0; i < n; i++)
+		if(cistrcmp(d, tab[i]) == 0)
+			return i;
+	return -1;
+}
blob - /dev/null
blob + 37d35baa0ce989459a4fb7e21452394bf1c5b96e (mode 644)
--- /dev/null
+++ src/libhttpd/escape.h
@@ -0,0 +1,123 @@
+
+Htmlesc htmlesc[] =
+{
+	{ "&#161;",	0x00a1, },
+	{ "&#162;",	0x00a2, },
+	{ "&#163;",	0x00a3, },
+	{ "&#164;",	0x00a4, },
+	{ "&#165;",	0x00a5, },
+	{ "&#166;",	0x00a6, },
+	{ "&#167;",	0x00a7, },
+	{ "&#168;",	0x00a8, },
+	{ "&#169;",	0x00a9, },
+	{ "&#170;",	0x00aa, },
+	{ "&#171;",	0x00ab, },
+	{ "&#172;",	0x00ac, },
+	{ "&#173;",	0x00ad, },
+	{ "&#174;",	0x00ae, },
+	{ "&#175;",	0x00af, },
+	{ "&#176;",	0x00b0, },
+	{ "&#177;",	0x00b1, },
+	{ "&#178;",	0x00b2, },
+	{ "&#179;",	0x00b3, },
+	{ "&#180;",	0x00b4, },
+	{ "&#181;",	0x00b5, },
+	{ "&#182;",	0x00b6, },
+	{ "&#183;",	0x00b7, },
+	{ "&#184;",	0x00b8, },
+	{ "&#185;",	0x00b9, },
+	{ "&#186;",	0x00ba, },
+	{ "&#187;",	0x00bb, },
+	{ "&#188;",	0x00bc, },
+	{ "&#189;",	0x00bd, },
+	{ "&#190;",	0x00be, },
+	{ "&#191;",	0x00bf, },
+	{ "&Agrave;",	0x00c0, },
+	{ "&Aacute;",	0x00c1, },
+	{ "&Acirc;",	0x00c2, },
+	{ "&Atilde;",	0x00c3, },
+	{ "&Auml;",	0x00c4, },
+	{ "&Aring;",	0x00c5, },
+	{ "&AElig;",	0x00c6, },
+	{ "&Ccedil;",	0x00c7, },
+	{ "&Egrave;",	0x00c8, },
+	{ "&Eacute;",	0x00c9, },
+	{ "&Ecirc;",	0x00ca, },
+	{ "&Euml;",	0x00cb, },
+	{ "&Igrave;",	0x00cc, },
+	{ "&Iacute;",	0x00cd, },
+	{ "&Icirc;",	0x00ce, },
+	{ "&Iuml;",	0x00cf, },
+	{ "&ETH;",	0x00d0, },
+	{ "&Ntilde;",	0x00d1, },
+	{ "&Ograve;",	0x00d2, },
+	{ "&Oacute;",	0x00d3, },
+	{ "&Ocirc;",	0x00d4, },
+	{ "&Otilde;",	0x00d5, },
+	{ "&Ouml;",	0x00d6, },
+	{ "&215;",	0x00d7, },
+	{ "&Oslash;",	0x00d8, },
+	{ "&Ugrave;",	0x00d9, },
+	{ "&Uacute;",	0x00da, },
+	{ "&Ucirc;",	0x00db, },
+	{ "&Uuml;",	0x00dc, },
+	{ "&Yacute;",	0x00dd, },
+	{ "&THORN;",	0x00de, },
+	{ "&szlig;",	0x00df, },
+	{ "&agrave;",	0x00e0, },
+	{ "&aacute;",	0x00e1, },
+	{ "&acirc;",	0x00e2, },
+	{ "&atilde;",	0x00e3, },
+	{ "&auml;",	0x00e4, },
+	{ "&aring;",	0x00e5, },
+	{ "&aelig;",	0x00e6, },
+	{ "&ccedil;",	0x00e7, },
+	{ "&egrave;",	0x00e8, },
+	{ "&eacute;",	0x00e9, },
+	{ "&ecirc;",	0x00ea, },
+	{ "&euml;",	0x00eb, },
+	{ "&igrave;",	0x00ec, },
+	{ "&iacute;",	0x00ed, },
+	{ "&icirc;",	0x00ee, },
+	{ "&iuml;",	0x00ef, },
+	{ "&eth;",	0x00f0, },
+	{ "&ntilde;",	0x00f1, },
+	{ "&ograve;",	0x00f2, },
+	{ "&oacute;",	0x00f3, },
+	{ "&ocirc;",	0x00f4, },
+	{ "&otilde;",	0x00f5, },
+	{ "&ouml;",	0x00f6, },
+	{ "&247;",	0x00f7, },
+	{ "&oslash;",	0x00f8, },
+	{ "&ugrave;",	0x00f9, },
+	{ "&uacute;",	0x00fa, },
+	{ "&ucirc;",	0x00fb, },
+	{ "&uuml;",	0x00fc, },
+	{ "&yacute;",	0x00fd, },
+	{ "&thorn;",	0x00fe, },
+	{ "&yuml;",	0x00ff, },
+
+	{ "&quot;",	0x0022, },
+	{ "&amp;",	0x0026, },
+	{ "&lt;",	0x003c, },
+	{ "&gt;",	0x003e, },
+
+	{ "CAP-DELTA",	0x0394, },
+	{ "ALPHA",	0x03b1, },
+	{ "BETA",	0x03b2, },
+	{ "DELTA",	0x03b4, },
+	{ "EPSILON",	0x03b5, },
+	{ "THETA",	0x03b8, },
+	{ "MU",		0x03bc, },
+	{ "PI",		0x03c0, },
+	{ "TAU",	0x03c4, },
+	{ "CHI",	0x03c7, },
+
+	{ "<-",		0x2190, },
+	{ "^",		0x2191, },
+	{ "->",		0x2192, },
+	{ "v",		0x2193, },
+	{ "!=",		0x2260, },
+	{ "<=",		0x2264, },
+	{ nil, 0 },
+};
blob - /dev/null
blob + 92da30c87df5b6f3c6a464d38c51d149748d37de (mode 644)
--- /dev/null
+++ src/libhttpd/fail.c
@@ -0,0 +1,81 @@
+#include <u.h>
+#include <libc.h>
+#include <bin.h>
+#include <httpd.h>
+
+typedef struct Error	Error;
+
+struct Error
+{
+	char	*num;
+	char	*concise;
+	char	*verbose;
+};
+
+Error errormsg[] =
+{
+	[HInternal]	{"500 Internal Error", "Internal Error",
+		"This server could not process your request due to an internal error."},
+	[HTempFail]	{"500 Internal Error", "Temporary Failure",
+		"The object %s is currently inaccessible.<p>Please try again later."},
+	[HUnimp]	{"501 Not implemented", "Command not implemented",
+		"This server does not implement the %s command."},
+	[HUnkVers]	{"501 Not Implemented", "Unknown http version",
+		"This server does not know how to respond to http version %s."},
+	[HBadCont]	{"501 Not Implemented", "Impossible format",
+		"This server cannot produce %s in any of the formats your client accepts."},
+	[HBadReq]	{"400 Bad Request", "Strange Request",
+		"Your client sent a query that this server could not understand."},
+	[HSyntax]	{"400 Bad Request", "Garbled Syntax",
+		"Your client sent a query with incoherent syntax."},
+	[HBadSearch]	{"400 Bad Request", "Inapplicable Search",
+		"Your client sent a search that cannot be applied to %s."},
+	[HNotFound]	{"404 Not Found", "Object not found",
+		"The object %s does not exist on this server."},
+	[HNoSearch]	{"403 Forbidden", "Search not supported",
+		"The object %s does not support the search command."},
+	[HNoData]	{"403 Forbidden", "No data supplied",
+		"Search or forms data must be supplied to %s."},
+	[HExpectFail]	{"403 Expectation Failed", "Expectation Failed",
+		"This server does not support some of your request's expectations."},
+	[HUnauth]	{"403 Forbidden", "Forbidden",
+		"You are not allowed to see the object %s."},
+	[HOK]		{"200 OK", "everything is fine"},
+};
+
+/*
+ * write a failure message to the net and exit
+ */
+int
+hfail(HConnect *c, int reason, ...)
+{
+	Hio *hout;
+	char makeup[HBufSize];
+	va_list arg;
+	int n;
+
+	hout = &c->hout;
+	va_start(arg, reason);
+	vseprint(makeup, makeup+HBufSize, errormsg[reason].verbose, arg);
+	va_end(arg);
+	n = snprint(c->xferbuf, HBufSize, "<head><title>%s</title></head>\n<body><h1>%s</h1>\n%s</body>\n",
+		errormsg[reason].concise, errormsg[reason].concise, makeup);
+
+	hprint(hout, "%s %s\r\n", hversion, errormsg[reason].num);
+	hprint(hout, "Date: %D\r\n", time(nil));
+	hprint(hout, "Server: Plan9\r\n");
+	hprint(hout, "Content-Type: text/html\r\n");
+	hprint(hout, "Content-Length: %d\r\n", n);
+	if(c->head.closeit)
+		hprint(hout, "Connection: close\r\n");
+	else if(!http11(c))
+		hprint(hout, "Connection: Keep-Alive\r\n");
+	hprint(hout, "\r\n");
+
+	if(c->req.meth == nil || strcmp(c->req.meth, "HEAD") != 0)
+		hwrite(hout, c->xferbuf, n);
+
+	if(c->replog)
+		c->replog(c, "Reply: %s\nReason: %s\n", errormsg[reason].num, errormsg[reason].concise);
+	return hflush(hout);
+}
blob - /dev/null
blob + 91abc7c9bc3a778d30675dcf3f90fd33594046a4 (mode 644)
--- /dev/null
+++ src/libhttpd/gethead.c
@@ -0,0 +1,40 @@
+#include <u.h>
+#include <libc.h>
+#include <bin.h>
+#include <httpd.h>
+
+/*
+ * read in some header lines, either one or all of them.
+ * copy results into header log buffer.
+ */
+int
+hgethead(HConnect *c, int many)
+{
+	Hio *hin;
+	char *s, *p, *pp;
+	int n;
+
+	hin = &c->hin;
+	for(;;){
+		s = (char*)hin->pos;
+		pp = s;
+		while(p = memchr(pp, '\n', (char*)hin->stop - pp)){
+			if(!many || p == pp || p == pp + 1 && *pp == '\r'){
+				pp = p + 1;
+				break;
+			}
+			pp = p + 1;
+		}
+		hin->pos = (uchar*)pp;
+		n = pp - s;
+		if(c->hstop + n > &c->header[HBufSize])
+			return 0;
+		memmove(c->hstop, s, n);
+		c->hstop += n;
+		*c->hstop = '\0';
+		if(p != nil)
+			return 1;
+		if(hreadbuf(hin, hin->pos) == nil || hin->state == Hend)
+			return 0;
+	}
+}
blob - /dev/null
blob + 7cfb555721bd2701c4224a02238d8482492422e5 (mode 644)
--- /dev/null
+++ src/libhttpd/hio.c
@@ -0,0 +1,473 @@
+#include <u.h>
+#include <libc.h>
+#include <httpd.h>
+
+static	char	hstates[] = "nrewE";
+static	char	hxfers[] = " x";
+
+int
+hinit(Hio *h, int fd, int mode)
+{
+	if(fd == -1 || mode != Hread && mode != Hwrite)
+		return -1;
+	h->hh = nil;
+	h->fd = fd;
+	h->seek = 0;
+	h->state = mode;
+	h->start = h->buf + 16;		/* leave space for chunk length */
+	h->stop = h->pos = h->start;
+	if(mode == Hread){
+		h->bodylen = ~0UL;
+		*h->pos = '\0';
+	}else
+		h->stop = h->start + Hsize;
+	return 0;
+}
+
+int
+hiserror(Hio *h)
+{
+	return h->state == Herr;
+}
+
+int
+hgetc(Hio *h)
+{
+	uchar *p;
+
+	p = h->pos;
+	if(p < h->stop){
+		h->pos = p + 1;
+		return *p;
+	}
+	p -= UTFmax;
+	if(p < h->start)
+		p = h->start;
+	if(!hreadbuf(h, p) || h->pos == h->stop)
+		return -1;
+	return *h->pos++;
+}
+
+int
+hungetc(Hio *h)
+{
+	if(h->state == Hend)
+		h->state = Hread;
+	else if(h->state == Hread)
+		h->pos--;
+	if(h->pos < h->start || h->state != Hread){
+		h->state = Herr;
+		h->pos = h->stop;
+		return -1;
+	}
+	return 0;
+}
+
+/*
+ * fill the buffer, saving contents from vsave onwards.
+ * nothing is saved if vsave is nil.
+ * returns the beginning of the buffer.
+ *
+ * understands message body sizes and chunked transfer encoding
+ */
+void *
+hreadbuf(Hio *h, void *vsave)
+{
+	Hio *hh;
+	uchar *save;
+	int c, in, cpy, dpos;
+
+	save = vsave;
+	if(save && (save < h->start || save > h->stop)
+	|| h->state != Hread && h->state != Hend){
+		h->state = Herr;
+		h->pos = h->stop;
+		return nil;
+	}
+
+	dpos = 0;
+	if(save && h->pos > save)
+		dpos = h->pos - save;
+	cpy = 0;
+	if(save){
+		cpy = h->stop - save;
+		memmove(h->start, save, cpy);
+	}
+	h->seek += h->stop - h->start - cpy;
+	h->pos = h->start + dpos;
+
+	in = Hsize - cpy;
+	if(h->state == Hend)
+		in = 0;
+	else if(in > h->bodylen)
+		in = h->bodylen;
+
+	/*
+	 * for chunked encoding, fill buffer,
+	 * then read in new chunk length and wipe out that line
+	 */
+	hh = h->hh;
+	if(hh != nil){
+		if(!in && h->xferenc && h->state != Hend){
+			if(h->xferenc == 2){
+				c = hgetc(hh);
+				if(c == '\r')
+					c = hgetc(hh);
+				if(c != '\n'){
+					h->pos = h->stop;
+					h->state = Herr;
+					return nil;
+				}
+			}
+			h->xferenc = 2;
+			in = 0;
+			while((c = hgetc(hh)) != '\n'){
+				if(c >= '0' && c <= '9')
+					c -= '0';
+				else if(c >= 'a' && c <= 'f')
+					c -= 'a' - 10;
+				else if(c >= 'A' && c <= 'F')
+					c -= 'A' - 10;
+				else
+					break;
+				in = in * 16 + c;
+			}
+			while(c != '\n'){
+				if(c < 0){
+					h->pos = h->stop;
+					h->state = Herr;
+					return nil;
+				}
+				c = hgetc(hh);
+			}
+			h->bodylen = in;
+
+			in = Hsize - cpy;
+			if(in > h->bodylen)
+				in = h->bodylen;
+		}
+		if(in){
+			while(hh->pos + in > hh->stop){
+				if(hreadbuf(hh, hh->pos) == nil){
+					h->pos = h->stop;
+					h->state = Herr;
+					return nil;
+				}
+			}
+			memmove(h->start + cpy, hh->pos, in);
+			hh->pos += in;
+		}
+	}else if(in && (in = read(h->fd, h->start + cpy, in)) < 0){
+		h->state = Herr;
+		h->pos = h->stop;
+		return nil;
+	}
+	if(in == 0)
+		h->state = Hend;
+
+	h->bodylen -= in;
+
+	h->stop = h->start + cpy + in;
+	*h->stop = '\0';
+	if(h->pos == h->stop)
+		return nil;
+	return h->start;
+}
+
+int
+hbuflen(Hio *h, void *p)
+{
+	return h->stop - (uchar*)p;
+}
+
+/*
+ * prepare to receive a message body
+ * len is the content length (~0 => unspecified)
+ * te is the transfer encoding
+ * returns < 0 if setup failed
+ */
+Hio*
+hbodypush(Hio *hh, ulong len, HFields *te)
+{
+	Hio *h;
+	int xe;
+
+	if(hh->state != Hread)
+		return nil;
+	xe = 0;
+	if(te != nil){
+		if(te->params != nil || te->next != nil)
+			return nil;
+		if(cistrcmp(te->s, "chunked") == 0){
+			xe = 1;
+			len = 0;
+		}else if(cistrcmp(te->s, "identity") == 0){
+			;
+		}else
+			return nil;
+	}
+
+	h = malloc(sizeof *h);
+	if(h == nil)
+		return nil;
+
+	h->hh = hh;
+	h->fd = -1;
+	h->seek = 0;
+	h->state = Hread;
+	h->xferenc = xe;
+	h->start = h->buf + 16;		/* leave space for chunk length */
+	h->stop = h->pos = h->start;
+	*h->pos = '\0';
+	h->bodylen = len;
+	return h;
+}
+
+/*
+ * dump the state of the io buffer into a string
+ */
+char *
+hunload(Hio *h)
+{
+	uchar *p, *t, *stop, *buf;
+	int ne, n, c;
+
+	stop = h->stop;
+	ne = 0;
+	for(p = h->pos; p < stop; p++){
+		c = *p;
+		if(c == 0x80)
+			ne++;
+	}
+	p = h->pos;
+
+	n = (stop - p) + ne + 3;
+	buf = mallocz(n, 1);
+	if(buf == nil)
+		return nil;
+	buf[0] = hstates[h->state];
+	buf[1] = hxfers[h->xferenc];
+
+	t = &buf[2];
+	for(; p < stop; p++){
+		c = *p;
+		if(c == 0 || c == 0x80){
+			*t++ = 0x80;
+			if(c == 0x80)
+				*t++ = 0x80;
+		}else
+			*t++ = c;
+	}
+	*t++ = '\0';
+	if(t != buf + n)
+		return nil;
+	return (char*)buf;
+}
+
+/*
+ * read the io buffer state from a string
+ */
+int
+hload(Hio *h, char *buf)
+{
+	uchar *p, *t, *stop;
+	char *s;
+	int c;
+
+	s = strchr(hstates, buf[0]);
+	if(s == nil)
+		return 0;
+	h->state = s - hstates;
+
+	s = strchr(hxfers, buf[1]);
+	if(s == nil)
+		return 0;
+	h->xferenc = s - hxfers;
+
+	t = h->start;
+	stop = t + Hsize;
+	for(p = (uchar*)&buf[2]; c = *p; p++){
+		if(c == 0x80){
+			if(p[1] != 0x80)
+				c = 0;
+			else
+				p++;
+		}
+		*t++ = c;
+		if(t >= stop)
+			return 0;
+	}
+	*t = '\0';
+	h->pos = h->start;
+	h->stop = t;
+	h->seek = 0;
+	return 1;
+}
+
+void
+hclose(Hio *h)
+{
+	if(h->fd >= 0){
+		if(h->state == Hwrite)
+			hxferenc(h, 0);
+		close(h->fd);
+	}
+	h->stop = h->pos = nil;
+	h->fd = -1;
+}
+
+/*
+ * flush the buffer and possibly change encoding modes
+ */
+int
+hxferenc(Hio *h, int on)
+{
+	if(h->xferenc && !on && h->pos != h->start)
+		hflush(h);
+	if(hflush(h) < 0)
+		return -1;
+	h->xferenc = !!on;
+	return 0;
+}
+
+int
+hputc(Hio *h, int c)
+{
+	uchar *p;
+
+	p = h->pos;
+	if(p < h->stop){
+		h->pos = p + 1;
+		return *p = c;
+	}
+	if(hflush(h) < 0)
+		return -1;
+	return *h->pos++ = c;
+}
+
+static int
+fmthflush(Fmt *f)
+{
+	Hio *h;
+
+	h = f->farg;
+	h->pos = f->to;
+	if(hflush(h) < 0)
+		return 0;
+	f->stop = h->stop;
+	f->to = h->pos;
+	f->start = h->pos;
+	return 1;
+}
+
+int
+hvprint(Hio *h, char *fmt, va_list args)
+{
+	int n;
+	Fmt f;
+
+	f.runes = 0;
+	f.stop = h->stop;
+	f.to = h->pos;
+	f.start = h->pos;
+	f.flush = fmthflush;
+	f.farg = h;
+	f.nfmt = 0;
+	f.args = args;
+	n = dofmt(&f, fmt);
+	h->pos = f.to;
+	return n;
+}
+
+int
+hprint(Hio *h, char *fmt, ...)
+{
+	int n;
+	va_list arg;
+
+	va_start(arg, fmt);
+	n = hvprint(h, fmt, arg);
+	va_end(arg);
+	return n;
+}
+
+int
+hflush(Hio *h)
+{
+	uchar *s;
+	int w;
+
+	if(h->state != Hwrite){
+		h->state = Herr;
+		h->stop = h->pos;
+		return -1;
+	}
+	s = h->start;
+	w = h->pos - s;
+	if(h->xferenc){
+		*--s = '\n';
+		*--s = '\r';
+		do{
+			*--s = "0123456789abcdef"[w & 0xf];
+			w >>= 4;
+		}while(w);
+		h->pos[0] = '\r';
+		h->pos[1] = '\n';
+		w = &h->pos[2] - s;
+	}
+	if(write(h->fd, s, w) != w){
+		h->state = Herr;
+		h->stop = h->pos;
+		return -1;
+	}
+	h->seek += w;
+	h->pos = h->start;
+	return 0;
+}
+
+int
+hwrite(Hio *h, void *vbuf, int len)
+{
+	uchar *pos, *buf;
+	int n, m;
+
+	buf = vbuf;
+	n = len;
+	if(n < 0 || h->state != Hwrite){
+		h->state = Herr;
+		h->stop = h->pos;
+		return -1;
+	}
+	pos = h->pos;
+	if(pos + n >= h->stop){
+		m = pos - h->start;
+		if(m){
+			m = Hsize - m;
+			if(m){
+				memmove(pos, buf, m);
+				buf += m;
+				n -= m;
+			}
+			if(write(h->fd, h->start, Hsize) != Hsize){
+				h->state = Herr;
+				h->stop = h->pos;
+				return -1;
+			}
+			h->seek += Hsize;
+		}
+		m = n % Hsize;
+		n -= m;
+		if(n != 0 && write(h->fd, buf, n) != n){
+			h->state = Herr;
+			h->stop = h->pos;
+			return -1;
+		}
+		h->seek += n;
+		buf += n;
+		pos = h->pos = h->start;
+		n = m;
+	}
+	memmove(pos, buf, n);
+	h->pos = pos + n;
+	return len;
+}
blob - /dev/null
blob + feedbd5e7842207ba1b7251f6072064b0897f41c (mode 644)
--- /dev/null
+++ src/libhttpd/httpfmt.c
@@ -0,0 +1,30 @@
+#include <u.h>
+#include <libc.h>
+#include <bin.h>
+#include <httpd.h>
+
+int
+httpfmt(Fmt *f)
+{
+	char buf[HMaxWord*2];
+	Rune r;
+	char *t, *s;
+	Htmlesc *l;
+
+	s = va_arg(f->args, char*);
+	for(t = buf; t < buf + sizeof(buf) - 8; ){
+		s += chartorune(&r, s);
+		if(r == 0)
+			break;
+		for(l = htmlesc; l->name != nil; l++)
+			if(l->value == r)
+				break;
+		if(l->name != nil){
+			strcpy(t, l->name);
+			t += strlen(t);
+		}else
+			*t++ = r;
+	}
+	*t = 0;
+	return fmtstrcpy(f, buf);
+}
blob - /dev/null
blob + 85a8df639a45d8b3a30bb5aadd371017c3ef7ed9 (mode 644)
--- /dev/null
+++ src/libhttpd/httpunesc.c
@@ -0,0 +1,49 @@
+#include <u.h>
+#include <libc.h>
+#include <bin.h>
+#include <httpd.h>
+
+/*
+ *  go from http with latin1 escapes to utf,
+ *  we assume that anything >= Runeself is already in utf
+ */
+char *
+httpunesc(HConnect *cc, char *s)
+{
+	char *t, *v;
+	int c;
+	Htmlesc *e;
+
+	v = halloc(cc, UTFmax*strlen(s) + 1);
+	for(t = v; c = *s;){
+		if(c == '&'){
+			if(s[1] == '#' && s[2] && s[3] && s[4] && s[5] == ';'){
+				c = atoi(s+2);
+				if(c < Runeself){
+					*t++ = c;
+					s += 6;
+					continue;
+				}
+				if(c < 256 && c >= 161){
+					e = &htmlesc[c-161];
+					t += runetochar(t, &e->value);
+					s += 6;
+					continue;
+				}
+			} else {
+				for(e = htmlesc; e->name != nil; e++)
+					if(strncmp(e->name, s, strlen(e->name)) == 0)
+						break;
+				if(e->name != nil){
+					t += runetochar(t, &e->value);
+					s += strlen(e->name);
+					continue;
+				}
+			}
+		}
+		*t++ = c;
+		s++;
+	}
+	*t = 0;
+	return v;
+}
blob - /dev/null
blob + 40f49d514c8059fd99fba72f51594a8aeb6ae752 (mode 644)
--- /dev/null
+++ src/libhttpd/lower.c
@@ -0,0 +1,19 @@
+#include <u.h>
+#include <libc.h>
+#include <bin.h>
+#include <httpd.h>
+
+char*
+hlower(char *p)
+{
+	char c;
+	char *x;
+
+	if(p == nil)
+		return p;
+
+	for(x = p; c = *x; x++)
+		if(c >= 'A' && c <= 'Z')
+			*x -= 'A' - 'a';
+	return p;
+}
blob - /dev/null
blob + bd4db82c241d704d93a39b9ce5d7d0b8f018027e (mode 644)
--- /dev/null
+++ src/libhttpd/mkfile
@@ -0,0 +1,30 @@
+PLAN9=../..
+<$PLAN9/src/mkhdr
+
+LIB=libhttpd.a
+
+OFILES=\
+	alloc.$O\
+	checkcontent.$O\
+	date.$O\
+	fail.$O\
+	gethead.$O\
+	hio.$O\
+	httpfmt.$O\
+	httpunesc.$O\
+	lower.$O\
+	okheaders.$O\
+	parse.$O\
+	parsereq.$O\
+	query.$O\
+	redirected.$O\
+	unallowed.$O\
+	urlfmt.$O\
+	urlunesc.$O\
+
+HFILES=\
+	$PLAN9/include/httpd.h\
+	escape.h\
+
+<$PLAN9/src/mksyslib
+
blob - /dev/null
blob + cba39a91c0173ad5614c0f00b277ae3497af1dd8 (mode 644)
--- /dev/null
+++ src/libhttpd/okheaders.c
@@ -0,0 +1,22 @@
+#include <u.h>
+#include <libc.h>
+#include <bin.h>
+#include <httpd.h>
+
+/*
+ * write initial part of successful header
+ */
+void
+hokheaders(HConnect *c)
+{
+	Hio *hout;
+
+	hout = &c->hout;
+	hprint(hout, "%s 200 OK\r\n", hversion);
+	hprint(hout, "Server: Plan9\r\n");
+	hprint(hout, "Date: %D\r\n", time(nil));
+	if(c->head.closeit)
+		hprint(hout, "Connection: close\r\n");
+	else if(!http11(c))
+		hprint(hout, "Connection: Keep-Alive\r\n");
+}
blob - /dev/null
blob + 5fd4092a388fb914bb8d2987581216ece1ee715c (mode 644)
--- /dev/null
+++ src/libhttpd/parse.c
@@ -0,0 +1,1057 @@
+#include <u.h>
+#include <libc.h>
+#include <bin.h>
+#include <httpd.h>
+#include "escape.h"
+
+typedef struct Hlex	Hlex;
+typedef struct MimeHead	MimeHead;
+
+enum
+{
+	/*
+	 * tokens
+	 */
+	Word	= 1,
+	QString,
+};
+
+#define UlongMax	4294967295UL
+
+struct Hlex
+{
+	int	tok;
+	int	eoh;
+	int	eol;			/* end of header line encountered? */
+	uchar	*hstart;		/* start of header */
+	jmp_buf	jmp;			/* jmp here to parse header */
+	char	wordval[HMaxWord];
+	HConnect *c;
+};
+
+struct MimeHead
+{
+	char	*name;
+	void	(*parse)(Hlex*, char*);
+	uchar	seen;
+	uchar	ignore;
+};
+
+static void	mimeaccept(Hlex*, char*);
+static void	mimeacceptchar(Hlex*, char*);
+static void	mimeacceptenc(Hlex*, char*);
+static void	mimeacceptlang(Hlex*, char*);
+static void	mimeagent(Hlex*, char*);
+static void	mimeauthorization(Hlex*, char*);
+static void	mimeconnection(Hlex*, char*);
+static void	mimecontlen(Hlex*, char*);
+static void	mimeexpect(Hlex*, char*);
+static void	mimefresh(Hlex*, char*);
+static void	mimefrom(Hlex*, char*);
+static void	mimehost(Hlex*, char*);
+static void	mimeifrange(Hlex*, char*);
+static void	mimeignore(Hlex*, char*);
+static void	mimematch(Hlex*, char*);
+static void	mimemodified(Hlex*, char*);
+static void	mimenomatch(Hlex*, char*);
+static void	mimerange(Hlex*, char*);
+static void	mimetransenc(Hlex*, char*);
+static void	mimeunmodified(Hlex*, char*);
+
+/*
+ * headers seen also include
+ * allow  cache-control chargeto
+ * content-encoding content-language content-location content-md5 content-range content-type
+ * date etag expires forwarded last-modified max-forwards pragma
+ * proxy-agent proxy-authorization proxy-connection
+ * ua-color ua-cpu ua-os ua-pixels
+ * upgrade via x-afs-tokens x-serial-number
+ */
+static MimeHead	mimehead[] =
+{
+	{"accept",		mimeaccept},
+	{"accept-charset",	mimeacceptchar},
+	{"accept-encoding",	mimeacceptenc},
+	{"accept-language",	mimeacceptlang},
+	{"authorization",	mimeauthorization},
+	{"connection",		mimeconnection},
+	{"content-length",	mimecontlen},
+	{"expect",		mimeexpect},
+	{"fresh",		mimefresh},
+	{"from",		mimefrom},
+	{"host",		mimehost},
+	{"if-match",		mimematch},
+	{"if-modified-since",	mimemodified},
+	{"if-none-match",	mimenomatch},
+	{"if-range",		mimeifrange},
+	{"if-unmodified-since",	mimeunmodified},
+	{"range",		mimerange},
+	{"transfer-encoding",	mimetransenc},
+	{"user-agent",		mimeagent},
+};
+
+char*		hmydomain;
+char*		hversion = "HTTP/1.1";
+
+static	void	lexhead(Hlex*);
+static	void	parsejump(Hlex*, char*);
+static	int	getc(Hlex*);
+static	void	ungetc(Hlex*);
+static	int	wordcr(Hlex*);
+static	int	wordnl(Hlex*);
+static	void	word(Hlex*, char*);
+static	int	lex1(Hlex*, int);
+static	int	lex(Hlex*);
+static	int	lexbase64(Hlex*);
+static	ulong	digtoul(char *s, char **e);
+
+/*
+ * flush an clean up junk from a request
+ */
+void
+hreqcleanup(HConnect *c)
+{
+	int i;
+
+	hxferenc(&c->hout, 0);
+	memset(&c->req, 0, sizeof(c->req));
+	memset(&c->head, 0, sizeof(c->head));
+	c->hpos = c->header;
+	c->hstop = c->header;
+	binfree(&c->bin);
+	for(i = 0; i < nelem(mimehead); i++){
+		mimehead[i].seen = 0;
+		mimehead[i].ignore = 0;
+	}
+}
+
+/*
+ * list of tokens
+ * if the client is HTTP/1.0,
+ * ignore headers which match one of the tokens.
+ * restarts parsing if necessary.
+ */
+static void
+mimeconnection(Hlex *h, char *unused)
+{
+	char *u, *p;
+	int reparse, i;
+
+	reparse = 0;
+	for(;;){
+		while(lex(h) != Word)
+			if(h->tok != ',')
+				goto breakout;
+
+		if(cistrcmp(h->wordval, "keep-alive") == 0)
+			h->c->head.persist = 1;
+		else if(cistrcmp(h->wordval, "close") == 0)
+			h->c->head.closeit = 1;
+		else if(!http11(h->c)){
+			for(i = 0; i < nelem(mimehead); i++){
+				if(cistrcmp(mimehead[i].name, h->wordval) == 0){
+					reparse = mimehead[i].seen && !mimehead[i].ignore;
+					mimehead[i].ignore = 1;
+					if(cistrcmp(mimehead[i].name, "authorization") == 0){
+						h->c->head.authuser = nil;
+						h->c->head.authpass = nil;
+					}
+				}
+			}
+		}
+
+		if(lex(h) != ',')
+			break;
+	}
+
+breakout:;
+	/*
+	 * if need to ignore headers we've already parsed,
+	 * reset & start over.  need to save authorization
+	 * info because it's written over when parsed.
+	 */
+	if(reparse){
+		u = h->c->head.authuser;
+		p = h->c->head.authpass;
+		memset(&h->c->head, 0, sizeof(h->c->head));
+		h->c->head.authuser = u;
+		h->c->head.authpass = p;
+
+		h->c->hpos = h->hstart;
+		longjmp(h->jmp, 1);
+	}
+}
+
+int
+hparseheaders(HConnect *c, int timeout)
+{
+	Hlex h;
+
+	c->head.fresh_thresh = 0;
+	c->head.fresh_have = 0;
+	c->head.persist = 0;
+	if(c->req.vermaj == 0){
+		c->head.host = hmydomain;
+		return 1;
+	}
+
+	memset(&h, 0, sizeof(h));
+	h.c = c;
+	alarm(timeout);
+	if(!hgethead(c, 1))
+		return -1;
+	alarm(0);
+	h.hstart = c->hpos;
+
+	if(setjmp(h.jmp) == -1)
+		return -1;
+
+	h.eol = 0;
+	h.eoh = 0;
+	h.tok = '\n';
+	while(lex(&h) != '\n'){
+		if(h.tok == Word && lex(&h) == ':')
+			parsejump(&h, hstrdup(c, h.wordval));
+		while(h.tok != '\n')
+			lex(&h);
+		h.eol = h.eoh;
+	}
+
+	if(http11(c)){
+		/*
+		 * according to the http/1.1 spec,
+		 * these rules must be followed
+		 */
+		if(c->head.host == nil){
+			hfail(c, HBadReq, nil);
+			return -1;
+		}
+		if(c->req.urihost != nil)
+			c->head.host = c->req.urihost;
+		/*
+		 * also need to check host is actually this one
+		 */
+	}else if(c->head.host == nil)
+		c->head.host = hmydomain;
+	return 1;
+}
+
+/*
+ * mimeparams	: | mimeparams ";" mimepara
+ * mimeparam	: token "=" token | token "=" qstring
+ */
+static HSPairs*
+mimeparams(Hlex *h)
+{
+	HSPairs *p;
+	char *s;
+
+	p = nil;
+	for(;;){
+		if(lex(h) != Word)
+			break;
+		s = hstrdup(h->c, h->wordval);
+		if(lex(h) != Word && h->tok != QString)
+			break;
+		p = hmkspairs(h->c, s, hstrdup(h->c, h->wordval), p);
+	}
+	return hrevspairs(p);
+}
+
+/*
+ * mimehfields	: mimehfield | mimehfields commas mimehfield
+ * mimehfield	: token mimeparams
+ * commas	: "," | commas ","
+ */
+static HFields*
+mimehfields(Hlex *h)
+{
+	HFields *f;
+
+	f = nil;
+	for(;;){
+		while(lex(h) != Word)
+			if(h->tok != ',')
+				goto breakout;
+
+		f = hmkhfields(h->c, hstrdup(h->c, h->wordval), nil, f);
+
+		if(lex(h) == ';')
+			f->params = mimeparams(h);
+		if(h->tok != ',')
+			break;
+	}
+breakout:;
+	return hrevhfields(f);
+}
+
+/*
+ * parse a list of acceptable types, encodings, languages, etc.
+ */
+static HContent*
+mimeok(Hlex *h, char *name, int multipart, HContent *head)
+{
+	char *generic, *specific, *s;
+	float v;
+
+	/*
+	 * each type is separated by one or more commas
+	 */
+	while(lex(h) != Word)
+		if(h->tok != ',')
+			return head;
+
+	generic = hstrdup(h->c, h->wordval);
+	lex(h);
+	if(h->tok == '/' || multipart){
+		/*
+		 * at one time, IE5 improperly said '*' for single types
+		 */
+		if(h->tok != '/')
+			return nil;
+		if(lex(h) != Word)
+			return head;
+		specific = hstrdup(h->c, h->wordval);
+		if(!multipart && strcmp(specific, "*") != 0)
+			return head;
+		lex(h);
+	}else
+		specific = nil;
+	head = hmkcontent(h->c, generic, specific, head);
+
+	for(;;){
+		switch(h->tok){
+		case ';':
+			/*
+			 * should make a list of these params
+			 * for accept, they fall into two classes:
+			 *	up to a q=..., they modify the media type.
+			 *	afterwards, they acceptance criteria
+			 */
+			if(lex(h) == Word){
+				s = hstrdup(h->c, h->wordval);
+				if(lex(h) != '=' || lex(h) != Word && h->tok != QString)
+					return head;
+				v = strtod(h->wordval, nil);
+				if(strcmp(s, "q") == 0)
+					head->q = v;
+				else if(strcmp(s, "mxb") == 0)
+					head->mxb = v;
+			}
+			break;
+		case ',':
+			return  mimeok(h, name, multipart, head);
+		default:
+			return head;
+		}
+		lex(h);
+	}
+	return head;
+}
+
+/*
+ * parse a list of entity tags
+ * 1#entity-tag
+ * entity-tag = [weak] opaque-tag
+ * weak = "W/"
+ * opaque-tag = quoted-string
+ */
+static HETag*
+mimeetag(Hlex *h, HETag *head)
+{
+	HETag *e;
+	int weak;
+
+	for(;;){
+		while(lex(h) != Word && h->tok != QString)
+			if(h->tok != ',')
+				return head;
+
+		weak = 0;
+		if(h->tok == Word && strcmp(h->wordval, "*") != 0){
+			if(strcmp(h->wordval, "W") != 0)
+				return head;
+			if(lex(h) != '/' || lex(h) != QString)
+				return head;
+			weak = 1;
+		}
+
+		e = halloc(h->c, sizeof(HETag));
+		e->etag = hstrdup(h->c, h->wordval);
+		e->weak = weak;
+		e->next = head;
+		head = e;
+
+		if(lex(h) != ',')
+			return head;
+	}
+	return head;
+}
+
+/*
+ * ranges-specifier = byte-ranges-specifier
+ * byte-ranges-specifier = "bytes" "=" byte-range-set
+ * byte-range-set = 1#(byte-range-spec|suffix-byte-range-spec)
+ * byte-range-spec = byte-pos "-" [byte-pos]
+ * byte-pos = 1*DIGIT
+ * suffix-byte-range-spec = "-" suffix-length
+ * suffix-length = 1*DIGIT
+ *
+ * syntactically invalid range specifiers cause the
+ * entire header field to be ignored.
+ * it is syntactically incorrect for the second byte pos
+ * to be smaller than the first byte pos
+ */
+static HRange*
+mimeranges(Hlex *h, HRange *head)
+{
+	HRange *r, *rh, *tail;
+	char *w;
+	ulong start, stop;
+	int suf;
+
+	if(lex(h) != Word || strcmp(h->wordval, "bytes") != 0 || lex(h) != '=')
+		return head;
+
+	rh = nil;
+	tail = nil;
+	for(;;){
+		while(lex(h) != Word){
+			if(h->tok != ','){
+				if(h->tok == '\n')
+					goto breakout;
+				return head;
+			}
+		}
+
+		w = h->wordval;
+		start = 0;
+		suf = 1;
+		if(w[0] != '-'){
+			suf = 0;
+			start = digtoul(w, &w);
+			if(w[0] != '-')
+				return head;
+		}
+		w++;
+		stop = ~0UL;
+		if(w[0] != '\0'){
+			stop = digtoul(w, &w);
+			if(w[0] != '\0')
+				return head;
+			if(!suf && stop < start)
+				return head;
+		}
+
+		r = halloc(h->c, sizeof(HRange));
+		r->suffix = suf;
+		r->start = start;
+		r->stop = stop;
+		r->next = nil;
+		if(rh == nil)
+			rh = r;
+		else
+			tail->next = r;
+		tail = r;
+
+		if(lex(h) != ','){
+			if(h->tok == '\n')
+				break;
+			return head;
+		}
+	}
+breakout:;
+
+	if(head == nil)
+		return rh;
+
+	for(tail = head; tail->next != nil; tail = tail->next)
+		;
+	tail->next = rh;
+	return head;
+}
+
+static void
+mimeaccept(Hlex *h, char *name)
+{
+	h->c->head.oktype = mimeok(h, name, 1, h->c->head.oktype);
+}
+
+static void
+mimeacceptchar(Hlex *h, char *name)
+{
+	h->c->head.okchar = mimeok(h, name, 0, h->c->head.okchar);
+}
+
+static void
+mimeacceptenc(Hlex *h, char *name)
+{
+	h->c->head.okencode = mimeok(h, name, 0, h->c->head.okencode);
+}
+
+static void
+mimeacceptlang(Hlex *h, char *name)
+{
+	h->c->head.oklang = mimeok(h, name, 0, h->c->head.oklang);
+}
+
+static void
+mimemodified(Hlex *h, char *unused)
+{
+	lexhead(h);
+	h->c->head.ifmodsince = hdate2sec(h->wordval);
+}
+
+static void
+mimeunmodified(Hlex *h, char *unused)
+{
+	lexhead(h);
+	h->c->head.ifunmodsince = hdate2sec(h->wordval);
+}
+
+static void
+mimematch(Hlex *h, char *unused)
+{
+	h->c->head.ifmatch = mimeetag(h, h->c->head.ifmatch);
+}
+
+static void
+mimenomatch(Hlex *h, char *unused)
+{
+	h->c->head.ifnomatch = mimeetag(h, h->c->head.ifnomatch);
+}
+
+/*
+ * argument is either etag or date
+ */
+static void
+mimeifrange(Hlex *h, char *unused)
+{
+	int c, d, et;
+
+	et = 0;
+	c = getc(h);
+	while(c == ' ' || c == '\t')
+		c = getc(h);
+	if(c == '"')
+		et = 1;
+	else if(c == 'W'){
+		d = getc(h);
+		if(d == '/')
+			et = 1;
+		ungetc(h);
+	}
+	ungetc(h);
+	if(et){
+		h->c->head.ifrangeetag = mimeetag(h, h->c->head.ifrangeetag);
+	}else{
+		lexhead(h);
+		h->c->head.ifrangedate = hdate2sec(h->wordval);
+	}
+}
+
+static void
+mimerange(Hlex *h, char *unused)
+{
+	h->c->head.range = mimeranges(h, h->c->head.range);
+}
+
+/*
+ * note: netscape and ie through versions 4.7 and 4
+ * support only basic authorization, so that is all that is supported here
+ *
+ * "Authorization" ":" "Basic" base64-user-pass
+ * where base64-user-pass is the base64 encoding of
+ * username ":" password
+ */
+static void
+mimeauthorization(Hlex *h, char *unused)
+{
+	char *up, *p;
+	int n;
+
+	if(lex(h) != Word || cistrcmp(h->wordval, "basic") != 0)
+		return;
+
+	n = lexbase64(h);
+	if(!n)
+		return;
+
+	/*
+	 * wipe out source for password, so it won't be logged.
+	 * it is replaced by a single =,
+	 * which is valid base64, but not ok for an auth reponse.
+	 * therefore future parses of the header field will not overwrite
+	 * authuser and authpass.
+	 */
+	memmove(h->c->hpos - (n - 1), h->c->hpos, h->c->hstop - h->c->hpos);
+	h->c->hstop -= n - 1;
+	*h->c->hstop = '\0';
+	h->c->hpos -= n - 1;
+	h->c->hpos[-1] = '=';
+
+	up = halloc(h->c, n + 1);
+	n = dec64((uchar*)up, n, h->wordval, n);
+	up[n] = '\0';
+	p = strchr(up, ':');
+	if(p != nil){
+		*p++ = '\0';
+		h->c->head.authuser = hstrdup(h->c, up);
+		h->c->head.authpass = hstrdup(h->c, p);
+	}
+}
+
+static void
+mimeagent(Hlex *h, char *unused)
+{
+	lexhead(h);
+	h->c->head.client = hstrdup(h->c, h->wordval);
+}
+
+static void
+mimefrom(Hlex *h, char *unused)
+{
+	lexhead(h);
+}
+
+static void
+mimehost(Hlex *h, char *unused)
+{
+	char *hd;
+
+	lexhead(h);
+	for(hd = h->wordval; *hd == ' ' || *hd == '\t'; hd++)
+		;
+	h->c->head.host = hlower(hstrdup(h->c, hd));
+}
+
+/*
+ * if present, implies that a message body follows the headers
+ * "content-length" ":" digits
+ */
+static void
+mimecontlen(Hlex *h, char *unused)
+{
+	char *e;
+	ulong v;
+
+	if(lex(h) != Word)
+		return;
+	e = h->wordval;
+	v = digtoul(e, &e);
+	if(v == ~0UL || *e != '\0')
+		return;
+	h->c->head.contlen = v;
+}
+
+/*
+ * mimexpect	: "expect" ":" expects
+ * expects	: | expects "," expect
+ * expect	: "100-continue" | token | token "=" token expectparams | token "=" qstring expectparams
+ * expectparams	: ";" token | ";" token "=" token | token "=" qstring
+ * for now, we merely parse "100-continue" or anything else.
+ */
+static void
+mimeexpect(Hlex *h, char *unused)
+{
+	if(lex(h) != Word || cistrcmp(h->wordval, "100-continue") != 0 || lex(h) != '\n')
+		h->c->head.expectother = 1;
+	h->c->head.expectcont = 1;
+}
+
+static void
+mimetransenc(Hlex *h, char *unused)
+{
+	h->c->head.transenc = mimehfields(h);
+}
+
+static void
+mimefresh(Hlex *h, char *unused)
+{
+	char *s;
+
+	lexhead(h);
+	for(s = h->wordval; *s && (*s==' ' || *s=='\t'); s++)
+		;
+	if(strncmp(s, "pathstat/", 9) == 0)
+		h->c->head.fresh_thresh = atoi(s+9);
+	else if(strncmp(s, "have/", 5) == 0)
+		h->c->head.fresh_have = atoi(s+5);
+}
+
+static void
+mimeignore(Hlex *h, char *unused)
+{
+	lexhead(h);
+}
+
+static void
+parsejump(Hlex *h, char *k)
+{
+	int l, r, m;
+
+	l = 1;
+	r = nelem(mimehead) - 1;
+	while(l <= r){
+		m = (r + l) >> 1;
+		if(cistrcmp(mimehead[m].name, k) <= 0)
+			l = m + 1;
+		else
+			r = m - 1;
+	}
+	m = l - 1;
+	if(cistrcmp(mimehead[m].name, k) == 0 && !mimehead[m].ignore){
+		mimehead[m].seen = 1;
+		(*mimehead[m].parse)(h, k);
+	}else
+		mimeignore(h, k);
+}
+
+static int
+lex(Hlex *h)
+{
+	return h->tok = lex1(h, 0);
+}
+
+static int
+lexbase64(Hlex *h)
+{
+	int c, n;
+
+	n = 0;
+	lex1(h, 1);
+
+	while((c = getc(h)) >= 0){
+		if(!(c >= 'A' && c <= 'Z'
+		|| c >= 'a' && c <= 'z'
+		|| c >= '0' && c <= '9'
+		|| c == '+' || c == '/')){
+			ungetc(h);
+			break;
+		}
+
+		if(n < HMaxWord-1)
+			h->wordval[n++] = c;
+	}
+	h->wordval[n] = '\0';
+	return n;
+}
+
+/*
+ * rfc 822/rfc 1521 lexical analyzer
+ */
+static int
+lex1(Hlex *h, int skipwhite)
+{
+	int level, c;
+
+	if(h->eol)
+		return '\n';
+
+top:
+	c = getc(h);
+	switch(c){
+	case '(':
+		level = 1;
+		while((c = getc(h)) >= 0){
+			if(c == '\\'){
+				c = getc(h);
+				if(c < 0)
+					return '\n';
+				continue;
+			}
+			if(c == '(')
+				level++;
+			else if(c == ')' && --level == 0)
+				break;
+			else if(c == '\n'){
+				c = getc(h);
+				if(c < 0)
+					return '\n';
+				if(c == ')' && --level == 0)
+					break;
+				if(c != ' ' && c != '\t'){
+					ungetc(h);
+					return '\n';
+				}
+			}
+		}
+		goto top;
+
+	case ' ': case '\t':
+		goto top;
+
+	case '\r':
+		c = getc(h);
+		if(c != '\n'){
+			ungetc(h);
+			goto top;
+		}
+
+	case '\n':
+		if(h->tok == '\n'){
+			h->eol = 1;
+			h->eoh = 1;
+			return '\n';
+		}
+		c = getc(h);
+		if(c < 0){
+			h->eol = 1;
+			return '\n';
+		}
+		if(c != ' ' && c != '\t'){
+			ungetc(h);
+			h->eol = 1;
+			return '\n';
+		}
+		goto top;
+
+	case ')':
+	case '<': case '>':
+	case '[': case ']':
+	case '@': case '/':
+	case ',': case ';': case ':': case '?': case '=':
+		if(skipwhite){
+			ungetc(h);
+			return c;
+		}
+		return c;
+
+	case '"':
+		if(skipwhite){
+			ungetc(h);
+			return c;
+		}
+		word(h, "\"");
+		getc(h);		/* skip the closing quote */
+		return QString;
+
+	default:
+		ungetc(h);
+		if(skipwhite)
+			return c;
+		word(h, "\"(){}<>@,;:/[]?=\r\n \t");
+		if(h->wordval[0] == '\0'){
+			h->c->head.closeit = 1;
+			hfail(h->c, HSyntax);
+			longjmp(h->jmp, -1);
+		}
+		return Word;
+	}
+	goto top;
+	return 0;
+}
+
+/*
+ * return the rest of an rfc 822, including \n
+ * do not map to lower case
+ */
+static void
+lexhead(Hlex *h)
+{
+	int c, n;
+
+	n = 0;
+	while((c = getc(h)) >= 0){
+		if(c == '\r')
+			c = wordcr(h);
+		else if(c == '\n')
+			c = wordnl(h);
+		if(c == '\n')
+			break;
+		if(c == '\\'){
+			c = getc(h);
+			if(c < 0)
+				break;
+		}
+
+		if(n < HMaxWord-1)
+			h->wordval[n++] = c;
+	}
+	h->tok = '\n';
+	h->eol = 1;
+	h->wordval[n] = '\0';
+}
+
+static void
+word(Hlex *h, char *stop)
+{
+	int c, n;
+
+	n = 0;
+	while((c = getc(h)) >= 0){
+		if(c == '\r')
+			c = wordcr(h);
+		else if(c == '\n')
+			c = wordnl(h);
+		if(c == '\\'){
+			c = getc(h);
+			if(c < 0)
+				break;
+		}else if(c < 32 || strchr(stop, c) != nil){
+			ungetc(h);
+			break;
+		}
+
+		if(n < HMaxWord-1)
+			h->wordval[n++] = c;
+	}
+	h->wordval[n] = '\0';
+}
+
+static int
+wordcr(Hlex *h)
+{
+	int c;
+
+	c = getc(h);
+	if(c == '\n')
+		return wordnl(h);
+	ungetc(h);
+	return ' ';
+}
+
+static int
+wordnl(Hlex *h)
+{
+	int c;
+
+	c = getc(h);
+	if(c == ' ' || c == '\t')
+		return c;
+	ungetc(h);
+
+	return '\n';
+}
+
+static int
+getc(Hlex *h)
+{
+	if(h->eoh)
+		return -1;
+	if(h->c->hpos < h->c->hstop)
+		return *h->c->hpos++;
+	h->eoh = 1;
+	h->eol = 1;
+	return -1;
+}
+
+static void
+ungetc(Hlex *h)
+{
+	if(h->eoh)
+		return;
+	h->c->hpos--;
+}
+
+static ulong
+digtoul(char *s, char **e)
+{
+	ulong v;
+	int c, ovfl;
+
+	v = 0;
+	ovfl = 0;
+	for(;;){
+		c = *s;
+		if(c < '0' || c > '9')
+			break;
+		s++;
+		c -= '0';
+		if(v > UlongMax/10 || v == UlongMax/10 && c >= UlongMax%10)
+			ovfl = 1;
+		v = v * 10 + c;
+	}
+
+	if(e)
+		*e = s;
+	if(ovfl)
+		return UlongMax;
+	return v;
+}
+
+int
+http11(HConnect *c)
+{
+	return c->req.vermaj > 1 || c->req.vermaj == 1 && c->req.vermin > 0;
+}
+
+char*
+hmkmimeboundary(HConnect *c)
+{
+	char buf[32];
+	int i;
+
+	srand((time(0)<<16)|getpid());
+	strcpy(buf, "upas-");
+	for(i = 5; i < sizeof(buf)-1; i++)
+		buf[i] = 'a' + nrand(26);
+	buf[i] = 0;
+	return hstrdup(c, buf);
+}
+
+HSPairs*
+hmkspairs(HConnect *c, char *s, char *t, HSPairs *next)
+{
+	HSPairs *sp;
+
+	sp = halloc(c, sizeof *sp);
+	sp->s = s;
+	sp->t = t;
+	sp->next = next;
+	return sp;
+}
+
+HSPairs*
+hrevspairs(HSPairs *sp)
+{
+	HSPairs *last, *next;
+
+	last = nil;
+	for(; sp != nil; sp = next){
+		next = sp->next;
+		sp->next = last;
+		last = sp;
+	}
+	return last;
+}
+
+HFields*
+hmkhfields(HConnect *c, char *s, HSPairs *p, HFields *next)
+{
+	HFields *hf;
+
+	hf = halloc(c, sizeof *hf);
+	hf->s = s;
+	hf->params = p;
+	hf->next = next;
+	return hf;
+}
+
+HFields*
+hrevhfields(HFields *hf)
+{
+	HFields *last, *next;
+
+	last = nil;
+	for(; hf != nil; hf = next){
+		next = hf->next;
+		hf->next = last;
+		last = hf;
+	}
+	return last;
+}
+
+HContent*
+hmkcontent(HConnect *c, char *generic, char *specific, HContent *next)
+{
+	HContent *ct;
+
+	ct = halloc(c, sizeof(HContent));
+	ct->generic = generic;
+	ct->specific = specific;
+	ct->next = next;
+	ct->q = 1;
+	ct->mxb = 0;
+	return ct;
+}
blob - /dev/null
blob + 19f031724912a90b1502699d8b2373b1bba12911 (mode 644)
--- /dev/null
+++ src/libhttpd/parsereq.c
@@ -0,0 +1,296 @@
+#include <u.h>
+#include <libc.h>
+#include <bin.h>
+#include <httpd.h>
+
+typedef struct Strings		Strings;
+
+struct Strings
+{
+	char	*s1;
+	char	*s2;
+};
+
+static	char*		abspath(HConnect *cc, char *origpath, char *curdir);
+static	int		getc(HConnect*);
+static	char*		getword(HConnect*);
+static	Strings		parseuri(HConnect *c, char*);
+static	Strings		stripmagic(char*);
+static	Strings		stripsearch(char*);
+
+/*
+ * parse the next request line
+ * returns:
+ *	1 ok
+ *	0 eof
+ *	-1 error
+ */
+int
+hparsereq(HConnect *c, int timeout)
+{
+	Strings ss;
+	char *vs, *v, *search, *uri, *origuri, *extra;
+
+	if(c->bin != nil){
+		hfail(c, HInternal);
+		return -1;
+	}
+
+	/*
+	 * serve requests until a magic request.
+	 * later requests have to come quickly.
+	 * only works for http/1.1 or later.
+	 */
+	alarm(timeout);
+	if(!hgethead(c, 0))
+		return 0;
+	alarm(0);
+	c->reqtime = time(nil);
+	c->req.meth = getword(c);
+	if(c->req.meth == nil){
+		hfail(c, HSyntax);
+		return -1;
+	}
+	uri = getword(c);
+	if(uri == nil || strlen(uri) == 0){
+		hfail(c, HSyntax);
+		return -1;
+	}
+	v = getword(c);
+	if(v == nil){
+		if(strcmp(c->req.meth, "GET") != 0){
+			hfail(c, HUnimp, c->req.meth);
+			return -1;
+		}
+		c->req.vermaj = 0;
+		c->req.vermin = 9;
+	}else{
+		vs = v;
+		if(strncmp(vs, "HTTP/", 5) != 0){
+			hfail(c, HUnkVers, vs);
+			return -1;
+		}
+		vs += 5;
+		c->req.vermaj = strtoul(vs, &vs, 10);
+		if(*vs != '.' || c->req.vermaj != 1){
+			hfail(c, HUnkVers, vs);
+			return -1;
+		}
+		vs++;
+		c->req.vermin = strtoul(vs, &vs, 10);
+		if(*vs != '\0'){
+			hfail(c, HUnkVers, vs);
+			return -1;
+		}
+
+		extra = getword(c);
+		if(extra != nil){
+			hfail(c, HSyntax);
+			return -1;
+		}
+	}
+
+	/*
+	 * the fragment is not supposed to be sent
+	 * strip it 'cause some clients send it
+	 */
+	origuri = uri;
+	uri = strchr(origuri, '#');
+	if(uri != nil)
+		*uri = 0;
+
+	/*
+	 * http/1.1 requires the server to accept absolute
+	 * or relative uri's.  convert to relative with an absolute path
+	 */
+	if(http11(c)){
+		ss = parseuri(c, origuri);
+		uri = ss.s1;
+		c->req.urihost = ss.s2;
+		if(uri == nil){
+			hfail(c, HBadReq, uri);
+			return -1;
+		}
+		origuri = uri;
+	}
+
+	/*
+	 * munge uri for search, protection, and magic
+	 */
+	ss = stripsearch(origuri);
+	origuri = ss.s1;
+	search = ss.s2;
+	uri = hurlunesc(c, origuri);
+	uri = abspath(c, uri, "/");
+	if(uri == nil || uri[0] == '\0'){
+		hfail(c, HNotFound, "no object specified");
+		return -1;
+	}
+
+	c->req.uri = uri;
+	c->req.search = search;
+
+	return 1;
+}
+
+static Strings
+parseuri(HConnect *c, char *uri)
+{
+	Strings ss;
+	char *urihost, *p;
+
+	urihost = nil;
+	if(uri[0] != '/'){
+		if(cistrncmp(uri, "http://", 7) != 0){
+			ss.s1 = nil;
+			ss.s2 = nil;
+			return ss;
+		}
+		uri += 5;	/* skip http: */
+	}
+
+	/*
+	 * anything starting with // is a host name or number
+	 * hostnames constists of letters, digits, - and .
+	 * for now, just ignore any port given
+	 */
+	if(uri[0] == '/' && uri[1] == '/'){
+		urihost = uri + 2;
+		p = strchr(urihost, '/');
+		if(p == nil)
+			uri = hstrdup(c, "/");
+		else{
+			uri = hstrdup(c, p);
+			*p = '\0';
+		}
+		p = strchr(urihost, ':');
+		if(p != nil)
+			*p = '\0';
+	}
+
+	if(uri[0] != '/' || uri[1] == '/'){
+		ss.s1 = nil;
+		ss.s2 = nil;
+		return ss;
+	}
+
+	ss.s1 = uri;
+	ss.s2 = hlower(urihost);
+	return ss;
+}
+static Strings
+stripsearch(char *uri)
+{
+	Strings ss;
+	char *search;
+
+	search = strchr(uri, '?');
+	if(search != nil)
+		*search++ = 0;
+	ss.s1 = uri;
+	ss.s2 = search;
+	return ss;
+}
+
+/*
+ *  to circumscribe the accessible files we have to eliminate ..'s
+ *  and resolve all names from the root.
+ */
+static char*
+abspath(HConnect *cc, char *origpath, char *curdir)
+{
+	char *p, *sp, *path, *work, *rpath;
+	int len, n, c;
+
+	if(curdir == nil)
+		curdir = "/";
+	if(origpath == nil)
+		origpath = "";
+	work = hstrdup(cc, origpath);
+	path = work;
+
+	/*
+	 * remove any really special characters
+	 */
+	for(sp = "`;| "; *sp; sp++){
+		p = strchr(path, *sp);
+		if(p)
+			*p = 0;
+	}
+
+	len = strlen(curdir) + strlen(path) + 2 + UTFmax;
+	if(len < 10)
+		len = 10;
+	rpath = halloc(cc, len);
+	if(*path == '/')
+		rpath[0] = 0;
+	else
+		strcpy(rpath, curdir);
+	n = strlen(rpath);
+
+	while(path){
+		p = strchr(path, '/');
+		if(p)
+			*p++ = 0;
+		if(strcmp(path, "..") == 0){
+			while(n > 1){
+				n--;
+				c = rpath[n];
+				rpath[n] = 0;
+				if(c == '/')
+					break;
+			}
+		}else if(strcmp(path, ".") == 0){
+			;
+		}else if(n == 1)
+			n += snprint(rpath+n, len-n, "%s", path);
+		else
+			n += snprint(rpath+n, len-n, "/%s", path);
+		path = p;
+	}
+
+	if(strncmp(rpath, "/bin/", 5) == 0)
+		strcpy(rpath, "/");
+	return rpath;
+}
+
+static char*
+getword(HConnect *c)
+{
+	char *buf;
+	int ch, n;
+
+	while((ch = getc(c)) == ' ' || ch == '\t' || ch == '\r')
+		;
+	if(ch == '\n')
+		return nil;
+	n = 0;
+	buf = halloc(c, 1);
+	for(;;){
+		switch(ch){
+		case ' ':
+		case '\t':
+		case '\r':
+		case '\n':
+			buf[n] = '\0';
+			return hstrdup(c, buf);
+		}
+
+		if(n < HMaxWord-1){
+			buf = bingrow(&c->bin, buf, n, n + 1, 0);
+			if(buf == nil)
+				return nil;
+			buf[n++] = ch;
+		}
+		ch = getc(c);
+	}
+	return nil;
+}
+
+static int
+getc(HConnect *c)
+{
+	if(c->hpos < c->hstop)
+		return *c->hpos++;
+	return '\n';
+}
blob - /dev/null
blob + 47768da2fe57f2d31b43ba2ddbdd2d17896d2cfb (mode 644)
--- /dev/null
+++ src/libhttpd/query.c
@@ -0,0 +1,39 @@
+#include <u.h>
+#include <libc.h>
+#include <httpd.h>
+
+/*
+ * parse a search string of the form
+ * tag=val&tag1=val1...
+ */
+HSPairs*
+hparsequery(HConnect *c, char *search)
+{
+	HSPairs *q;
+	char *tag, *val, *s;
+
+	while((s = strchr(search, '?')) != nil)
+		search = s + 1;
+	s = search;
+	while((s = strchr(s, '+')) != nil)
+		*s++ = ' ';
+	q = nil;
+	while(*search){
+		tag = search;
+		while(*search != '='){
+			if(*search == '\0')
+				return q;
+			search++;
+		}
+		*search++ = 0;
+		val = search;
+		while(*search != '&'){
+			if(*search == '\0')
+				return hmkspairs(c, hurlunesc(c, tag), hurlunesc(c, val), q);
+			search++;
+		}
+		*search++ = '\0';
+		q = hmkspairs(c, hurlunesc(c, tag), hurlunesc(c, val), q);
+	}
+	return q;
+}
blob - /dev/null
blob + c2419457c18090fdae4860691b522691087bfb91 (mode 644)
--- /dev/null
+++ src/libhttpd/redirected.c
@@ -0,0 +1,64 @@
+#include <u.h>
+#include <libc.h>
+#include <bin.h>
+#include <httpd.h>
+
+int
+hredirected(HConnect *c, char *how, char *uri)
+{
+	Hio *hout;
+	char *s, *ss, *host;
+	int n;
+
+	host = c->head.host;
+	if(strchr(uri, ':')){
+		host = "";
+	}else if(uri[0] != '/'){
+		s = strrchr(c->req.uri, '/');
+		if(s != nil)
+			*s = '\0';
+		ss = halloc(c, strlen(c->req.uri) + strlen(uri) + 2 + UTFmax);
+		sprint(ss, "%s/%s", c->req.uri, uri);
+		uri = ss;
+		if(s != nil)
+			*s = '/';
+	}
+
+	n = snprint(c->xferbuf, HBufSize, 
+			"<head><title>Redirection</title></head>\r\n"
+			"<body><h1>Redirection</h1>\r\n"
+			"Your selection can be found <a href=\"%U\"> here</a>.<p></body>\r\n", uri);
+
+	hout = &c->hout;
+	hprint(hout, "%s %s\r\n", hversion, how);
+	hprint(hout, "Date: %D\r\n", time(nil));
+	hprint(hout, "Server: Plan9\r\n");
+	hprint(hout, "Content-type: text/html\r\n");
+	hprint(hout, "Content-Length: %d\r\n", n);
+	if(host == nil || host[0] == 0)
+		hprint(hout, "Location: %U\r\n", uri);
+	else
+		hprint(hout, "Location: http://%U%U\r\n", host, uri);
+	if(c->head.closeit)
+		hprint(hout, "Connection: close\r\n");
+	else if(!http11(c))
+		hprint(hout, "Connection: Keep-Alive\r\n");
+	hprint(hout, "\r\n");
+
+	if(strcmp(c->req.meth, "HEAD") != 0)
+		hwrite(hout, c->xferbuf, n);
+
+	if(c->replog){
+		if(host == nil || host[0] == 0)
+			c->replog(c, "Reply: %s\nRedirect: %U\n", how, uri);
+		else
+			c->replog(c, "Reply: %s\nRedirect: http://%U%U\n", how, host, uri);
+	}
+	return hflush(hout);
+}
+
+int
+hmoved(HConnect *c, char *uri)
+{
+	return hredirected(c, "301 Moved Permanently", uri);
+}
blob - /dev/null
blob + ff50856f1cae894e4937d9ae0efadd7e5cfe23d1 (mode 644)
--- /dev/null
+++ src/libhttpd/unallowed.c
@@ -0,0 +1,35 @@
+#include <u.h>
+#include <libc.h>
+#include <bin.h>
+#include <httpd.h>
+
+int
+hunallowed(HConnect *c, char *allowed)
+{
+	Hio *hout;
+	int n;
+
+	n = snprint(c->xferbuf, HBufSize, "<head><title>Method Not Allowed</title></head>\r\n"
+		"<body><h1>Method Not Allowed</h1>\r\n"
+		"You can't %s on <a href=\"%U\"> here</a>.<p></body>\r\n", c->req.meth, c->req.uri);
+
+	hout = &c->hout;
+	hprint(hout, "%s 405 Method Not Allowed\r\n", hversion);
+	hprint(hout, "Date: %D\r\n", time(nil));
+	hprint(hout, "Server: Plan9\r\n");
+	hprint(hout, "Content-Type: text/html\r\n");
+	hprint(hout, "Allow: %s\r\n", allowed);
+	hprint(hout, "Content-Length: %d\r\n", n);
+	if(c->head.closeit)
+		hprint(hout, "Connection: close\r\n");
+	else if(!http11(c))
+		hprint(hout, "Connection: Keep-Alive\r\n");
+	hprint(hout, "\r\n");
+
+	if(strcmp(c->req.meth, "HEAD") != 0)
+		hwrite(hout, c->xferbuf, n);
+
+	if(c->replog)
+		c->replog(c, "Reply: 405 Method Not Allowed\nReason: Method Not Allowed\n");
+	return hflush(hout);
+}
blob - /dev/null
blob + 2ce28acec6b58d4fce9ec2155385b21b2dfd9bd9 (mode 644)
--- /dev/null
+++ src/libhttpd/urlfmt.c
@@ -0,0 +1,26 @@
+#include <u.h>
+#include <libc.h>
+#include <bin.h>
+#include <httpd.h>
+
+int
+hurlfmt(Fmt *f)
+{
+	char buf[HMaxWord*2];
+	Rune r;
+	char *s;
+	int t;
+
+	s = va_arg(f->args, char*);
+	for(t = 0; t < sizeof(buf) - 8; ){
+		s += chartorune(&r, s);
+		if(r == 0)
+			break;
+		if(r <= ' ' || r == '%' || r >= Runeself)
+			t += snprint(&buf[t], sizeof(buf)-t, "%%%2.2x", r);
+		else
+			buf[t++] = r;
+	}
+	buf[t] = 0;
+	return fmtstrcpy(f, buf);
+}
blob - /dev/null
blob + ae8ca81f5442b3475992aa881447815150f7479c (mode 644)
--- /dev/null
+++ src/libhttpd/urlunesc.c
@@ -0,0 +1,58 @@
+#include <u.h>
+#include <libc.h>
+#include <bin.h>
+#include <httpd.h>
+
+/* go from url with escaped utf to utf */
+char *
+hurlunesc(HConnect *cc, char *s)
+{
+	char *t, *v, *u;
+	Rune r;
+	int c, n;
+
+	/* unescape */
+	u = halloc(cc, strlen(s)+1);
+	for(t = u; c = *s; s++){
+		if(c == '%'){
+			n = s[1];
+			if(n >= '0' && n <= '9')
+				n = n - '0';
+			else if(n >= 'A' && n <= 'F')
+				n = n - 'A' + 10;
+			else if(n >= 'a' && n <= 'f')
+				n = n - 'a' + 10;
+			else
+				break;
+			r = n;
+			n = s[2];
+			if(n >= '0' && n <= '9')
+				n = n - '0';
+			else if(n >= 'A' && n <= 'F')
+				n = n - 'A' + 10;
+			else if(n >= 'a' && n <= 'f')
+				n = n - 'a' + 10;
+			else
+				break;
+			s += 2;
+			c = (r<<4)+n;
+		}
+		*t++ = c;
+	}
+	*t = '\0';
+
+	/* convert to valid utf */
+	v = halloc(cc, UTFmax*strlen(u) + 1);
+	s = u;
+	t = v;
+	while(*s){
+		/* in decoding error, assume latin1 */
+		if((n=chartorune(&r, s)) == 1 && r == Runeerror)
+			r = (uchar)*s;
+		s += n;
+		t += runetochar(t, &r);
+	}
+	*t = '\0';
+
+	return v;
+}