summaryrefslogtreecommitdiff
path: root/sys/src/cmd/ip/httpd
diff options
context:
space:
mode:
authorTaru Karttunen <taruti@taruti.net>2011-03-30 15:46:40 +0300
committerTaru Karttunen <taruti@taruti.net>2011-03-30 15:46:40 +0300
commite5888a1ffdae813d7575f5fb02275c6bb07e5199 (patch)
treed8d51eac403f07814b9e936eed0c9a79195e2450 /sys/src/cmd/ip/httpd
Import sources from 2011-03-30 iso image
Diffstat (limited to 'sys/src/cmd/ip/httpd')
-rwxr-xr-xsys/src/cmd/ip/httpd/anonymous.c14
-rwxr-xr-xsys/src/cmd/ip/httpd/authorize.c116
-rwxr-xr-xsys/src/cmd/ip/httpd/classify.c427
-rwxr-xr-xsys/src/cmd/ip/httpd/content.c172
-rwxr-xr-xsys/src/cmd/ip/httpd/emem.c25
-rwxr-xr-xsys/src/cmd/ip/httpd/hints.c297
-rwxr-xr-xsys/src/cmd/ip/httpd/httpd.c609
-rwxr-xr-xsys/src/cmd/ip/httpd/httpsrv.h79
-rwxr-xr-xsys/src/cmd/ip/httpd/imagemap.c320
-rwxr-xr-xsys/src/cmd/ip/httpd/init.c113
-rwxr-xr-xsys/src/cmd/ip/httpd/log.c104
-rwxr-xr-xsys/src/cmd/ip/httpd/man2html.c449
-rwxr-xr-xsys/src/cmd/ip/httpd/mkfile79
-rwxr-xr-xsys/src/cmd/ip/httpd/netlib_find.c278
-rwxr-xr-xsys/src/cmd/ip/httpd/netlib_history.c218
-rwxr-xr-xsys/src/cmd/ip/httpd/redirect.c227
-rwxr-xr-xsys/src/cmd/ip/httpd/save.c154
-rwxr-xr-xsys/src/cmd/ip/httpd/sendfd.c462
-rwxr-xr-xsys/src/cmd/ip/httpd/webls.c335
-rwxr-xr-xsys/src/cmd/ip/httpd/webls.denied1
-rwxr-xr-xsys/src/cmd/ip/httpd/wikipost.c305
21 files changed, 4784 insertions, 0 deletions
diff --git a/sys/src/cmd/ip/httpd/anonymous.c b/sys/src/cmd/ip/httpd/anonymous.c
new file mode 100755
index 000000000..1213cf4ce
--- /dev/null
+++ b/sys/src/cmd/ip/httpd/anonymous.c
@@ -0,0 +1,14 @@
+#include <u.h>
+#include <libc.h>
+#include "httpd.h"
+#include "httpsrv.h"
+
+void
+anonymous(HConnect *c)
+{
+ if(bind(webroot, "/", MREPL) < 0){
+ hfail(c, HInternal);
+ exits(nil);
+ }
+ chdir("/");
+}
diff --git a/sys/src/cmd/ip/httpd/authorize.c b/sys/src/cmd/ip/httpd/authorize.c
new file mode 100755
index 000000000..031945434
--- /dev/null
+++ b/sys/src/cmd/ip/httpd/authorize.c
@@ -0,0 +1,116 @@
+#include <u.h>
+#include <libc.h>
+#include <auth.h>
+#include "httpd.h"
+#include "httpsrv.h"
+
+static char* readfile(char*);
+
+/*
+ * these should be done better; see the response codes in /lib/rfc/rfc2616 for
+ * more info on what should be included.
+ */
+#define UNAUTHED "You are not authorized to see this area.\n"
+
+/*
+ * check for authorization for some parts of the server tree.
+ * the user name supplied with the authorization request is ignored;
+ * instead, we authenticate as the realm's user.
+ *
+ * authorization should be done before opening any files so that
+ * unauthorized users don't get to validate file names.
+ *
+ * returns 1 if authorized, 0 if unauthorized, -1 for io failure.
+ */
+int
+authorize(HConnect *c, char *file)
+{
+ char *p, *p0;
+ Hio *hout;
+ char *buf;
+ int i, n;
+ char *t[257];
+
+ p0 = halloc(c, strlen(file)+STRLEN("/.httplogin")+1);
+ strcpy(p0, file);
+ for(;;){
+ p = strrchr(p0, '/');
+ if(p == nil)
+ return hfail(c, HInternal);
+ if(*(p+1) != 0)
+ break;
+
+ /* ignore trailing '/'s */
+ *p = 0;
+ }
+ strcpy(p, "/.httplogin");
+
+ buf = readfile(p0);
+ if(buf == nil){
+ return 1;
+ }
+ n = tokenize(buf, t, nelem(t));
+
+ if(c->head.authuser != nil && c->head.authpass != 0){
+ for(i = 1; i+1 < n; i += 2){
+ if(strcmp(t[i], c->head.authuser) == 0
+ && strcmp(t[i+1], c->head.authpass) == 0){
+ free(buf);
+ return 1;
+ }
+ }
+ }
+
+ hout = &c->hout;
+ hprint(hout, "%s 401 Unauthorized\r\n", hversion);
+ hprint(hout, "Server: Plan9\r\n");
+ hprint(hout, "Date: %D\r\n", time(nil));
+ hprint(hout, "WWW-Authenticate: Basic realm=\"%s\"\r\n", t[0]);
+ hprint(hout, "Content-Type: text/html\r\n");
+ hprint(hout, "Content-Length: %d\r\n", STRLEN(UNAUTHED));
+ if(c->head.closeit)
+ hprint(hout, "Connection: close\r\n");
+ else if(!http11(c))
+ hprint(hout, "Connection: Keep-Alive\r\n");
+ hprint(hout, "\r\n");
+ if(strcmp(c->req.meth, "HEAD") != 0)
+ hprint(hout, "%s", UNAUTHED);
+ writelog(c, "Reply: 401 Unauthorized\n");
+ free(buf);
+ return hflush(hout);
+}
+
+static char*
+readfile(char *file)
+{
+ Dir *d;
+ int fd;
+ char *buf;
+ int n, len;
+
+ fd = open(file, OREAD);
+ if(fd < 0)
+ return nil;
+ d = dirfstat(fd);
+ if(d == nil){ /* shouldn't happen */
+ close(fd);
+ return nil;
+ }
+ len = d->length;
+ free(d);
+
+ buf = malloc(len+1);
+ if(buf == 0){
+ close(fd);
+ return nil;
+ }
+
+ n = readn(fd, buf, len);
+ close(fd);
+ if(n <= 0){
+ free(buf);
+ return nil;
+ }
+ buf[n] = '\0';
+ return buf;
+}
diff --git a/sys/src/cmd/ip/httpd/classify.c b/sys/src/cmd/ip/httpd/classify.c
new file mode 100755
index 000000000..147d1c285
--- /dev/null
+++ b/sys/src/cmd/ip/httpd/classify.c
@@ -0,0 +1,427 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <ndb.h>
+#include "whois.h"
+
+typedef struct Country Country;
+
+struct Country
+{
+ char *code;
+ char *name;
+};
+
+Country badc[] =
+{
+ {"af", "afghanistan"},
+ {"cu", "cuba"},
+ {"ir", "iran"},
+ {"iq", "iraq"},
+ {"ly", "libya"},
+ {"kp", "north korea"},
+ {"sd", "sudan"},
+ {"sy", "syria"},
+ { 0, 0 }
+};
+
+Country goodc[] =
+{
+ // the original, us and canada
+ {"us", "united states of america"},
+ {"ca", "canada"},
+ {"gov", "gov"},
+ {"mil", "mil"},
+
+ // the european union
+ { "eu", "european union" },
+ { "be", "belgium" },
+ { "de", "germany" },
+ { "fr", "france" },
+ { "it", "italy" },
+ { "lu", "luxembourg" },
+ { "nl", "netherlands" },
+ { "dk", "denmark" },
+ { "ie", "ireland" },
+ { "gb", "great britain" },
+ { "uk", "united kingdom" },
+ { "gr", "greece" },
+ { "es", "spain" },
+ { "pt", "portugal" },
+ { "au", "australia" },
+ { "fi", "finland" },
+ { "se", "sweden" },
+
+ // the rest
+ {"au", "australia"},
+ {"no", "norway"},
+ {"cz", "czech republic"},
+ {"hu", "hungary"},
+ {"pl", "poland"},
+ {"jp", "japan"},
+ {"ch", "switzerland"},
+ {"nz", "new zealand"},
+ { 0, 0 }
+};
+
+char *gov[] =
+{
+ "gov",
+ "gouv",
+ "mil",
+ "government",
+ 0,
+};
+
+Country allc[] =
+{
+ { "ad", "andorra" },
+ { "ae", "united arab emirates" },
+ { "af", "afghanistan" },
+ { "ag", "antigua and barbuda" },
+ { "ai", "anguilla" },
+ { "al", "albania" },
+ { "am", "armenia" },
+ { "an", "netherlands antilles" },
+ { "ao", "angola" },
+ { "aq", "antarctica" },
+ { "ar", "argentina" },
+ { "as", "american samoa" },
+ { "at", "austria" },
+ { "au", "australia" },
+ { "aw", "aruba" },
+ { "az", "azerbaijan" },
+ { "ba", "bosnia and herzegovina" },
+ { "bb", "barbados" },
+ { "bd", "bangladesh" },
+ { "be", "belgium" },
+ { "bf", "burkina faso" },
+ { "bg", "bulgaria" },
+ { "bh", "bahrain" },
+ { "bi", "burundi" },
+ { "bj", "benin" },
+ { "bm", "bermuda" },
+ { "bn", "brunei darussalam" },
+ { "bo", "bolivia" },
+ { "br", "brazil" },
+ { "bs", "bahamas" },
+ { "bt", "bhutan" },
+ { "bu", "burma" },
+ { "bv", "bouvet island" },
+ { "bw", "botswana" },
+ { "by", "belarus" },
+ { "bz", "belize" },
+ { "ca", "canada" },
+ { "cc", "cocos (keeling) islands" },
+ { "cf", "central african republic" },
+ { "cg", "congo" },
+ { "ch", "switzerland" },
+ { "ci", "cote d'ivoire (ivory coast)" },
+ { "ck", "cook islands" },
+ { "cl", "chile" },
+ { "cm", "cameroon" },
+ { "cn", "china" },
+ { "co", "colombia" },
+ { "cr", "costa rica" },
+ { "cs", "czechoslovakia (former)" },
+ { "ct", "canton and enderbury island" },
+ { "cu", "cuba" },
+ { "cv", "cape verde" },
+ { "cx", "christmas island" },
+ { "cy", "cyprus" },
+ { "cz", "czech republic" },
+ { "dd", "german democratic republic" },
+ { "de", "germany" },
+ { "dj", "djibouti" },
+ { "dk", "denmark" },
+ { "dm", "dominica" },
+ { "do", "dominican republic" },
+ { "dz", "algeria" },
+ { "ec", "ecuador" },
+ { "ee", "estonia" },
+ { "eg", "egypt" },
+ { "eh", "western sahara" },
+ { "er", "eritrea" },
+ { "es", "spain" },
+ { "et", "ethiopia" },
+ { "eu", "european union" },
+ { "fi", "finland" },
+ { "fj", "fiji" },
+ { "fk", "falkland islands (malvinas)" },
+ { "fm", "micronesia" },
+ { "fo", "faroe islands" },
+ { "fr", "france" },
+ { "fx", "france, metropolitan" },
+ { "ga", "gabon" },
+ { "gb", "great britain (uk)" },
+ { "gd", "grenada" },
+ { "ge", "georgia" },
+ { "gf", "french guiana" },
+ { "gh", "ghana" },
+ { "gi", "gibraltar" },
+ { "gl", "greenland" },
+ { "gm", "gambia" },
+ { "gn", "guinea" },
+ { "gp", "guadeloupe" },
+ { "gq", "equatorial guinea" },
+ { "gr", "greece" },
+ { "gs", "s. georgia and s. sandwich isls." },
+ { "gt", "guatemala" },
+ { "gu", "guam" },
+ { "gw", "guinea-bissau" },
+ { "gy", "guyana" },
+ { "hk", "hong kong" },
+ { "hm", "heard and mcdonald islands" },
+ { "hn", "honduras" },
+ { "hr", "croatia (hrvatska)" },
+ { "ht", "haiti" },
+ { "hu", "hungary" },
+ { "id", "indonesia" },
+ { "ie", "ireland" },
+ { "il", "israel" },
+ { "in", "india" },
+ { "io", "british indian ocean territory" },
+ { "iq", "iraq" },
+ { "ir", "iran" },
+ { "is", "iceland" },
+ { "it", "italy" },
+ { "jm", "jamaica" },
+ { "jo", "jordan" },
+ { "jp", "japan" },
+ { "jt", "johnston island" },
+ { "ke", "kenya" },
+ { "kg", "kyrgyzstan" },
+ { "kh", "cambodia (democratic kampuchea)" },
+ { "ki", "kiribati" },
+ { "km", "comoros" },
+ { "kn", "saint kitts and nevis" },
+ { "kp", "korea (north)" },
+ { "kr", "korea (south)" },
+ { "kw", "kuwait" },
+ { "ky", "cayman islands" },
+ { "kz", "kazakhstan" },
+ { "la", "laos" },
+ { "lb", "lebanon" },
+ { "lc", "saint lucia" },
+ { "li", "liechtenstein" },
+ { "lk", "sri lanka" },
+ { "lr", "liberia" },
+ { "ls", "lesotho" },
+ { "lt", "lithuania" },
+ { "lu", "luxembourg" },
+ { "lv", "latvia" },
+ { "ly", "libya" },
+ { "ma", "morocco" },
+ { "mc", "monaco" },
+ { "md", "moldova" },
+ { "mg", "madagascar" },
+ { "mh", "marshall islands" },
+ { "mi", "midway islands" },
+ { "mk", "macedonia" },
+ { "ml", "mali" },
+ { "mm", "myanmar" },
+ { "mn", "mongolia" },
+ { "mo", "macau" },
+ { "mp", "northern mariana islands" },
+ { "mq", "martinique" },
+ { "mr", "mauritania" },
+ { "ms", "montserrat" },
+ { "mt", "malta" },
+ { "mu", "mauritius" },
+ { "mv", "maldives" },
+ { "mw", "malawi" },
+ { "mx", "mexico" },
+ { "my", "malaysia" },
+ { "mz", "mozambique" },
+ { "na", "namibia" },
+ { "nc", "new caledonia" },
+ { "ne", "niger" },
+ { "nf", "norfolk island" },
+ { "ng", "nigeria" },
+ { "ni", "nicaragua" },
+ { "nl", "netherlands" },
+ { "no", "norway" },
+ { "np", "nepal" },
+ { "nq", "dronning maud land" },
+ { "nr", "nauru" },
+ { "nt", "neutral zone" },
+ { "nu", "niue" },
+ { "nz", "new zealand (aotearoa)" },
+ { "om", "oman" },
+ { "pa", "panama" },
+ { "pc", "pacific islands" },
+ { "pe", "peru" },
+ { "pf", "french polynesia" },
+ { "pg", "papua new guinea" },
+ { "ph", "philippines" },
+ { "pk", "pakistan" },
+ { "pl", "poland" },
+ { "pm", "st. pierre and miquelon" },
+ { "pn", "pitcairn" },
+ { "pr", "puerto rico" },
+ { "pu", "united states misc. pacific islands" },
+ { "pt", "portugal" },
+ { "pw", "palau" },
+ { "py", "paraguay" },
+ { "qa", "qatar" },
+ { "re", "reunion" },
+ { "ro", "romania" },
+ { "ru", "russian federation" },
+ { "rw", "rwanda" },
+ { "sa", "saudi arabia" },
+ { "sb", "solomon islands" },
+ { "sc", "seychelles" },
+ { "sd", "sudan" },
+ { "se", "sweden" },
+ { "sg", "singapore" },
+ { "sh", "st. helena" },
+ { "si", "slovenia" },
+ { "sj", "svalbard and jan mayen islands" },
+ { "sk", "slovak republic" },
+ { "sl", "sierra leone" },
+ { "sm", "san marino" },
+ { "sn", "senegal" },
+ { "so", "somalia" },
+ { "sr", "suriname" },
+ { "st", "sao tome and principe" },
+ { "su", "ussr (former)" },
+ { "sv", "el salvador" },
+ { "sy", "syria" },
+ { "sz", "swaziland" },
+ { "tc", "turks and caicos islands" },
+ { "td", "chad" },
+ { "tf", "french southern territories" },
+ { "tg", "togo" },
+ { "th", "thailand" },
+ { "tj", "tajikistan" },
+ { "tk", "tokelau" },
+ { "tm", "turkmenistan" },
+ { "tn", "tunisia" },
+ { "to", "tonga" },
+ { "tp", "east timor" },
+ { "tr", "turkey" },
+ { "tt", "trinidad and tobago" },
+ { "tv", "tuvalu" },
+ { "tw", "taiwan" },
+ { "tz", "tanzania" },
+ { "ua", "ukraine" },
+ { "ug", "uganda" },
+ { "uk", "united kingdom" },
+ { "um", "us minor outlying islands" },
+ { "us", "united states" },
+ { "uy", "uruguay" },
+ { "uz", "uzbekistan" },
+ { "va", "vatican city state (holy see)" },
+ { "vc", "saint vincent and the grenadines" },
+ { "ve", "venezuela" },
+ { "vg", "virgin islands (british)" },
+ { "vi", "virgin islands (u.s.)" },
+ { "vn", "viet nam" },
+ { "vu", "vanuatu" },
+ { "wf", "wallis and futuna islands" },
+ { "wk", "wake island" },
+ { "ws", "samoa" },
+ { "yd", "democratic yemen" },
+ { "ye", "yemen" },
+ { "yt", "mayotte" },
+ { "yu", "yugoslavia" },
+ { "za", "south africa" },
+ { "zm", "zambia" },
+ { "zr", "zaire" },
+ { "zw", "zimbabwe" },
+
+ {"gov", "gov"},
+ {"mil", "mil"},
+
+ { 0, 0 }
+};
+
+int classdebug;
+
+static int
+incountries(char *s, Country *cp)
+{
+ for(; cp->code != 0; cp++)
+ if(cistrcmp(s, cp->code) == 0
+ || cistrcmp(s, cp->name) == 0)
+ return 1;
+ return 0;
+}
+
+static int
+indomains(char *s, char **dp)
+{
+ for(; *dp != nil; dp++)
+ if(cistrcmp(s, *dp) == 0)
+ return 1;
+
+ return 0;
+}
+
+int
+classify(char *ip, Ndbtuple *t)
+{
+ int isgov, iscountry, isbadc, isgoodc;
+ char dom[256];
+ char *df[128];
+ Ndbtuple *nt, *x;
+ int n;
+
+ isgov = iscountry = isbadc = 0;
+ isgoodc = 1;
+
+ for(nt = t; nt != nil; nt = nt->entry){
+ if(strcmp(nt->attr, "country") == 0){
+ iscountry = 1;
+ if(incountries(nt->val, badc)){
+ if(classdebug)fprint(2, "isbadc\n");
+ isbadc = 1;
+ isgoodc = 0;
+ } else if(!incountries(nt->val, goodc)){
+ if(classdebug)fprint(2, "!isgoodc\n");
+ isgoodc = 0;
+ }
+ }
+
+ /* domain names can always hurt, even without forward verification */
+ if(strcmp(nt->attr, "dom") == 0){
+ strncpy(dom, nt->val, sizeof dom);
+ dom[sizeof(dom)-1] = 0;
+ n = getfields(dom, df, nelem(df), 0, ".");
+
+ /* a bad country in a domain name is always believed */
+ if(incountries(df[n-1], badc)){
+ if(classdebug)fprint(2, "isbadc dom\n");
+ isbadc = 1;
+ isgoodc = 0;
+ }
+
+ /* a goverment in a domain name is always believed */
+ if(n > 1 && indomains(df[n-2], gov))
+ isgov = 1;
+ }
+ }
+ if(iscountry == 0){
+ /* did the forward lookup work? */
+ for(nt = t; nt != nil; nt = nt->entry){
+ if(strcmp(nt->attr, "ip") == 0 && strcmp(nt->val, ip) == 0)
+ break;
+ }
+
+ /* see if the domain name ends in a country code */
+ if(nt != nil && (x = ndbfindattr(t, nt, "dom")) != nil){
+ strncpy(dom, x->val, sizeof dom);
+ dom[sizeof(dom)-1] = 0;
+ n = getfields(dom, df, nelem(df), 0, ".");
+ if(incountries(df[n-1], allc))
+ iscountry = 1;
+ }
+ }
+ if(iscountry == 0)
+ return Cunknown;
+ if(isbadc)
+ return Cbadc;
+ if(!isgoodc && isgov)
+ return Cbadgov;
+ return Cok;
+}
diff --git a/sys/src/cmd/ip/httpd/content.c b/sys/src/cmd/ip/httpd/content.c
new file mode 100755
index 000000000..3f9d44acf
--- /dev/null
+++ b/sys/src/cmd/ip/httpd/content.c
@@ -0,0 +1,172 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include "httpd.h"
+#include "httpsrv.h"
+
+typedef struct Suffix Suffix;
+struct Suffix
+{
+ Suffix *next;
+ char *suffix;
+ char *generic;
+ char *specific;
+ char *encoding;
+};
+
+Suffix *suffixes = nil;
+
+static Suffix* parsesuffix(char*, Suffix*);
+static char* skipwhite(char*);
+static HContents suffixclass(char*);
+static char* towhite(char*);
+
+int
+updateQid(int fd, Qid *q)
+{
+ Dir *dir;
+ Qid dq;
+
+ dir = dirfstat(fd);
+ if(dir == nil)
+ sysfatal("can't dirfstat");
+ dq = dir->qid;
+ free(dir);
+ if(q->path == dq.path && q->vers == dq.vers && q->type == dq.type)
+ return 0;
+ *q = dq;
+ return 1;
+}
+
+void
+contentinit(void)
+{
+ static Biobuf *b = nil;
+ static Qid qid;
+ char *file, *s;
+ Suffix *this;
+
+ file = "/sys/lib/mimetype";
+ if(b == nil){ /* first time */
+ b = Bopen(file, OREAD);
+ if(b == nil)
+ sysfatal("can't read from %s", file);
+ }
+ if(updateQid(Bfildes(b), &qid) == 0)
+ return;
+ Bseek(b, 0, 0);
+ while(suffixes!=nil){
+ this = suffixes;
+ suffixes = suffixes->next;
+ free(this->suffix);
+ free(this->generic);
+ free(this->specific);
+ free(this->encoding);
+ free(this);
+ }
+
+ while((s = Brdline(b, '\n')) != nil){
+ s[Blinelen(b) - 1] = 0;
+ suffixes = parsesuffix(s, suffixes);
+ }
+}
+
+static Suffix*
+parsesuffix(char *line, Suffix *suffix)
+{
+ Suffix *s;
+ char *p, *fields[5];
+ int i, nf;
+
+ p = strchr(line, '#');
+ if(p != nil)
+ *p = '\0';
+ nf = tokenize(line, fields, 5);
+ for(i = 0; i < 4; i++)
+ if(i >= nf || fields[i][0] == '-')
+ fields[i] = nil;
+
+ if(fields[2] == nil)
+ fields[1] = nil;
+ if(fields[1] == nil && fields[3] == nil)
+ return suffix;
+ if(fields[0] == nil)
+ return suffix;
+
+ s = ezalloc(sizeof *s);
+ s->next = suffix;
+ s->suffix = estrdup(fields[0]);
+ if(fields[1] != nil){
+ s->generic = estrdup(fields[1]);
+ s->specific = estrdup(fields[2]);
+ }
+ if(fields[3] != nil)
+ s->encoding = estrdup(fields[3]);
+ return s;
+}
+
+/*
+ * classify by file name extensions
+ */
+HContents
+uriclass(HConnect *hc, char *name)
+{
+ HContents conts;
+ Suffix *s;
+ HContent *type, *enc;
+ char *buf, *p;
+
+ type = nil;
+ enc = nil;
+ if((p = strrchr(name, '/')) != nil)
+ name = p + 1;
+ buf = hstrdup(hc, name);
+ while((p = strrchr(buf, '.')) != nil){
+ for(s = suffixes; s; s = s->next){
+ if(strcmp(p, s->suffix) == 0){
+ if(s->generic != nil && type == nil)
+ type = hmkcontent(hc, s->generic, s->specific, nil);
+ if(s->encoding != nil && enc == nil)
+ enc = hmkcontent(hc, s->encoding, nil, nil);
+ }
+ }
+ *p = 0;
+ }
+ conts.type = type;
+ conts.encoding = enc;
+ return conts;
+}
+
+/*
+ * classify by initial contents of file
+ */
+HContents
+dataclass(HConnect *hc, char *buf, int n)
+{
+ HContents conts;
+ Rune r;
+ int c, m;
+
+ for(; n > 0; n -= m){
+ c = *buf;
+ if(c < Runeself){
+ if(c < 32 && c != '\n' && c != '\r' && c != '\t' && c != '\v'){
+ conts.type = nil;
+ conts.encoding = nil;
+ return conts;
+ }
+ m = 1;
+ }else{
+ m = chartorune(&r, buf);
+ if(r == Runeerror){
+ conts.type = nil;
+ conts.encoding = nil;
+ return conts;
+ }
+ }
+ buf += m;
+ }
+ conts.type = hmkcontent(hc, "text", "plain", nil);
+ conts.encoding = nil;
+ return conts;
+}
diff --git a/sys/src/cmd/ip/httpd/emem.c b/sys/src/cmd/ip/httpd/emem.c
new file mode 100755
index 000000000..47e1e096b
--- /dev/null
+++ b/sys/src/cmd/ip/httpd/emem.c
@@ -0,0 +1,25 @@
+#include <u.h>
+#include <libc.h>
+#include "httpd.h"
+
+void*
+ezalloc(ulong n)
+{
+ void *p;
+
+ p = malloc(n);
+ if(p == nil)
+ sysfatal("out of memory");
+ memset(p, 0, n);
+ return p;
+}
+
+char*
+estrdup(char *s)
+{
+ s = strdup(s);
+ if(s == nil)
+ sysfatal("out of memory");
+ return s;
+}
+
diff --git a/sys/src/cmd/ip/httpd/hints.c b/sys/src/cmd/ip/httpd/hints.c
new file mode 100755
index 000000000..27dd51043
--- /dev/null
+++ b/sys/src/cmd/ip/httpd/hints.c
@@ -0,0 +1,297 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include "httpd.h"
+#include "httpsrv.h"
+
+enum{ URLmax = 65536, HINTmax = 20 };
+#define RECIPLOG2 1.44269504089
+
+char **urlname; /* array of url strings 1,...,nurl */
+static int nurl;
+static uint urltab[URLmax]; /* hashstr(url) 1,...,nurl */
+static int urlnext[URLmax]; /* index urltab of next url in chain */
+static int urlhash[URLmax]; /* initially 0, meaning empty buckets */
+
+typedef struct Hint {
+ ushort url;
+ uchar prob;
+} Hint;
+Hint *hints[URLmax];
+uchar nhint[URLmax];
+
+vlong
+Bfilelen(void *vb)
+{
+ Biobuf *b;
+ vlong n;
+
+ b = vb;
+ n = Bseek(b, 0L, 2);
+ Bseek(b, 0L, 0);
+ return n;
+}
+
+static uint
+hashstr(char* key)
+{
+ /* asu works better than pjw for urls */
+ uchar *k = (unsigned char*)key;
+ uint h = 0;
+ while(*k!=0)
+ h = 65599*h + *k++;
+ return h;
+}
+
+static int
+urllookup(uint url)
+{
+ /* returns +index into urltab, else -hash */
+ int j, hash;
+
+ hash = 1 + url%(URLmax-1);
+ j = urlhash[hash];
+ for(;;){
+ if(j==0)
+ return -hash;
+ if(url==urltab[j])
+ return j;
+ j = urlnext[j];
+ }
+}
+
+int
+Bage(Biobuf *b)
+{
+ Dir *dir;
+ long mtime;
+
+ dir = dirfstat(Bfildes(b));
+ if(dir != nil)
+ mtime = dir->mtime;
+ else
+ mtime = 0;
+ free(dir);
+ return time(nil) - mtime;
+}
+
+void
+urlinit(void)
+{
+ static Biobuf *b = nil;
+ static vlong filelen = 0;
+ vlong newlen;
+ char *s, *arena;
+ int i, j, n;
+ uint url;
+ char *file;
+
+ if(filelen < 0)
+ return;
+ file = "/sys/log/httpd/url";
+ if(b == nil){
+ b = Bopen(file, OREAD); /* first time */
+ if(b == nil){
+ syslog(0, HTTPLOG, "no %s, abandon prefetch hints", file);
+ filelen = -1;
+ return;
+ }
+ }
+ newlen = Bfilelen(b); /* side effect: rewinds b */
+ if(newlen == filelen || Bage(b)<300)
+ return;
+ filelen = newlen;
+ if(filelen < 0)
+ return;
+ if(nurl){ /* free existing tables */
+ free(urlname[0]); /* arena */
+ memset(urlhash,0,sizeof urlhash);
+ memset(urlnext,0,sizeof urlnext);
+ nurl = 0;
+ }
+ if(urlname==nil)
+ urlname = (char**)ezalloc(URLmax*sizeof(*urlname));
+ arena = (char*)ezalloc(filelen); /* enough for all the strcpy below */
+ i = 1;
+ while((s=Brdline(b,'\n'))!=0){
+ /* read lines of the form: 999 /url/path */
+ n = Blinelen(b) - 1;
+ if(n>2 && s[n]=='\n'){
+ s[n] = '\0';
+ }else{
+ sysfatal("missing fields or newline in url-db");
+ }
+ j = strtoul(s,&s,10);
+ while(*s==' ')
+ s++;
+ if(i++!=j)
+ sysfatal("url-db synchronization error");
+ url = hashstr(s);
+ j = urllookup(url);
+ if(j>=0)
+ sysfatal("duplicate url");
+ j = -j;
+ nurl++;
+ if(nurl>=URLmax){
+ syslog(0, HTTPLOG, "urlinit overflow at %s",s);
+ free(urlname[0]); /* arena */
+ memset(urlhash,0,sizeof urlhash);
+ memset(urlnext,0,sizeof urlnext);
+ nurl = 0;
+ return;
+ }
+ urltab[nurl] = url;
+ urlnext[nurl] = urlhash[j];
+ urlhash[j] = nurl;
+ strcpy(arena,s);
+ urlname[nurl] = arena;
+ arena += strlen(s)+1;
+ }
+ syslog(0, HTTPLOG, "prefetch-hints url=%d (%.1fMB)", nurl, 1.e-6*(URLmax*sizeof(*urlname)+filelen));
+ /* b is held open, because namespace will be chopped */
+}
+
+void
+statsinit(void)
+{
+ static Biobuf *b = nil;
+ static vlong filelen = 0;
+ vlong newlen;
+ int iq, n, i, nstats = 0;
+ uchar *s, buf[3+HINTmax*3]; /* iq, n, (url,prob)... */
+ Hint *arena, *h;
+ char *file;
+ static void *oldarena = nil;
+
+ file = "/sys/log/httpd/pathstat";
+ if(b == nil){
+ if(filelen == -1)
+ return; /* if failed first time */
+ b = Bopen(file, OREAD); /* first time */
+ if(b == nil){
+ syslog(0, HTTPLOG, "no %s, abandon prefetch hints", file);
+ filelen = -1;
+ return;
+ }
+ }
+ newlen = Bfilelen(b); /* side effect: rewinds b */
+ if(newlen == filelen || Bage(b)<300)
+ return;
+ filelen = newlen;
+ if(oldarena){
+ free(oldarena);
+ memset(nhint,0,sizeof nhint);
+ }
+ arena = (Hint*)ezalloc((filelen/3)*sizeof(Hint));
+ oldarena = arena;
+ for(;;){
+ i = Bread(b,buf,3);
+ if(i<3)
+ break;
+ nstats++;
+ iq = buf[0];
+ iq = (iq<<8) | buf[1];
+ n = buf[2];
+ h = arena;
+ arena += n;
+ hints[iq] = h;
+ nhint[iq] = n;
+ if(Bread(b,buf,3*n)!=3*n)
+ sysfatal("stats read error");
+ for(i=0; i<n; i++){
+ s = &buf[3*i];
+ h[i].url = (s[0]<<8) | s[1];
+ h[i].prob = s[2];
+ }
+ }
+ syslog(0, HTTPLOG, "prefetch-hints stats=%d (%.1fMB)", nstats, 1.e-6*((filelen/3)*sizeof(Hint)));
+}
+
+void
+urlcanon(char *url)
+{
+ /* all the changes here can be implemented by rewriting in-place */
+ char *p, *q;
+
+ /* remove extraneous '/' in the middle and at the end */
+ p = url+1; /* first char needs no change */
+ q = p;
+ while(q[0]){
+ if(q[0]=='/' && q[-1]=='/'){
+ q++;
+ continue;
+ }
+ *p++ = *q++;
+ }
+ if(q[-1]=='/'){ /* trailing '/' */
+ p[-1] = '\0';
+ }else{
+ p[0] = '\0';
+ }
+
+ /* specific to the cm.bell-labs.com web site */
+ if(strncmp(url,"/cm/",4)==0){
+ if(strchr("cims",url[4]) && strncmp(url+5,"s/who/",6)==0)
+ /* strip off /cm/cs */
+ memmove(url,url+6,strlen(url+6)+1);
+ else if(strncmp(url+4,"ms/what/wavelet",15)==0)
+ /* /cm/ms/what */
+ memmove(url,url+11,strlen(url+11)+1);
+ }
+}
+
+void
+hintprint(HConnect *hc, Hio *hout, char *uri, int thresh, int havej)
+{
+ int i, j, pr, prefix, fd, siz, havei, newhint = 0, n;
+ char *query, *sf, etag[32], *wurl;
+ Dir *dir;
+ Hint *h, *haveh;
+
+ query = hstrdup(hc, uri);
+ urlcanon(query);
+ j = urllookup(hashstr(query));
+ if(j < 0)
+ return;
+ query = strrchr(uri,'/');
+ if(!query)
+ return; /* can't happen */
+ prefix = query-uri+1; /* = strlen(dirname)+1 */
+ h = hints[j];
+ for(i=0; i<nhint[j]; i++){
+ if(havej > 0 && havej < URLmax){ /* exclude hints client has */
+ haveh = hints[havej];
+ for(havei=0; havei<nhint[havej]; havei++)
+ if( haveh[havei].url == h[i].url)
+ goto continuei;
+ }
+ sf = urlname[h[i].url];
+ pr = h[i].prob;
+ if(pr<thresh)
+ break;
+ n = strlen(webroot) + strlen(sf) + 1;
+ wurl = halloc(hc, n);
+ strcpy(wurl, webroot);
+ strcat(wurl, sf);
+ fd = open(wurl, OREAD);
+ if(fd<0)
+ continue;
+ dir = dirfstat(fd);
+ if(dir == nil){
+ close(fd);
+ continue;
+ }
+ close(fd);
+ snprint(etag, sizeof(etag), "\"%lluxv%lux\"", dir->qid.path, dir->qid.vers);
+ siz = (int)( log((double)dir->length) * RECIPLOG2 + 0.9999);
+ free(dir);
+ if(strncmp(uri,sf,prefix)==0 && strchr(sf+prefix,'/')==0 && sf[prefix]!=0)
+ sf = sf+prefix;
+ hprint(hout, "Fresh: %d,%s,%d,%s\r\n", pr, etag, siz, sf);
+ newhint++;
+continuei: ;
+ }
+ if(newhint)
+ hprint(hout, "Fresh: have/%d\r\n", j);
+}
+
diff --git a/sys/src/cmd/ip/httpd/httpd.c b/sys/src/cmd/ip/httpd/httpd.c
new file mode 100755
index 000000000..895bd15e2
--- /dev/null
+++ b/sys/src/cmd/ip/httpd/httpd.c
@@ -0,0 +1,609 @@
+#include <u.h>
+#include <libc.h>
+#include <auth.h>
+#include <mp.h>
+#include <libsec.h>
+#include "httpd.h"
+#include "httpsrv.h"
+
+typedef struct Strings Strings;
+
+struct Strings
+{
+ char *s1;
+ char *s2;
+};
+
+char *netdir;
+char *HTTPLOG = "httpd/log";
+
+static char netdirb[256];
+static char *namespace;
+
+static void becomenone(char*);
+static char *csquery(char*, char*, char*);
+static void dolisten(char*);
+static int doreq(HConnect*);
+static int send(HConnect*);
+static Strings stripmagic(HConnect*, char*);
+static char* stripprefix(char*, char*);
+static char* sysdom(void);
+static int notfound(HConnect *c, char *url);
+
+uchar *certificate;
+int certlen;
+PEMChain *certchain;
+
+void
+usage(void)
+{
+ fprint(2, "usage: httpd [-c certificate] [-C CAchain] [-a srvaddress] "
+ "[-d domain] [-n namespace] [-w webroot]\n");
+ exits("usage");
+}
+
+void
+main(int argc, char **argv)
+{
+ char *address;
+
+ namespace = nil;
+ address = nil;
+ hmydomain = nil;
+ netdir = "/net";
+ fmtinstall('D', hdatefmt);
+ fmtinstall('H', httpfmt);
+ fmtinstall('U', hurlfmt);
+ ARGBEGIN{
+ case 'c':
+ certificate = readcert(EARGF(usage()), &certlen);
+ if(certificate == nil)
+ sysfatal("reading certificate: %r");
+ break;
+ case 'C':
+ certchain = readcertchain(EARGF(usage()));
+ if (certchain == nil)
+ sysfatal("reading certificate chain: %r");
+ break;
+ case 'n':
+ namespace = EARGF(usage());
+ break;
+ case 'a':
+ address = EARGF(usage());
+ break;
+ case 'd':
+ hmydomain = EARGF(usage());
+ break;
+ case 'w':
+ webroot = EARGF(usage());
+ break;
+ default:
+ usage();
+ break;
+ }ARGEND
+
+ if(argc)
+ usage();
+
+ if(namespace == nil)
+ namespace = "/lib/namespace.httpd";
+ if(address == nil)
+ address = "*";
+ if(webroot == nil)
+ webroot = "/usr/web";
+ else{
+ cleanname(webroot);
+ if(webroot[0] != '/')
+ webroot = "/usr/web";
+ }
+
+ switch(rfork(RFNOTEG|RFPROC|RFFDG|RFNAMEG)) {
+ case -1:
+ sysfatal("fork");
+ case 0:
+ break;
+ default:
+ exits(nil);
+ }
+
+ /*
+ * open all files we might need before castrating namespace
+ */
+ time(nil);
+ if(hmydomain == nil)
+ hmydomain = sysdom();
+ syslog(0, HTTPLOG, nil);
+ logall[0] = open("/sys/log/httpd/0", OWRITE);
+ logall[1] = open("/sys/log/httpd/1", OWRITE);
+ logall[2] = open("/sys/log/httpd/clf", OWRITE);
+ redirectinit();
+ contentinit();
+ urlinit();
+ statsinit();
+
+ becomenone(namespace);
+ dolisten(netmkaddr(address, "tcp", certificate == nil ? "http" : "https"));
+ exits(nil);
+}
+
+static void
+becomenone(char *namespace)
+{
+ int fd;
+
+ fd = open("#c/user", OWRITE);
+ if(fd < 0 || write(fd, "none", strlen("none")) < 0)
+ sysfatal("can't become none");
+ close(fd);
+ if(newns("none", nil) < 0)
+ sysfatal("can't build normal namespace");
+ if(addns("none", namespace) < 0)
+ sysfatal("can't build httpd namespace");
+}
+
+static HConnect*
+mkconnect(char *scheme, char *port)
+{
+ HConnect *c;
+
+ c = ezalloc(sizeof(HConnect));
+ c->hpos = c->header;
+ c->hstop = c->header;
+ c->replog = writelog;
+ c->scheme = scheme;
+ c->port = port;
+ return c;
+}
+
+static HSPriv*
+mkhspriv(void)
+{
+ HSPriv *p;
+
+ p = ezalloc(sizeof(HSPriv));
+ return p;
+}
+
+static void
+dolisten(char *address)
+{
+ HSPriv *hp;
+ HConnect *c;
+ NetConnInfo *nci;
+ char ndir[NETPATHLEN], dir[NETPATHLEN], *p, *scheme;
+ int ctl, nctl, data, t, ok, spotchk;
+ TLSconn conn;
+
+ spotchk = 0;
+ syslog(0, HTTPLOG, "httpd starting");
+ ctl = announce(address, dir);
+ if(ctl < 0){
+ syslog(0, HTTPLOG, "can't announce on %s: %r", address);
+ return;
+ }
+ strcpy(netdirb, dir);
+ p = nil;
+ if(netdir[0] == '/'){
+ p = strchr(netdirb+1, '/');
+ if(p != nil)
+ *p = '\0';
+ }
+ if(p == nil)
+ strcpy(netdirb, "/net");
+ netdir = netdirb;
+
+ for(;;){
+
+ /*
+ * wait for a call (or an error)
+ */
+ nctl = listen(dir, ndir);
+ if(nctl < 0){
+ syslog(0, HTTPLOG, "can't listen on %s: %r", address);
+ syslog(0, HTTPLOG, "ctls = %d", ctl);
+ return;
+ }
+
+ /*
+ * start a process for the service
+ */
+ switch(rfork(RFFDG|RFPROC|RFNOWAIT|RFNAMEG)){
+ case -1:
+ close(nctl);
+ continue;
+ case 0:
+ /*
+ * see if we know the service requested
+ */
+ data = accept(ctl, ndir);
+ if(data >= 0 && certificate != nil){
+ memset(&conn, 0, sizeof(conn));
+ conn.cert = certificate;
+ conn.certlen = certlen;
+ if (certchain != nil)
+ conn.chain = certchain;
+ data = tlsServer(data, &conn);
+ scheme = "https";
+ }else
+ scheme = "http";
+ if(data < 0){
+ syslog(0, HTTPLOG, "can't open %s/data: %r", ndir);
+ exits(nil);
+ }
+ dup(data, 0);
+ dup(data, 1);
+ dup(data, 2);
+ close(data);
+ close(ctl);
+ close(nctl);
+
+ nci = getnetconninfo(ndir, -1);
+ c = mkconnect(scheme, nci->lserv);
+ hp = mkhspriv();
+ hp->remotesys = nci->rsys;
+ hp->remoteserv = nci->rserv;
+ c->private = hp;
+
+ hinit(&c->hin, 0, Hread);
+ hinit(&c->hout, 1, Hwrite);
+
+ /*
+ * serve requests until a magic request.
+ * later requests have to come quickly.
+ * only works for http/1.1 or later.
+ */
+ for(t = 15*60*1000; ; t = 15*1000){
+ if(hparsereq(c, t) <= 0)
+ exits(nil);
+ ok = doreq(c);
+
+ hflush(&c->hout);
+
+ if(c->head.closeit || ok < 0)
+ exits(nil);
+
+ hreqcleanup(c);
+ }
+ /* not reached */
+
+ default:
+ close(nctl);
+ break;
+ }
+
+ if(++spotchk > 50){
+ spotchk = 0;
+ redirectinit();
+ contentinit();
+ urlinit();
+ statsinit();
+ }
+ }
+}
+
+static int
+doreq(HConnect *c)
+{
+ HSPriv *hp;
+ Strings ss;
+ char *magic, *uri, *newuri, *origuri, *newpath, *hb;
+ char virtualhost[100], logfd0[10], logfd1[10], vers[16];
+ int n, nredirect;
+ uint flags;
+
+ /*
+ * munge uri for magic
+ */
+ uri = c->req.uri;
+ nredirect = 0;
+ werrstr("");
+top:
+ if(++nredirect > 10){
+ if(hparseheaders(c, 15*60*1000) < 0)
+ exits("failed");
+ werrstr("redirection loop");
+ return hfail(c, HNotFound, uri);
+ }
+ ss = stripmagic(c, uri);
+ uri = ss.s1;
+ origuri = uri;
+ magic = ss.s2;
+ if(magic)
+ goto magic;
+
+ /*
+ * Apply redirects. Do this before reading headers
+ * (if possible) so that we can redirect to magic invisibly.
+ */
+ flags = 0;
+ if(origuri[0]=='/' && origuri[1]=='~'){
+ n = strlen(origuri) + 4 + UTFmax;
+ newpath = halloc(c, n);
+ snprint(newpath, n, "/who/%s", origuri+2);
+ c->req.uri = newpath;
+ newuri = newpath;
+ }else if(origuri[0]=='/' && origuri[1]==0){
+ /* can't redirect / until we read the headers below */
+ newuri = nil;
+ }else
+ newuri = redirect(c, origuri, &flags);
+
+ if(newuri != nil){
+ if(flags & Redirsilent) {
+ c->req.uri = uri = newuri;
+ logit(c, "%s: silent replacement %s", origuri, uri);
+ goto top;
+ }
+ if(hparseheaders(c, 15*60*1000) < 0)
+ exits("failed");
+ if(flags & Redirperm) {
+ logit(c, "%s: permanently moved to %s", origuri, newuri);
+ return hmoved(c, newuri);
+ } else if (flags & (Redironly | Redirsubord))
+ logit(c, "%s: top-level or many-to-one replacement %s",
+ origuri, uri);
+
+ /*
+ * try temporary redirect instead of permanent
+ */
+ if (http11(c))
+ return hredirected(c, "307 Temporary Redirect", newuri);
+ else
+ return hredirected(c, "302 Temporary Redirect", newuri);
+ }
+
+ /*
+ * for magic we exec a new program and serve no more requests
+ */
+magic:
+ if(magic != nil && strcmp(magic, "httpd") != 0){
+ snprint(c->xferbuf, HBufSize, "/bin/ip/httpd/%s", magic);
+ snprint(logfd0, sizeof(logfd0), "%d", logall[0]);
+ snprint(logfd1, sizeof(logfd1), "%d", logall[1]);
+ snprint(vers, sizeof(vers), "HTTP/%d.%d", c->req.vermaj, c->req.vermin);
+ hb = hunload(&c->hin);
+ if(hb == nil){
+ hfail(c, HInternal);
+ return -1;
+ }
+ hp = c->private;
+ execl(c->xferbuf, magic, "-d", hmydomain, "-w", webroot,
+ "-s", c->scheme, "-p", c->port,
+ "-r", hp->remotesys, "-N", netdir, "-b", hb,
+ "-L", logfd0, logfd1, "-R", c->header,
+ c->req.meth, vers, uri, c->req.search, nil);
+ logit(c, "no magic %s uri %s", magic, uri);
+ hfail(c, HNotFound, uri);
+ return -1;
+ }
+
+ /*
+ * normal case is just file transfer
+ */
+ if(hparseheaders(c, 15*60*1000) < 0)
+ exits("failed");
+ if(origuri[0] == '/' && origuri[1] == 0){
+ snprint(virtualhost, sizeof virtualhost, "http://%s/", c->head.host);
+ newuri = redirect(c, virtualhost, nil);
+ if(newuri == nil)
+ newuri = redirect(c, origuri, nil);
+ if(newuri)
+ return hmoved(c, newuri);
+ }
+ if(!http11(c) && !c->head.persist)
+ c->head.closeit = 1;
+ return send(c);
+}
+
+static int
+send(HConnect *c)
+{
+ Dir *dir;
+ char *w, *w2, *p, *masque;
+ int fd, fd1, n, force301, ok;
+
+/*
+ if(c->req.search)
+ return hfail(c, HNoSearch, c->req.uri);
+ */
+ if(strcmp(c->req.meth, "GET") != 0 && strcmp(c->req.meth, "HEAD") != 0)
+ return hunallowed(c, "GET, HEAD");
+ if(c->head.expectother || c->head.expectcont)
+ return hfail(c, HExpectFail);
+
+ masque = masquerade(c->head.host);
+
+ /*
+ * check for directory/file mismatch with trailing /,
+ * and send any redirections.
+ */
+ n = strlen(webroot) + strlen(masque) + strlen(c->req.uri) +
+ STRLEN("/index.html") + STRLEN("/.httplogin") + 1;
+ w = halloc(c, n);
+ strcpy(w, webroot);
+ strcat(w, masque);
+ strcat(w, c->req.uri);
+
+ /*
+ * favicon can be overridden by hostname.ico
+ */
+ if(strcmp(c->req.uri, "/favicon.ico") == 0){
+ w2 = halloc(c, n+strlen(c->head.host)+2);
+ strcpy(w2, webroot);
+ strcat(w2, masque);
+ strcat(w2, "/");
+ strcat(w2, c->head.host);
+ strcat(w2, ".ico");
+ if(access(w2, AREAD)==0)
+ w = w2;
+ }
+
+ /*
+ * don't show the contents of .httplogin
+ */
+ n = strlen(w);
+ if(strcmp(w+n-STRLEN(".httplogin"), ".httplogin") == 0)
+ return notfound(c, c->req.uri);
+
+ fd = open(w, OREAD);
+ if(fd < 0 && strlen(masque)>0 && strncmp(c->req.uri, masque, strlen(masque)) == 0){
+ // may be a URI from before virtual hosts; try again without masque
+ strcpy(w, webroot);
+ strcat(w, c->req.uri);
+ fd = open(w, OREAD);
+ }
+ if(fd < 0)
+ return notfound(c, c->req.uri);
+ dir = dirfstat(fd);
+ if(dir == nil){
+ close(fd);
+ return hfail(c, HInternal);
+ }
+ p = strchr(w, '\0');
+ if(dir->mode & DMDIR){
+ free(dir);
+ if(p > w && p[-1] == '/'){
+ strcat(w, "index.html");
+ force301 = 0;
+ }else{
+ strcat(w, "/index.html");
+ force301 = 1;
+ }
+ fd1 = open(w, OREAD);
+ if(fd1 < 0){
+ close(fd);
+ return notfound(c, c->req.uri);
+ }
+ c->req.uri = w + strlen(webroot) + strlen(masque);
+ if(force301 && c->req.vermaj){
+ close(fd);
+ close(fd1);
+ return hmoved(c, c->req.uri);
+ }
+ close(fd);
+ fd = fd1;
+ dir = dirfstat(fd);
+ if(dir == nil){
+ close(fd);
+ return hfail(c, HInternal);
+ }
+ }else if(p > w && p[-1] == '/'){
+ free(dir);
+ close(fd);
+ *strrchr(c->req.uri, '/') = '\0';
+ return hmoved(c, c->req.uri);
+ }
+
+ ok = authorize(c, w);
+ if(ok <= 0){
+ free(dir);
+ close(fd);
+ return ok;
+ }
+
+ return sendfd(c, fd, dir, nil, nil);
+}
+
+static Strings
+stripmagic(HConnect *hc, char *uri)
+{
+ Strings ss;
+ char *newuri, *prog, *s;
+
+ prog = stripprefix("/magic/", uri);
+ if(prog == nil){
+ ss.s1 = uri;
+ ss.s2 = nil;
+ return ss;
+ }
+
+ s = strchr(prog, '/');
+ if(s == nil)
+ newuri = "";
+ else{
+ newuri = hstrdup(hc, s);
+ *s = 0;
+ s = strrchr(s, '/');
+ if(s != nil && s[1] == 0)
+ *s = 0;
+ }
+ ss.s1 = newuri;
+ ss.s2 = prog;
+ return ss;
+}
+
+static char*
+stripprefix(char *pre, char *str)
+{
+ while(*pre)
+ if(*str++ != *pre++)
+ return nil;
+ return str;
+}
+
+/*
+ * couldn't open a file
+ * figure out why and return and error message
+ */
+static int
+notfound(HConnect *c, char *url)
+{
+ c->xferbuf[0] = 0;
+ rerrstr(c->xferbuf, sizeof c->xferbuf);
+ if(strstr(c->xferbuf, "file does not exist") != nil)
+ return hfail(c, HNotFound, url);
+ if(strstr(c->xferbuf, "permission denied") != nil)
+ return hfail(c, HUnauth, url);
+ return hfail(c, HNotFound, url);
+}
+
+static char*
+sysdom(void)
+{
+ char *dn;
+
+ dn = csquery("sys" , sysname(), "dom");
+ if(dn == nil)
+ dn = "who cares";
+ return dn;
+}
+
+/*
+ * query the connection server
+ */
+static char*
+csquery(char *attr, char *val, char *rattr)
+{
+ char token[64+4];
+ char buf[256], *p, *sp;
+ int fd, n;
+
+ if(val == nil || val[0] == 0)
+ return nil;
+ snprint(buf, sizeof(buf), "%s/cs", netdir);
+ fd = open(buf, ORDWR);
+ if(fd < 0)
+ return nil;
+ fprint(fd, "!%s=%s", attr, val);
+ seek(fd, 0, 0);
+ snprint(token, sizeof(token), "%s=", rattr);
+ for(;;){
+ n = read(fd, buf, sizeof(buf)-1);
+ if(n <= 0)
+ break;
+ buf[n] = 0;
+ p = strstr(buf, token);
+ if(p != nil && (p == buf || *(p-1) == 0)){
+ close(fd);
+ sp = strchr(p, ' ');
+ if(sp)
+ *sp = 0;
+ p = strchr(p, '=');
+ if(p == nil)
+ return nil;
+ return estrdup(p+1);
+ }
+ }
+ close(fd);
+ return nil;
+}
diff --git a/sys/src/cmd/ip/httpd/httpsrv.h b/sys/src/cmd/ip/httpd/httpsrv.h
new file mode 100755
index 000000000..e279f0f88
--- /dev/null
+++ b/sys/src/cmd/ip/httpd/httpsrv.h
@@ -0,0 +1,79 @@
+typedef struct HSPriv HSPriv;
+
+enum
+{
+ HSTIMEOUT = 15 * 60 * 1000,
+
+ /* rewrite replacement field modifiers */
+ Modsilent = '@', /* don't tell the browser about the redirect. */
+ Modperm = '=', /* generate permanent redirection */
+ Modsubord = '*', /* map page & all subordinates to same URL */
+ Modonly = '>', /* match only this page, not subordinates */
+
+ Redirsilent = 1<<0,
+ Redirperm = 1<<1,
+ Redirsubord = 1<<2,
+ Redironly = 1<<3,
+};
+
+struct HSPriv
+{
+ char *remotesys;
+ char *remoteserv;
+};
+
+extern int logall[3];
+extern char* HTTPLOG;
+extern char* webroot;
+extern char* netdir;
+
+#define STRLEN(s) (sizeof(s)-1)
+
+/* emem.c */
+char *estrdup(char*);
+void* ezalloc(ulong);
+
+/* sendfd.c */
+int authcheck(HConnect *c);
+int checkreq(HConnect *c, HContent *type, HContent *enc, long mtime, char *etag);
+int etagmatch(int, HETag*, char*);
+HRange *fixrange(HRange *h, long length);
+int sendfd(HConnect *c, int fd, Dir *dir, HContent *type, HContent *enc);
+
+/* content.c */
+void contentinit(void);
+HContents dataclass(HConnect *, char*, int);
+int updateQid(int, Qid*);
+HContents uriclass(HConnect *, char*);
+
+/* anonymous.c */
+void anonymous(HConnect*);
+
+/* hint.c */
+void hintprint(HConnect *hc, Hio*, char *, int, int);
+void statsinit(void);
+void urlcanon(char *url);
+void urlinit(void);
+
+/* init.c */
+HConnect* init(int, char**);
+
+vlong Bfilelen(void*);
+
+/* redirect.c */
+void redirectinit(void);
+char* redirect(HConnect *hc, char*, uint *);
+char* masquerade(char*);
+char* authrealm(HConnect *hc, char *path);
+char *undecorated(char *repl);
+
+/* log.c */
+void logit(HConnect*, char*, ...);
+#pragma varargck argpos logit 2
+void writelog(HConnect*, char*, ...);
+#pragma varargck argpos writelog 2
+
+/* authorize.c */
+int authorize(HConnect*, char*);
+
+char *webroot;
diff --git a/sys/src/cmd/ip/httpd/imagemap.c b/sys/src/cmd/ip/httpd/imagemap.c
new file mode 100755
index 000000000..9ecf42782
--- /dev/null
+++ b/sys/src/cmd/ip/httpd/imagemap.c
@@ -0,0 +1,320 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include "httpd.h"
+#include "httpsrv.h"
+
+typedef struct Point Point;
+typedef struct OkPoint OkPoint;
+typedef struct Strings Strings;
+
+struct Point
+{
+ int x;
+ int y;
+};
+
+struct OkPoint
+{
+ Point p;
+ int ok;
+};
+
+struct Strings
+{
+ char *s1;
+ char *s2;
+};
+
+static char *me;
+
+int polytest(int, Point, Point, Point);
+Strings getfield(char*);
+OkPoint pt(char*);
+char* translate(HConnect*, char*, char*);
+Point sub(Point, Point);
+float dist(Point, Point);
+
+void
+main(int argc, char **argv)
+{
+ HConnect *c;
+ Hio *hout;
+ char *dest;
+
+ me = "imagemap";
+ c = init(argc, argv);
+ hout = &c->hout;
+ if(hparseheaders(c, HSTIMEOUT) < 0)
+ exits("failed");
+ anonymous(c);
+
+ if(strcmp(c->req.meth, "GET") != 0 && strcmp(c->req.meth, "HEAD") != 0){
+ hunallowed(c, "GET, HEAD");
+ exits("unallowed");
+ }
+ if(c->head.expectother || c->head.expectcont){
+ hfail(c, HExpectFail, nil);
+ exits("failed");
+ }
+ dest = translate(c, c->req.uri, c->req.search);
+
+ if(dest == nil){
+ if(c->req.vermaj){
+ hokheaders(c);
+ hprint(hout, "Content-type: text/html\r\n");
+ hprint(hout, "\r\n");
+ }
+ hprint(hout, "<head><title>Nothing Found</title></head><body>\n");
+ hprint(hout, "Nothing satisfying your search request could be found.\n</body>\n");
+ hflush(hout);
+ writelog(c, "Reply: 200 imagemap %ld %ld\n", hout->seek, hout->seek);
+ exits(nil);
+ }
+
+ if(http11(c) && strcmp(c->req.meth, "POST") == 0)
+ hredirected(c, "303 See Other", dest);
+ else
+ hredirected(c, "302 Found", dest);
+ exits(nil);
+}
+
+char*
+translate(HConnect *c, char *uri, char *search)
+{
+ Biobuf *b;
+ Strings ss;
+ OkPoint okp;
+ Point p, cen, q, start;
+ float close, d;
+ char *line, *to, *def, *s, *dst;
+ int n, inside, r, ncsa;
+
+ if(search == nil){
+ hfail(c, HNoData, me);
+ exits("failed");
+ }
+ okp = pt(search);
+ if(!okp.ok){
+ hfail(c, HBadSearch, me);
+ exits("failed");
+ }
+ p = okp.p;
+
+ b = Bopen(uri, OREAD);
+ if(b == nil){
+ hfail(c, HNotFound, uri);
+ exits("failed");
+ }
+
+ to = nil;
+ def = nil;
+ dst = nil;
+ close = 0.;
+ ncsa = 1;
+ while(line = Brdline(b, '\n')){
+ line[Blinelen(b)-1] = 0;
+
+ ss = getfield(line);
+ s = ss.s1;
+ line = ss.s2;
+ if(ncsa){
+ ss = getfield(line);
+ dst = ss.s1;
+ line = ss.s2;
+ }
+ if(strcmp(s, "#cern") == 0){
+ ncsa = 0;
+ continue;
+ }
+ if(strcmp(s, "rect") == 0){
+ ss = getfield(line);
+ s = ss.s1;
+ line = ss.s2;
+ okp = pt(s);
+ q = okp.p;
+ if(!okp.ok || q.x > p.x || q.y > p.y)
+ continue;
+ ss = getfield(line);
+ s = ss.s1;
+ line = ss.s2;
+ okp = pt(s);
+ q = okp.p;
+ if(!okp.ok || q.x < p.x || q.y < p.y)
+ continue;
+ if(!ncsa){
+ ss = getfield(line);
+ dst = ss.s1;
+ }
+ return dst;
+ }else if(strcmp(s, "circle") == 0){
+ ss = getfield(line);
+ s = ss.s1;
+ line = ss.s2;
+ okp = pt(s);
+ cen = okp.p;
+ if(!okp.ok)
+ continue;
+ ss = getfield(line);
+ s = ss.s1;
+ line = ss.s2;
+ if(ncsa){
+ okp = pt(s);
+ if(!okp.ok)
+ continue;
+ if(dist(okp.p, cen) >= dist(p, cen))
+ return dst;
+ }else{
+ r = strtol(s, nil, 10);
+ ss = getfield(line);
+ dst = ss.s1;
+ d = (float)r * r;
+ if(d >= dist(p, cen))
+ return dst;
+ }
+ }else if(strcmp(s, "poly") == 0){
+ ss = getfield(line);
+ s = ss.s1;
+ line = ss.s2;
+ okp = pt(s);
+ start = okp.p;
+ if(!okp.ok)
+ continue;
+ inside = 0;
+ cen = start;
+ for(n = 1; ; n++){
+ ss = getfield(line);
+ s = ss.s1;
+ line = ss.s2;
+ okp = pt(s);
+ q = okp.p;
+ if(!okp.ok)
+ break;
+ inside = polytest(inside, p, cen, q);
+ cen = q;
+ }
+ inside = polytest(inside, p, cen, start);
+ if(!ncsa)
+ dst = s;
+ if(n >= 3 && inside)
+ return dst;
+ }else if(strcmp(s, "point") == 0){
+ ss = getfield(line);
+ s = ss.s1;
+ line = ss.s2;
+ okp = pt(s);
+ q = okp.p;
+ if(!okp.ok)
+ continue;
+ d = dist(p, q);
+ if(!ncsa){
+ ss = getfield(line);
+ dst = ss.s1;
+ }
+ if(d == 0.)
+ return dst;
+ if(close == 0. || d < close){
+ close = d;
+ to = dst;
+ }
+ }else if(strcmp(s, "default") == 0){
+ if(!ncsa){
+ ss = getfield(line);
+ dst = ss.s1;
+ }
+ def = dst;
+ }
+ }
+ if(to == nil)
+ to = def;
+ return to;
+}
+
+int
+polytest(int inside, Point p, Point b, Point a)
+{
+ Point pa, ba;
+
+ if(b.y>a.y){
+ pa=sub(p, a);
+ ba=sub(b, a);
+ }else{
+ pa=sub(p, b);
+ ba=sub(a, b);
+ }
+ if(0<=pa.y && pa.y<ba.y && pa.y*ba.x<=pa.x*ba.y)
+ inside = !inside;
+ return inside;
+}
+
+Point
+sub(Point p, Point q)
+{
+ p.x -= q.x;
+ p.y -= q.y;
+ return p;
+}
+
+float
+dist(Point p, Point q)
+{
+ p.x -= q.x;
+ p.y -= q.y;
+ return (float)p.x * p.x + (float)p.y * p.y;
+}
+
+OkPoint
+pt(char *s)
+{
+ OkPoint okp;
+ Point p;
+ char *t, *e;
+
+ if(*s == '(')
+ s++;
+ t = strchr(s, ')');
+ if(t != nil)
+ *t = 0;
+ p.x = 0;
+ p.y = 0;
+ t = strchr(s, ',');
+ if(t == nil){
+ okp.p = p;
+ okp.ok = 0;
+ return okp;
+ }
+ e = nil;
+ p.x = strtol(s, &e, 10);
+ if(e != t){
+ okp.p = p;
+ okp.ok = 0;
+ return okp;
+ }
+ p.y = strtol(t+1, &e, 10);
+ if(e == nil || *e != 0){
+ okp.p = p;
+ okp.ok = 0;
+ return okp;
+ }
+ okp.p = p;
+ okp.ok = 1;
+ return okp;
+}
+
+Strings
+getfield(char *s)
+{
+ Strings ss;
+ char *f;
+
+ while(*s == '\t' || *s == ' ')
+ s++;
+ f = s;
+ while(*s && *s != '\t' && *s != ' ')
+ s++;
+ if(*s)
+ *s++ = 0;
+ ss.s1 = f;
+ ss.s2 = s;
+ return ss;
+}
diff --git a/sys/src/cmd/ip/httpd/init.c b/sys/src/cmd/ip/httpd/init.c
new file mode 100755
index 000000000..8d3eb0624
--- /dev/null
+++ b/sys/src/cmd/ip/httpd/init.c
@@ -0,0 +1,113 @@
+#include <u.h>
+#include <libc.h>
+#include "httpd.h"
+#include "httpsrv.h"
+
+void
+usage(void)
+{
+ fprint(2, "usage: %s [-b inbuf] [-d domain] [-p localport]"
+ " [-r remoteip] [-s uri-scheme] [-w webroot]"
+ " [-L logfd0 logfd1] [-N netdir] [-R reqline]"
+ " method version uri [search]\n", argv0);
+ exits("usage");
+}
+
+char *netdir;
+char *webroot;
+char *HTTPLOG = "httpd/log";
+
+static HConnect connect;
+static HSPriv priv;
+
+HConnect*
+init(int argc, char **argv)
+{
+ char *vs;
+
+ hinit(&connect.hin, 0, Hread);
+ hinit(&connect.hout, 1, Hwrite);
+ hmydomain = nil;
+ connect.replog = writelog;
+ connect.scheme = "http";
+ connect.port = "80";
+ connect.private = &priv;
+ priv.remotesys = nil;
+ priv.remoteserv = nil;
+ fmtinstall('D', hdatefmt);
+ fmtinstall('H', httpfmt);
+ fmtinstall('U', hurlfmt);
+ netdir = "/net";
+ ARGBEGIN{
+ case 'b':
+ hload(&connect.hin, EARGF(usage()));
+ break;
+ case 'd':
+ hmydomain = EARGF(usage());
+ break;
+ case 'p':
+ connect.port = EARGF(usage());
+ break;
+ case 'r':
+ priv.remotesys = EARGF(usage());
+ break;
+ case 's':
+ connect.scheme = EARGF(usage());
+ break;
+ case 'w':
+ webroot = EARGF(usage());
+ break;
+ case 'L':
+ logall[0] = strtol(EARGF(usage()), nil, 10);
+ logall[1] = strtol(EARGF(usage()), nil, 10);
+ break;
+ case 'N':
+ netdir = EARGF(usage());
+ break;
+ case 'R':
+ snprint((char*)connect.header, sizeof(connect.header), "%s",
+ EARGF(usage()));
+ break;
+ default:
+ usage();
+ }ARGEND
+
+ if(priv.remotesys == nil)
+ priv.remotesys = "unknown";
+ if(priv.remoteserv == nil)
+ priv.remoteserv = "unknown";
+ if(hmydomain == nil)
+ hmydomain = "unknown";
+ if(webroot == nil)
+ webroot = "/usr/web";
+
+ /*
+ * open all files we might need before castrating namespace
+ */
+ time(nil);
+ syslog(0, HTTPLOG, nil);
+
+ if(argc != 4 && argc != 3)
+ usage();
+
+ connect.req.meth = argv[0];
+
+ vs = argv[1];
+ connect.req.vermaj = 0;
+ connect.req.vermin = 9;
+ if(strncmp(vs, "HTTP/", 5) == 0){
+ vs += 5;
+ connect.req.vermaj = strtoul(vs, &vs, 10);
+ if(*vs == '.')
+ vs++;
+ connect.req.vermin = strtoul(vs, &vs, 10);
+ }
+
+ connect.req.uri = argv[2];
+ connect.req.search = argv[3];
+ connect.head.closeit = 1;
+ connect.hpos = (uchar*)strchr((char*)connect.header, '\0');
+ connect.hstop = connect.hpos;
+ connect.reqtime = time(nil); /* not quite right, but close enough */
+ return &connect;
+}
diff --git a/sys/src/cmd/ip/httpd/log.c b/sys/src/cmd/ip/httpd/log.c
new file mode 100755
index 000000000..ad97238e9
--- /dev/null
+++ b/sys/src/cmd/ip/httpd/log.c
@@ -0,0 +1,104 @@
+#include <u.h>
+#include <libc.h>
+#include "httpd.h"
+#include "httpsrv.h"
+
+int logall[3]; /* logall[2] is in "Common Log Format" */
+
+static char *
+monname[12] =
+{
+ "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
+};
+
+void
+logit(HConnect *c, char *fmt, ...)
+{
+ char buf[4096];
+ va_list arg;
+ HSPriv *p;
+
+ va_start(arg, fmt);
+ vseprint(buf, buf+sizeof(buf), fmt, arg);
+ va_end(arg);
+ p = nil;
+ if(c != nil)
+ p = c->private;
+ if(p != nil && p->remotesys != nil)
+ syslog(0, HTTPLOG, "%s %s", p->remotesys, buf);
+ else
+ syslog(0, HTTPLOG, "%s", buf);
+}
+
+void
+writelog(HConnect *c, char *fmt, ...)
+{
+ HSPriv *p;
+ char buf[HBufSize+500], *bufp, *bufe;
+ char statuscode[4];
+ vlong objectsize;
+ ulong now, today;
+ int logfd;
+ va_list arg;
+ Tm *tm;
+
+ if(c == nil)
+ return;
+ p = c->private;
+ bufe = buf + sizeof(buf);
+ now = time(nil);
+ tm = gmtime(now);
+ today = now / (24*60*60);
+
+ /* verbose logfile, for research on web traffic */
+ logfd = logall[today & 1];
+ if(logfd > 0){
+ if(c->hstop == c->header || c->hstop[-1] != '\n')
+ *c->hstop = '\n';
+ *c->hstop = '\0';
+ bufp = seprint(buf, bufe, "==========\n");
+ bufp = seprint(bufp, bufe, "LogTime: %D\n", now);
+ bufp = seprint(bufp, bufe, "ConnTime: %D\n", c->reqtime);
+ bufp = seprint(bufp, bufe, "RemoteIP: %s\n", p->remotesys);
+ bufp = seprint(bufp, bufe, "Port: %s\n", p->remoteserv);
+ va_start(arg, fmt);
+ bufp = vseprint(bufp, bufe, fmt, arg);
+ va_end(arg);
+ if(c->req.uri != nil && c->req.uri[0] != 0)
+ bufp = seprint(bufp, bufe, "FinalURI: %s\n", c->req.uri);
+ bufp = seprint(bufp, bufe, "----------\n%s\n", (char*)c->header);
+ write(logfd, buf, bufp-buf); /* append-only file */
+ }
+
+ /* another log, with less information but formatted for common analysis tools */
+ if(logall[2] > 0 && strncmp(fmt, "Reply: ", 7) == 0){
+ objectsize = 0;
+ strecpy(statuscode, statuscode+4, fmt+7);
+ if( fmt[7] == '%'){
+ va_start(arg, fmt);
+ vseprint(statuscode, statuscode+4, fmt+7, arg);
+ va_end(arg);
+ }else if(
+ strcmp(fmt+7, "200 file %lld %lld\n") == 0 ||
+ strcmp(fmt+7, "206 partial content %lld %lld\n") == 0 ||
+ strcmp(fmt+7, "206 partial content, early termination %lld %lld\n") == 0){
+ va_start(arg, fmt);
+ objectsize = va_arg(arg, vlong); /* length in sendfd.c */
+ USED(objectsize);
+ objectsize = va_arg(arg, vlong); /* wrote in sendfd.c */
+ va_end(arg);
+ }
+ bufp = seprint(buf, bufe, "%s - -", p->remotesys);
+ bufp = seprint(bufp, bufe, " [%.2d/%s/%d:%.2d:%.2d:%.2d +0000]", tm->mday, monname[tm->mon], tm->year+1900, tm->hour, tm->min, tm->sec);
+ if(c->req.uri == nil || c->req.uri[0] == 0){
+ bufp = seprint(bufp, bufe, " \"%.*s\"",
+ (int)utfnlen((char*)c->header, strcspn((char*)c->header, "\r\n")),
+ (char*)c->header);
+ }else{
+ /* use more canonical form of URI, if available */
+ bufp = seprint(bufp, bufe, " \"%s %s HTTP/%d.%d\"", c->req.meth, c->req.uri, c->req.vermaj, c->req.vermin);
+ }
+ bufp = seprint(bufp, bufe, " %s %lld\n", statuscode, objectsize);
+ write(logall[2], buf, bufp-buf); /* append-only file */
+ }
+}
diff --git a/sys/src/cmd/ip/httpd/man2html.c b/sys/src/cmd/ip/httpd/man2html.c
new file mode 100755
index 000000000..7971bf3bf
--- /dev/null
+++ b/sys/src/cmd/ip/httpd/man2html.c
@@ -0,0 +1,449 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include "httpd.h"
+#include "httpsrv.h"
+
+static Hio *hout;
+static Hio houtb;
+static HConnect *connect;
+
+void doconvert(char*, int);
+
+void
+error(char *title, char *fmt, ...)
+{
+ va_list arg;
+ char buf[1024], *out;
+
+ va_start(arg, fmt);
+ out = vseprint(buf, buf+sizeof(buf), fmt, arg);
+ va_end(arg);
+ *out = 0;
+
+ hprint(hout, "%s 404 %s\n", hversion, title);
+ hprint(hout, "Date: %D\n", time(nil));
+ hprint(hout, "Server: Plan9\n");
+ hprint(hout, "Content-type: text/html\n");
+ hprint(hout, "\n");
+ hprint(hout, "<head><title>%s</title></head>\n", title);
+ hprint(hout, "<body><h1>%s</h1></body>\n", title);
+ hprint(hout, "%s\n", buf);
+ hflush(hout);
+ writelog(connect, "Reply: 404\nReason: %s\n", title);
+ exits(nil);
+}
+
+typedef struct Hit Hit;
+struct Hit
+{
+ Hit *next;
+ char *file;
+};
+
+void
+lookup(char *object, int section, Hit **list)
+{
+ int fd;
+ char *p, *f;
+ Biobuf b;
+ char file[256];
+ Hit *h;
+
+ while(*list != nil)
+ list = &(*list)->next;
+
+ snprint(file, sizeof(file), "/sys/man/%d/INDEX", section);
+ fd = open(file, OREAD);
+ if(fd > 0){
+ Binit(&b, fd, OREAD);
+ for(;;){
+ p = Brdline(&b, '\n');
+ if(p == nil)
+ break;
+ p[Blinelen(&b)-1] = 0;
+ f = strchr(p, ' ');
+ if(f == nil)
+ continue;
+ *f++ = 0;
+ if(strcmp(p, object) == 0){
+ h = ezalloc(sizeof *h);
+ *list = h;
+ h->next = nil;
+ snprint(file, sizeof(file), "/%d/%s", section, f);
+ h->file = estrdup(file);
+ close(fd);
+ return;
+ }
+ }
+ close(fd);
+ }
+ snprint(file, sizeof(file), "/sys/man/%d/%s", section, object);
+ if(access(file, 0) == 0){
+ h = ezalloc(sizeof *h);
+ *list = h;
+ h->next = nil;
+ h->file = estrdup(file+8);
+ }
+}
+
+void
+manindex(int sect, int vermaj)
+{
+ int i;
+
+ if(vermaj){
+ hokheaders(connect);
+ hprint(hout, "Content-type: text/html\r\n");
+ hprint(hout, "\r\n");
+ }
+
+ hprint(hout, "<head><title>plan 9 section index");
+ if(sect)
+ hprint(hout, "(%d)\n", sect);
+ hprint(hout, "</title></head><body>\n");
+ hprint(hout, "<H6>Section Index");
+ if(sect)
+ hprint(hout, "(%d)\n", sect);
+ hprint(hout, "</H6>\n");
+
+ if(sect)
+ hprint(hout, "<p><a href=\"/plan9/man%d.html\">/plan9/man%d.html</a>\n",
+ sect, sect);
+ else for(i = 1; i < 10; i++)
+ hprint(hout, "<p><a href=\"/plan9/man%d.html\">/plan9/man%d.html</a>\n",
+ i, i);
+ hprint(hout, "</body>\n");
+}
+
+void
+man(char *o, int sect, int vermaj)
+{
+ int i;
+ Hit *list;
+
+ list = nil;
+
+ if(*o == 0){
+ manindex(sect, vermaj);
+ return;
+ }
+
+ if(sect > 0 && sect < 10)
+ lookup(o, sect, &list);
+ else
+ for(i = 1; i < 9; i++)
+ lookup(o, i, &list);
+
+ if(list != nil && list->next == nil){
+ doconvert(list->file, vermaj);
+ return;
+ }
+
+ if(vermaj){
+ hokheaders(connect);
+ hprint(hout, "Content-type: text/html\r\n");
+ hprint(hout, "\r\n");
+ }
+
+ hprint(hout, "<head><title>plan 9 man %H", o);
+ if(sect)
+ hprint(hout, "(%d)\n", sect);
+ hprint(hout, "</title></head><body>\n");
+ hprint(hout, "<H6>Search for %H", o);
+ if(sect)
+ hprint(hout, "(%d)\n", sect);
+ hprint(hout, "</H6>\n");
+
+ for(; list; list = list->next)
+ hprint(hout, "<p><a href=\"/magic/man2html%U\">/magic/man2html%H</a>\n",
+ list->file, list->file);
+ hprint(hout, "</body>\n");
+}
+
+void
+strlwr(char *p)
+{
+ for(; *p; p++)
+ if('A' <= *p && *p <= 'Z')
+ *p += 'a'-'A';
+}
+
+void
+redirectto(char *uri)
+{
+ if(connect){
+ hmoved(connect, uri);
+ exits(0);
+ }else
+ hprint(hout, "Your selection moved to <a href=\"%U\"> here</a>.<p></body>\r\n", uri);
+}
+
+void
+searchfor(char *search)
+{
+ int i, j, n, fd;
+ char *p, *sp;
+ Biobufhdr *b;
+ char *arg[32];
+
+ hprint(hout, "<head><title>plan 9 search for %H</title></head>\n", search);
+ hprint(hout, "<body>\n");
+
+ hprint(hout, "<p>This is a keyword search through Plan 9 man pages.\n");
+ hprint(hout, "The search is case insensitive; blanks denote \"boolean and\".\n");
+ hprint(hout, "<FORM METHOD=\"GET\" ACTION=\"/magic/man2html\">\n");
+ hprint(hout, "<INPUT NAME=\"pat\" TYPE=\"text\" SIZE=\"60\">\n");
+ hprint(hout, "<INPUT TYPE=\"submit\" VALUE=\"Submit\">\n");
+ hprint(hout, "</FORM>\n");
+
+ hprint(hout, "<hr><H6>Search for %H</H6>\n", search);
+ n = getfields(search, arg, 32, 1, "+");
+ for(i = 0; i < n; i++){
+ for(j = i+1; j < n; j++){
+ if(strcmp(arg[i], arg[j]) > 0){
+ sp = arg[j];
+ arg[j] = arg[i];
+ arg[i] = sp;
+ }
+ }
+ sp = malloc(strlen(arg[i]) + 2);
+ if(sp != nil){
+ strcpy(sp+1, arg[i]);
+ sp[0] = ' ';
+ arg[i] = sp;
+ }
+ }
+
+ /*
+ * search index till line starts alphabetically < first token
+ */
+ fd = open("/sys/man/searchindex", OREAD);
+ if(fd < 0){
+ hprint(hout, "<body>error: No Plan 9 search index\n");
+ hprint(hout, "</body>");
+ return;
+ }
+ p = malloc(32*1024);
+ if(p == nil){
+ close(fd);
+ return;
+ }
+ b = ezalloc(sizeof *b);
+ Binits(b, fd, OREAD, (uchar*)p, 32*1024);
+ for(;;){
+ p = Brdline(b, '\n');
+ if(p == nil)
+ break;
+ p[Blinelen(b)-1] = 0;
+ for(i = 0; i < n; i++){
+ sp = strstr(p, arg[i]);
+ if(sp == nil)
+ break;
+ p = sp;
+ }
+ if(i < n)
+ continue;
+ sp = strrchr(p, '\t');
+ if(sp == nil)
+ continue;
+ sp++;
+ hprint(hout, "<p><a href=\"/magic/man2html/%U\">/magic/man2html/%H</a>\n",
+ sp, sp);
+ }
+ hprint(hout, "</body>");
+
+ Bterm(b);
+ free(b);
+ free(p);
+ close(fd);
+}
+
+/*
+ * find man pages mentioning the search string
+ */
+void
+dosearch(int vermaj, char *search)
+{
+ int sect;
+ char *p;
+
+ if(strncmp(search, "man=", 4) == 0){
+ sect = 0;
+ search = hurlunesc(connect, search+4);
+ p = strchr(search, '&');
+ if(p != nil){
+ *p++ = 0;
+ if(strncmp(p, "sect=", 5) == 0)
+ sect = atoi(p+5);
+ }
+ man(search, sect, vermaj);
+ return;
+ }
+
+ if(vermaj){
+ hokheaders(connect);
+ hprint(hout, "Content-type: text/html\r\n");
+ hprint(hout, "\r\n");
+ }
+
+ if(strncmp(search, "pat=", 4) == 0){
+ search = hurlunesc(connect, search+4);
+ search = hlower(search);
+ searchfor(search);
+ return;
+ }
+
+ hprint(hout, "<head><title>illegal search</title></head>\n");
+ hprint(hout, "<body><p>Illegally formatted Plan 9 man page search</p>\n");
+ search = hurlunesc(connect, search);
+ hprint(hout, "<body><p>%H</p>\n", search);
+ hprint(hout, "</body>");
+}
+
+/*
+ * convert a man page to html and output
+ */
+void
+doconvert(char *uri, int vermaj)
+{
+ char *p;
+ char file[256];
+ char title[256];
+ char err[ERRMAX];
+ int pfd[2];
+ Dir *d;
+ Waitmsg *w;
+ int x;
+
+ if(strstr(uri, ".."))
+ error("bad URI", "man page URI cannot contain ..");
+ p = strstr(uri, "/intro");
+
+ if(p == nil){
+ while(*uri == '/')
+ uri++;
+ /* redirect section requests */
+ snprint(file, sizeof(file), "/sys/man/%s", uri);
+ d = dirstat(file);
+ if(d == nil){
+ strlwr(file);
+ if(dirstat(file) != nil){
+ snprint(file, sizeof(file), "/magic/man2html/%s", uri);
+ strlwr(file);
+ redirectto(file);
+ }
+ error(uri, "man page not found");
+ }
+ x = d->qid.type;
+ free(d);
+ if(x & QTDIR){
+ if(*uri == 0 || strcmp(uri, "/") == 0)
+ redirectto("/sys/man/index.html");
+ else {
+ snprint(file, sizeof(file), "/sys/man/%s/INDEX.html",
+ uri+1);
+ redirectto(file);
+ }
+ return;
+ }
+ } else {
+ /* rewrite the name intro */
+ *p = 0;
+ snprint(file, sizeof(file), "/sys/man/%s/0intro", uri);
+ d = dirstat(file);
+ free(d);
+ if(d == nil)
+ error(uri, "man page not found");
+ }
+
+ if(vermaj){
+ hokheaders(connect);
+ hprint(hout, "Content-type: text/html\r\n");
+ hprint(hout, "\r\n");
+ }
+ hflush(hout);
+
+ if(pipe(pfd) < 0)
+ error("out of resources", "pipe failed");
+
+ /* troff -manhtml <file> | troff2html -t '' */
+ switch(fork()){
+ case -1:
+ error("out of resources", "fork failed");
+ case 0:
+ snprint(title, sizeof(title), "Plan 9 %s", file);
+ close(0);
+ dup(pfd[0], 0);
+ close(pfd[0]);
+ close(pfd[1]);
+ execl("/bin/troff2html", "troff2html", "-t", title, nil);
+ errstr(err, sizeof err);
+ exits(err);
+ }
+ switch(fork()){
+ case -1:
+ error("out of resources", "fork failed");
+ case 0:
+ snprint(title, sizeof(title), "Plan 9 %s", file);
+ close(0);
+ close(1);
+ dup(pfd[1], 1);
+ close(pfd[0]);
+ close(pfd[1]);
+ execl("/bin/troff", "troff", "-manhtml", file, nil);
+ errstr(err, sizeof err);
+ exits(err);
+ }
+ close(pfd[0]);
+ close(pfd[1]);
+
+ /* wait for completion */
+ for(;;){
+ w = wait();
+ if(w == nil)
+ break;
+ if(w->msg[0] != 0)
+ print("whoops %s\n", w->msg);
+ free(w);
+ }
+}
+
+void
+main(int argc, char **argv)
+{
+ fmtinstall('H', httpfmt);
+ fmtinstall('U', hurlfmt);
+
+ if(argc == 2){
+ hinit(&houtb, 1, Hwrite);
+ hout = &houtb;
+ doconvert(argv[1], 0);
+ exits(nil);
+ }
+ close(2);
+
+ connect = init(argc, argv);
+ hout = &connect->hout;
+ if(hparseheaders(connect, HSTIMEOUT) < 0)
+ exits("failed");
+
+ if(strcmp(connect->req.meth, "GET") != 0 && strcmp(connect->req.meth, "HEAD") != 0){
+ hunallowed(connect, "GET, HEAD");
+ exits("not allowed");
+ }
+ if(connect->head.expectother || connect->head.expectcont){
+ hfail(connect, HExpectFail, nil);
+ exits("failed");
+ }
+
+ bind("/usr/web/sys/man", "/sys/man", MREPL);
+
+ if(connect->req.search != nil)
+ dosearch(connect->req.vermaj, connect->req.search);
+ else
+ doconvert(connect->req.uri, connect->req.vermaj);
+ hflush(hout);
+ writelog(connect, "200 man2html %ld %ld\n", hout->seek, hout->seek);
+ exits(nil);
+}
diff --git a/sys/src/cmd/ip/httpd/mkfile b/sys/src/cmd/ip/httpd/mkfile
new file mode 100755
index 000000000..fe8b67d92
--- /dev/null
+++ b/sys/src/cmd/ip/httpd/mkfile
@@ -0,0 +1,79 @@
+</$objtype/mkfile
+
+HFILES=\
+ /sys/include/httpd.h\
+ httpsrv.h\
+
+TARG=\
+ httpd\
+ imagemap\
+ man2html\
+ save\
+ netlib_find\
+ netlib_history\
+ webls\
+ wikipost\
+
+XTARG=\
+ httpd\
+ imagemap\
+ netlib_find\
+ netlib_history\
+ man2html\
+ save\
+ wikipost\
+
+LIB=libhttps.a$O
+
+LIBS=libhttps.a$O
+LIBSOFILES=\
+ anonymous.$O\
+ content.$O\
+ emem.$O\
+ hints.$O\
+ init.$O\
+ log.$O\
+ redirect.$O\
+ sendfd.$O\
+ authorize.$O\
+
+BIN=/$objtype/bin/ip/httpd
+
+UPDATE=\
+ $HFILES\
+ ${LIBSOFILES:%.$O=%.c}\
+ ${XTARG:%=%.c}\
+
+</sys/src/cmd/mkmany
+
+trial: $O.netlib_history
+ # should first mount -b /srv/histnetlib /usr/web/historic
+ echo '
+ ' | $O.netlib_history GET HTTP/1.0 xxx 'file=fp%2Fdtoa.c.gz'
+
+trial2: $O.netlib_find
+ echo "\n" | $O.netlib_find GET HTTP/1.0 xxx 'db=1&pat=Hearing' > /tmp/search
+ sed 17q /tmp/search
+
+$LIBS: $LIBSOFILES
+ ar vu $LIBS $newprereq
+ rm $newprereq
+ # rm $newmember - cannot do this because of mk race
+
+
+re:N: v.re
+ v.re redirect.urls
+
+none:VQ:
+ echo usage: mk all, install, installall, '$O'.cmd, cmd.install, or cmd.installall
+ echo usage: mk safeinstall, safeinstallall, cmd.safeinstallall, or cmd.safeinstallall
+
+$O.9down: 9down.$O whois.$O classify.$O $LIB
+ $LD -o $target $prereq
+
+$O.test9down: 9down4e.$O whois.$O classify.$O $LIB
+ $LD -o $target $prereq
+
+$O.testclassify: testclassify.$O whois.$O classify.$O $LIB
+ $LD -o $target $prereq
+
diff --git a/sys/src/cmd/ip/httpd/netlib_find.c b/sys/src/cmd/ip/httpd/netlib_find.c
new file mode 100755
index 000000000..d594e3f7f
--- /dev/null
+++ b/sys/src/cmd/ip/httpd/netlib_find.c
@@ -0,0 +1,278 @@
+/* invoked from /netlib/pub/search.html */
+
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include "httpd.h"
+#include "httpsrv.h"
+
+void bib_fmt(char*,char*);
+void index_fmt(char*,char*);
+void no_fmt(char*,char*);
+int send(HConnect*);
+
+Hio *hout;
+
+/********** table of databases ************/
+
+typedef struct DB DB;
+struct DB
+{
+ int SELECT; /* value from search.html */
+ char *log; /* abbreviation for logfile */
+ int maxhit; /* maximum number of hits to return */
+ char *file; /* searchfs database */
+ void (*fmt)(char*,char*); /* convert one record to HTML */
+ char *postlude; /* trailer text */
+};
+
+DB db[] =
+{
+ {0, "netlib", 250, "/srv/netlib_DEFAULT", index_fmt,
+ "<HR><A HREF=\"/netlib/master\">browse netlib</A></BODY>\r\n"},
+ {1, "BibNet", 250, "/srv/netlib_bibnet", bib_fmt,
+ "<HR><A HREF=\"/netlib/bibnet\">browse BibNet</A></BODY>\r\n"},
+ {2, "compgeom", 250, "/srv/netlib_compgeom", no_fmt, "</BODY>\r\n"},
+ {3, "approx", 250, "/srv/netlib_approximation", no_fmt,
+ "<HR><A HREF=\"/netlib/a/catalog.html.gz\">hierarchical catalog</A></BODY>\r\n"},
+ {4, "siam", 50, "/srv/netlib_siam-Secret", no_fmt, "</BODY>\r\n"},
+ {-1,"",0,"",no_fmt,""}
+};
+
+
+
+/********** reformat database record as HTML ************/
+
+void /* tr '\015' '\012' ("uncombline") */
+no_fmt(char*s,char*e)
+{
+ /* s = start, e = (one past) end of database record */
+ char *p;
+ for(p = s; p<e; p++)
+ if(*p=='\r'){
+ hwrite(hout, s,p-s);
+ hprint(hout, "\n");
+ s = p+1;
+ }
+}
+
+int /* should the filename have .gz appended? */
+suffix(char*filename)
+{
+ int n;
+ char *z;
+
+ if(!filename || *filename==0)
+ return(0);
+ n = strlen(filename);
+ if(strncmp(".html",filename+n-5,5)==0)
+ return(0);
+ z = malloc(n+50);
+ if(z == nil)
+ return(0);
+ strcpy(z,"/netlib/pub/");
+ strcat(z,filename);
+ strcat(z,".gz");
+ if(access(z,4)==0){
+ free(z);
+ return(1);
+ }
+ free(z);
+ return(0);
+}
+
+void /* add HREF to "file:" lines */
+index_fmt(char*s,char*e)
+{
+ char *p, *filename;
+ if(strncmp(s,"file",4)==0 && (s[4]==' '||s[4]=='\t')){
+ for(filename = s+4; strchr(" \t",*filename); filename++){}
+ for(s = filename; *s && strchr("\r\n",*s)==nil; s++){}
+ *s++ = '\0';
+ if(*s=='\n') s++;
+ hprint(hout, "file: <A HREF=\"/netlib/%s",filename);
+ if(suffix(filename))
+ hprint(hout, ".gz");
+ hprint(hout, "\">%s</A>\r\n",filename);
+ for(p = s; p<e; p++)
+ if(*p=='\r'){
+ hwrite(hout, s,p-s);
+ hprint(hout, "\n");
+ s = p+1;
+ }
+ }else if(strncmp(s,"lib",3)==0 && (s[3]==' '||s[3]=='\t')){
+ for(filename = s+3; strchr(" \t",*filename); filename++){}
+ for(s = filename; *s && strchr("\r\n",*s)==nil; s++){}
+ *s++ = '\0';
+ if(*s=='\n') s++;
+ hprint(hout, "lib: <A HREF=\"/netlib/%s",filename);
+ hprint(hout, "\">%s</A>\r\n",filename);
+ for(p = s; p<e; p++)
+ if(*p=='\r'){
+ hwrite(hout, s,p-s);
+ hprint(hout, "\n");
+ s = p+1;
+ }
+ }else{
+ no_fmt(s,e);
+ }
+}
+
+void /* add HREF to "URL" lines */
+bib_fmt(char*s,char*e)
+{
+ char *p, *filename;
+ for(p = s; p<e; p++)
+ if(*p=='\r'){
+ hwrite(hout, s,p-s);
+ hprint(hout, "\n");
+ s = p+1;
+ if(strncmp(s," URL =",6)==0 &&
+ (filename = strchr(s+6,'"'))!=nil){
+ filename++;
+ for(s = filename; *s && strchr("\"\r\n",*s)==nil; s++){}
+ *s++ = '\0';
+ p = s;
+ hprint(hout, " URL =<A HREF=\"%s\">%s</A>",
+ filename,filename);
+ }
+ }
+}
+
+
+/********** main() calls httpheadget() calls send() ************/
+
+void
+main(int argc, char **argv)
+{
+ HConnect *c;
+
+ c = init(argc, argv);
+ hout = &c->hout;
+ if(hparseheaders(c, HSTIMEOUT) >= 0)
+ send(c);
+ exits(nil);
+}
+
+Biobuf Blist;
+
+Biobuf*
+init800fs(char*name,char*pat)
+{
+ int fd800fs, n;
+ char*search;
+
+ fd800fs = open(name, ORDWR);
+ if(fd800fs < 0)
+ exits("can't connect to 800fs server");
+ if(mount(fd800fs, -1, "/mnt", MREPL, "") < 0)
+ exits("can't mount /mnt");
+ fd800fs = open("/mnt/search", ORDWR);
+ n = strlen("search=")+strlen(pat)+1;
+ search = ezalloc(n);
+ strcpy(search,"search=");
+ strcat(search,pat);
+ write(fd800fs,search,n);
+ free(search);
+ Binit(&Blist, fd800fs,OREAD);
+ return(&Blist);
+}
+
+
+static char *
+hq(char *text)
+{
+ int textlen = strlen(text), escapedlen = textlen;
+ char *escaped, *s, *w;
+
+ for(s = text; *s; s++)
+ if(*s=='<' || *s=='>' || *s=='&')
+ escapedlen += 4;
+ escaped = ezalloc(escapedlen+1);
+ for(s = text, w = escaped; *s; s++){
+ if(*s == '<'){
+ strcpy(w, "&lt;");
+ w += 4;
+ }else if(*s == '>'){
+ strcpy(w, "&gt;");
+ w += 4;
+ }else if(*s == '&'){
+ strcpy(w, "&amp;");
+ w += 5;
+ }else{
+ *w++ = *s;
+ }
+ }
+ return escaped;
+}
+
+int
+send(HConnect *c)
+{
+ Biobuf*blist;
+ int m, n, dbi, nmatch;
+ char *pat, *s, *e;
+ HSPairs *q;
+
+ if(strcmp(c->req.meth, "GET") != 0 && strcmp(c->req.meth, "HEAD") != 0)
+ return hunallowed(c, "GET, HEAD");
+ if(c->head.expectother || c->head.expectcont)
+ return hfail(c, HExpectFail, nil);
+ if(c->req.search == nil || !*c->req.search)
+ return hfail(c, HNoData, "netlib_find");
+ s = c->req.search;
+ while((s = strchr(s, '+')) != nil)
+ *s++ = ' ';
+ dbi = -1;
+ pat = nil;
+ for(q = hparsequery(c, hstrdup(c, c->req.search)); q; q = q->next){
+ if(strcmp(q->s, "db") == 0){
+ m = atoi(q->t);
+ for(dbi = 0; m!=db[dbi].SELECT; dbi++)
+ if(db[dbi].SELECT<0)
+ exits("unrecognized db");
+ }else if(strcmp(q->s, "pat") == 0){
+ pat = q->t;
+ }
+ }
+ if(dbi < 0)
+ exits("missing db field in query");
+ if(pat == nil)
+ exits("missing pat field in query");
+ logit(c, "netlib_find %s %s", db[dbi].log,pat);
+
+ blist = init800fs(db[dbi].file,pat);
+
+ if(c->req.vermaj){
+ hokheaders(c);
+ hprint(hout, "Content-type: text/html\r\n");
+ hprint(hout, "\r\n");
+ }
+ if(strcmp(c->req.meth, "HEAD") == 0){
+ writelog(c, "Reply: 200 netlib_find 0\n");
+ hflush(hout);
+ exits(nil);
+ }
+
+ hprint(hout, "<HEAD><TITLE>%s/%s</TITLE></HEAD>\r\n<BODY>\r\n",
+ db[dbi].log,hq(pat));
+ nmatch = 0;
+
+ while(s = Brdline(blist, '\n')){ /* get next database record */
+ n = Blinelen(blist);
+ e = s+n;
+ hprint(hout, "<PRE>");
+ (*db[dbi].fmt)(s,e);
+ hprint(hout, "</PRE>\r\n");
+ if(nmatch++>=db[dbi].maxhit){
+ hprint(hout, "<H4>reached limit at %d hits</H4>\n\r",nmatch);
+ break;
+ }
+ }
+ if(nmatch==0)
+ hprint(hout, "<H4>Nothing Found.</H4>\r\n");
+ hprint(hout, db[dbi].postlude);
+ hflush(hout);
+ writelog(c, "Reply: 200 netlib_find %ld %ld\n", hout->seek, hout->seek);
+ return 1;
+}
diff --git a/sys/src/cmd/ip/httpd/netlib_history.c b/sys/src/cmd/ip/httpd/netlib_history.c
new file mode 100755
index 000000000..d24806225
--- /dev/null
+++ b/sys/src/cmd/ip/httpd/netlib_history.c
@@ -0,0 +1,218 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include "httpd.h"
+#include "httpsrv.h"
+
+Hio *HO;
+int diffb;
+
+enum{ DAY = 24*60*60 };
+
+void
+lastbefore(ulong t, char *f, char *b)
+{
+ Tm *tm;
+ Dir *dir;
+ int try;
+ ulong t0, mtime;
+
+ t0 = t;
+ for(try=0; try<10; try++) {
+ tm = localtime(t);
+ t -= DAY;
+ sprint(b,"%.4d/%.2d%.2d/netlib/pub/%s",tm->year+1900,tm->mon+1,tm->mday,f);
+ dir = dirstat(b);
+ if(dir == nil)
+ continue;
+ mtime = dir->mtime;
+ free(dir);
+ if(mtime > t0)
+ continue;
+ return;
+ }
+ strcpy(b, "filenotfound");
+}
+
+// create explicit file for diff, which otherwise would create a
+// mode 0600 file that it couldn't read (because running as none)
+void
+gunzip(char *f, char *tmp)
+{
+ int fd = open(tmp, OWRITE);
+
+ if(fd < 0) // can't happen
+ return;
+ switch(fork()){
+ case 0:
+ dup(fd, 1);
+ close(fd);
+ close(0);
+ execl("/bin/gunzip", "gunzip", "-c", f, nil);
+ hprint(HO, "can't exec gunzip: %r\n");
+ break;
+ case -1:
+ hprint(HO, "fork failed: %r\n");
+ default:
+ while(waitpid() != -1)
+ ;
+ break;
+ }
+ close(fd);
+}
+
+void
+netlibhistory(char *file)
+{
+ char buf[500], pair[2][500], tmpf[2][30], *f;
+ int toggle = 0, started = 0, limit;
+ Dir *dir;
+ ulong otime, dt;
+ int i, fd, tmpcnt;
+
+ if(strncmp(file, "../", 3) == 0 || strstr(file, "/../") ||
+ strlen(file) >= sizeof(buf) - strlen("1997/0204/netlib/pub/0"))
+ return;
+ limit = 50;
+ if(diffb){
+ limit = 10;
+ // create two tmp files for gunzip
+ for(i = 0, tmpcnt = 0; i < 2 && tmpcnt < 20; tmpcnt++){
+ snprint(tmpf[i], sizeof(tmpf[0]), "/tmp/d%x", tmpcnt);
+ if(access(buf, AEXIST) == 0)
+ continue;
+ fd = create(tmpf[i], OWRITE, 0666);
+ if(fd < 0)
+ goto done;
+ close(fd);
+ i++;
+ }
+ }
+ otime = time(0);
+ hprint(HO,"<UL>\n");
+ while(limit--){
+ lastbefore(otime, file, buf);
+ dir = dirstat(buf);
+ if(dir == nil)
+ goto done;
+ dt = DAY/2;
+ while(otime <= dir->mtime){
+ lastbefore(otime-dt, file, buf);
+ free(dir);
+ dir = dirstat(buf);
+ if(dir == nil)
+ goto done;
+ dt += DAY/2;
+ }
+ f = pair[toggle];
+ strcpy(f, buf);
+ if(diffb && strcmp(f+strlen(f)-3, ".gz") == 0){
+ gunzip(f, tmpf[toggle]);
+ strcpy(f, tmpf[toggle]);
+ }
+ if(diffb && started){
+ hprint(HO, "<PRE>\n");
+ hflush(HO);
+ switch(fork()){
+ case 0:
+ execl("/bin/diff", "diff", "-nb",
+ pair[1-toggle], pair[toggle], nil);
+ hprint(HO, "can't exec diff: %r\n");
+ break;
+ case -1:
+ hprint(HO, "fork failed: %r\n");
+ break;
+ default:
+ while(waitpid() != -1)
+ ;
+ break;
+ }
+ hprint(HO, "</PRE>\n");
+ }
+ hprint(HO,"<LI><A HREF=\"/historic/%s\">%s</A> %lld bytes\n",
+ buf, 4+asctime(gmtime(dir->mtime)), dir->length);
+ if(diffb)
+ hprint(HO," <FONT SIZE=-1>(%s)</FONT>\n", pair[toggle]);
+ toggle = 1-toggle;
+ started = 1;
+ otime = dir->mtime;
+ free(dir);
+ }
+ hprint(HO,"<LI>...\n");
+done:
+ hprint(HO,"</UL>\n");
+ if(diffb){
+ remove(tmpf[0]);
+ remove(tmpf[1]);
+ }
+}
+
+int
+send(HConnect *c)
+{
+ char *file, *s;
+ HSPairs *q;
+
+ if(strcmp(c->req.meth, "GET") != 0 && strcmp(c->req.meth, "HEAD") != 0)
+ return hunallowed(c, "GET, HEAD");
+ if(c->head.expectother || c->head.expectcont)
+ return hfail(c, HExpectFail, nil);
+ if(c->req.search == nil || !*c->req.search)
+ return hfail(c, HNoData, "netlib_history");
+ s = c->req.search;
+ while((s = strchr(s, '+')) != nil)
+ *s++ = ' ';
+ file = nil;
+ for(q = hparsequery(c, hstrdup(c, c->req.search)); q; q = q->next){
+ if(strcmp(q->s, "file") == 0)
+ file = q->t;
+ else if(strcmp(q->s, "diff") == 0)
+ diffb = 1;
+ }
+ if(file == nil)
+ return hfail(c, HNoData, "netlib_history missing file field");
+ logit(c, "netlib_hist %s%s", file, diffb?" DIFF":"");
+
+ if(c->req.vermaj){
+ hokheaders(c);
+ hprint(HO, "Content-type: text/html\r\n");
+ hprint(HO, "\r\n");
+ }
+ if(strcmp(c->req.meth, "HEAD") == 0){
+ writelog(c, "Reply: 200 netlib_history 0\n");
+ hflush(HO);
+ exits(nil);
+ }
+
+ hprint(HO, "<HEAD><TITLE>%s history</TITLE></HEAD>\n<BODY>\n",file);
+ hprint(HO, "<H2>%s history</H2>\n",file);
+ hprint(HO, "<I>Netlib's copy of %s was changed\n", file);
+ hprint(HO, "on the dates shown. <BR>Click on the date link\n");
+ hprint(HO, "to retrieve the corresponding version.</I>\n");
+ if(diffb){
+ hprint(HO, "<BR><I>Lines beginning with &lt; are for the\n");
+ hprint(HO, "newer of the two versions.</I>\n");
+ }
+
+ if(chdir("/usr/web/historic") < 0)
+ hprint(HO, "chdir failed: %r\n");
+ netlibhistory(file);
+
+ hprint(HO, "<BR><A HREF=\"http://cm.bell-labs.com/who/ehg\">Eric Grosse</A>\n");
+ hprint(HO, "</BODY></HTML>\n");
+ hflush(HO);
+ writelog(c, "Reply: 200 netlib_history %ld %ld\n", HO->seek, HO->seek);
+ return 1;
+}
+
+void
+main(int argc, char **argv)
+{
+ HConnect *c;
+
+ c = init(argc, argv);
+ HO = &c->hout;
+ if(hparseheaders(c, HSTIMEOUT) >= 0)
+ send(c);
+ exits(nil);
+}
diff --git a/sys/src/cmd/ip/httpd/redirect.c b/sys/src/cmd/ip/httpd/redirect.c
new file mode 100755
index 000000000..d7626452e
--- /dev/null
+++ b/sys/src/cmd/ip/httpd/redirect.c
@@ -0,0 +1,227 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include "httpd.h"
+#include "httpsrv.h"
+
+enum
+{
+ HASHSIZE = 1019,
+};
+
+typedef struct Redir Redir;
+struct Redir
+{
+ Redir *next;
+ char *pat;
+ char *repl;
+ uint flags; /* generated from repl's decorations */
+};
+
+static Redir *redirtab[HASHSIZE];
+static Redir *vhosttab[HASHSIZE];
+static char emptystring[1];
+/* these two arrays must be kept in sync */
+static char decorations[] = { Modsilent, Modperm, Modsubord, Modonly, '\0' };
+static uint redirflags[] = { Redirsilent, Redirperm, Redirsubord, Redironly, };
+
+/* replacement field decorated with redirection modifiers? */
+static int
+isdecorated(char *repl)
+{
+ return strchr(decorations, repl[0]) != nil;
+}
+
+static uint
+decor2flags(char *repl)
+{
+ uint flags;
+ char *p;
+
+ flags = 0;
+ while ((p = strchr(decorations, *repl++)) != nil)
+ flags |= redirflags[p - decorations];
+ return flags;
+}
+
+/* return replacement without redirection modifiers */
+char *
+undecorated(char *repl)
+{
+ while (isdecorated(repl))
+ repl++;
+ return repl;
+}
+
+static int
+hashasu(char *key, int n)
+{
+ ulong h;
+
+ h = 0;
+ while(*key != 0)
+ h = 65599*h + *(uchar*)key++;
+ return h % n;
+}
+
+static void
+insert(Redir **tab, char *pat, char *repl)
+{
+ Redir **l;
+ Redir *srch;
+ ulong hash;
+
+ hash = hashasu(pat, HASHSIZE);
+ for(l = &tab[hash]; *l; l = &(*l)->next)
+ ;
+ *l = srch = ezalloc(sizeof(Redir));
+ srch->pat = pat;
+ srch->flags = decor2flags(repl);
+ srch->repl = undecorated(repl);
+ srch->next = 0;
+}
+
+static void
+cleartab(Redir **tab)
+{
+ Redir *t;
+ int i;
+
+ for(i = 0; i < HASHSIZE; i++){
+ while((t = tab[i]) != nil){
+ tab[i] = t->next;
+ free(t->pat);
+ free(t->repl);
+ free(t);
+ }
+ }
+}
+
+void
+redirectinit(void)
+{
+ static Biobuf *b = nil;
+ static Qid qid;
+ char *file, *line, *s, *host, *field[3];
+ static char pfx[] = "http://";
+
+ file = "/sys/lib/httpd.rewrite";
+ if(b != nil){
+ if(updateQid(Bfildes(b), &qid) == 0)
+ return;
+ Bterm(b);
+ }
+ b = Bopen(file, OREAD);
+ if(b == nil)
+ sysfatal("can't read from %s", file);
+ updateQid(Bfildes(b), &qid);
+
+ cleartab(redirtab);
+ cleartab(vhosttab);
+
+ while((line = Brdline(b, '\n')) != nil){
+ line[Blinelen(b)-1] = 0;
+ s = strchr(line, '#');
+ if(s != nil && (s == line || s[-1] == ' ' || s[-1] == '\t'))
+ *s = '\0'; /* chop comment iff after whitespace */
+ if(tokenize(line, field, nelem(field)) == 2){
+ if(strncmp(field[0], pfx, STRLEN(pfx)) == 0 &&
+ strncmp(undecorated(field[1]), pfx, STRLEN(pfx)) != 0){
+ /* url -> filename */
+ host = field[0] + STRLEN(pfx);
+ s = strrchr(host, '/');
+ if(s)
+ *s = 0; /* chop trailing slash */
+
+ insert(vhosttab, estrdup(host), estrdup(field[1]));
+ }else{
+ insert(redirtab, estrdup(field[0]), estrdup(field[1]));
+ }
+ }
+ }
+ syslog(0, HTTPLOG, "redirectinit pid=%d", getpid());
+}
+
+static Redir*
+lookup(Redir **tab, char *pat, int count)
+{
+ Redir *srch;
+ ulong hash;
+
+ hash = hashasu(pat,HASHSIZE);
+ for(srch = tab[hash]; srch != nil; srch = srch->next)
+ if(strcmp(pat, srch->pat) == 0) {
+ /* only exact match wanted? */
+ if (!(srch->flags & Redironly) || count == 0)
+ return srch;
+ }
+ return nil;
+}
+
+static char*
+prevslash(char *p, char *s)
+{
+ while(--s > p)
+ if(*s == '/')
+ break;
+ return s;
+}
+
+/*
+ * find the longest match of path against the redirection table,
+ * chopping off the rightmost path component until success or
+ * there's nothing left. return a copy of the replacement string
+ * concatenated with a slash and the portion of the path *not* matched.
+ * So a match of /who/gre/some/stuff.html matched against
+ * /who/gre http://gremlinsrus.org
+ * returns
+ * http://gremlinsrus.org/some/stuff.html
+ *
+ * further flags: if Redironly, match only the named page and no
+ * subordinate ones. if Redirsubord, map the named patch and any
+ * subordinate ones to the same replacement URL.
+ */
+char*
+redirect(HConnect *hc, char *path, uint *flagp)
+{
+ Redir *redir;
+ char *s, *newpath, *repl;
+ int c, n, count;
+
+ count = 0;
+ for(s = strchr(path, '\0'); s > path; s = prevslash(path, s)){
+ c = *s;
+ *s = '\0';
+ redir = lookup(redirtab, path, count++);
+ *s = c;
+ if(redir != nil){
+ if (flagp)
+ *flagp = redir->flags;
+ repl = redir->repl;
+ if(redir->flags & Redirsubord)
+ /* don't append s, all matches map to repl */
+ s = "";
+ n = strlen(repl) + strlen(s) + 2 + UTFmax;
+ newpath = halloc(hc, n);
+ snprint(newpath, n, "%s%s", repl, s);
+ return newpath;
+ }
+ }
+ return nil;
+}
+
+/*
+ * if host is virtual, return implicit prefix for URI within webroot.
+ * if not, return empty string.
+ * return value should not be freed by caller.
+ */
+char*
+masquerade(char *host)
+{
+ Redir *redir;
+
+ redir = lookup(vhosttab, host, 0);
+ if(redir == nil)
+ return emptystring;
+ return redir->repl;
+}
diff --git a/sys/src/cmd/ip/httpd/save.c b/sys/src/cmd/ip/httpd/save.c
new file mode 100755
index 000000000..d80323ae1
--- /dev/null
+++ b/sys/src/cmd/ip/httpd/save.c
@@ -0,0 +1,154 @@
+/*
+ * for GET or POST to /magic/save/foo.
+ * add incoming data to foo.data.
+ * send foo.html as reply.
+ *
+ * supports foo.data with "exclusive use" mode to prevent interleaved saves.
+ * thus http://cm.bell-labs.com/magic/save/t?args should access:
+ * -lrw-rw--w- M 21470 ehg web 1533 May 21 18:19 /usr/web/save/t.data
+ * --rw-rw-r-- M 21470 ehg web 73 May 21 18:17 /usr/web/save/t.html
+*/
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include "httpd.h"
+#include "httpsrv.h"
+
+enum
+{
+ MaxLog = 24*1024, /* limit on length of any one log request */
+ LockSecs = MaxLog/500, /* seconds to wait before giving up on opening the data file */
+};
+
+static int
+dangerous(char *s)
+{
+ if(s == nil)
+ return 1;
+
+ /*
+ * This check shouldn't be needed;
+ * filename folding is already supposed to have happened.
+ * But I'm paranoid.
+ */
+ while(s = strchr(s,'/')){
+ if(s[1]=='.' && s[2]=='.')
+ return 1;
+ s++;
+ }
+ return 0;
+}
+
+/*
+ * open a file which might be locked.
+ * if it is, spin until available
+ */
+int
+openLocked(char *file, int mode)
+{
+ char buf[ERRMAX];
+ int tries, fd;
+
+ for(tries = 0; tries < LockSecs*2; tries++){
+ fd = open(file, mode);
+ if(fd >= 0)
+ return fd;
+ errstr(buf, sizeof buf);
+ if(strstr(buf, "locked") == nil)
+ break;
+ sleep(500);
+ }
+ return -1;
+}
+
+void
+main(int argc, char **argv)
+{
+ HConnect *c;
+ Dir *dir;
+ Hio *hin, *hout;
+ char *s, *t, *fn;
+ int n, nfn, datafd, htmlfd;
+
+ c = init(argc, argv);
+
+ if(dangerous(c->req.uri)){
+ hfail(c, HSyntax);
+ exits("failed");
+ }
+
+ if(hparseheaders(c, HSTIMEOUT) < 0)
+ exits("failed");
+ hout = &c->hout;
+ if(c->head.expectother){
+ hfail(c, HExpectFail, nil);
+ exits("failed");
+ }
+ if(c->head.expectcont){
+ hprint(hout, "100 Continue\r\n");
+ hprint(hout, "\r\n");
+ hflush(hout);
+ }
+
+ s = nil;
+ if(strcmp(c->req.meth, "POST") == 0){
+ hin = hbodypush(&c->hin, c->head.contlen, c->head.transenc);
+ if(hin != nil){
+ alarm(HSTIMEOUT);
+ s = hreadbuf(hin, hin->pos);
+ alarm(0);
+ }
+ if(s == nil){
+ hfail(c, HBadReq, nil);
+ exits("failed");
+ }
+ t = strchr(s, '\n');
+ if(t != nil)
+ *t = '\0';
+ }else if(strcmp(c->req.meth, "GET") != 0 && strcmp(c->req.meth, "HEAD") != 0){
+ hunallowed(c, "GET, HEAD, PUT");
+ exits("unallowed");
+ }else
+ s = c->req.search;
+ if(s == nil){
+ hfail(c, HNoData, "save");
+ exits("failed");
+ }
+
+ if(strlen(s) > MaxLog)
+ s[MaxLog] = '\0';
+ n = snprint(c->xferbuf, HBufSize, "at %ld %s\n", time(0), s);
+
+
+ nfn = strlen(c->req.uri) + 64;
+ fn = halloc(c, nfn);
+
+ /*
+ * open file descriptors & write log line
+ */
+ snprint(fn, nfn, "/usr/web/save/%s.html", c->req.uri);
+ htmlfd = open(fn, OREAD);
+ if(htmlfd < 0 || (dir = dirfstat(htmlfd)) == nil){
+ hfail(c, HNotFound, c->req.uri);
+ exits("failed");
+ return;
+ }
+
+ snprint(fn, nfn, "/usr/web/save/%s.data", c->req.uri);
+ datafd = openLocked(fn, OWRITE);
+ if(datafd < 0){
+ errstr(c->xferbuf, sizeof c->xferbuf);
+ if(strstr(c->xferbuf, "locked") != nil)
+ hfail(c, HTempFail, c->req.uri);
+ else
+ hfail(c, HNotFound, c->req.uri);
+ exits("failed");
+ }
+ seek(datafd, 0, 2);
+ write(datafd, c->xferbuf, n);
+ close(datafd);
+
+ sendfd(c, htmlfd, dir, hmkcontent(c, "text", "html", nil), nil);
+
+ exits(nil);
+}
diff --git a/sys/src/cmd/ip/httpd/sendfd.c b/sys/src/cmd/ip/httpd/sendfd.c
new file mode 100755
index 000000000..6ae4e7d73
--- /dev/null
+++ b/sys/src/cmd/ip/httpd/sendfd.c
@@ -0,0 +1,462 @@
+#include <u.h>
+#include <libc.h>
+#include <auth.h>
+#include "httpd.h"
+#include "httpsrv.h"
+
+static void printtype(Hio *hout, HContent *type, HContent *enc);
+
+/*
+ * these should be done better; see the reponse codes in /lib/rfc/rfc2616 for
+ * more info on what should be included.
+ */
+#define UNAUTHED "You are not authorized to see this area.\n"
+#define NOCONTENT "No acceptable type of data is available.\n"
+#define NOENCODE "No acceptable encoding of the contents is available.\n"
+#define UNMATCHED "The entity requested does not match the existing entity.\n"
+#define BADRANGE "No bytes are avaible for the range you requested.\n"
+
+/*
+ * fd references a file which has been authorized & checked for relocations.
+ * send back the headers & its contents.
+ * includes checks for conditional requests & ranges.
+ */
+int
+sendfd(HConnect *c, int fd, Dir *dir, HContent *type, HContent *enc)
+{
+ Qid qid;
+ HRange *r;
+ HContents conts;
+ Hio *hout;
+ char *boundary, etag[32];
+ long mtime;
+ ulong tr;
+ int n, nw, multir, ok;
+ vlong wrote, length;
+
+ hout = &c->hout;
+ length = dir->length;
+ mtime = dir->mtime;
+ qid = dir->qid;
+ free(dir);
+
+ /*
+ * figure out the type of file and send headers
+ */
+ n = -1;
+ r = nil;
+ multir = 0;
+ boundary = nil;
+ if(c->req.vermaj){
+ if(type == nil && enc == nil){
+ conts = uriclass(c, c->req.uri);
+ type = conts.type;
+ enc = conts.encoding;
+ if(type == nil && enc == nil){
+ n = read(fd, c->xferbuf, HBufSize-1);
+ if(n > 0){
+ c->xferbuf[n] = '\0';
+ conts = dataclass(c, c->xferbuf, n);
+ type = conts.type;
+ enc = conts.encoding;
+ }
+ }
+ }
+ if(type == nil)
+ type = hmkcontent(c, "application", "octet-stream", nil);
+
+ snprint(etag, sizeof(etag), "\"%lluxv%lux\"", qid.path, qid.vers);
+ ok = checkreq(c, type, enc, mtime, etag);
+ if(ok <= 0){
+ close(fd);
+ return ok;
+ }
+
+ /*
+ * check for if-range requests
+ */
+ if(c->head.range == nil
+ || c->head.ifrangeetag != nil && !etagmatch(1, c->head.ifrangeetag, etag)
+ || c->head.ifrangedate != 0 && c->head.ifrangedate != mtime){
+ c->head.range = nil;
+ c->head.ifrangeetag = nil;
+ c->head.ifrangedate = 0;
+ }
+
+ if(c->head.range != nil){
+ c->head.range = fixrange(c->head.range, length);
+ if(c->head.range == nil){
+ if(c->head.ifrangeetag == nil && c->head.ifrangedate == 0){
+ hprint(hout, "%s 416 Request range not satisfiable\r\n", hversion);
+ hprint(hout, "Date: %D\r\n", time(nil));
+ hprint(hout, "Server: Plan9\r\n");
+ hprint(hout, "Content-Range: bytes */%lld\r\n", length);
+ hprint(hout, "Content-Length: %d\r\n", STRLEN(BADRANGE));
+ hprint(hout, "Content-Type: text/html\r\n");
+ if(c->head.closeit)
+ hprint(hout, "Connection: close\r\n");
+ else if(!http11(c))
+ hprint(hout, "Connection: Keep-Alive\r\n");
+ hprint(hout, "\r\n");
+ if(strcmp(c->req.meth, "HEAD") != 0)
+ hprint(hout, "%s", BADRANGE);
+ hflush(hout);
+ writelog(c, "Reply: 416 Request range not satisfiable\n");
+ close(fd);
+ return 1;
+ }
+ c->head.ifrangeetag = nil;
+ c->head.ifrangedate = 0;
+ }
+ }
+ if(c->head.range == nil)
+ hprint(hout, "%s 200 OK\r\n", hversion);
+ else
+ hprint(hout, "%s 206 Partial Content\r\n", hversion);
+
+ hprint(hout, "Server: Plan9\r\n");
+ hprint(hout, "Date: %D\r\n", time(nil));
+ hprint(hout, "ETag: %s\r\n", etag);
+
+ /*
+ * can't send some entity headers if partially responding
+ * to an if-range: etag request
+ */
+ r = c->head.range;
+ if(r == nil)
+ hprint(hout, "Content-Length: %lld\r\n", length);
+ else if(r->next == nil){
+ hprint(hout, "Content-Range: bytes %ld-%ld/%lld\r\n", r->start, r->stop, length);
+ hprint(hout, "Content-Length: %ld\r\n", r->stop - r->start);
+ }else{
+ multir = 1;
+ boundary = hmkmimeboundary(c);
+ hprint(hout, "Content-Type: multipart/byteranges; boundary=%s\r\n", boundary);
+ }
+ if(c->head.ifrangeetag == nil){
+ hprint(hout, "Last-Modified: %D\r\n", mtime);
+ if(!multir)
+ printtype(hout, type, enc);
+ if(c->head.fresh_thresh)
+ hintprint(c, hout, c->req.uri, c->head.fresh_thresh, c->head.fresh_have);
+ }
+
+ if(c->head.closeit)
+ hprint(hout, "Connection: close\r\n");
+ else if(!http11(c))
+ hprint(hout, "Connection: Keep-Alive\r\n");
+ hprint(hout, "\r\n");
+ }
+ if(strcmp(c->req.meth, "HEAD") == 0){
+ if(c->head.range == nil)
+ writelog(c, "Reply: 200 file 0\n");
+ else
+ writelog(c, "Reply: 206 file 0\n");
+ hflush(hout);
+ close(fd);
+ return 1;
+ }
+
+ /*
+ * send the file if it's a normal file
+ */
+ if(r == nil){
+ hflush(hout);
+
+ wrote = 0;
+ if(n > 0)
+ wrote = write(hout->fd, c->xferbuf, n);
+ if(n <= 0 || wrote == n){
+ while((n = read(fd, c->xferbuf, HBufSize)) > 0){
+ nw = write(hout->fd, c->xferbuf, n);
+ if(nw != n){
+ if(nw > 0)
+ wrote += nw;
+ break;
+ }
+ wrote += nw;
+ }
+ }
+ writelog(c, "Reply: 200 file %lld %lld\n", length, wrote);
+ close(fd);
+ if(length == wrote)
+ return 1;
+ return -1;
+ }
+
+ /*
+ * for multipart/byterange messages,
+ * it is not ok for the boundary string to appear within a message part.
+ * however, it probably doesn't matter, since there are lengths for every part.
+ */
+ wrote = 0;
+ ok = 1;
+ for(; r != nil; r = r->next){
+ if(multir){
+ hprint(hout, "\r\n--%s\r\n", boundary);
+ printtype(hout, type, enc);
+ hprint(hout, "Content-Range: bytes %ld-%ld/%lld\r\n", r->start, r->stop, length);
+ hprint(hout, "Content-Length: %ld\r\n", r->stop - r->start);
+ hprint(hout, "\r\n");
+ }
+ hflush(hout);
+
+ if(seek(fd, r->start, 0) != r->start){
+ ok = -1;
+ break;
+ }
+ for(tr = r->stop - r->start + 1; tr; tr -= n){
+ n = tr;
+ if(n > HBufSize)
+ n = HBufSize;
+ if(read(fd, c->xferbuf, n) != n){
+ ok = -1;
+ goto breakout;
+ }
+ nw = write(hout->fd, c->xferbuf, n);
+ if(nw != n){
+ if(nw > 0)
+ wrote += nw;
+ ok = -1;
+ goto breakout;
+ }
+ wrote += nw;
+ }
+ }
+breakout:;
+ if(r == nil){
+ if(multir){
+ hprint(hout, "--%s--\r\n", boundary);
+ hflush(hout);
+ }
+ writelog(c, "Reply: 206 partial content %lld %lld\n", length, wrote);
+ }else
+ writelog(c, "Reply: 206 partial content, early termination %lld %lld\n", length, wrote);
+ close(fd);
+ return ok;
+}
+
+static void
+printtype(Hio *hout, HContent *type, HContent *enc)
+{
+ hprint(hout, "Content-Type: %s/%s", type->generic, type->specific);
+/*
+ if(cistrcmp(type->generic, "text") == 0)
+ hprint(hout, ";charset=utf-8");
+*/
+ hprint(hout, "\r\n");
+ if(enc != nil)
+ hprint(hout, "Content-Encoding: %s\r\n", enc->generic);
+}
+
+int
+etagmatch(int strong, HETag *tags, char *e)
+{
+ char *s, *t;
+
+ for(; tags != nil; tags = tags->next){
+ if(strong && tags->weak)
+ continue;
+ s = tags->etag;
+ if(s[0] == '*' && s[1] == '\0')
+ return 1;
+
+ t = e + 1;
+ while(*t != '"'){
+ if(*s != *t)
+ break;
+ s++;
+ t++;
+ }
+
+ if(*s == '\0' && *t == '"')
+ return 1;
+ }
+ return 0;
+}
+
+static char *
+acceptcont(char *s, char *e, HContent *ok, char *which)
+{
+ char *sep;
+
+ if(ok == nil)
+ return seprint(s, e, "Your browser accepts any %s.<br>\n", which);
+ s = seprint(s, e, "Your browser accepts %s: ", which);
+ sep = "";
+ for(; ok != nil; ok = ok->next){
+ if(ok->specific)
+ s = seprint(s, e, "%s%s/%s", sep, ok->generic, ok->specific);
+ else
+ s = seprint(s, e, "%s%s", sep, ok->generic);
+ sep = ", ";
+ }
+ return seprint(s, e, ".<br>\n");
+}
+
+/*
+ * send back a nice error message if the content is unacceptable
+ * to get this message in ie, go to tools, internet options, advanced,
+ * and turn off Show Friendly HTTP Error Messages under the Browsing category
+ */
+static int
+notaccept(HConnect *c, HContent *type, HContent *enc, char *which)
+{
+ Hio *hout;
+ char *s, *e;
+
+ hout = &c->hout;
+ e = &c->xferbuf[HBufSize];
+ s = c->xferbuf;
+ s = seprint(s, e, "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\">\n");
+ s = seprint(s, e, "<html>\n<title>Unacceptable %s</title>\n<body>\n", which);
+ s = seprint(s, e, "Your browser will not accept this data, %H, because of its %s.<br>\n", c->req.uri, which);
+ s = seprint(s, e, "Its Content-Type is %s/%s", type->generic, type->specific);
+ if(enc != nil)
+ s = seprint(s, e, ", and Content-Encoding is %s", enc->generic);
+ s = seprint(s, e, ".<br>\n\n");
+
+ s = acceptcont(s, e, c->head.oktype, "Content-Type");
+ s = acceptcont(s, e, c->head.okencode, "Content-Encoding");
+ s = seprint(s, e, "</body>\n</html>\n");
+
+ hprint(hout, "%s 406 Not Acceptable\r\n", hversion);
+ hprint(hout, "Server: Plan9\r\n");
+ hprint(hout, "Date: %D\r\n", time(nil));
+ hprint(hout, "Content-Type: text/html\r\n");
+ hprint(hout, "Content-Length: %lud\r\n", s - c->xferbuf);
+ if(c->head.closeit)
+ hprint(hout, "Connection: close\r\n");
+ else if(!http11(c))
+ hprint(hout, "Connection: Keep-Alive\r\n");
+ hprint(hout, "\r\n");
+ if(strcmp(c->req.meth, "HEAD") != 0)
+ hwrite(hout, c->xferbuf, s - c->xferbuf);
+ writelog(c, "Reply: 406 Not Acceptable\nReason: %s\n", which);
+ return hflush(hout);
+}
+
+/*
+ * check time and entity tag conditions.
+ */
+int
+checkreq(HConnect *c, HContent *type, HContent *enc, long mtime, char *etag)
+{
+ Hio *hout;
+ int m;
+
+ hout = &c->hout;
+ if(c->req.vermaj >= 1 && c->req.vermin >= 1 && !hcheckcontent(type, c->head.oktype, "Content-Type", 0))
+ return notaccept(c, type, enc, "Content-Type");
+ if(c->req.vermaj >= 1 && c->req.vermin >= 1 && !hcheckcontent(enc, c->head.okencode, "Content-Encoding", 0))
+ return notaccept(c, type, enc, "Content-Encoding");
+
+ /*
+ * can use weak match only with get or head;
+ * this always uses strong matches
+ */
+ m = etagmatch(1, c->head.ifnomatch, etag);
+
+ if(m && strcmp(c->req.meth, "GET") != 0 && strcmp(c->req.meth, "HEAD") != 0
+ || c->head.ifunmodsince && c->head.ifunmodsince < mtime
+ || c->head.ifmatch != nil && !etagmatch(1, c->head.ifmatch, etag)){
+ hprint(hout, "%s 412 Precondition Failed\r\n", hversion);
+ hprint(hout, "Server: Plan9\r\n");
+ hprint(hout, "Date: %D\r\n", time(nil));
+ hprint(hout, "Content-Type: text/html\r\n");
+ hprint(hout, "Content-Length: %d\r\n", STRLEN(UNMATCHED));
+ if(c->head.closeit)
+ hprint(hout, "Connection: close\r\n");
+ else if(!http11(c))
+ hprint(hout, "Connection: Keep-Alive\r\n");
+ hprint(hout, "\r\n");
+ if(strcmp(c->req.meth, "HEAD") != 0)
+ hprint(hout, "%s", UNMATCHED);
+ writelog(c, "Reply: 412 Precondition Failed\n");
+ return hflush(hout);
+ }
+
+ if(c->head.ifmodsince >= mtime
+ && (m || c->head.ifnomatch == nil)){
+ /*
+ * can only send back Date, ETag, Content-Location,
+ * Expires, Cache-Control, and Vary entity-headers
+ */
+ hprint(hout, "%s 304 Not Modified\r\n", hversion);
+ hprint(hout, "Server: Plan9\r\n");
+ hprint(hout, "Date: %D\r\n", time(nil));
+ hprint(hout, "ETag: %s\r\n", etag);
+ if(c->head.closeit)
+ hprint(hout, "Connection: close\r\n");
+ else if(!http11(c))
+ hprint(hout, "Connection: Keep-Alive\r\n");
+ hprint(hout, "\r\n");
+ writelog(c, "Reply: 304 Not Modified\n");
+ return hflush(hout);
+ }
+ return 1;
+}
+
+/*
+ * length is the actual length of the entity requested.
+ * discard any range requests which are invalid,
+ * ie start after the end, or have stop before start.
+ * rewrite suffix requests
+ */
+HRange*
+fixrange(HRange *h, long length)
+{
+ HRange *r, *rr;
+
+ if(length == 0)
+ return nil;
+
+ /*
+ * rewrite each range to reflect the actual length of the file
+ * toss out any invalid ranges
+ */
+ rr = nil;
+ for(r = h; r != nil; r = r->next){
+ if(r->suffix){
+ r->start = length - r->stop;
+ if(r->start >= length)
+ r->start = 0;
+ r->stop = length - 1;
+ r->suffix = 0;
+ }
+ if(r->stop >= length)
+ r->stop = length - 1;
+ if(r->start > r->stop){
+ if(rr == nil)
+ h = r->next;
+ else
+ rr->next = r->next;
+ }else
+ rr = r;
+ }
+
+ /*
+ * merge consecutive overlapping or abutting ranges
+ *
+ * not clear from rfc2616 how much merging needs to be done.
+ * this code merges only if a range is adjacent to a later starting,
+ * over overlapping or abutting range. this allows a client
+ * to request wanted data first, followed by other data.
+ * this may be useful then fetching part of a page, then the adjacent regions.
+ */
+ if(h == nil)
+ return h;
+ r = h;
+ for(;;){
+ rr = r->next;
+ if(rr == nil)
+ break;
+ if(r->start <= rr->start && r->stop + 1 >= rr->start){
+ if(r->stop < rr->stop)
+ r->stop = rr->stop;
+ r->next = rr->next;
+ }else
+ r = rr;
+ }
+ return h;
+}
diff --git a/sys/src/cmd/ip/httpd/webls.c b/sys/src/cmd/ip/httpd/webls.c
new file mode 100755
index 000000000..0da5d5e68
--- /dev/null
+++ b/sys/src/cmd/ip/httpd/webls.c
@@ -0,0 +1,335 @@
+#include <u.h>
+#include <libc.h>
+#include <ctype.h>
+#include <bio.h>
+#include <regexp.h>
+#include <fcall.h>
+#include "httpd.h"
+#include "httpsrv.h"
+
+static Hio *hout;
+static Hio houtb;
+static HConnect *connect;
+static int vermaj, gidwidth, uidwidth, lenwidth, devwidth;
+static Biobuf *aio, *dio;
+
+static void
+doctype(void)
+{
+ hprint(hout, "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\"\n");
+ hprint(hout, " \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n");
+}
+
+void
+error(char *title, char *fmt, ...)
+{
+ va_list arg;
+ char buf[1024], *out;
+
+ va_start(arg, fmt);
+ out = vseprint(buf, buf+sizeof(buf), fmt, arg);
+ va_end(arg);
+ *out = 0;
+
+ hprint(hout, "%s 404 %s\r\n", hversion, title);
+ hprint(hout, "Date: %D\r\n", time(nil));
+ hprint(hout, "Server: Plan9\r\n");
+ hprint(hout, "Content-type: text/html\r\n");
+ hprint(hout, "\r\n");
+ doctype();
+ hprint(hout, "<html xmlns=\"http://www.w3.org/1999/xhtml\">\n");
+ hprint(hout, "<head><title>%s</title></head>\n", title);
+ hprint(hout, "<body>\n");
+ hprint(hout, "<h1>%s</h1>\n", title);
+ hprint(hout, "%s\n", buf);
+ hprint(hout, "</body>\n");
+ hprint(hout, "</html>\n");
+ hflush(hout);
+ writelog(connect, "Reply: 404\nReason: %s\n", title);
+ exits(nil);
+}
+
+/*
+ * Are we actually allowed to look in here?
+ *
+ * Rules:
+ * 1) If neither allowed nor denied files exist, access is granted.
+ * 2) If allowed exists and denied does not, dir *must* be in allowed
+ * for access to be granted, otherwise, access is denied.
+ * 3) If denied exists and allowed does not, dir *must not* be in
+ * denied for access to be granted, otherwise, access is enied.
+ * 4) If both exist, okay if either (a) file is not in denied, or
+ * (b) in denied and in allowed. Otherwise, access is denied.
+ */
+static Reprog *
+getre(Biobuf *buf)
+{
+ Reprog *re;
+ char *p, *t;
+ char *bbuf;
+ int n;
+
+ if (buf == nil)
+ return(nil);
+ for ( ; ; free(p)) {
+ p = Brdstr(buf, '\n', 0);
+ if (p == nil)
+ return(nil);
+ t = strchr(p, '#');
+ if (t != nil)
+ *t = '\0';
+ t = p + strlen(p);
+ while (--t > p && isspace(*t))
+ *t = '\0';
+ n = strlen(p);
+ if (n == 0)
+ continue;
+
+ /* root the regular expresssion */
+ bbuf = malloc(n+2);
+ if(bbuf == nil)
+ sysfatal("out of memory");
+ bbuf[0] = '^';
+ strcpy(bbuf+1, p);
+ re = regcomp(bbuf);
+ free(bbuf);
+
+ if (re == nil)
+ continue;
+ free(p);
+ return(re);
+ }
+}
+
+static int
+allowed(char *dir)
+{
+ Reprog *re;
+ int okay;
+ Resub match;
+
+ if (strcmp(dir, "..") == 0 || strncmp(dir, "../", 3) == 0)
+ return(0);
+ if (aio == nil)
+ return(0);
+
+ if (aio != nil)
+ Bseek(aio, 0, 0);
+ if (dio != nil)
+ Bseek(dio, 0, 0);
+
+ /* if no deny list, assume everything is denied */
+ okay = (dio != nil);
+
+ /* go through denials till we find a match */
+ while (okay && (re = getre(dio)) != nil) {
+ memset(&match, 0, sizeof(match));
+ okay = (regexec(re, dir, &match, 1) != 1);
+ free(re);
+ }
+
+ /* go through accepts till we have a match */
+ if (aio == nil)
+ return(okay);
+ while (!okay && (re = getre(aio)) != nil) {
+ memset(&match, 0, sizeof(match));
+ okay = (regexec(re, dir, &match, 1) == 1);
+ free(re);
+ }
+ return(okay);
+}
+
+/*
+ * Comparison routine for sorting the directory.
+ */
+static int
+compar(Dir *a, Dir *b)
+{
+ return(strcmp(a->name, b->name));
+}
+
+/*
+ * These is for formating; how wide are variable-length
+ * fields?
+ */
+static void
+maxwidths(Dir *dp, long n)
+{
+ long i;
+ char scratch[64];
+
+ for (i = 0; i < n; i++) {
+ if (snprint(scratch, sizeof scratch, "%ud", dp[i].dev) > devwidth)
+ devwidth = strlen(scratch);
+ if (strlen(dp[i].uid) > uidwidth)
+ uidwidth = strlen(dp[i].uid);
+ if (strlen(dp[i].gid) > gidwidth)
+ gidwidth = strlen(dp[i].gid);
+ if (snprint(scratch, sizeof scratch, "%lld", dp[i].length) > lenwidth)
+ lenwidth = strlen(scratch);
+ }
+}
+
+/*
+ * Do an actual directory listing.
+ * asciitime is lifted directly out of ls.
+ */
+char *
+asciitime(long l)
+{
+ ulong clk;
+ static char buf[32];
+ char *t;
+
+ clk = time(nil);
+ t = ctime(l);
+ /* 6 months in the past or a day in the future */
+ if(l<clk-180L*24*60*60 || clk+24L*60*60<l){
+ memmove(buf, t+4, 7); /* month and day */
+ memmove(buf+7, t+23, 5); /* year */
+ }else
+ memmove(buf, t+4, 12); /* skip day of week */
+ buf[12] = 0;
+ return buf;
+}
+
+static void
+dols(char *dir)
+{
+ Dir *d;
+ char *f, *p,*nm;
+ long i, n;
+ int fd;
+
+ cleanname(dir); // expands "" to "."; ``dir+1'' access below depends on that
+ if (!allowed(dir)) {
+ error("Permission denied", "<p>Cannot list directory %s: Access prohibited</p>", dir);
+ return;
+ }
+ fd = open(dir, OREAD);
+ if (fd < 0) {
+ error("Cannot read directory", "<p>Cannot read directory %s: %r</p>", dir);
+ return;
+ }
+ if (vermaj) {
+ hokheaders(connect);
+ hprint(hout, "Content-type: text/html\r\n");
+ hprint(hout, "\r\n");
+ }
+ doctype();
+ hprint(hout, "<html xmlns=\"http://www.w3.org/1999/xhtml\">\n");
+ hprint(hout, "<head><title>Index of %s</title></head>\n", dir);
+ hprint(hout, "<body>\n");
+ hprint(hout, "<h1>Index of ");
+ nm = dir;
+ while((p = strchr(nm, '/')) != nil){
+ *p = '\0';
+ f = (*dir == '\0') ? "/" : dir;
+ if (!(*dir == '\0' && *(dir+1) == '\0') && allowed(f))
+ hprint(hout, "<a href=\"/magic/webls?dir=%H\">%s/</a>", f, nm);
+ else
+ hprint(hout, "%s/", nm);
+ *p = '/';
+ nm = p+1;
+ }
+ hprint(hout, "%s</h1>\n", nm);
+ n = dirreadall(fd, &d);
+ close(fd);
+ maxwidths(d, n);
+ qsort(d, n, sizeof(Dir), (int (*)(void *, void *))compar);
+ hprint(hout, "<pre>\n");
+ for (i = 0; i < n; i++) {
+ f = smprint("%s/%s", dir, d[i].name);
+ cleanname(f);
+ if (d[i].mode & DMDIR) {
+ p = smprint("/magic/webls?dir=%H", f);
+ free(f);
+ f = p;
+ }
+ hprint(hout, "%M %C %*ud %-*s %-*s %*lld %s <a href=\"%s\">%s</a>\n",
+ d[i].mode, d[i].type,
+ devwidth, d[i].dev,
+ uidwidth, d[i].uid,
+ gidwidth, d[i].gid,
+ lenwidth, d[i].length,
+ asciitime(d[i].mtime), f, d[i].name);
+ free(f);
+ }
+ f = smprint("%s/..", dir);
+ cleanname(f);
+ if (strcmp(f, dir) != 0 && allowed(f))
+ hprint(hout, "\nGo to <a href=\"/magic/webls?dir=%H\">parent</a> directory\n", f);
+ else
+ hprint(hout, "\nEnd of directory listing\n");
+ free(f);
+ hprint(hout, "</pre>\n</body>\n</html>\n");
+ hflush(hout);
+ free(d);
+}
+
+/*
+ * Handle unpacking the request in the URI and
+ * invoking the actual handler.
+ */
+static void
+dosearch(char *search)
+{
+ if (strncmp(search, "dir=", 4) == 0){
+ search = hurlunesc(connect, search+4);
+ dols(search);
+ return;
+ }
+
+ /*
+ * Otherwise, we've gotten an illegal request.
+ * spit out a non-apologetic error.
+ */
+ search = hurlunesc(connect, search);
+ error("Bad directory listing request",
+ "<p>Illegal formatted directory listing request:</p>\n"
+ "<p>%H</p>", search);
+}
+
+void
+main(int argc, char **argv)
+{
+ fmtinstall('H', httpfmt);
+ fmtinstall('U', hurlfmt);
+ fmtinstall('M', dirmodefmt);
+
+ aio = Bopen("/sys/lib/webls.allowed", OREAD);
+ dio = Bopen("/sys/lib/webls.denied", OREAD);
+
+ if(argc == 2){
+ hinit(&houtb, 1, Hwrite);
+ hout = &houtb;
+ dols(argv[1]);
+ exits(nil);
+ }
+ close(2);
+
+ connect = init(argc, argv);
+ hout = &connect->hout;
+ vermaj = connect->req.vermaj;
+ if(hparseheaders(connect, HSTIMEOUT) < 0)
+ exits("failed");
+
+ if(strcmp(connect->req.meth, "GET") != 0 && strcmp(connect->req.meth, "HEAD") != 0){
+ hunallowed(connect, "GET, HEAD");
+ exits("not allowed");
+ }
+ if(connect->head.expectother || connect->head.expectcont){
+ hfail(connect, HExpectFail, nil);
+ exits("failed");
+ }
+
+ bind(webroot, "/", MREPL);
+
+ if(connect->req.search != nil)
+ dosearch(connect->req.search);
+ else
+ error("Bad argument", "<p>Need a search argument</p>");
+ hflush(hout);
+ writelog(connect, "200 webls %ld %ld\n", hout->seek, hout->seek);
+ exits(nil);
+}
diff --git a/sys/src/cmd/ip/httpd/webls.denied b/sys/src/cmd/ip/httpd/webls.denied
new file mode 100755
index 000000000..8d98f9deb
--- /dev/null
+++ b/sys/src/cmd/ip/httpd/webls.denied
@@ -0,0 +1 @@
+.*
diff --git a/sys/src/cmd/ip/httpd/wikipost.c b/sys/src/cmd/ip/httpd/wikipost.c
new file mode 100755
index 000000000..31fbedda1
--- /dev/null
+++ b/sys/src/cmd/ip/httpd/wikipost.c
@@ -0,0 +1,305 @@
+/*
+ * Accept new wiki pages or modifications to existing ones via POST method.
+ *
+ * Talks to the server at /srv/wiki.service.
+ */
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include "httpd.h"
+#include "httpsrv.h"
+
+#define LOG "wiki"
+
+HConnect *hc;
+HSPriv *hp;
+
+
+/* go from possibly-latin1 url with escapes to utf */
+char *
+_urlunesc(char *s)
+{
+ char *t, *v, *u;
+ Rune r;
+ int c, n;
+
+ /* unescape */
+ u = halloc(hc, strlen(s)+1);
+ for(t = u; c = *s; s++){
+ if(c == '%'){
+ n = s[1];
+ if(n >= '0' && n <= '9')
+ n = n - '0';
+ else if(n >= 'A' && n <= 'F')
+ n = n - 'A' + 10;
+ else if(n >= 'a' && n <= 'f')
+ n = n - 'a' + 10;
+ else
+ break;
+ r = n;
+ n = s[2];
+ if(n >= '0' && n <= '9')
+ n = n - '0';
+ else if(n >= 'A' && n <= 'F')
+ n = n - 'A' + 10;
+ else if(n >= 'a' && n <= 'f')
+ n = n - 'a' + 10;
+ else
+ break;
+ s += 2;
+ c = r*16+n;
+ }
+ *t++ = c;
+ }
+ *t = 0;
+
+ /* latin1 heuristic */
+ v = halloc(hc, UTFmax*strlen(u) + 1);
+ s = u;
+ t = v;
+ while(*s){
+ /* in decoding error, assume latin1 */
+ if((n=chartorune(&r, s)) == 1 && r == 0x80)
+ r = *s;
+ s += n;
+ t += runetochar(t, &r);
+ }
+ *t = 0;
+
+ return v;
+}
+
+enum
+{
+ MaxLog = 100*1024, /* limit on length of any one log request */
+};
+
+static int
+dangerous(char *s)
+{
+ if(s == nil)
+ return 1;
+
+ /*
+ * This check shouldn't be needed;
+ * filename folding is already supposed to have happened.
+ * But I'm paranoid.
+ */
+ while(s = strchr(s,'/')){
+ if(s[1]=='.' && s[2]=='.')
+ return 1;
+ s++;
+ }
+ return 0;
+}
+
+char*
+unhttp(char *s)
+{
+ char *p, *r, *w;
+
+ if(s == nil)
+ return nil;
+
+ for(p=s; *p; p++)
+ if(*p=='+')
+ *p = ' ';
+ s = _urlunesc(s);
+
+ for(r=w=s; *r; r++){
+ if(*r != '\r')
+ *w++ = *r;
+ }
+ *w = '\0';
+ return s;
+}
+
+void
+mountwiki(HConnect *c, char *service)
+{
+ char buf[128];
+ int fd;
+
+ /* already in (possibly private) namespace? */
+ snprint(buf, sizeof buf, "/mnt/wiki.%s/new", service);
+ if (access(buf, AREAD) == 0){
+ if (bind(buf, "/mnt/wiki", MREPL) < 0){
+ syslog(0, LOG, "%s bind /mnt/wiki failed: %r",
+ hp->remotesys);
+ hfail(c, HNotFound);
+ exits("bind /mnt/wiki failed");
+ }
+ return;
+ }
+
+ /* old way: public wikifs from /srv */
+ snprint(buf, sizeof buf, "/srv/wiki.%s", service);
+ if((fd = open(buf, ORDWR)) < 0){
+ syslog(0, LOG, "%s open %s failed: %r", buf, hp->remotesys);
+ hfail(c, HNotFound);
+ exits("failed");
+ }
+ if(mount(fd, -1, "/mnt/wiki", MREPL, "") < 0){
+ syslog(0, LOG, "%s mount /mnt/wiki failed: %r", hp->remotesys);
+ hfail(c, HNotFound);
+ exits("failed");
+ }
+ close(fd);
+}
+
+char*
+dowiki(HConnect *c, char *title, char *author, char *comment, char *base, ulong version, char *text)
+{
+ int fd, l, n, err;
+ char *p, tmp[256];
+int i;
+
+ if((fd = open("/mnt/wiki/new", ORDWR)) < 0){
+ syslog(0, LOG, "%s open /mnt/wiki/new failed: %r", hp->remotesys);
+ hfail(c, HNotFound);
+ exits("failed");
+ }
+
+i=0;
+ if((i++,fprint(fd, "%s\nD%lud\nA%s (%s)\n", title, version, author, hp->remotesys) < 0)
+ || (i++,(comment && comment[0] && fprint(fd, "C%s\n", comment) < 0))
+ || (i++,fprint(fd, "\n") < 0)
+ || (i++,(text[0] && write(fd, text, strlen(text)) != strlen(text)))){
+ syslog(0, LOG, "%s write failed %d %ld fd %d: %r", hp->remotesys, i, strlen(text), fd);
+ hfail(c, HInternal);
+ exits("failed");
+ }
+
+ err = write(fd, "", 0);
+ if(err)
+ syslog(0, LOG, "%s commit failed %d: %r", hp->remotesys, err);
+
+ seek(fd, 0, 0);
+ if((n = read(fd, tmp, sizeof(tmp)-1)) <= 0){
+ if(n == 0)
+ werrstr("short read");
+ syslog(0, LOG, "%s read failed: %r", hp->remotesys);
+ hfail(c, HInternal);
+ exits("failed");
+ }
+
+ tmp[n] = '\0';
+
+ p = halloc(c, l=strlen(base)+strlen(tmp)+40);
+ snprint(p, l, "%s/%s/%s.html", base, tmp, err ? "werror" : "index");
+ return p;
+}
+
+
+void
+main(int argc, char **argv)
+{
+ Hio *hin, *hout;
+ char *s, *t, *p, *f[10];
+ char *text, *title, *service, *base, *author, *comment, *url;
+ int i, nf;
+ ulong version;
+
+ hc = init(argc, argv);
+ hp = hc->private;
+
+ if(dangerous(hc->req.uri)){
+ hfail(hc, HSyntax);
+ exits("failed");
+ }
+
+ if(hparseheaders(hc, HSTIMEOUT) < 0)
+ exits("failed");
+ hout = &hc->hout;
+ if(hc->head.expectother){
+ hfail(hc, HExpectFail, nil);
+ exits("failed");
+ }
+ if(hc->head.expectcont){
+ hprint(hout, "100 Continue\r\n");
+ hprint(hout, "\r\n");
+ hflush(hout);
+ }
+
+ s = nil;
+ if(strcmp(hc->req.meth, "POST") == 0){
+ hin = hbodypush(&hc->hin, hc->head.contlen, hc->head.transenc);
+ if(hin != nil){
+ alarm(15*60*1000);
+ s = hreadbuf(hin, hin->pos);
+ alarm(0);
+ }
+ if(s == nil){
+ hfail(hc, HBadReq, nil);
+ exits("failed");
+ }
+ t = strchr(s, '\n');
+ if(t != nil)
+ *t = '\0';
+ }else{
+ hunallowed(hc, "GET, HEAD, PUT");
+ exits("unallowed");
+ }
+
+ if(s == nil){
+ hfail(hc, HNoData, "wiki");
+ exits("failed");
+ }
+
+ text = nil;
+ title = nil;
+ service = nil;
+ author = "???";
+ comment = "";
+ base = nil;
+ version = ~0;
+ nf = getfields(s, f, nelem(f), 1, "&");
+ for(i=0; i<nf; i++){
+ if((p = strchr(f[i], '=')) == nil)
+ continue;
+ *p++ = '\0';
+ if(strcmp(f[i], "title")==0)
+ title = p;
+ else if(strcmp(f[i], "version")==0)
+ version = strtoul(unhttp(p), 0, 10);
+ else if(strcmp(f[i], "text")==0)
+ text = p;
+ else if(strcmp(f[i], "service")==0)
+ service = p;
+ else if(strcmp(f[i], "comment")==0)
+ comment = p;
+ else if(strcmp(f[i], "author")==0)
+ author = p;
+ else if(strcmp(f[i], "base")==0)
+ base = p;
+ }
+
+ syslog(0, LOG, "%s post s %s t '%s' v %ld a %s c %s b %s t 0x%p",
+ hp->remotesys, service, title, (long)version, author, comment, base, text);
+
+ title = unhttp(title);
+ comment = unhttp(comment);
+ service = unhttp(service);
+ text = unhttp(text);
+ author = unhttp(author);
+ base = unhttp(base);
+
+ if(title==nil || version==~0 || text==nil || text[0]=='\0' || base == nil
+ || service == nil || strchr(title, '\n') || strchr(comment, '\n')
+ || dangerous(service) || strchr(service, '/') || strlen(service)>20){
+ syslog(0, LOG, "%s failed dangerous", hp->remotesys);
+ hfail(hc, HSyntax);
+ exits("failed");
+ }
+
+ syslog(0, LOG, "%s post s %s t '%s' v %ld a %s c %s",
+ hp->remotesys, service, title, (long)version, author, comment);
+
+ if(strlen(text) > MaxLog)
+ text[MaxLog] = '\0';
+
+ mountwiki(hc, service);
+ url = dowiki(hc, title, author, comment, base, version, text);
+ hredirected(hc, "303 See Other", url);
+ exits(nil);
+}