shithub: tcp80x

Download patch

ref: 746bd1037c4d34bfd5b5a39350f61d1e09f0e60f
author: phil9 <telephil9@gmail.com>
date: Mon Nov 8 08:50:56 EST 2021

initial import

--- /dev/null
+++ b/LICENSE
@@ -1,0 +1,21 @@
+MIT License
+
+Copyright (c) 2021 phil9 <telephil9@gmail.com>
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
--- /dev/null
+++ b/README
@@ -1,0 +1,6 @@
+tcp80x
+=======
+This is a merger of execfs and tcp80, both written by cinap_lenrek.
+Basically, for each incoming request we look for a dispatch rule and execute
+the command associated with the rule. If no rule was found then we fallback
+to serving static files which should be present in /usr/web.
--- /dev/null
+++ b/mkfile
@@ -1,0 +1,11 @@
+</$objtype/mkfile
+
+TARG=tcp80x
+BIN=/$objtype/bin
+CFLAGS=-FTVw
+
+HFILES=
+
+OFILES=tcp80x.$O
+
+</sys/src/cmd/mkone
--- /dev/null
+++ b/tcp80x.c
@@ -1,0 +1,680 @@
+#include <u.h>
+#include <libc.h>
+#include <ctype.h>
+#include <auth.h>
+#include <bio.h>
+#include <regexp.h>
+
+typedef struct Pair Pair;
+
+struct Pair
+{
+	Pair	*next;
+
+	char	key[64];
+	char	val[256];
+	char	*att;
+};
+
+int trusted;
+
+char remote[128];
+char method[64];
+char location[1024];
+
+Pair *header;
+int naheader;
+Pair aheader[64];
+
+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
+dispatchrule(char *cmd)
+{
+	if(rfork(RFPROC|RFNOWAIT|RFFDG|RFREND) == 0){
+		execl("/bin/rc", "rc", "-c", cmd, nil);
+		exits("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 strncpy(d, s, n-1);
+}
+
+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 && (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]);
+	}
+	isdir = d && (d->qid.type & QTDIR);
+	if(isdir || cistrstr(path, ".htm"))
+		print("Content-Type: text/html; charset=utf-8\r\n");
+	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 ? "http://" : "", host ? host : "", 
+		path ? path : "/", name ? name : "",
+		query ? "?" : "", query ? query : "");
+	return buf;
+}
+
+void
+respond(char *status)
+{
+	syslog(0, "tcp80x", "%s %s %s %s", remote, method, location, status);
+	print("HTTP/1.1 %s\r\n", status);
+}
+
+int
+dispatch(void)
+{
+	static char buf[8192], tmp[1024];
+	char *p, *s, *status;
+	int i, n, fd, badmeth, nobody, noindex, noslash;
+	Pair *h;
+	Dir *d;
+
+	nobody = !cistrcmp(method, "HEAD");
+	badmeth = !nobody && cistrcmp(method, "GET");
+	if(badmeth){
+		werrstr("%s method unsupported", method);
+		status = "405 Method Not Allowed";
+Error:
+		if(!nobody)
+			n = snprint(buf, sizeof(buf), 
+			"<html><head><title>%s</title></head>\n"
+			"<body><h1>%s</h1><pre>%r</pre></body></html>\n",
+			status, status);
+		else
+			n = 0;
+		respond(status);
+		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 Forbidden";
+			goto Error;
+		}
+		status = "404 Not found";
+		goto Error;
+	}
+
+	if((d = dirfstat(fd)) == nil){
+		close(fd);
+		status = "500 Internal Server Error";
+		goto Error;
+	}
+
+	if(d->qid.type & QTDIR){
+		int fd2;
+		Dir *d2;
+
+		if(noslash){
+			status = "301 Moved Permanently";
+			respond(status);
+			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",
+				status, status, 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);
+			}
+		}
+
+		respond("200 OK");
+		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){
+					respond("304 Not Modified");
+					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;
+			respond("206 Partial content");
+			print("Content-Range: bytes %lld-%lld/%lld\r\n",
+				start, end-1, d->length);
+			goto Content;
+		}
+		start = 0;
+		end = d->length;
+		respond("200 OK");
+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];
+	char *r, *c;
+	int n;
+
+	r = nil;
+	ARGBEGIN {
+	case 'r':
+		r = ARGF();
+		break;
+	case 't':
+		trusted++;
+		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)
+		strcpy(remote, "-");
+
+	if(parsequery() < 0){
+		respond("400 Bad Request");
+		return;
+	}
+
+	if(r){
+		c = findrule(r, location);
+		if(c){
+			if(cistrcmp(method, "GET") != 0){
+				respond("405 Method Not Allowed");
+				return;
+			}
+			dispatchrule(c);
+			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;
+}