about summary refs log tree commit diff
diff options
context:
space:
mode:
authorJune McEnroe <june@causal.agency>2020-04-09 20:24:45 -0400
committerJune McEnroe <june@causal.agency>2020-04-09 20:24:45 -0400
commita0a148b1c3ba5ffd4ccd6ee52607629bc58875d0 (patch)
tree9c70d51767187802e71afb54dbad0a61675dd56c
parentRender basic HTML envelopes with templating (diff)
downloadbubger-a0a148b1c3ba5ffd4ccd6ee52607629bc58875d0.tar.gz
bubger-a0a148b1c3ba5ffd4ccd6ee52607629bc58875d0.zip
Render escaped mailto URL
-rw-r--r--archive.h4
-rw-r--r--bubger.111
-rw-r--r--html.c67
-rw-r--r--template.c44
4 files changed, 97 insertions, 29 deletions
diff --git a/archive.h b/archive.h
index 8d96878..3d3cfc5 100644
--- a/archive.h
+++ b/archive.h
@@ -58,6 +58,7 @@ static inline void envelopeFree(struct Envelope envelope) {
 }
 
 #define TEMPLATE(...) #__VA_ARGS__
+#define ESCAPE_URL_CAP(len) (3 * (len))
 
 struct Variable {
 	const char *name;
@@ -66,6 +67,9 @@ struct Variable {
 
 typedef int EscapeFn(FILE *file, const char *str);
 
+int escapeURL(FILE *file, const char *str);
+int escapeXML(FILE *file, const char *str);
+
 int templateRender(
 	FILE *file, const char *template,
 	const struct Variable *vars, EscapeFn *escape
diff --git a/bubger.1 b/bubger.1
index d92ecbb..df02b5a 100644
--- a/bubger.1
+++ b/bubger.1
@@ -131,6 +131,17 @@ Rendered Atom, HTML and mboxrd files for each thread.
 .%D June 2008
 .%U https://tools.ietf.org/html/rfc5256
 .Re
+.It
+.Rs
+.%A T. Berners-Lee
+.%A L. Masinter
+.%A M. McCahill
+.%T Uniform Resource Locators (URL)
+.%I IETF
+.%N RFC 1738
+.%D December 1994
+.%U https://tools.ietf.org/html/rfc1738
+.Re
 .El
 .
 .Sh AUTHORS
diff --git a/html.c b/html.c
index 4903c5b..50a8378 100644
--- a/html.c
+++ b/html.c
@@ -14,6 +14,7 @@
  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
  */
 
+#include <assert.h>
 #include <err.h>
 #include <stdio.h>
 #include <stdlib.h>
@@ -23,47 +24,57 @@
 
 #include "archive.h"
 
-static int htmlEscape(FILE *file, const char *str) {
-	while (*str) {
-		int n = 0;
-		switch (*str) {
-			break; case '"': n = fprintf(file, "&quot;"); str++;
-			break; case '&': n = fprintf(file, "&amp;"); str++;
-			break; case '<': n = fprintf(file, "&lt;"); str++;
-			break; case '>': n = fprintf(file, "&gt;"); str++;
-		}
-		if (n < 0) return n;
-		size_t len = strcspn(str, "\"&<>");
-		if (len) {
-			size_t n = fwrite(str, len, 1, file);
-			if (!n) return -1;
-		}
-		str += len;
-	}
-	return 0;
-}
+static const char *Mailto = {
+	"mailto:[mailbox]@[host]?subject=[re][subject]&In-Reply-To=[messageID]"
+};
 
-static const char *Summary = TEMPLATE(
+static const char *Envelope = TEMPLATE(
 	<details id="[messageID]">
 	<summary>
 		<h1><a href="#[messageID]">[subject]</a></h1>
 		<address>
-			<a href="mailto:[from.mailbox]@[from.host]">[from.name]</a>
+			<a href="[mailto]">[from]</a>
 		</address>
 	</summary>
 );
 
 int htmlEnvelope(FILE *file, const struct Envelope *envelope) {
-	const char *fromName = envelope->from.name;
-	if (!fromName) fromName = envelope->from.mailbox;
+	struct Variable mailtoVars[] = {
+		{ "mailbox", envelope->from.mailbox },
+		{ "host", envelope->from.host },
+		{ "re", (strncmp(envelope->subject, "Re: ", 4) ? "Re: " : "") },
+		{ "subject", envelope->subject },
+		{ "messageID", envelope->messageID },
+		{0},
+	};
+
+	size_t cap = sizeof(Mailto);
+	for (struct Variable *var = mailtoVars; var->value; ++var) {
+		cap += ESCAPE_URL_CAP(strlen(var->value));
+	}
+	char *mailto = malloc(cap);
+	if (!mailto) err(EX_OSERR, "malloc");
+
+	FILE *url = fmemopen(mailto, cap, "w");
+	if (!url) err(EX_OSERR, "fmemopen");
+
+	int error = 0
+		|| templateRender(url, Mailto, mailtoVars, escapeURL)
+		|| fclose(url);
+	assert(!error);
+
+	const char *from = envelope->from.name;
+	if (!from) from = envelope->from.mailbox;
 
 	struct Variable vars[] = {
-		{ "subject", envelope->subject },
-		{ "from.name", fromName },
-		{ "from.mailbox", envelope->from.mailbox },
-		{ "from.host", envelope->from.host },
 		{ "messageID", envelope->messageID },
+		{ "subject", envelope->subject },
+		{ "mailto", mailto },
+		{ "from", from },
 		{0},
 	};
-	return templateRender(file, Summary, vars, htmlEscape);
+	error = templateRender(file, Envelope, vars, escapeXML);
+
+	free(mailto);
+	return error;
 }
diff --git a/template.c b/template.c
index 3caa8b2..92c6381 100644
--- a/template.c
+++ b/template.c
@@ -24,6 +24,48 @@
 
 #include "archive.h"
 
+int escapeURL(FILE *file, const char *str) {
+	static const char *Safe = {
+		"$-_.+!*'(),"
+		"0123456789"
+		"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+		"abcdefghijklmnopqrstuvwxyz"
+	};
+	while (*str) {
+		size_t len = strspn(str, Safe);
+		if (len) {
+			size_t n = fwrite(str, len, 1, file);
+			if (!n) return -1;
+		}
+		str += len;
+		if (*str) {
+			int n = fprintf(file, "%%%02X", *str++);
+			if (n < 0) return n;
+		}
+	}
+	return 0;
+}
+
+int escapeXML(FILE *file, const char *str) {
+	while (*str) {
+		int n = 0;
+		switch (*str) {
+			break; case '"': n = fprintf(file, "&quot;"); str++;
+			break; case '&': n = fprintf(file, "&amp;"); str++;
+			break; case '<': n = fprintf(file, "&lt;"); str++;
+			break; case '>': n = fprintf(file, "&gt;"); str++;
+		}
+		if (n < 0) return n;
+		size_t len = strcspn(str, "\"&<>");
+		if (len) {
+			size_t n = fwrite(str, len, 1, file);
+			if (!n) return -1;
+		}
+		str += len;
+	}
+	return 0;
+}
+
 int templateRender(
 	FILE *file, const char *template,
 	const struct Variable *vars, EscapeFn *escape
@@ -46,7 +88,7 @@ int templateRender(
 			}
 			int error = escape(file, value);
 			if (error) return error;
-		} else {
+		} else if (len) {
 			size_t n = fwrite(template, len, 1, file);
 			if (!n) return -1;
 		}