shithub: tcp80

ref: f423629013854dfd09b37ce2f1c7ba637df8a541
dir: /tcp80.c/

View raw version
#include <u.h>
#include <libc.h>
#include <ctype.h>
#include <auth.h>
#include <bio.h>
#include <regexp.h>

typedef struct Pair Pair;
typedef struct Ctype Ctype;
typedef struct HResponse HResponse;

struct Pair {
	Pair *next;

	char key[64];
	char val[256];
	char *att;
};

struct Ctype {
	char *suffix;
	char *type;
};

int trusted;

char remote[128];
char method[64];
char location[1024];

int redir_errno[64];

Pair *header;
int naheader;
Pair aheader[64];

// Expanded with information from
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types
Ctype ctypemap[] = {
    ".htm",    "text/html;charset=utf-8",
    ".html",   "text/html;charset=utf-8",
    ".txt",    "text/plain;charset=utf-8",
    ".md",     "text/markdown;charset=utf-8",
    ".css",    "text/css;charset=utf-8",
    ".aac",    "audio/aac",
    ".avif",   "image/avif",
    ".avi",    "video/x-msvideo",
    ".azw",    "application/vnd.amazon.ebook",
    ".bin",    "application/octet-stream",
    ".bmp",    "image/bmp",
    ".bz",     "application/x-bzip",
    ".bz2",    "application/x-bzip2",
    ".cda",    "application/x-cdf",
    ".csh",    "application/x-csh",
    ".csv",    "text/csv",
    ".doc",    "application/msword",
    ".docx",   "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
    ".eot",    "application/vnd.ms-fontobject",
    ".epub",   "application/epub+zip",
    ".gz",     "application/gzip",
    ".gif",    "image/gif",
    ".ico",    "image/vnd.microsoft.icon",
    ".ics",    "text/calendar",
    ".jar",    "application/java-archive",
    ".jpeg",   "image/jpeg",
    ".jpg",    "image/jpeg",
    ".js",     "text/javascript",
    ".json",   "application/json",
    ".jsonld", "application/ld+json",
    ".mid",    "audio/midi",
    ".midi",   "audio/midi",
    ".mjs",    "text/javascript",
    ".mp3",    "audio/mpeg",
    ".mp4",    "video/mp4",
    ".mpeg",   "video/mpeg",
    ".odp",    "application/vnd.oasis.opendocument.presentation",
    ".ods",    "application/vnd.oasis.opendocument.spreadsheet",
    ".odt",    "application/vnd.oasis.opendocument.text",
    ".oga",    "audio/ogg",
    ".ogg",    "audio/ogg",
    ".ogv",    "video/ogg",
    ".ogx",    "application/ogg",
    ".opus",   "audio/opus",
    ".otf",    "font/otf",
    ".png",    "image/png",
    ".pdf",    "application/pdf",
    ".ppt",    "application/vnd.ms-powerpoint",
    ".pptx",   "application/vnd.openxmlformats-officedocument.presentationml.presentation",
    ".rar",    "application/vnd.rar",
    ".rtf",    "application/rtf",
    ".sh",     "application/x-sh",
    ".svg",    "image/svg+xml",
    ".tar",    "application/x-tar",
    ".tif",    "image/tiff",
    ".tiff",   "image/tiff",
    ".ts",     "video/mp2t",
    ".ttf",    "font/ttf",
    ".vsd",    "application/vnd.visio",
    ".wav",    "audio/wav",
    ".weba",   "audio/webm",
    ".webm",   "video/webm",
    ".webp",   "image/webp",
    ".woff",   "font/woff",
    ".woff2",  "font/woff2",
    ".xhtml",  "application/xhtml+xml",
    ".xls",    "application/vnd.ms-excel",
    ".xlsx",   "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
    ".xml",    "application/xml",
    ".xul",    "application/vnd.mozilla.xul+xml",
    ".zip",    "application/zip",
    ".3gp",    "video/3gpp",
    ".3g2",    "video/3gpp2",
    ".7z",     "application/x-7z-compressed",
};

struct HResponse {
	int code;
	const char *message;
};

