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