greenhouse

greenhouse

minimal software forge

git clone git@git.lostduk.com:greenhouse.git

feat(herbarium): t.mbox.gz for b4, readable message-id URLs

commitef305878ade57064e5ca67e287560f020bc09899
parent117bc277b6bf294d849d75217fed1969794385e0
authorlostduk <hi@lostduk.com>
date2026-10-18 00:13 +0700
herbarium/herbarium.15+-
herbarium/herbarium.c108++++++++++++++++++++++++++++++----------

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 &rarr;</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&amp;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 &middot; <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);