// RFC 2616 responses
HResponse responsemap[] = {
    100, "Continue",
    101, "Switching Protocols",
    200, "OK",
    201, "Created",
    202, "Accepted",
    203, "Non-Authoritative Information",
    204, "No Content",
    205, "Reset Content",
    206, "Partial Content",
    300, "Multiple Choices",
    301, "Moved Permanently",
    302, "Found",
    303, "See Other",
    304, "Not Modified",
    305, "Use Proxy",
    307, "Temporary Request",
    400, "Bad Request",
    401, "Unauthorized",
    402, "Payment Required",
    403, "Forbidden",
    404, "Not Found",
    405, "Method Not Allowed",
    406, "Not Acceptable",
    407, "Proxy Authentication Required",
    408, "Request Time-out",
    409, "Conflict",
    410, "Gone",
    411, "Length Required",
    412, "Precondition Failed",
    413, "Request Entity Too Large",
    414, "Request-URI Too Large",
    415, "Unsupported Media Type",
    416, "Requested range not satisfiable",
    417, "Expectation Failed",
    500, "Internal Server Error",
    501, "Not Implemented",
    502, "Bad Gateway",
    503, "Service Unavailable",
    504, "Gateway Time-out",
    505, "HTTP Version not supported",
};

void createenv(char *env, char *data);
int dircmp(Dir *a, Dir *b);
int dispatch(void);
void dispatchrule(char *cmd);
Pair *findhdr(Pair *h, char *key);
char *findrule(char *rulesfile, char *path);
char *fullurl(char *host, char *path, char *name, char *query);
void getresponse(char *dst, int bufsz, int code);
long hdate(char *s);
void headers(char *path, Dir *d);
int isleap(int year);
void main(int argc, char **argv);
char *nstrcpy(char *d, char *s, int n);
int parsequery(void);
int redirerr(int status, Dir *d);
void respond(char *status);
char *token(char *s, char *delim, char **pe);
char *urldec(char *d, char *s, int n);
char *urlenc(char *d, char *s, int n);

void
getresponse(char *dst, int bufsz, int code)
{
	int i;

	for(i = 0; i < nelem(responsemap); i++){
		if(responsemap[i].code == code){
			seprint(dst, dst + bufsz, "%d %s", responsemap[i].code,
				responsemap[i].message);
			return;
		}
	}

	getresponse(dst, bufsz, 500);
}

char *
findrule(char *rulesfile, char *path)
{
	Biobuf *bio;
	char *s, *p, *d, *r;
	Reprog *re;
	Resub m[16];

	if((bio = Bopen(rulesfile, OREAD)) == nil)
		sysfatal("open: %r");
	while(s = Brdstr(bio, '\n', 1)){
		p = s;
		while(strchr("\t ", *p)) p++;
		d = nil;
		if(*p != '#'){
			if(d = strchr(p, '\t'))
				*d++ = 0;
			else if(d = strchr(p, ' '))
				*d++ = 0;
		}
		if(d == nil){
			free(s);
			continue;
		}
		while(strchr("\t ", *d)) d++;

		if(re = regcomp(p)){
			memset(m, 0, sizeof(m));
			if(regexec(re, path, m, nelem(m))){
				r = malloc(1024);
				regsub(d, r, 1024, m, nelem(m));
				free(s);
				Bterm(bio);
				return r;
			}
		}
		free(s);
	}
	Bterm(bio);
	return nil;
}

void
createenv(char *env, char *data)
{
	static char statbuf[32];
	int fd;
	char *path = "/env/";
	int plen   = strlen(path);
	int elen   = strlen(env);
	int flen   = plen + elen;
	int dlen   = strlen(data);
	char *filename;

	if((filename = malloc(plen + elen + 1)) == nil){
		getresponse(statbuf, 32, 500);
		respond(statbuf);
		sysfatal("malloc");
	}
	nstrcpy(filename, path, flen);
	strncat(filename, env, elen);

	if((fd = create(filename, OWRITE, 0644)) < 0){
		getresponse(statbuf, 32, 500);
		respond(statbuf);
		sysfatal("create");
	}
	if(write(fd, data, dlen) != dlen){
		getresponse(statbuf, 32, 500);
		respond(statbuf);
		sysfatal("write");
	}
	close(fd);
	free(filename);
	return;
}

