feat(herbarium): t.mbox.gz for b4, readable message-id URLs
2 files changed, -29/+84
herbarium/herbarium.1
@@ -66,8 +66,9 @@ levels. The message as archived, served as .BR text/plain . .TP-.IB /list / id /t.mbox-The whole thread as an mboxrd file.+.IB /list / id /t.mbox.gz+The whole thread as a gzipped mboxrd file, as fetched by+.BR b4 (1). .P Every response carries a Content-Security-Policy that allows only the stylesheet and icon from the same origin.
herbarium/herbarium.c
@@ -92,6 +92,8 @@ static const char *envor(const char *name, const char *fallback); static char *field(const char *v); static void freemsg(Msg *m); static void freemsgs(void);+static void gzblock(int last);+static void gzput(const char *s, size_t n); static size_t hdrend(const char *s, size_t n, size_t *body); static char *hdrget(const char *h, size_t n, const char *name); static int hex(int c);@@ -134,7 +136,7 @@ static const char *skipcfws(const char *s); static void threadmsgs(void); static void threadpage(size_t t); static int tou8(const char *cs, const char *s, size_t n, Buf *out);-static void urlencode(const char *s);+static void urlencode(const char *s, const char *safe); static int validid(const char *s, size_t n); static int validlist(const char *s); static void xmlencode(const char *s);@@ -142,6 +144,11 @@ static void xmlencoden(const char *s, size_t len); static char *xstrndup(const char *s, size_t n); /* variables */+static const char pathsafe[] = "-._~!$()*+,;=:@";+static const char mailsafe[] = "-._~@";+static unsigned char gzbuf[65535];+static size_t gzlen;+static unsigned long gzcrc, gzsize; static const char *base; static const char *list; static Msg *msgs;@@ -520,6 +527,45 @@ freemsgs(void) nmsgs = 0; } +void+gzblock(int last)+{+ unsigned char h[5];++ h[0] = last ? 1 : 0;+ h[1] = gzlen & 0xff;+ h[2] = gzlen >> 8 & 0xff;+ h[3] = ~gzlen & 0xff;+ h[4] = ~gzlen >> 8 & 0xff;+ fwrite(h, 1, sizeof(h), stdout);+ fwrite(gzbuf, 1, gzlen, stdout);+ gzlen = 0;+}++void+gzput(const char *s, size_t n)+{+ static unsigned long tab[256];+ unsigned long c;+ size_t i;+ int k;++ if (!tab[1]) {+ for (i = 0; i < LENGTH(tab); i++) {+ for (c = i, k = 0; k < 8; k++)+ c = c & 1 ? 0xedb88320UL ^ c >> 1 : c >> 1;+ tab[i] = c;+ }+ }+ for (i = 0; i < n; i++) {+ gzcrc = tab[(gzcrc ^ (unsigned char)s[i]) & 0xff] ^ gzcrc >> 8;+ gzbuf[gzlen++] = s[i];+ if (gzlen == sizeof(gzbuf))+ gzblock(0);+ }+ gzsize += n;+}+ size_t hdrend(const char *s, size_t n, size_t *body) {@@ -647,13 +693,13 @@ indexrow(const char *name) fputs("<tr><td class=\"name\"><a href=\"", stdout); xmlencode(base); putchar('/');- urlencode(name);+ urlencode(name, pathsafe); fputs("/\">", stdout); xmlencode(name); fputs("</a></td><td class=\"desc sm\">", stdout); xmlencode(desc); fputs("</td><td class=\"sm\"><a href=\"mailto:", stdout);- urlencode(name);+ urlencode(name, mailsafe); putchar('@'); xmlencode(domain); fputs("\">", stdout);@@ -740,7 +786,7 @@ listheader(int addrs) fputs("<header>\n<h1><a href=\"", stdout); xmlencode(base); putchar('/');- urlencode(list);+ urlencode(list, pathsafe); fputs("/\">", stdout); xmlencode(list); fputs("</a></h1>\n", stdout);@@ -754,7 +800,7 @@ listheader(int addrs) for (i = 0; i < LENGTH(cmds); i++) { printf("<tr><td>%s</td><td><a href=\"mailto:", *cmds[i] ? cmds[i] : "post");- urlencode(list);+ urlencode(list, mailsafe); printf("%s%s@", *cmds[i] ? "+" : "", cmds[i]); xmlencode(domain); fputs("\">", stdout);@@ -824,7 +870,7 @@ listpage(void) fputs("<a href=\"", stdout); xmlencode(base); putchar('/');- urlencode(list);+ urlencode(list, pathsafe); putchar('/'); if (ofs > perpage) printf("?ofs=%ld", ofs - perpage);@@ -836,7 +882,7 @@ listpage(void) fputs("<a href=\"", stdout); xmlencode(base); putchar('/');- urlencode(list);+ urlencode(list, pathsafe); printf("/?ofs=%zu\">older →</a>", i); } fputs("</nav>\n", stdout);@@ -911,27 +957,35 @@ mboxpage(size_t t) FILE *fp; int last; - httpheader(200, "application/mbox");+ httpheader(200, "application/gzip");+ fwrite("\x1f\x8b\x08\0\0\0\0\0\0\xff", 1, 10, stdout);+ gzcrc = 0xffffffffUL; for (i = 0; i < nmsgs; i++) { if (msgs[i].root != r || !(fp = openmsg(msgs[i].file))) continue;- fputs("From mboxrd@z Thu Jan 1 00:00:00 1970\n", stdout);+ gzput("From mboxrd@z Thu Jan 1 00:00:00 1970\n", 39); last = '\n'; while ((len = getline(&line, &cap, fp)) > 0) { for (k = 0; k < (size_t)len && line[k] == '>'; k++) ; if ((size_t)len - k >= 5 && !memcmp(line + k, "From ", 5))- putchar('>');- fwrite(line, 1, len, stdout);+ gzput(">", 1);+ gzput(line, len); last = line[len - 1]; } if (last != '\n')- putchar('\n');- putchar('\n');+ gzput("\n", 1);+ gzput("\n", 1); fclose(fp); } free(line);+ gzblock(1);+ gzcrc ^= 0xffffffffUL;+ for (k = 0; k < 4; k++)+ putchar((int)(gzcrc >> 8 * k & 0xff));+ for (k = 0; k < 4; k++)+ putchar((int)(gzsize >> 8 * k & 0xff)); } void@@ -939,9 +993,9 @@ msgurl(const char *id, const char *page) { xmlencode(base); putchar('/');- urlencode(list);+ urlencode(list, pathsafe); putchar('/');- urlencode(id);+ urlencode(id, pathsafe); putchar('/'); fputs(page, stdout); }@@ -1423,15 +1477,15 @@ printmsg(size_t i, int sel) fputs("\">permalink</a><a href=\"", stdout); msgurl(m->id, "raw"); fputs("\">raw</a><a href=\"mailto:", stdout);- urlencode(list);+ urlencode(list, mailsafe); putchar('@'); xmlencode(domain); fputs("?In-Reply-To=%3C", stdout);- urlencode(m->id);+ urlencode(m->id, mailsafe); fputs("%3E&subject=", stdout); if (strncasecmp(m->subject, "re:", 3)) fputs("Re%3A%20", stdout);- urlencode(m->subject);+ urlencode(m->subject, mailsafe); fputs("\">reply</a></p>\n", stdout); left = (size_t)maxbody;@@ -1624,8 +1678,8 @@ route(char *path) } else if (n > 4 && !strcmp(rest + n - 4, "/raw")) { rest[n -= 4] = '\0'; page = 1;- } else if (n > 7 && !strcmp(rest + n - 7, "/t.mbox")) {- rest[n -= 7] = '\0';+ } else if (n > 10 && !strcmp(rest + n - 10, "/t.mbox.gz")) {+ rest[n -= 10] = '\0'; page = 2; } if (!validid(rest, n)) {@@ -1817,11 +1871,11 @@ threadpage(size_t t) if (msgs[t].root != t) { printf("Status: 302 Found\r\nLocation: %s/", base);- urlencode(list);+ urlencode(list, pathsafe); putchar('/');- urlencode(r->id);+ urlencode(r->id, pathsafe); fputs("/#", stdout);- urlencode(msgs[t].id);+ urlencode(msgs[t].id, pathsafe); fputs("\r\n", stdout); httpheader(302, "text/plain; charset=utf-8"); return;@@ -1835,8 +1889,8 @@ threadpage(size_t t) xmlencode(*r->subject ? r->subject : "(no subject)"); printf("</h2>\n<p class=\"info\">%zu message%s · <a href=\"", n, n == 1 ? "" : "s");- msgurl(r->id, "t.mbox");- fputs("\">t.mbox</a></p>\n", stdout);+ msgurl(r->id, "t.mbox.gz");+ fputs("\">t.mbox.gz</a></p>\n", stdout); for (i = 0; i < nmsgs; i++) if (msgs[i].root == t) printmsg(i, i == t);@@ -1888,10 +1942,10 @@ tou8(const char *cs, const char *s, size_t n, Buf *out) } void-urlencode(const char *s)+urlencode(const char *s, const char *safe) { for (; *s; s++) {- if (isalnum((unsigned char)*s) || strchr("-._~@", *s))+ if (isalnum((unsigned char)*s) || strchr(safe, *s)) putchar(*s); else printf("%%%02X", (unsigned char)*s);