about summary refs log tree commit diff
path: root/archive.c
diff options
context:
space:
mode:
authorJune McEnroe <june@causal.agency>2020-04-09 15:53:31 -0400
committerJune McEnroe <june@causal.agency>2020-04-09 15:53:31 -0400
commitcd60193ce40431cd05da4da66fb4156034b8f330 (patch)
treea433e65f0b01ceb3711fbf29fc277ff389a2f849 /archive.c
parentSend FETCH for uncached UIDs (diff)
downloadbubger-cd60193ce40431cd05da4da66fb4156034b8f330.tar.gz
bubger-cd60193ce40431cd05da4da66fb4156034b8f330.zip
Parse envelopes
Diffstat (limited to 'archive.c')
-rw-r--r--archive.c130
1 files changed, 130 insertions, 0 deletions
diff --git a/archive.c b/archive.c
index f3c0059..cdfd982 100644
--- a/archive.c
+++ b/archive.c
@@ -15,14 +15,19 @@
  */
 
 #include <err.h>
+#include <errno.h>
 #include <inttypes.h>
 #include <limits.h>
 #include <stdint.h>
 #include <stdio.h>
 #include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
 #include <sysexits.h>
+#include <time.h>
 #include <unistd.h>
 
+#include "archive.h"
 #include "imap.h"
 
 #define ENV_PASSWORD "BUBGER_IMAP_PASSWORD"
@@ -62,6 +67,9 @@ static enum Atom fetchNew(FILE *imap, struct List threads) {
 	enum Atom tag = 0;
 	if (!uids.len) goto done;
 
+	int error = mkdir("UID", 0775);
+	if (error && errno != EEXIST) err(EX_CANTCREAT, "UID");
+
 	tag = atom("fetchNew");
 	fprintf(imap, "%s UID FETCH ", Atoms[tag]);
 	for (size_t i = 0; i < uids.len; ++i) {
@@ -74,6 +82,121 @@ done:
 	return tag;
 }
 
+static struct Address parseAddress(struct List list) {
+	if (list.len < 4) {
+		errx(EX_PROTOCOL, "missing address structure fields");
+	}
+	struct Address addr = {0};
+	if (list.ptr[0].type == String) {
+		// TODO: Decode UTF-8 in name.
+		addr.name = strdup(list.ptr[0].string);
+		if (!addr.name) err(EX_OSERR, "strdup");
+	}
+	if (list.ptr[2].type == String) addr.mailbox = list.ptr[2].string;
+	if (list.ptr[3].type == String) addr.host = list.ptr[3].string;
+	return addr;
+}
+
+static struct AddressList parseAddressList(struct List list) {
+	struct Address *addrs = calloc(list.len, sizeof(*addrs));
+	if (!addrs) err(EX_OSERR, "calloc");
+	for (size_t i = 0; i < list.len; ++i) {
+		if (list.ptr[i].type != List) {
+			errx(EX_PROTOCOL, "invalid address field");
+		}
+		addrs[i] = parseAddress(list.ptr[i].list);
+	}
+	return (struct AddressList) { list.len, addrs };
+}
+
+static struct Envelope parseEnvelope(struct List list) {
+	enum {
+		Date, Subject, From, Sender, ReplyTo,
+		To, Cc, Bcc, InReplyTo, MessageID,
+		EnvelopeLen,
+	};
+	if (list.len < EnvelopeLen) {
+		errx(EX_PROTOCOL, "missing envelope structure fields");
+	}
+	struct Envelope envelope = {0};
+
+	if (list.ptr[Date].type != String) {
+		errx(EX_PROTOCOL, "invalid envelope date field");
+	}
+	const char *date = list.ptr[Date].string;
+	date = strptime(date, "%a, %e %b %Y %H:%M:%S %z", &envelope.date);
+	if (!date) errx(EX_PROTOCOL, "invalid envelope date format");
+
+	if (list.ptr[Subject].type != String) {
+		errx(EX_PROTOCOL, "invalid envelope subject field");
+	}
+	// TODO: Decode UTF-8 in subject.
+	envelope.subject = strdup(list.ptr[Subject].string);
+	if (!envelope.subject) err(EX_OSERR, "strdup");
+
+	for (size_t i = From; i <= Bcc; ++i) {
+		if (list.ptr[i].type == List) continue;
+		if (list.ptr[i].type == Atom && list.ptr[i].atom == AtomNil) {
+			list.ptr[i].type = List;
+			list.ptr[i].list = (struct List) {0};
+			continue;
+		}
+		errx(EX_PROTOCOL, "invalid envelope address field");
+	}
+	for (size_t i = From; i <= ReplyTo; ++i) {
+		if (!list.ptr[i].list.len || list.ptr[i].list.ptr[0].type != List) {
+			errx(EX_PROTOCOL, "invalid envelope address field");
+		}
+	}
+	envelope.from = parseAddress(list.ptr[From].list.ptr[0].list);
+	envelope.sender = parseAddress(list.ptr[Sender].list.ptr[0].list);
+	envelope.replyTo = parseAddress(list.ptr[ReplyTo].list.ptr[0].list);
+	envelope.to = parseAddressList(list.ptr[To].list);
+	envelope.cc = parseAddressList(list.ptr[Cc].list);
+	envelope.bcc = parseAddressList(list.ptr[Bcc].list);
+
+	if (list.ptr[InReplyTo].type == String) {
+		envelope.inReplyTo = list.ptr[InReplyTo].string;
+	}
+	if (list.ptr[MessageID].type != String) {
+		errx(EX_PROTOCOL, "invalid envelope message-id field");
+	}
+	envelope.messageID = list.ptr[MessageID].string;
+
+	return envelope;
+}
+
+static void exportMessage(struct List items) {
+	static enum Atom AtomUID;
+	static enum Atom AtomEnvelope;
+	if (!AtomUID) AtomUID = atom("UID");
+	if (!AtomEnvelope) AtomEnvelope = atom("ENVELOPE");
+
+	uint32_t uid = 0;
+	struct Envelope envelope = {0};
+	for (size_t i = 0; i + 1 < items.len; i += 2) {
+		if (items.ptr[i].type != Atom) {
+			errx(EX_PROTOCOL, "invalid data item name");
+		}
+		enum Atom name = items.ptr[i].atom;
+		if (name == AtomUID) {
+			if (items.ptr[i + 1].type != Number) {
+				errx(EX_PROTOCOL, "invalid UID data item value");
+			}
+			uid = items.ptr[i + 1].number;
+		} else if (name == AtomEnvelope) {
+			if (items.ptr[i + 1].type != List) {
+				errx(EX_PROTOCOL, "invalid ENVELOPE data item value");
+			}
+			envelope = parseEnvelope(items.ptr[i + 1].list);
+		}
+	}
+	if (!uid) errx(EX_PROTOCOL, "missing UID data item");
+	if (!envelope.subject) errx(EX_PROTOCOL, "missing ENVELOPE data item");
+
+	envelopeFree(envelope);
+}
+
 static void checkValidity(uint32_t validity) {
 	FILE *file = fopen("UIDVALIDITY", "r");
 	if (file) {
@@ -197,6 +320,13 @@ int main(int argc, char *argv[]) {
 			export = fetchNew(imap, resp.data);
 		}
 
+		if (export && resp.resp == AtomFetch) {
+			if (!resp.data.len || resp.data.ptr[0].type != List) {
+				errx(EX_PROTOCOL, "invalid FETCH data");
+			}
+			exportMessage(resp.data.ptr[0].list);
+		}
+
 		respFree(resp);
 	}
 	fclose(imap);