void
dispatchrule(char *cmd)
{
	static char statbuf[32];
	if(rfork(RFPROC | RFNOWAIT | RFFDG | RFREND | RFENVG) == 0){
		if(cistrcmp(cmd, "cgi") == 0){
			char *query = strchr(location, '?');
			if(query != nil){
				query[0] = 0;
				query++;
			}
			createenv("GATEWAY_INTERFACE", "CGI/1.1");
			createenv("QUERY_STRING", query ? query : "");
			createenv("REMOTE_ADDR", remote);
			createenv("REQUEST_METHOD", method);
			createenv("REQUEST_URI", location);
			createenv("SCRIPT_NAME", location);
			Pair *host = findhdr(nil, "Host");
			if(host != nil){
				createenv("SERVER_NAME", host->val);
			}else{
				createenv("SERVER_NAME", "localhost");
			}
			// This is a nasty lie. It could be any port, but we don't actually know
			createenv("SERVER_PORT", "80");
			createenv("SERVER_PROTOCOL", "HTTP");
			createenv("SERVER_SOFTWARE", "tcp80");
			chdir("/usr/web/bin");
			const char* base = "/usr/web";
			const int blen = strlen(base);
			const int llen = strlen(location);
			char *newloc = malloc(blen+llen+1);
			nstrcpy(newloc, base, blen+llen+1);
			strncat(newloc, location, blen+llen+1);
			newloc[blen+llen] = 0;
			execl("/bin/rc", "rc", "-c", newloc, nil);

			getresponse(statbuf, 32, 500);
			respond(statbuf);
			sysfatal("exec");
		}
		execl("/bin/rc", "rc", "-c", cmd, nil);

		getresponse(statbuf, 32, 500);
		respond(statbuf);
		sysfatal("exec");
	}
	exits(nil);
}

Pair *
findhdr(Pair *h, char *key)
{
	if(h == nil)
		h = header;
	else
		h = h->next;
	for(; h; h = h->next)
		if(cistrcmp(h->key, key) == 0)
			break;
	return h;
}

char *
nstrcpy(char *d, char *s, int n)
{
	d[n - 1] = 0;
	return strecpy(d, d + n, s);
}

char hex[] = "0123456789ABCDEF";

char *
urldec(char *d, char *s, int n)
{
	int c, x;
	char *r;

	r = d;
	x = 0;
	while(n > 1 && (c = *s++)){
		if(x){
			char *p;

			if((p = strchr(hex, toupper(c))) == nil)
				continue;
			*d <<= 4;
			*d |= p - hex;
			if(--x)
				continue;
		}else{
			if(c == '%'){
				x = 2;
				continue;
			}
			*d = c;
		}
		d++;
		n--;
	}
	*d = 0;
	return r;
}

char *
urlenc(char *d, char *s, int n)
{
	char *r;
	int c;

	r = d;
	while(n > 1 && (c = *s++)){
		if(isalnum(c) || strchr("$-_.+!*'(),", c) || strchr("/:;=@", c)){
			*d++ = c;
			n--;
		}else{
			if(n <= 3)
				break;
			*d++ = '%';
			*d++ = hex[(c >> 4) & 15];
			*d++ = hex[c & 15];
			n -= 3;
		}
	}
	*d = 0;
	return r;
}

int
isleap(int year)
{
	return year % 4 == 0 && (year % 100 != 0 || year % 400 == 0);
}

