diff options
Diffstat (limited to '')
-rw-r--r-- | Makefile | 1 | ||||
-rw-r--r-- | archive.c | 250 | ||||
-rw-r--r-- | archive.h | 5 | ||||
-rw-r--r-- | export.c | 271 | ||||
-rw-r--r-- | imap.h | 5 |
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 */ |