/* Copyright (C) 2020 June 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 . * * Additional permission under GNU GPL version 3 section 7: * * If you modify this Program, or any covered work, by linking or * combining it with OpenSSL (or a modified version of that library), * containing parts covered by the terms of the OpenSSL License and the * original SSLeay license, the licensors of this Program grant you * additional permission to convey the resulting work. Corresponding * Source for a non-source form of such a combination shall include the * source code for the parts of OpenSSL used as well as that of the * covered work. */ #include #include #include #include #include #include #include #include "imap.h" #define Q(...) #__VA_ARGS__ #define GENERATOR_URL "https://git.causal.agency/bubger" #define PATH_UID "UID/[uid].[type]" #define PATH_MESSAGE "message/[messageID].[type]" #define PATH_THREAD "thread/[messageID].[type]" #define PATH_ATTACHMENT \ "attachment/[messageID]/[section]/[name][disposition][.][subtype]" #define PATH_INDEX "[name].[type]" #define MBOX_HEADERS \ "Date Subject From Sender Reply-To To Cc Bcc " \ "Message-Id In-Reply-To References " \ "MIME-Version Content-Type Content-Disposition Content-Transfer-Encoding" extern const char Stylesheet[]; extern bool quiet; extern const char *baseURL; extern const char *baseTitle; extern const char *baseMailto; extern const char *baseSubscribe; extern const char *baseStylesheet; extern struct Search { size_t cap; size_t len; char **names; char **exprs; } search; static inline struct U32 { char s[sizeof("4294967295")]; } u32(uint32_t u) { struct U32 buf; snprintf(buf.s, sizeof(buf.s), "%" PRIu32, u); return buf; } static inline struct ISO8601 { char s[sizeof("0000-00-00T00:00:00Z")]; } iso8601(time_t time) { struct ISO8601 buf; strftime(buf.s, sizeof(buf.s), "%FT%TZ", gmtime(&time)); return buf; } struct Variable { const char *name; const char *value; }; typedef int EscapeFn(FILE *file, const char *str); int escapePath(FILE *file, const char *str); int escapeURL(FILE *file, const char *str); int escapeXML(FILE *file, const char *str); int templateRender( FILE *file, const char *template, const struct Variable vars[], EscapeFn *escape ); char *templateString( const char *template, const struct Variable vars[], EscapeFn *escape ); struct Address { char *name; const char *mailbox; const char *host; }; struct AddressList { size_t len; struct Address *addrs; }; struct Envelope { time_t time; const char *date; char *subject; struct Address from, sender, replyTo; struct AddressList to, cc, bcc; const char *inReplyTo; const char *messageID; }; struct BodyPart { bool multipart; union { const char *type; struct { size_t len; struct BodyPart *ptr; } parts; }; const char *subtype; struct List params; const char *contentID; const char *description; const char *encoding; uint32_t size; struct { struct Envelope *envelope; struct BodyPart *structure; uint32_t lines; } message; struct { uint32_t lines; } text; const char *md5; struct { const char *type; struct List params; } disposition; struct Data language; struct List location; }; void parseEnvelope(struct Envelope *envelope, struct List list); void parseBodyPart(struct BodyPart *part, struct List list); static inline const char *addressName(struct Address addr) { return (addr.name ? addr.name : addr.mailbox); } static inline bool bodyPartType( const struct BodyPart *part, const char *type, const char *subtype ) { const char *partType = (part->multipart ? "multipart" : part->type); return !strcasecmp(partType, type) && !strcasecmp(part->subtype, subtype); } static inline char *paramGet(struct List params, const char *key) { for (size_t i = 0; i + 1 < params.len; i += 2) { if (!strcasecmp(dataCheck(params.ptr[i], String).string, key)) { return dataCheck(params.ptr[i + 1], String).string; } } return NULL; } static inline void addressListFree(struct AddressList list) { for (size_t i = 0; i < list.len; ++i) { free(list.addrs[i].name); } } static inline void envelopeFree(struct Envelope envelope) { free(envelope.subject); free(envelope.from.name); free(envelope.sender.name); free(envelope.replyTo.name); addressListFree(envelope.to); addressListFree(envelope.cc); addressListFree(envelope.bcc); free(envelope.to.addrs); free(envelope.cc.addrs); free(envelope.bcc.addrs); } static inline void bodyPartFree(struct BodyPart part) { if (part.multipart) { for (size_t i = 0; i < part.parts.len; ++i) { bodyPartFree(part.parts.ptr[i]); } free(part.parts.ptr); } if (part.message.envelope) { envelopeFree(*part.message.envelope); free(part.message.envelope); } if (part.message.structure) { bodyPartFree(*part.message.structure); free(part.message.structure); } } char *decodeHeader(const char *header); char *decodeToString(const struct BodyPart *part, const char *content); int decodeToFile(FILE *file, const struct BodyPart *part, const char *content); static inline uint32_t threadRoot(struct List thread) { if (!thread.len) errx(EX_PROTOCOL, "empty thread"); while (thread.ptr[0].type == List) { thread = thread.ptr[0].list; if (!thread.len) errx(EX_PROTOCOL, "empty subthread"); } return dataCheck(thread.ptr[0], Number).number; } static inline void compressUIDs(FILE *imap, struct List uids) { uint32_t base = 0, prev = 0; for (size_t i = 0; i < uids.len; ++i) { uint32_t uid = uids.ptr[i].type == List ? threadRoot(uids.ptr[i].list) : dataCheck(uids.ptr[i], Number).number; if (!base) { fprintf(imap, "%" PRIu32, uid); base = prev = uid; } else if (uid == prev + 1) { prev = uid; } else { if (prev > base) fprintf(imap, ":%" PRIu32, prev); fprintf(imap, ",%" PRIu32, uid); base = prev = uid; } } if (prev > base) fprintf(imap, ":%" PRIu32, prev); } bool exportFetch(FILE *imap, enum Atom tag, struct List threads); bool exportData(FILE *imap, enum Atom tag, struct List items); extern const char *concatHead; extern size_t concatIndexEntries; void concatFetch(FILE *imap, enum Atom tag, struct List threads); void concatSearch( FILE *imap, enum Atom tag, struct List threads, const char *expr ); void concatData( struct List threads, struct Envelope *envelopes, struct List items ); void concatThreads(struct List threads, const struct Envelope *envelopes); void concatIndex( const char *name, struct List roots, struct List threads, const struct Envelope *envelopes ); int mboxFrom(FILE *file); int mboxHeader(FILE *file, const char *header); int mboxBody(FILE *file, const char *body); int atomEntryOpen(FILE *file, const struct Envelope *envelope); int atomContent(FILE *file, const char *content); int atomEntryClose(FILE *file); int atomThreadOpen(FILE *file, const struct Envelope *envelope); int atomThreadClose(FILE *file); int atomIndexOpen(FILE *file, const char *name); int atomIndexClose(FILE *file); int htmlMessageOpen(FILE *file, const struct Envelope *envelope, bool nested); int htmlInline(FILE *file, const struct BodyPart *part, const char *content); int htmlAttachmentOpen(FILE *file); int htmlAttachment( FILE *file, const struct BodyPart *part, const struct Variable pathVars[] ); int htmlAttachmentClose(FILE *file); int htmlMessageClose(FILE *file); int htmlThreadHead(FILE *file, const struct Envelope *envelope); int htmlThreadOpen(FILE *file, const struct Envelope *envelope); int htmlSubthreadOpen(FILE *file, struct List thread); int htmlSubthreadClose(FILE *file); int htmlThreadClose(FILE *file); int htmlIndexHead(FILE *file, const char *name); int htmlIndexOpen(FILE *file, const char *name); int htmlIndexThread( FILE *file, const struct Envelope *envelope, struct List thread ); int htmlIndexClose(FILE *file);