long
hdate(char *s)
{
	int i;
	Tm tm;

	static int mday[2][12] = {
	    31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31,
	    31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31,
	};
	static char *wday[] = {
	    "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday",
	};
	static char *mon[] = {
	    "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
	};

	/* Sunday, */
	for(i = 0; i < nelem(wday); i++){
		if(cistrncmp(s, wday[i], strlen(wday[i])) == 0){
			s += strlen(wday[i]);
			break;
		}
		if(cistrncmp(s, wday[i], 3) == 0){
			s += 3;
			break;
		}
	}
	if(*s == ',')
		s++;
	if(*s == ' ')
		s++;
	/* 25- */
	if(!isdigit(s[0]) || !isdigit(s[1]) || (s[2] != '-' && s[2] != ' '))
		return -1;
	tm.mday = strtol(s, 0, 10);
	s += 3;
	/* Jan- */
	for(i = 0; i < nelem(mon); i++)
		if(cistrncmp(s, mon[i], 3) == 0){
			tm.mon = i;
			s += 3;
			break;
		}
	if(i == nelem(mon))
		return -1;
	if(s[0] != '-' && s[0] != ' ')
		return -1;
	s++;
	/* 2002 */
	if(!isdigit(s[0]) || !isdigit(s[1]))
		return -1;
	tm.year = strtol(s, 0, 10);
	s += 2;
	if(isdigit(s[0]) && isdigit(s[1]))
		s += 2;
	else{
		if(tm.year <= 68)
			tm.year += 2000;
		else
			tm.year += 1900;
	}
	if(tm.mday == 0 || tm.mday > mday[isleap(tm.year)][tm.mon])
		return -1;
	tm.year -= 1900;
	if(*s++ != ' ')
		return -1;
	if(!isdigit(s[0]) || !isdigit(s[1]) || s[2] != ':' || !isdigit(s[3]) || !isdigit(s[4]) ||
	   s[5] != ':' || !isdigit(s[6]) || !isdigit(s[7]) || s[8] != ' ')
		return -1;
	tm.hour = atoi(s);
	tm.min	= atoi(s + 3);
	tm.sec	= atoi(s + 6);
	if(tm.hour >= 24 || tm.min >= 60 || tm.sec >= 60)
		return -1;
	s += 9;
	if(cistrcmp(s, "GMT") != 0)
		return -1;
	nstrcpy(tm.zone, s, sizeof(tm.zone));
	tm.yday = 0;
	return tm2sec(&tm);
}

void
headers(char *path, Dir *d)
{
	char buf[1024], *f[6];
	int isdir;
	Tm *tm;

	if(tm = localtime(time(0))){
		nstrcpy(buf, asctime(tm), sizeof(buf));
		if(tokenize(buf, f, 6) == 6)
			print("Date: %s, %.2d %s %s %s %s\r\n", f[0], tm->mday, f[1], f[5], f[3],
			      f[4]);
	}
	if(d && d->mtime != 0 && (tm = localtime(d->mtime))){
		nstrcpy(buf, asctime(tm), sizeof(buf));
		if(tokenize(buf, f, 6) == 6)
			print("Last-Modified: %s, %.2d %s %s %s %s\r\n", f[0], tm->mday, f[1], f[5],
			      f[3], f[4]);
	}else if(d && d->mtime == 0 && (tm = localtime(time(0)))){
		nstrcpy(buf, asctime(tm), sizeof(buf));
		if(tokenize(buf, f, 6) == 6)
			print("Last-Modified: %s, %.2d %s %s %s %s\r\n", f[0], tm->mday, f[1], f[5],
			      f[3], f[4]);
	}
	isdir = d && (d->qid.type & QTDIR);
	if(isdir){
		print("Content-Type: text/html; charset=utf-8\r\n");
	}else{
		int i;
		for(i = 0; i < nelem(ctypemap); i++){
			int offset = strlen(path) - strlen(ctypemap[i].suffix);
			if(offset <= 0)
				break;
			if(cistrcmp(path + offset, ctypemap[i].suffix) == 0){
				print("Content-Type: %s\r\n", ctypemap[i].type);
				break;
			}
		}
	}
	if(*path == '/')
		print("Content-Location: %s%s\r\n", urlenc(buf, path, sizeof(buf)),
		      isdir ? "/" : "");
}

int
dircmp(Dir *a, Dir *b)
{
	return strcmp(a->name, b->name);
}

char *
fullurl(char *host, char *path, char *name, char *query)
{
	static char buf[1024];

	snprint(buf, sizeof(buf), "%s%s%s%s%s%s", host ? "/" : "", host ? host : "",
		path ? path : "/", name ? name : "", query ? "?" : "", query ? query : "");
	return buf;
}

