ref: db15b1a63659f3f731f15bbabce7d4257e98ff3d
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; 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; }