summary refs log tree commit diff
diff options
context:
space:
mode:
authorJune McEnroe <june@causal.agency>2020-05-01 17:15:22 -0400
committerJune McEnroe <june@causal.agency>2020-05-01 17:20:45 -0400
commiteeed8059e2eca4e078f2b95ae6650963b3edfd2e (patch)
tree3cc786d51b298e181bea4220fa3ab5e47db74d4b
parentRemove unused includes (diff)
downloadimbox-eeed8059e2eca4e078f2b95ae6650963b3edfd2e.tar.gz
imbox-eeed8059e2eca4e078f2b95ae6650963b3edfd2e.zip
Update IMAP parser
-rw-r--r--imap.c31
-rw-r--r--imap.h95
-rw-r--r--imbox.c18
3 files changed, 85 insertions, 59 deletions
diff --git a/imap.c b/imap.c
index a5daaf6..7984e0e 100644
--- a/imap.c
+++ b/imap.c
@@ -60,10 +60,11 @@ static int imapClose(void *_tls) {
 	struct tls *tls = _tls;
 	int error = tls_close(tls);
 	if (error) errx(EX_IOERR, "tls_close: %s", tls_error(tls));
+	tls_free(tls);
 	return error;
 }
 
-FILE *imapOpen(const char *host, const char *port) {
+void imapOpen(FILE **read, FILE **write, const char *host, const char *port) {
 	struct tls *client = tls_client();
 	if (!client) errx(EX_SOFTWARE, "tls_client");
 
@@ -77,11 +78,11 @@ FILE *imapOpen(const char *host, const char *port) {
 	error = tls_connect(client, host, port);
 	if (error) errx(EX_NOHOST, "tls_connect: %s", tls_error(client));
 
-	FILE *imap = funopen(client, imapRead, imapWrite, NULL, imapClose);
-	if (!imap) err(EX_SOFTWARE, "funopen");
+	*read = funopen(client, imapRead, NULL, NULL, NULL);
+	*write = funopen(client, NULL, imapWrite, NULL, imapClose);
+	if (!*read || !*write) err(EX_SOFTWARE, "funopen");
 
-	setlinebuf(imap);
-	return imap;
+	setlinebuf(*write);
 }
 
 static size_t cap;
@@ -98,7 +99,7 @@ static void imapLine(FILE *imap) {
 }
 
 static struct Data parseAtom(void) {
-	size_t len = strcspn(ptr, " ()[]{\"");
+	size_t len = (*ptr == '.' ? 1 : strcspn(ptr, " .()[]{\""));
 	struct Data data = {
 		.type = Atom,
 		.atom = atomn(ptr, len),
@@ -149,18 +150,7 @@ static struct Data parseList(FILE *imap, char close) {
 	if (*ptr) ptr++;
 	struct Data data = { .type = List };
 	while (*ptr != close) {
-		if (data.list.len == data.list.cap) {
-			if (data.list.cap) {
-				data.list.cap *= 2;
-			} else {
-				data.list.cap = 4;
-			}
-			data.list.ptr = realloc(
-				data.list.ptr, sizeof(*data.list.ptr) * data.list.cap
-			);
-			if (!data.list.ptr) err(EX_OSERR, "realloc");
-		}
-		data.list.ptr[data.list.len++] = parseData(imap);
+		listPush(&data.list, parseData(imap));
 	}
 	if (*ptr) ptr++;
 	return data;
@@ -185,6 +175,11 @@ struct Resp imapResp(FILE *imap) {
 	data = parseData(imap);
 	if (data.type != Atom) errx(EX_PROTOCOL, "expected tag atom");
 	resp.tag = data.atom;
+	if (resp.tag == AtomContinue) {
+		if (*ptr == ' ') ptr++;
+		resp.text = ptr;
+		return resp;
+	}
 
 	data = parseData(imap);
 	if (data.type == Number) {
diff --git a/imap.h b/imap.h
index 0c9f6b9..7896b97 100644
--- a/imap.h
+++ b/imap.h
@@ -14,6 +14,9 @@
  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
  */
 
+#ifndef IMAP_H
+#define IMAP_H
+
 #include <err.h>
 #include <stdbool.h>
 #include <stdint.h>
@@ -24,32 +27,19 @@
 
 #define ENUM_ATOM \
 	X(AtomNil, "NIL") \
+	X(AtomUntagged, "*") \
+	X(AtomContinue, "+") \
 	X(AtomOk, "OK") \
 	X(AtomNo, "NO") \
 	X(AtomBad, "BAD") \
 	X(AtomPreauth, "PREAUTH") \
 	X(AtomBye, "BYE") \
-	X(AtomAlert, "ALERT") \
-	X(AtomBadCharset, "BADCHARSET") \
-	X(AtomCapability, "CAPABILITY") \
-	X(AtomParse, "PARSE") \
-	X(AtomPermanentFlags, "PERMANENTFLAGS") \
-	X(AtomReadOnly, "READ-ONLY") \
-	X(AtomReadWrite, "READ-WRITE") \
-	X(AtomTryCreate, "TRYCREATE") \
-	X(AtomUIDNext, "UIDNEXT") \
-	X(AtomUIDValidity, "UIDVALIDITY") \
-	X(AtomUnseen, "UNSEEN") \
-	X(AtomList, "LIST") \
-	X(AtomLSub, "LSUB") \
-	X(AtomStatus, "STATUS") \
 	X(AtomSearch, "SEARCH") \
-	X(AtomFlags, "FLAGS") \
-	X(AtomExists, "EXISTS") \
-	X(AtomRecent, "RECENT") \
-	X(AtomExpunge, "EXPUNGE") \
 	X(AtomFetch, "FETCH") \
-	X(AtomUntagged, "*")
+	X(AtomBody, "BODY") \
+	X(AtomDot, ".") \
+	X(AtomHeader, "HEADER") \
+	X(AtomText, "TEXT")
 
 enum Atom {
 #define X(id, str) id,
@@ -96,16 +86,57 @@ struct Data {
 	};
 };
 
-static inline void dataFree(struct Data data) {
-	if (data.type == String) free(data.string);
-	if (data.type == List) {
-		for (size_t i = 0; i < data.list.len; ++i) {
-			dataFree(data.list.ptr[i]);
+static inline struct Data dataCheck(struct Data data, enum Type type) {
+	const char *Types[] = { "atom", "number", "string", "list" };
+	if (data.type != type) {
+		errx(
+			EX_PROTOCOL, "expected %s, found %s",
+			Types[type], Types[data.type]
+		);
+	}
+	return data;
+}
+
+static inline struct Data dataTake(struct Data *from) {
+	struct Data take = *from;
+	from->type = Atom;
+	from->atom = AtomNil;
+	return take;
+}
+
+static inline void listPush(struct List *list, struct Data data) {
+	if (list->len == list->cap) {
+		list->cap = (list->cap ? list->cap * 2 : 4);
+		list->ptr = realloc(list->ptr, sizeof(*list->ptr) * list->cap);
+		if (!list->ptr) err(EX_OSERR, "realloc");
+	}
+	list->ptr[list->len++] = data;
+}
+
+static inline void listFlatten(struct List *flat, struct List nested) {
+	for (size_t i = 0; i < nested.len; ++i) {
+		if (nested.ptr[i].type == List) {
+			listFlatten(flat, nested.ptr[i].list);
+		} else {
+			listPush(flat, nested.ptr[i]);
 		}
-		free(data.list.ptr);
 	}
 }
 
+static inline void dataFree(struct Data data);
+
+static inline void listFree(struct List list) {
+	for (size_t i = 0; i < list.len; ++i) {
+		dataFree(list.ptr[i]);
+	}
+	free(list.ptr);
+}
+
+static inline void dataFree(struct Data data) {
+	if (data.type == String) free(data.string);
+	if (data.type == List) listFree(data.list);
+}
+
 struct Resp {
 	enum Atom tag;
 	uint32_t number;
@@ -116,14 +147,12 @@ struct Resp {
 };
 
 static inline void respFree(struct Resp resp) {
-	for (size_t i = 0; i < resp.code.len; ++i) {
-		dataFree(resp.code.ptr[i]);
-	}
-	for (size_t i = 0; i < resp.data.len; ++i) {
-		dataFree(resp.data.ptr[i]);
-	}
+	listFree(resp.code);
+	listFree(resp.data);
 }
 
 extern bool imapVerbose;
-FILE *imapOpen(const char *host, const char *port);
-struct Resp imapResp(FILE *imap);
+void imapOpen(FILE **read, FILE **write, const char *host, const char *port);
+struct Resp imapResp(FILE *imapRead);
+
+#endif /* IMAP_H */
diff --git a/imbox.c b/imbox.c
index d63763d..a37b24d 100644
--- a/imbox.c
+++ b/imbox.c
@@ -172,11 +172,14 @@ int main(int argc, char *argv[]) {
 
 	enum Atom login = 0;
 	enum Atom examine = atom("examine");
-	enum Atom headerFields = atom("HEADER.FIELDS");
-	enum Atom text = atom("TEXT");
 
-	FILE *imap = imapOpen(host, port);
-	for (struct Resp resp; resp = imapResp(imap), resp.resp != AtomBye;) {
+	FILE *imapRead, *imap;
+	imapOpen(&imapRead, &imap, host, port);
+	for (
+		struct Resp resp;
+		resp = imapResp(imapRead), resp.resp != AtomBye;
+		respFree(resp)
+	) {
 		if (resp.resp == AtomNo || resp.resp == AtomBad) {
 			errx(EX_CONFIG, "%s: %s", Atoms[resp.resp], resp.text);
 		}
@@ -240,10 +243,10 @@ int main(int argc, char *argv[]) {
 				if (item.type != List) continue;
 				if (!item.list.len) continue;
 				if (item.list.ptr[0].type != Atom) continue;
-				if (item.list.ptr[0].atom == headerFields) {
+				if (item.list.ptr[0].atom == AtomHeader) {
 					if (i + 1 < items.len) headers = items.ptr[i + 1];
 				}
-				if (item.list.ptr[0].atom == text) {
+				if (item.list.ptr[0].atom == AtomText) {
 					if (i + 1 < items.len) body = items.ptr[i + 1];
 				}
 			}
@@ -260,8 +263,7 @@ int main(int argc, char *argv[]) {
 		if (resp.tag == AtomFetch) {
 			fprintf(imap, "ayy LOGOUT\r\n");
 		}
-
-		respFree(resp);
 	}
+	fclose(imapRead);
 	fclose(imap);
 }