void
respond(char *status)
{
	syslog(0, "tcp80", "%s %s %s %s", remote, method, location, status);
	print("HTTP/1.1 %s\r\n", status);
}

int
redirerr(int status, Dir *d)
{
	static char buf[8192], tmp[1024], statbuf[32];
	int i;

	for(i = 0; i < nelem(redir_errno); i++){
		if(status == redir_errno[i]){
			getresponse(statbuf, 32, 303);
			respond(statbuf);
			headers(buf, d);

			snprint(buf, sizeof(buf), "/%d%s", status, location);

			print("Location: %s\r\n\r\n", buf);
			return 1;
		}
	}
	return 0;
}

int
dispatch(void)
{
	static char buf[8192], tmp[1024], statbuf[32];
	char *p, *s;
	int i, n, fd, badmeth, nobody, noindex, noslash;
	int status = 0;
	Pair *h;
	Dir *d;

	nobody	= !cistrcmp(method, "HEAD");
	badmeth = !nobody && cistrcmp(method, "GET");
	if(badmeth){
		werrstr("%s method unsupported", method);
		status = 405;
	Error:
		if(redirerr(status, d))
			goto Out;
		getresponse(statbuf, 32, status);
		if(!nobody)
			n = snprint(buf, sizeof(buf),
				    "<html><head><title>%s</title></head>\n"
				    "<body><h1>%s</h1><pre>%r</pre></body></html>\n",
				    statbuf, statbuf);
		else
			n = 0;
		respond(statbuf);
		headers(".html", nil);
		print("Content-Length: %d\r\n\r\n%*s", n, n, buf);
		return -badmeth;
	}

	s = location;
	if(cistrncmp(s, "http:", 5) == 0)
		s += 5;
	else if(cistrncmp(s, "https:", 6) == 0)
		s += 6;
	if(s[0] == '/' && s[1] == '/')
		s = strchr(s + 2, '/');
	if(s == nil || *s == 0)
		s = "/";
	nstrcpy(tmp, s, sizeof(tmp));
	if(s = strchr(tmp, '#'))
		*s = 0;
	noindex = 0;
	if(s = strchr(tmp, '?')){
		*s++	= 0;
		noindex = !cistrcmp(s, "noindex");
	}
	urldec(buf, tmp, sizeof(buf));

	noslash = 1;
	if(s = strrchr(buf, '/'))
		if(s[1] == 0)
			noslash = 0;

	cleanname(buf);
	if((fd = open(buf, OREAD)) < 0){
		rerrstr(buf, sizeof(buf));
		if(strstr(buf, "permission denied")){
			status = 403;
			goto Error;
		}
		status = 404;
		goto Error;
	}

	if((d = dirfstat(fd)) == nil){
		close(fd);
		status = 500;
		goto Error;
	}

	if(d->qid.type & QTDIR){
		int fd2;
		Dir *d2;

		if(noslash){
			getresponse(statbuf, 32, 301);
			respond(statbuf);
			headers(buf, d);

			h = findhdr(nil, "Host");
			p = strchr(location, '?');
			s = fullurl(h ? h->val : nil, urlenc(tmp, buf, sizeof(tmp)), "/",
				    p ? p + 1 : nil);
			if(!nobody)
				n = snprint(buf, sizeof(buf),
					    "<html><head><title>%s</title></head>\n"
					    "<body><h1>%s</h1><pre>Moved to <a "
					    "href=\"%s\">%s</a></pre></body></html>\n",
					    statbuf, statbuf, s, s);
			else
				n = 0;
			print("Location: %s\r\nContent-Length: %d\r\n\r\n%*s", s, n, n, buf);
			goto Out;
		}

		if(!noindex){
			snprint(tmp, sizeof(tmp), "%s/index.html", buf);
			cleanname(tmp);
			if((fd2 = open(tmp, OREAD)) >= 0){
				if(d2 = dirfstat(fd2)){
					if((d2->qid.type & QTDIR) == 0){
						nstrcpy(buf, tmp, sizeof(buf));
						close(fd);
						fd = fd2;
						free(d);
						d = d2;
						goto Filecont;
					}
					free(d2);
				}
				close(fd2);
			}
		}

		getresponse(statbuf, 32, 200);
		respond(statbuf);
		headers(buf, d);
		print("\r\n");
		if(nobody)
			goto Out;

		print(
		    "<html><head><title>%s</title></head><body>"
		    "<pre>\n<a href=\"/%s\">/</a>",
		    buf, noindex ? "?noindex" : "");
		for(p = buf + 1; *p; p = s + 1){
			if(s = strchr(p, '/'))
				*s = 0;
			print("<a href=\"%s/%s\">%s</a>/", urlenc(tmp, buf, sizeof(tmp)),
			      noindex ? "?noindex" : "", p);
			if(s == nil)
				break;
			*s = '/';
		}
		print("<hr>");

		free(d);
		d = nil;
		if((n = dirreadall(fd, &d)) > 0){
			qsort(d, n, sizeof d[0], (int (*)(void *, void *))dircmp);
			for(i = 0; i < n; i++)
				print("<a href=\"%s%s\">%s</a>%s\n",
				      urlenc(tmp, d[i].name, sizeof(tmp)),
				      (d[i].qid.type & QTDIR) ? (noindex ? "/?noindex" : "/") : "",
				      d[i].name, (d[i].qid.type & QTDIR) ? "/" : "");
			free(d);
		}
		print("</pre></body></html>\n");
		return 1;
	}else{
		vlong start, end;

	Filecont:
		h = findhdr(nil, "If-Modified-Since");
		if(h && !nobody){
			long t;

			if((t = hdate(h->val)) != -1){
				if(d->mtime <= t){
					getresponse(statbuf, 32, 304);
					respond(statbuf);
					headers(buf, d);
					print("\r\n");
					goto Out;
				}
			}
		}

		h = findhdr(nil, "Range");
		while(h){
			if(findhdr(h, "Range"))
				break;
			if(s = strchr(h->val, '='))
				s++;
			else
				s = h->val;
			start = strtoll(s, &s, 10);
			if(*s++ != '-')
				break;
			if(*s == 0)
				end = d->length;
			else
				end = strtoll(s, &s, 10) + 1;
			if(*s != 0 || (end <= start))
				break;
			getresponse(statbuf, 32, 206);
			respond(statbuf);
			print("Content-Range: bytes %lld-%lld/%lld\r\n", start, end - 1, d->length);
			goto Content;
		}
		start = 0;
		end   = d->length;
		getresponse(statbuf, 32, 200);
		respond(statbuf);
	Content:
		headers(buf, d);
		if(end > start){
			print("Content-Length: %lld\r\n\r\n", end - start);
			if(nobody)
				goto Out;
			while(start < end){
				n = sizeof(buf);
				if((end - start) < n)
					n = end - start;
				if((n = pread(fd, buf, n, start)) <= 0)
					return -1;
				if(write(1, buf, n) != n)
					return -1;
				start += n;
			}
		}else{
			print("\r\n");
			if(nobody)
				goto Out;
			while((n = read(fd, buf, sizeof(buf))) > 0)
				if(write(1, buf, n) != n)
					return -1;
			return 1;
		}
	}
Out:
	close(fd);
	free(d);
	return 0;
}

