/* Copyright (C) 2020 C. McEnroe * * 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 . */ #include #include #include #include #include #include #include #include "archive.h" #include "imap.h" bool exportFetch(FILE *imap, enum Atom tag, struct List threads) { struct List uids = {0}; listFlatten(&uids, threads); for (size_t i = uids.len - 1; i < uids.len; --i) { uint32_t uid = dataCheck(uids.ptr[i], Number).number; int error = 0 || access(pathUID(uid, "atom"), F_OK) || access(pathUID(uid, "html"), F_OK) || access(pathUID(uid, "mbox"), F_OK); if (!error) uids.ptr[i] = uids.ptr[--uids.len]; } if (!uids.len) { listFree(uids); return false; } 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 BODYSTRUCTURE" " BODY[HEADER.FIELDS (" MBOX_HEADERS ")] BODY[TEXT])\r\n" ); return true; } static void exportMbox( uint32_t uid, const struct Envelope *envelope, const char *header, const char *body ) { const char *path = pathUID(uid, "mbox"); FILE *file = fopen(path, "w"); if (!file) err(EX_CANTCREAT, "%s", path); int error = 0 || mboxFrom(file) || mboxHeader(file, header) || mboxBody(file, body) || fclose(file); if (error) err(EX_IOERR, "%s", path); const char *msg = pathMessage(envelope->messageID, "mbox"); unlink(msg); error = link(path, msg); if (error) err(EX_CANTCREAT, "%s", msg); } static void exportAtom( uint32_t uid, const struct Envelope *envelope, const struct BodyPart *structure, struct Data body ) { const char *path = pathUID(uid, "atom"); FILE *file = fopen(path, "w"); if (!file) err(EX_CANTCREAT, "%s", path); int error = atomEntryOpen(file, envelope); if (error) err(EX_IOERR, "%s", path); const struct BodyPart *part = structure; if (bodyPartType(part, "multipart", "mixed")) { part = &part->parts.ptr[0]; body = dataCheck(body, List).list.ptr[0]; } if (bodyPartType(part, "multipart", "alternative")) { for (size_t i = 0; i < part->parts.len; ++i) { if (bodyPartType(&part->parts.ptr[i], "text", "plain")) { part = &part->parts.ptr[i]; body = dataCheck(body, List).list.ptr[i]; break; } } } if (bodyPartType(part, "text", "plain")) { char *content = decodeToString(part, dataCheck(body, String).string); error = atomContent(file, content); if (error) err(EX_IOERR, "%s", path); free(content); } error = atomEntryClose(file) || fclose(file); if (error) err(EX_IOERR, "%s", path); } static int exportHTMLBody( FILE *file, struct List *section, const struct BodyPart *structure, struct Data body ) { int error = 0; if (structure->multipart) { // TODO: Choose a part from multipart/alternative. for (size_t i = 0; i < structure->parts.len; ++i) { struct Data part = { .type = Number, .number = 1 + i }; listPush(section, part); error = exportHTMLBody( file, section, &structure->parts.ptr[i], dataCheck(body, List).list.ptr[i] ); if (error) return error; section->len--; } } else if (structure->message.envelope) { error = 0 || htmlMessageOpen(file, structure->message.envelope) || exportHTMLBody(file, section, structure->message.structure, body) || htmlMessageClose(file); } else if (bodyPartType(structure, "text", "plain")) { // TODO: Check if not inline. char *content = decodeToString( structure, dataCheck(body, String).string ); error = htmlInline(file, structure, content); free(content); } return error; } static void exportHTML( uint32_t uid, const struct Envelope *envelope, const struct BodyPart *structure, struct Data body ) { const char *path = pathUID(uid, "html"); FILE *file = fopen(path, "w"); if (!file) err(EX_CANTCREAT, "%s", path); int error = htmlMessageOpen(file, envelope); if (error) err(EX_IOERR, "%s", path); struct List section = {0}; error = exportHTMLBody(file, §ion, structure, body); if (error) err(EX_IOERR, "%s", path); listFree(section); error = htmlMessageClose(file) || fclose(file); if (error) err(EX_IOERR, "%s", path); } static void fetchParts( FILE *imap, struct List *section, const struct BodyPart *structure ) { if (structure->multipart) { for (size_t i = 0; i < structure->parts.len; ++i) { struct Data part = { .type = Number, .number = 1 + i }; listPush(section, part); fetchParts(imap, section, &structure->parts.ptr[i]); section->len--; } } else if ( structure->message.structure && structure->message.structure->multipart ) { fetchParts(imap, section, structure->message.structure); } else { fprintf(imap, " BODY["); for (size_t i = 0; i < section->len; ++i) { fprintf(imap, "%s%" PRIu32, (i ? "." : ""), section->ptr[i].number); } if (structure->message.structure) { fprintf(imap, ".TEXT"); } fprintf(imap, "]"); } } static void checkBodyParts(const struct BodyPart *structure, struct Data body) { if (structure->multipart) { struct List list = dataCheck(body, List).list; if (list.len < structure->parts.len) { errx(EX_PROTOCOL, "missing body parts"); } for (size_t i = 0; i < structure->parts.len; ++i) { checkBodyParts(&structure->parts.ptr[i], list.ptr[i]); } } else if ( structure->message.structure && structure->message.structure->multipart ) { checkBodyParts(structure->message.structure, body); } else if (body.type != String) { errx(EX_PROTOCOL, "missing body part"); } } bool exportData(FILE *imap, enum Atom tag, struct List items) { uint32_t uid = 0; struct Envelope envelope = {0}; struct BodyPart structure = {0}; struct Data bodyHeader = {0}; struct Data bodyText = {0}; struct Data bodyParts = {0}; for (size_t i = 0; i + 1 < items.len; i += 2) { enum Atom name = dataCheck(items.ptr[i], Atom).atom; struct Data data = items.ptr[i + 1]; if (name == AtomUID) { uid = dataCheck(data, Number).number; } else if (name == AtomEnvelope) { parseEnvelope(&envelope, dataCheck(data, List).list); } else if (name == AtomBodyStructure) { parseBodyPart(&structure, dataCheck(data, List).list); } if (name != AtomBody) continue; struct List section = dataCheck(data, List).list; if (!section.len) { errx(EX_PROTOCOL, "missing body data item section"); } i++; if (i + 1 >= items.len) { errx(EX_PROTOCOL, "missing body data item value"); } data = items.ptr[i + 1]; if (section.ptr[0].type == Atom) { name = section.ptr[0].atom; if (name == AtomHeader) { bodyHeader = data; } else if (name == AtomText) { bodyText = data; } continue; } struct Data *dest = &bodyParts; for (size_t i = 0; i < section.len; ++i) { if (section.ptr[i].type != Number) continue; uint32_t num = section.ptr[i].number; if (dest->type != List) { *dest = (struct Data) { .type = List }; } while (dest->list.len < num) { listPush(&dest->list, (struct Data) {0}); } dest = &dest->list.ptr[num - 1]; } *dest = dataTake(&items.ptr[i + 1]); } if (!uid) { errx(EX_PROTOCOL, "missing UID data item"); } if (!envelope.subject) { errx(EX_PROTOCOL, "missing ENVELOPE data item"); } if (!structure.subtype) { errx(EX_PROTOCOL, "missing BODYSTRUCTURE data item"); } if (bodyParts.type == List) { checkBodyParts(&structure, bodyParts); } if (bodyHeader.type == String && bodyText.type == String) { exportMbox(uid, &envelope, bodyHeader.string, bodyText.string); } bool fetch = false; if (!structure.multipart) { exportAtom(uid, &envelope, &structure, bodyText); exportHTML(uid, &envelope, &structure, bodyText); } else if (bodyParts.type == List) { exportAtom(uid, &envelope, &structure, bodyParts); exportHTML(uid, &envelope, &structure, bodyParts); } else { fetch = true; fprintf( imap, "%s UID FETCH %" PRIu32 " (UID ENVELOPE BODYSTRUCTURE", Atoms[tag], uid ); struct List section = {0}; fetchParts(imap, §ion, &structure); listFree(section); fprintf(imap, ")\r\n"); } envelopeFree(envelope); bodyPartFree(structure); dataFree(bodyParts); return fetch; }