summary refs log tree commit diff
diff options
context:
space:
mode:2020-02-12
    terJune McEnroe <june@causal.agency>2020-04-09 22:25:00 -0400 commit78d880bcfe4799f069bba4ad79c6332c25d6db04 (patch) tree8531af4ec57f9ce471718d5a298f26d9bfa3b332 parentRender Atom envelopes (diff)downloadbubger-78d880bcfe4799f069bba4ad79c6332c25d6db04.tar.gz
    bubger-78d880bcfe4799f069bba4ad79c6332c25d6db04.zip
    Move export code to export.c
    Diffstat (limited to '')
    -rw-r--r--Makefile1
    -rw-r--r--archive.c250
    -rw-r--r--archive.h5
    -rw-r--r--export.c271
    -rw-r--r--imap.h5
    5 files changed, 286 insertions, 246 deletions
    diff --git a/Makefile b/Makefile
    index 106ceda..000620e 100644
    --- a/Makefile
    +++ b/Makefile
    @@ -3,6 +3,7 @@ LDLIBS = -ltls
     
     OBJS += archive.o
     OBJS += atom.o
    +OBJS += export.o
     OBJS += html.o
     OBJS += imap.o
     OBJS += mbox.o
    diff --git a/archive.c b/archive.c
    index b045a7a..2e8bf31 100644
    --- a/archive.c
    +++ b/archive.c
    @@ -17,14 +17,11 @@
     #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"
    @@ -32,247 +29,6 @@
     
     #define ENV_PASSWORD "BUBGER_IMAP_PASSWORD"
     
    -static const char *uidPath(uint32_t uid, const char *type) {
    -	static char buf[PATH_MAX];
    -	snprintf(buf, sizeof(buf), "UID/%" PRIu32 ".%s", uid, type);
    -	return buf;
    -}
    -
    -static void flatten(struct List *flat, struct List nested) {
    -	for (size_t i = 0; i < nested.len; ++i) {
    -		if (nested.ptr[i].type == List) {
    -			flatten(flat, nested.ptr[i].list);
    -		} else {
    -			listPush(flat, nested.ptr[i]);
    -		}
    -	}
    -}
    -
    -static enum Atom fetchNew(FILE *imap, struct List threads) {
    -	struct List uids = {0};
    -	flatten(&uids, threads);
    -	for (size_t i = uids.len - 1; i < uids.len; --i) {
    -		if (uids.ptr[i].type != Number) {
    -			errx(EX_PROTOCOL, "invalid thread UID");
    -		}
    -		uint32_t uid = uids.ptr[i].number;
    -		int error = 0;
    -		if (!error) error = access(uidPath(uid, "atom"), F_OK);
    -		if (!error) error = access(uidPath(uid, "html"), F_OK);
    -		if (!error) error = access(uidPath(uid, "mbox"), F_OK);
    -		if (!error) {
    -			uids.ptr[i] = uids.ptr[--uids.len];
    -		}
    -	}
    -	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) {
    -		fprintf(imap, "%s%" PRIu32, (i ? "," : ""), uids.ptr[i].number);
    -	}
    -	fprintf(
    -		imap,
    -		" ("
    -		"UID ENVELOPE "
    -		"BODY[HEADER.FIELDS (" MBOX_HEADERS ")] BODY[TEXT]"
    -		")\r\n"
    -	);
    -
    -done:
    -	listFree(uids);
    -	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 char *parseID(char *id) {
    -	size_t len = strlen(id);
    -	if (id[0] != '<' || !len || id[len - 1] != '>') {
    -		errx(EX_PROTOCOL, "invalid message ID");
    -	}
    -	id[len - 1] = '\0';
    -	return &id[1];
    -}
    -
    -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");
    -
    -	envelope.date.tm_isdst = -1;
    -	envelope.utc = mktime(&envelope.date);
    -
    -	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 = parseID(list.ptr[InReplyTo].string);
    -	}
    -	if (list.ptr[MessageID].type != String) {
    -		errx(EX_PROTOCOL, "invalid envelope message-id field");
    -	}
    -	envelope.messageID = parseID(list.ptr[MessageID].string);
    -
    -	return envelope;
    -}
    -
    -static void exportMessage(struct List items) {
    -	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) {
    -		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");
    -		}
    -
    -		if (name == AtomBody) {
    -			i--;
    -			continue;
    -		} else 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);
    -		} 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;
    -	FILE *file;
    -	int error;
    -
    -	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);
    -
    -	path = uidPath(uid, "atom");
    -	file = fopen(path, "w");
    -	if (!file) err(EX_CANTCREAT, "%s", path);
    -	error = atomEnvelope(file, &envelope)
    -		|| fclose(file);
    -	if (error) err(EX_IOERR, "%s", path);
    -
    -	envelopeFree(envelope);
    -}
    -
     static void checkValidity(uint32_t validity) {
     	FILE *file = fopen("UIDVALIDITY", "r");
     	if (file) {
    @@ -393,14 +149,16 @@ int main(int argc, char *argv[]) {
     			if (!resp.data.len) {
     				errx(EX_TEMPFAIL, "no messages matching %s", search);
     			}
    -			export = fetchNew(imap, resp.data);
    +			int error = mkdir("UID", 0775);
    +			if (error && errno != EEXIST) err(EX_CANTCREAT, "UID");
    +			export = exportThreads(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);
    +			exportData(resp.data.ptr[0].list);
     		}
     
     		respFree(resp);
    diff --git a/archive.h b/archive.h
    index cc29953..540b898 100644
    --- a/archive.h
    +++ b/archive.h
    @@ -18,6 +18,8 @@
     #include <stdlib.h>
     #include <time.h>
     
    +#include "imap.h"
    +
     struct Address {
     	char *name;
     	const char *mailbox;
    @@ -58,6 +60,9 @@ static inline void envelopeFree(struct Envelope envelope) {
     	free(envelope.bcc.addrs);
     }
     
    +enum Atom exportThreads(FILE *imap, struct List threads);
    +void exportData(struct List items);
    +
     #define TEMPLATE(...) #__VA_ARGS__
     
     struct Variable {
    diff --git a/export.c b/export.c
    new file mode 100644
    index 0000000..3e87521
    --- /dev/null
    +++ b/export.c
    @@ -0,0 +1,271 @@
    +/* 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 <inttypes.h>
    +#include <limits.h>
    +#include <stdint.h>
    +#include <stdio.h>
    +#include <stdlib.h>
    +#include <string.h>
    +#include <sysexits.h>
    +#include <time.h>
    +#include <unistd.h>
    +
    +#include "archive.h"
    +#include "imap.h"
    +
    +static const char *uidPath(uint32_t uid, const char *type) {
    +	static char buf[PATH_MAX];
    +	snprintf(buf, sizeof(buf), "UID/%" PRIu32 ".%s", uid, type);
    +	return buf;
    +}
    +
    +static void flatten(struct List *flat, struct List nested) {
    +	for (size_t i = 0; i < nested.len; ++i) {
    +		if (nested.ptr[i].type == List) {
    +			flatten(flat, nested.ptr[i].list);
    +		} else {
    +			listPush(flat, nested.ptr[i]);
    +		}
    +	}
    +}
    +
    +enum Atom exportThreads(FILE *imap, struct List threads) {
    +	struct List uids = {0};
    +	flatten(&uids, threads);
    +	for (size_t i = uids.len - 1; i < uids.len; --i) {
    +		if (uids.ptr[i].type != Number) {
    +			errx(EX_PROTOCOL, "invalid thread UID");
    +		}
    +		uint32_t uid = uids.ptr[i].number;
    +		int error = 0;
    +		if (!error) error = access(uidPath(uid, "atom"), F_OK);
    +		if (!error) error = access(uidPath(uid, "html"), F_OK);
    +		if (!error) error = access(uidPath(uid, "mbox"), F_OK);
    +		if (!error) {
    +			uids.ptr[i] = uids.ptr[--uids.len];
    +		}
    +	}
    +	enum Atom tag = 0;
    +	if (!uids.len) goto done;
    +
    +	tag = atom("exportThreads");
    +	fprintf(imap, "%s UID FETCH ", Atoms[tag]);
    +	for (size_t i = 0; i < uids.len; ++i) {
    +		fprintf(imap, "%s%" PRIu32, (i ? "," : ""), uids.ptr[i].number);
    +	}
    +	fprintf(
    +		imap,
    +		" ("
    +		"UID ENVELOPE "
    +		"BODY[HEADER.FIELDS (" MBOX_HEADERS ")] BODY[TEXT]"
    +		")\r\n"
    +	);
    +
    +done:
    +	listFree(uids);
    +	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 char *parseID(char *id) {
    +	size_t len = strlen(id);
    +	if (id[0] != '<' || !len || id[len - 1] != '>') {
    +		errx(EX_PROTOCOL, "invalid message ID");
    +	}
    +	id[len - 1] = '\0';
    +	return &id[1];
    +}
    +
    +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");
    +
    +	envelope.date.tm_isdst = -1;
    +	envelope.utc = mktime(&envelope.date);
    +
    +	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 = parseID(list.ptr[InReplyTo].string);
    +	}
    +	if (list.ptr[MessageID].type != String) {
    +		errx(EX_PROTOCOL, "invalid envelope message-id field");
    +	}
    +	envelope.messageID = parseID(list.ptr[MessageID].string);
    +
    +	return envelope;
    +}
    +
    +static enum Atom AtomBody;
    +static enum Atom AtomEnvelope;
    +static enum Atom AtomHeaderFields;
    +static enum Atom AtomText;
    +static enum Atom AtomUID;
    +
    +void exportData(struct List items) {
    +	if (!AtomBody) AtomBody = atom("BODY");
    +	if (!AtomEnvelope) AtomEnvelope = atom("ENVELOPE");
    +	if (!AtomHeaderFields) AtomHeaderFields = atom("HEADER.FIELDS");
    +	if (!AtomText) AtomText = atom("TEXT");
    +	if (!AtomUID) AtomUID = atom("UID");
    +
    +	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) {
    +		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");
    +		}
    +
    +		if (name == AtomBody) {
    +			i--;
    +			continue;
    +		} else 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);
    +		} 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;
    +	FILE *file;
    +	int error;
    +
    +	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);
    +
    +	path = uidPath(uid, "atom");
    +	file = fopen(path, "w");
    +	if (!file) err(EX_CANTCREAT, "%s", path);
    +	error = atomEnvelope(file, &envelope)
    +		|| fclose(file);
    +	if (error) err(EX_IOERR, "%s", path);
    +
    +	envelopeFree(envelope);
    +}
    diff --git a/imap.h b/imap.h
    index e4ac7ea..f9b3106 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>
    @@ -136,3 +139,5 @@ static inline void respFree(struct Resp resp) {
     extern bool imapVerbose;
     FILE *imapOpen(const char *host, const char *port);
     struct Resp imapResp(FILE *imap);
    +
    +#endif /* IMAP_H */