char *
token(char *s, char *delim, char **pe)
{
	char *e;
	int d;

	d = 0;
	while(*s == ' ' || *s == '\t') s++;
	for(e = s; *e; e++){
		if(*e == '(')
			d++;
		if(d > 0){
			if(*e == ')')
				d--;
			s = e + 1;
			continue;
		}
		if(strchr(delim, *e)){
			*e++ = 0;
			break;
		}
	}
	if(pe)
		*pe = e;
	while(s < e && *s == ' ' || *s == '\t') s++;
	while(--e >= s){
		if(*e != ' ' && *e != '\t')
			break;
		*e = 0;
	}
	return s;
}

int
parsequery(void)
{
	static char buf[1024], line[1024];
	char *p, *e, *k, *x, *s;
	int lineno, n;
	Pair *h;

	naheader = 0;
	lineno	 = 0;
	*line	 = 0;
	p	 = buf;
	e	 = buf + sizeof(buf);
	while((n = read(0, p, e - p)) > 0){
		p += n;
		while((p > buf) && (e = memchr(buf, '\n', p - buf))){
			if((e > buf) && (e[-1] == '\r'))
				e[-1] = 0;
			*e++ = 0;
			if(*buf != ' ' && *buf != '\t' && *line){
				if(lineno++ == 0){
					nstrcpy(method, token(line, "\t ", &s), sizeof(method));
					nstrcpy(location, token(s, "\t ", nil), sizeof(location));
				}else{
					if(lineno > 100)
						return -1;
					k = token(line, ":", &s);
					while(*s){
						if(naheader >= nelem(aheader))
							return -1;
						x = token(s, ",", &s);
						h = aheader + naheader++;
						nstrcpy(h->key, k, sizeof(h->key));
						nstrcpy(h->val, x, sizeof(h->val));
						if(x = strchr(h->val, ';')){
							*x++ = 0;
							x    = token(x, ";", nil);
						}
						h->att	= x;
						h->next = header;
						header	= h;
					}
				}
			}
			nstrcpy(line, buf, sizeof(line));
			p -= e - buf;
			if(p > buf)
				memmove(buf, e, p - buf);
			if(*line == 0){
				if(method[0] == 0)
					return -1;
				return 0;
			}
		}
		e = buf + sizeof(buf);
	}
	return -1;
}

