ref: 594e3fdedb7a4d4fc78641199d764cefd4b43a2e
dir: /tcp80.c/
#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;
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];
Pair *header;
int naheader;
Pair aheader[64];
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",
};
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){
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 ? "http://" : "", 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
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], line[1024];
char *p, *e, *k, *x, *s, *r, *c;
int lineno, n;
Pair *h;
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;
}
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;
k = token(line, ":", &s);
while(*s){
if(naheader >= nelem(aheader))
return;
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;
if(dispatch())
return;
h = nil;
while(h = findhdr(h, "Connection"))
if(cistrcmp(h->val, "Keep-Alive") == 0)
break;
if(h == nil)
return;
method[0] = 0;
naheader = 0;
header = nil;
lineno = 0;
}
}
e = buf + sizeof(buf);
}
}