about summary refs log tree commit diff
diff options
context:
space:
mode:
authorJune McEnroe <june@causal.agency>2020-04-09 18:55:16 -0400
committerJune McEnroe <june@causal.agency>2020-04-09 18:55:16 -0400
commitef48616f2b63973830a07199c6bbe3b9a7ea6cc8 (patch)
tree7d92c7dc3560d75469e72e33f1f69dfc2089dcf1
parentTrim angle brackets from message IDs (diff)
downloadbubger-ef48616f2b63973830a07199c6bbe3b9a7ea6cc8.tar.gz
bubger-ef48616f2b63973830a07199c6bbe3b9a7ea6cc8.zip
Render basic HTML envelopes with templating
-rw-r--r--Makefile2
-rw-r--r--archive.c18
-rw-r--r--archive.h17
-rw-r--r--html.c69
-rw-r--r--template.c57
5 files changed, 159 insertions, 4 deletions
diff --git a/Makefile b/Makefile
index 20b440f..74b0de4 100644
--- a/Makefile
+++ b/Makefile
@@ -2,8 +2,10 @@ CFLAGS += -std=c11 -Wall -Wextra -Wpedantic
 LDLIBS = -ltls
 
 OBJS += archive.o
+OBJS += html.o
 OBJS += imap.o
 OBJS += mbox.o
+OBJS += template.o
 
 dev: tags all
 
diff --git a/archive.c b/archive.c
index 889f253..fd8ef53 100644
--- a/archive.c
+++ b/archive.c
@@ -240,16 +240,26 @@ static void exportMessage(struct List items) {
 	if (!header) errx(EX_PROTOCOL, "missing BODY[HEADER.FIELDS] data item");
 	if (!body) errx(EX_PROTOCOL, "missing BODY[TEXT] data item");
 
-	const char *path = uidPath(uid, "mbox");
-	FILE *file = fopen(path, "w");
-	if (!file) err(EX_CANTCREAT, "%s", path);
+	const char *path;
+	FILE *file;
+	int error;
 
-	int error = mboxFrom(file)
+	path = uidPath(uid, "mbox");
+	file = fopen(path, "w");
+	if (!file) err(EX_CANTCREAT, "%s", path);
+	error = mboxFrom(file)
 		|| mboxHeader(file, header)
 		|| mboxBody(file, body)
 		|| fclose(file);
 	if (error) err(EX_IOERR, "%s", path);
 
+	path = uidPath(uid, "html");
+	file = fopen(path, "w");
+	if (!file) err(EX_CANTCREAT, "%s", path);
+	error = htmlEnvelope(file, &envelope)
+		|| fclose(file);
+	if (error) err(EX_IOERR, "%s", path);
+
 	envelopeFree(envelope);
 }
 
diff --git a/archive.h b/archive.h
index d804021..8d96878 100644
--- a/archive.h
+++ b/archive.h
@@ -14,6 +14,7 @@
  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
  */
 
+#include <stdio.h>
 #include <stdlib.h>
 #include <time.h>
 
@@ -56,6 +57,20 @@ static inline void envelopeFree(struct Envelope envelope) {
 	free(envelope.bcc.addrs);
 }
 
+#define TEMPLATE(...) #__VA_ARGS__
+
+struct Variable {
+	const char *name;
+	const char *value;
+};
+
+typedef int EscapeFn(FILE *file, const char *str);
+
+int templateRender(
+	FILE *file, const char *template,
+	const struct Variable *vars, EscapeFn *escape
+);
+
 #define MBOX_HEADERS \
 	"Date Subject From Sender Reply-To To Cc Bcc " \
 	"Message-Id In-Reply-To References " \
@@ -64,3 +79,5 @@ static inline void envelopeFree(struct Envelope envelope) {
 int mboxFrom(FILE *file);
 int mboxHeader(FILE *file, char *header);
 int mboxBody(FILE *file, char *body);
+
+int htmlEnvelope(FILE *file, const struct Envelope *envelope);
diff --git a/html.c b/html.c
new file mode 100644
index 0000000..4903c5b
--- /dev/null
+++ b/html.c
@@ -0,0 +1,69 @@
+/* Copyright (C) 2020  C. McEnroe <june@causal.agency>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <err.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sysexits.h>
+#include <time.h>
+
+#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 *Summary = TEMPLATE(
+	<details id="[messageID]">
+	<summary>
+		<h1><a href="#[messageID]">[subject]</a></h1>
+		<address>
+			<a href="mailto:[from.mailbox]@[from.host]">[from.name]</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 vars[] = {
+		{ "subject", envelope->subject },
+		{ "from.name", fromName },
+		{ "from.mailbox", envelope->from.mailbox },
+		{ "from.host", envelope->from.host },
+		{ "messageID", envelope->messageID },
+		{0},
+	};
+	return templateRender(file, Summary, vars, htmlEscape);
+}
diff --git a/template.c b/template.c
new file mode 100644
index 0000000..3caa8b2
--- /dev/null
+++ b/template.c
@@ -0,0 +1,57 @@
+/* Copyright (C) 2020  C. McEnroe <june@causal.agency>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <assert.h>
+#include <err.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sysexits.h>
+
+#include "archive.h"
+
+int templateRender(
+	FILE *file, const char *template,
+	const struct Variable *vars, EscapeFn *escape
+) {
+	for (bool subst = false; *template; subst ^= true) {
+		size_t len = strcspn(template, "[]");
+		if (subst) {
+			const char *value = NULL;
+			for (const struct Variable *var = vars; var->name; ++var) {
+				if (strlen(var->name) != len) continue;
+				if (strncmp(var->name, template, len)) continue;
+				value = var->value;
+				break;
+			}
+			if (!value) {
+				errx(
+					EX_SOFTWARE, "no value for template variable %.*s",
+					(int)len, template
+				);
+			}
+			int error = escape(file, value);
+			if (error) return error;
+		} else {
+			size_t n = fwrite(template, len, 1, file);
+			if (!n) return -1;
+		}
+		template += len;
+		if (*template) template++;
+	}
+	return 0;
+}