void
main(int argc, char **argv)
{
	static char buf[1024], line[1024], statbuf[32];
	Pair *host;
	char *hosts;
	char *r, *c;
	int i = 0;
	int n;

	r = nil;
	ARGBEGIN
	{
	case 'e':
		redir_errno[i] = strtol(ARGF(), 0, 0);
		i++;
		break;
	case 'r': r = ARGF(); break;
	case 't': trusted++; break;
	case 'h': hosts = ARGF(); break;
	}
	ARGEND

	time(0);
	if(argc){
		int fd;
		snprint(buf, sizeof(buf), "%s/remote", argv[argc - 1]);
		if((fd = open(buf, OREAD)) >= 0){
			if((n = read(fd, remote, sizeof(remote) - 1)) >= 0){
				while(n > 0 && remote[n - 1] == '\n') n--;
				remote[n] = 0;
			}
			close(fd);
		}
	}
	if(remote[0] == 0)
		strecpy(remote, remote + sizeof remote, "-");
	if(parsequery() < 0){
		getresponse(statbuf, 32, 400);
		respond(statbuf);
		return;
	}

	if(hosts){
		host = findhdr(nil, "Host");
		c    = findrule(hosts, host->val);
		if(c){
			if(bind(c, "/mnt/web", MREPL) < 0)
				return;
			if(bind("/mnt/web", "/usr/web", MREPL) < 0)
				return;
		}
	}

	if(r){
		char *loc;
		if(addns("none", "/lib/namespace.httpd") < 0)
			return;
		if(cistrcmp(location, "/") == 0){
			loc = "/index.html";
		}else{
			loc = location;
		}
		c = findrule(r, loc);
		if(c){
			Dir fakedir  = {0};
			fakedir.mode = 0644;
			fakedir.name = "unrealfile";
			fakedir.uid  = "none";
			fakedir.gid  = "none";
			fakedir.muid = "none";
			if(cistrcmp(method, "GET") != 0)
				if(redirerr(405, &fakedir))
					goto rOut;
			if(cistrcmp(c, "cgi") != 0){
				getresponse(statbuf, 32, 200);
				respond(statbuf);
				headers(loc, &fakedir);
			}
			dispatchrule(c);
		rOut:
			free(c);
			return;
		}
	}
	if(!trusted){
		if(addns("none", "/lib/namespace.httpd") < 0)
			return;

		if(bind("/usr/web", "/", MREPL) < 0)
			return;

		if(rfork(RFNOMNT) < 0)
			return;
	}
	dispatch();
	return;
}