about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--Makefile1
-rw-r--r--archive.c59
-rw-r--r--archive.h9
-rw-r--r--mbox.c54
4 files changed, 117 insertions, 6 deletions
diff --git a/Makefile b/Makefile
index 6a72dd9..20b440f 100644
--- a/Makefile
+++ b/Makefile
@@ -3,6 +3,7 @@ LDLIBS = -ltls
 
 OBJS += archive.o
 OBJS += imap.o
+OBJS += mbox.o
 
 dev: tags all
 
diff --git a/archive.c b/archive.c
index cdfd982..497b5a6 100644
--- a/archive.c
+++ b/archive.c
@@ -75,7 +75,13 @@ static enum Atom fetchNew(FILE *imap, struct List threads) {
 	for (size_t i = 0; i < uids.len; ++i) {
 		fprintf(imap, "%s%" PRIu32, (i ? "," : ""), uids.ptr[i].number);
 	}
-	fprintf(imap, " (UID ENVELOPE)\r\n");
+	fprintf(
+		imap,
+		" ("
+		"UID ENVELOPE "
+		"BODY[HEADER.FIELDS (" MBOX_HEADERS ")] BODY[TEXT]"
+		")\r\n"
+	);
 
 done:
 	listFree(uids);
@@ -167,19 +173,37 @@ static struct Envelope parseEnvelope(struct List list) {
 }
 
 static void exportMessage(struct List items) {
-	static enum Atom AtomUID;
-	static enum Atom AtomEnvelope;
+	static enum Atom AtomUID, AtomEnvelope, AtomBody;
+	static enum Atom AtomHeaderFields, AtomText;
 	if (!AtomUID) AtomUID = atom("UID");
 	if (!AtomEnvelope) AtomEnvelope = atom("ENVELOPE");
+	if (!AtomBody) AtomBody = atom("BODY");
+	if (!AtomHeaderFields) AtomHeaderFields = atom("HEADER.FIELDS");
+	if (!AtomText) AtomText = atom("TEXT");
 
 	uint32_t uid = 0;
 	struct Envelope envelope = {0};
+	char *header = NULL;
+	char *body = NULL;
+
 	for (size_t i = 0; i + 1 < items.len; i += 2) {
-		if (items.ptr[i].type != Atom) {
+		enum Atom name;
+		if (items.ptr[i].type == Atom) {
+			name = items.ptr[i].atom;
+		} else if (
+			items.ptr[i].type == List &&
+			items.ptr[i].list.len &&
+			items.ptr[i].list.ptr[0].type == Atom
+		) {
+			name = items.ptr[i].list.ptr[0].atom;
+		} else {
 			errx(EX_PROTOCOL, "invalid data item name");
 		}
-		enum Atom name = items.ptr[i].atom;
-		if (name == AtomUID) {
+
+		if (name == AtomBody) {
+			i--;
+			continue;
+		} else if (name == AtomUID) {
 			if (items.ptr[i + 1].type != Number) {
 				errx(EX_PROTOCOL, "invalid UID data item value");
 			}
@@ -189,10 +213,33 @@ static void exportMessage(struct List items) {
 				errx(EX_PROTOCOL, "invalid ENVELOPE data item value");
 			}
 			envelope = parseEnvelope(items.ptr[i + 1].list);
+		} else if (name == AtomHeaderFields) {
+			if (items.ptr[i + 1].type != String) {
+				errx(EX_PROTOCOL, "invalid BODY[HEADER.FIELDS] data item value");
+			}
+			header = items.ptr[i + 1].string;
+		} else if (name == AtomText) {
+			if (items.ptr[i + 1].type != String) {
+				errx(EX_PROTOCOL, "invalid BODY[TEXT] data item value");
+			}
+			body = items.ptr[i + 1].string;
 		}
 	}
+
 	if (!uid) errx(EX_PROTOCOL, "missing UID data item");
 	if (!envelope.subject) errx(EX_PROTOCOL, "missing ENVELOPE data item");
+	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);
+
+	int error = mboxFrom(file)
+		|| mboxHeader(file, header)
+		|| mboxBody(file, body)
+		|| fclose(file);
+	if (error) err(EX_IOERR, "%s", path);
 
 	envelopeFree(envelope);
 }
diff --git a/archive.h b/archive.h
index 9ec1f89..d804021 100644
--- a/archive.h
+++ b/archive.h
@@ -55,3 +55,12 @@ static inline void envelopeFree(struct Envelope envelope) {
 	free(envelope.cc.addrs);
 	free(envelope.bcc.addrs);
 }
+
+#define MBOX_HEADERS \
+	"Date Subject From Sender Reply-To To Cc Bcc " \
+	"Message-Id In-Reply-To References " \
+	"MIME-Version Content-Type Content-Disposition Content-Transfer-Encoding"
+
+int mboxFrom(FILE *file);
+int mboxHeader(FILE *file, char *header);
+int mboxBody(FILE *file, char *body);
diff --git a/mbox.c b/mbox.c
new file mode 100644
index 0000000..f8684de
--- /dev/null
+++ b/mbox.c
@@ -0,0 +1,54 @@
+/* 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 "archive.h"
+
+int mboxFrom(FILE *file) {
+	int n = fprintf(file, "From mboxrd@z Thu Jan  1 00:00:00 1970\n");
+	return (n < 0 ? n : 0);
+}
+
+int mboxHeader(FILE *file, char *header) {
+	for (char *crlf; (crlf = strstr(header, "\r\n")); header = &crlf[2]) {
+		*crlf = '\0';
+		int n = fprintf(file, "%s\n", header);
+		if (n < 0) return n;
+	}
+	return 0;
+}
+
+int mboxBody(FILE *file, char *body) {
+	int n;
+	for (char *crlf; (crlf = strstr(body, "\r\n")); body = &crlf[2]) {
+		*crlf = '\0';
+		char *from = body;
+		while (*from == '>') from++;
+		if (!strncmp(from, "From ", 5)) {
+			n = fprintf(file, ">%s\n", body);
+		} else {
+			n = fprintf(file, "%s\n", body);
+		}
+		if (n < 0) return n;
+	}
+	n = fprintf(file, "\n");
+	return (n < 0 ? n : 0);
+}