diff options
-rw-r--r-- | archive.h | 49 | ||||
-rw-r--r-- | atom.c | 9 | ||||
-rw-r--r-- | concat.c | 38 | ||||
-rw-r--r-- | decode.c | 14 | ||||
-rw-r--r-- | export.c | 124 | ||||
-rw-r--r-- | html.c | 60 | ||||
-rw-r--r-- | template.c | 35 |
7 files changed, 181 insertions, 148 deletions
diff --git a/archive.h b/archive.h index b1cb3dc..532ef47 100644 --- a/archive.h +++ b/archive.h @@ -15,7 +15,6 @@ */ #include <inttypes.h> -#include <limits.h> #include <stdint.h> #include <stdio.h> #include <stdlib.h> @@ -108,6 +107,15 @@ static inline bool bodyPartType( 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 bodyPartFree(struct BodyPart part) { if (part.multipart) { for (size_t i = 0; i < part.parts.len; ++i) { @@ -143,6 +151,7 @@ struct Variable { 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); @@ -160,37 +169,11 @@ 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); -struct Attachment { - char path[3][NAME_MAX + 1]; -}; - -static inline const char *pathUID(uint32_t uid, const char *type) { - static char buf[PATH_MAX + 1]; - snprintf(buf, sizeof(buf), "UID/%" PRIu32 ".%s", uid, type); - return buf; -} - -static inline const char *pathSafe(const char *messageID) { - if (!strchr(messageID, '/')) return messageID; - static char buf[NAME_MAX + 1]; - strlcpy(buf, messageID, sizeof(buf)); - for (char *ptr = buf; (ptr = strchr(ptr, '/')); ++ptr) { - *ptr = ';'; - } - return buf; -} - -static inline const char *pathMessage(const char *messageID, const char *type) { - static char buf[PATH_MAX + 1]; - snprintf(buf, sizeof(buf), "message/%s.%s", pathSafe(messageID), type); - return buf; -} - -static inline const char *pathThread(const char *messageID, const char *type) { - static char buf[PATH_MAX + 1]; - snprintf(buf, sizeof(buf), "thread/%s.%s", pathSafe(messageID), type); - return buf; -} +#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 MBOX_HEADERS \ "Date Subject From Sender Reply-To To Cc Bcc " \ @@ -212,7 +195,7 @@ extern const char *htmlTitle; int htmlMessageOpen(FILE *file, const struct Envelope *envelope); int htmlInline(FILE *file, const struct BodyPart *part, const char *content); int htmlAttachment( - FILE *file, const struct BodyPart *part, const struct Attachment *attach + FILE *file, const struct BodyPart *part, const struct Variable pathVars[] ); int htmlMessageClose(FILE *file); int htmlThreadHead(FILE *file, const struct Envelope *envelope); diff --git a/atom.c b/atom.c index 1c4d0de..405bf39 100644 --- a/atom.c +++ b/atom.c @@ -51,10 +51,11 @@ static int atomAuthor(FILE *file, struct Address addr) { static char *atomEntryURL(const struct Envelope *envelope) { struct Variable vars[] = { - { "name", pathSafe(envelope->messageID) }, + { "messageID", envelope->messageID }, + { "type", "mbox" }, {0}, }; - return templateURL("/message/[name].mbox", vars); + return templateURL("/" PATH_MESSAGE, vars); } int atomEntryOpen(FILE *file, const struct Envelope *envelope) { @@ -102,11 +103,11 @@ int atomEntryClose(FILE *file) { static char *atomFeedURL(const struct Envelope *envelope, const char *type) { struct Variable vars[] = { - { "name", pathSafe(envelope->messageID) }, + { "messageID", envelope->messageID }, { "type", type }, {0}, }; - return templateURL("/thread/[name].[type]", vars); + return templateURL("/" PATH_THREAD, vars); } int atomFeedOpen(FILE *file, const struct Envelope *envelope) { diff --git a/concat.c b/concat.c index 94f9bed..a4bf1aa 100644 --- a/concat.c +++ b/concat.c @@ -52,10 +52,23 @@ void concatFetch(FILE *imap, enum Atom tag, struct List threads) { fprintf(imap, " (UID ENVELOPE)\r\n"); } +static const char *uidPath(uint32_t uid, const char *type) { + char str[32]; + snprintf(str, sizeof(str), "%" PRIu32, uid); + struct Variable vars[] = { + { "uid", str }, + { "type", type }, + {0}, + }; + static char buf[PATH_MAX + 1]; + templateBuffer(buf, sizeof(buf), PATH_UID, vars, escapePath); + return buf; +} + static time_t uidNewest(struct List uids, const char *type) { time_t newest = 0; for (size_t i = 0; i < uids.len; ++i) { - const char *path = pathUID(dataCheck(uids.ptr[i], Number).number, type); + const char *path = uidPath(dataCheck(uids.ptr[i], Number).number, type); struct stat status; int error = stat(path, &status); if (error) err(EX_DATAERR, "%s", path); @@ -86,13 +99,24 @@ static int concatHTML(FILE *file, struct List thread) { || htmlSubthreadClose(file); } else { uint32_t uid = dataCheck(thread.ptr[i], Number).number; - error = concatFile(file, pathUID(uid, "html")); + error = concatFile(file, uidPath(uid, "html")); } if (error) return error; } return 0; } +static const char *threadPath(const char *messageID, const char *type) { + static char buf[PATH_MAX + 1]; + struct Variable vars[] = { + { "messageID", messageID }, + { "type", type }, + {0}, + }; + templateBuffer(buf, sizeof(buf), PATH_THREAD, vars, escapePath); + return buf; +} + const char *concatHead; void concatData(struct List threads, struct List items) { @@ -121,7 +145,7 @@ void concatData(struct List threads, struct List items) { const char *path; struct stat status; - path = pathThread(envelope.messageID, "mbox"); + path = threadPath(envelope.messageID, "mbox"); error = stat(path, &status); if (error || status.st_mtime < uidNewest(flat, "mbox")) { file = fopen(path, "w"); @@ -129,7 +153,7 @@ void concatData(struct List threads, struct List items) { for (size_t i = 0; i < flat.len; ++i) { uint32_t uid = dataCheck(flat.ptr[i], Number).number; - error = concatFile(file, pathUID(uid, "mbox")); + error = concatFile(file, uidPath(uid, "mbox")); if (error) err(EX_IOERR, "%s", path); } @@ -137,7 +161,7 @@ void concatData(struct List threads, struct List items) { if (error) err(EX_IOERR, "%s", path); } - path = pathThread(envelope.messageID, "atom"); + path = threadPath(envelope.messageID, "atom"); error = stat(path, &status); if (error || status.st_mtime < uidNewest(flat, "atom")) { FILE *file = fopen(path, "w"); @@ -148,7 +172,7 @@ void concatData(struct List threads, struct List items) { for (size_t i = 0; i < flat.len; ++i) { uint32_t uid = dataCheck(flat.ptr[i], Number).number; - error = concatFile(file, pathUID(uid, "atom")); + error = concatFile(file, uidPath(uid, "atom")); if (error) err(EX_IOERR, "%s", path); } @@ -156,7 +180,7 @@ void concatData(struct List threads, struct List items) { if (error) err(EX_IOERR, "%s", path); } - path = pathThread(envelope.messageID, "html"); + path = threadPath(envelope.messageID, "html"); error = stat(path, &status); if (error || status.st_mtime < uidNewest(flat, "html")) { FILE *file = fopen(path, "w"); diff --git a/decode.c b/decode.c index 35e6dfd..2604f73 100644 --- a/decode.c +++ b/decode.c @@ -244,25 +244,15 @@ char *decodeHeader(const char *header) { return bufferString(&buf); } -static const char *partCharset(const struct BodyPart *part) { - const char *charset = NULL; - for (size_t i = 0; i + 1 < part->params.len; i += 2) { - const char *key = dataCheck(part->params.ptr[i], String).string; - if (strcasecmp(key, "charset")) continue; - charset = dataCheck(part->params.ptr[i + 1], String).string; - } - return charset; -} - char *decodeToString(const struct BodyPart *part, const char *src) { struct Buffer dst = bufferAlloc(strlen(src) + 1); - decode(&dst, part->encoding, partCharset(part), src); + decode(&dst, part->encoding, paramGet(part->params, "charset"), src); return bufferString(&dst); } int decodeToFile(FILE *file, const struct BodyPart *part, const char *src) { struct Buffer dst = bufferAlloc(strlen(src)); - decode(&dst, part->encoding, partCharset(part), src); + decode(&dst, part->encoding, paramGet(part->params, "charset"), src); size_t n = fwrite(dst.ptr, dst.len, 1, file); free(dst.ptr); return (n ? 0 : -1); diff --git a/export.c b/export.c index 9aa3126..873870c 100644 --- a/export.c +++ b/export.c @@ -29,15 +29,28 @@ #include "archive.h" #include "imap.h" +static const char *exportPath(uint32_t uid, const char *type) { + char str[32]; + snprintf(str, sizeof(str), "%" PRIu32, uid); + struct Variable vars[] = { + { "uid", str }, + { "type", type }, + {0}, + }; + static char buf[PATH_MAX + 1]; + templateBuffer(buf, sizeof(buf), PATH_UID, vars, escapePath); + return buf; +} + 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); + || access(exportPath(uid, "atom"), F_OK) + || access(exportPath(uid, "html"), F_OK) + || access(exportPath(uid, "mbox"), F_OK); if (!error) uids.ptr[i] = uids.ptr[--uids.len]; } if (!uids.len) { @@ -61,7 +74,7 @@ static void exportMbox( uint32_t uid, const struct Envelope *envelope, const char *header, const char *body ) { - const char *path = pathUID(uid, "mbox"); + const char *path = exportPath(uid, "mbox"); FILE *file = fopen(path, "w"); if (!file) err(EX_CANTCREAT, "%s", path); int error = 0 @@ -71,10 +84,17 @@ static void exportMbox( || 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); + char buf[PATH_MAX + 1]; + struct Variable vars[] = { + { "messageID", envelope->messageID }, + { "type", "mbox" }, + {0}, + }; + templateBuffer(buf, sizeof(buf), PATH_MESSAGE, vars, escapePath); + + unlink(buf); + error = link(path, buf); + if (error) err(EX_CANTCREAT, "%s", buf); } static bool isInline(const struct BodyPart *part) { @@ -87,7 +107,7 @@ static void exportAtom( uint32_t uid, const struct Envelope *envelope, const struct BodyPart *structure, struct Data body ) { - const char *path = pathUID(uid, "atom"); + const char *path = exportPath(uid, "atom"); FILE *file = fopen(path, "w"); if (!file) err(EX_CANTCREAT, "%s", path); @@ -118,61 +138,55 @@ static void exportAtom( if (error) err(EX_IOERR, "%s", path); } -static struct Attachment exportAttachment( - const struct Envelope *envelope, struct List section, +static int exportHTMLAttachment( + FILE *file, const struct Envelope *envelope, struct List *section, const struct BodyPart *part, struct Data body ) { - struct Attachment attach = { "", "", "" }; - strlcpy( - attach.path[0], pathSafe(envelope->messageID), sizeof(attach.path[0]) - ); - for (size_t i = 0; i < section.len; ++i) { - uint32_t num = dataCheck(section.ptr[i], Number).number; - char buf[32]; - snprintf(buf, sizeof(buf), "%s%" PRIu32, (i ? "." : ""), num); - strlcat(attach.path[1], buf, sizeof(attach.path[1])); - } - struct List params = part->disposition.params; - for (size_t i = 0; i + 1 < params.len; i += 2) { - const char *key = dataCheck(params.ptr[i], String).string; - if (strcasecmp(key, "filename")) continue; - const char *value = dataCheck(params.ptr[i + 1], String).string; - strlcpy(attach.path[2], pathSafe(value), sizeof(attach.path[2])); - } - if (!attach.path[2][0]) { - const char *disposition = part->disposition.type; - if (!disposition) disposition = "INLINE"; - strlcat(attach.path[2], pathSafe(disposition), sizeof(attach.path[2])); - strlcat(attach.path[2], ".", sizeof(attach.path[2])); - strlcat(attach.path[2], pathSafe(part->subtype), sizeof(attach.path[2])); + char buf[256] = ""; + for (size_t i = 0; i < section->len; ++i) { + snprintf( + &buf[strlen(buf)], sizeof(buf) - strlen(buf), "%s%" PRIu32, + (i ? "." : ""), dataCheck(section->ptr[i], Number).number + ); } - - char path[PATH_MAX + 1] = "attachment"; - for (int i = 0; i < 2; ++i) { - strlcat(path, "/", sizeof(path)); - strlcat(path, attach.path[i], sizeof(path)); + const char *name = paramGet(part->disposition.params, "filename"); + const char *disposition = part->disposition.type; + if (!disposition) disposition = "INLINE"; + + char path[PATH_MAX + 1]; + struct Variable vars[] = { + { "messageID", envelope->messageID }, + { "section", buf }, + { "name", (name ? name : "") }, + { "disposition", (name ? "" : disposition) }, + { ".", (name ? "" : ".") }, + { "subtype", (name ? "" : part->subtype) }, + {0}, + }; + templateBuffer(path, sizeof(path), PATH_ATTACHMENT, vars, escapePath); + + for (char *ch = path; (ch = strchr(ch, '/')); ++ch) { + *ch = '\0'; int error = mkdir(path, 0775); if (error && errno != EEXIST) err(EX_CANTCREAT, "%s", path); + *ch = '/'; } - strlcat(path, "/", sizeof(path)); - strlcat(path, attach.path[2], sizeof(path)); - FILE *file = fopen(path, "w"); + FILE *attachment = fopen(path, "w"); if (!file) err(EX_CANTCREAT, "%s", path); int error = 0 - || decodeToFile(file, part, dataCheck(body, String).string) - || fclose(file); + || decodeToFile(attachment, part, dataCheck(body, String).string) + || fclose(attachment); if (error) err(EX_IOERR, "%s", path); - return attach; + return htmlAttachment(file, part, vars); } static int exportHTMLBody( FILE *file, const struct Envelope *envelope, struct List *section, const struct BodyPart *part, struct Data body ) { - int error = 0; if (bodyPartType(part, "multipart", "alternative")) { for (size_t i = part->parts.len - 1; i < part->parts.len; --i) { if (!isInline(&part->parts.ptr[i])) continue; @@ -191,41 +205,39 @@ static int exportHTMLBody( for (size_t i = 0; i < part->parts.len; ++i) { struct Data num = { .type = Number, .number = 1 + i }; listPush(section, num); - error = exportHTMLBody( + int error = exportHTMLBody( file, envelope, section, &part->parts.ptr[i], dataCheck(body, List).list.ptr[i] ); if (error) return error; section->len--; } + return 0; } else if (part->message.structure) { const struct BodyPart *structure = part->message.structure; - error = 0 + int error = 0 || htmlMessageOpen(file, part->message.envelope) || exportHTMLBody(file, envelope, section, structure, body) || htmlMessageClose(file); + return error; } else if (isInline(part)) { char *content = decodeToString(part, dataCheck(body, String).string); - error = htmlInline(file, part, content); + int error = htmlInline(file, part, content); free(content); + return error; } else { - // TODO: Open and close attachment lists. - struct Attachment attach = exportAttachment( - envelope, *section, part, body - ); - error = htmlAttachment(file, part, &attach); + return exportHTMLAttachment(file, envelope, section, part, body); } - 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"); + const char *path = exportPath(uid, "html"); FILE *file = fopen(path, "w"); if (!file) err(EX_CANTCREAT, "%s", path); diff --git a/html.c b/html.c index 2d24ce2..403caef 100644 --- a/html.c +++ b/html.c @@ -94,10 +94,11 @@ static char *htmlFragment(const struct Envelope *envelope) { static char *htmlMbox(const struct Envelope *envelope) { struct Variable vars[] = { - { "name", pathSafe(envelope->messageID) }, + { "messageID", envelope->messageID }, + { "type", "mbox" }, {0}, }; - return templateURL("../message/[name].mbox", vars); + return templateURL("../" PATH_MESSAGE, vars); } int htmlMessageOpen(FILE *file, const struct Envelope *envelope) { @@ -164,26 +165,20 @@ int htmlInline(FILE *file, const struct BodyPart *part, const char *content) { return templateRender(file, template, vars, escapeXML); } -static char *htmlAttachmentURL(const struct Attachment *attach) { - struct Variable vars[] = { - { "path0", attach->path[0] }, - { "path1", attach->path[1] }, - { "path2", attach->path[2] }, - {0}, - }; - return templateURL("../attachment/[path0]/[path1]/[path2]", vars); -} - int htmlAttachment( - FILE *file, const struct BodyPart *part, const struct Attachment *attach + FILE *file, const struct BodyPart *part, const struct Variable *path ) { const char *template = TEMPLATE( - <a class="attachment" href="[url]">[name]</a> + <a class="attachment" href="[url]">[name][type][/][subtype]</a> ); - char *url = htmlAttachmentURL(attach); + char *url = templateURL("../" PATH_ATTACHMENT, path); + const char *name = paramGet(part->disposition.params, "filename"); struct Variable vars[] = { { "url", url }, - { "name", attach->path[2] }, // FIXME: Show intended name or type. + { "name", (name ? name : "") }, + { "type", (name ? "" : part->type) }, + { "/", (name ? "" : "/") }, + { "subtype", (name ? "" : part->subtype) }, {0}, }; int error = templateRender(file, template, vars, escapeXML); @@ -197,12 +192,13 @@ int htmlMessageClose(FILE *file) { const char *htmlTitle; -static char *htmlThreadURL(const struct Envelope *envelope) { +static char *htmlThreadURL(const struct Envelope *envelope, const char *type) { struct Variable vars[] = { - { "name", pathSafe(envelope->messageID) }, + { "messageID", envelope->messageID }, + { "type", type }, {0}, }; - return templateURL("[name]", vars); + return templateURL("../" PATH_THREAD, vars); } int htmlThreadHead(FILE *file, const struct Envelope *envelope) { @@ -210,18 +206,21 @@ int htmlThreadHead(FILE *file, const struct Envelope *envelope) { <!DOCTYPE html> <meta charset="utf-8"> <title>[subject] · [title]</title> - <link rel="alternate" type="application/atom+xml" href="[url].atom"> - <link rel="alternate" type="application/mbox" href="[url].mbox"> + <link rel="alternate" type="application/atom+xml" href="[atom]"> + <link rel="alternate" type="application/mbox" href="[mbox]"> ); - char *url = htmlThreadURL(envelope); + char *atom = htmlThreadURL(envelope, "atom"); + char *mbox = htmlThreadURL(envelope, "mbox"); struct Variable vars[] = { { "subject", envelope->subject }, { "title", htmlTitle }, - { "url", url }, + { "atom", atom }, + { "mbox", mbox }, {0}, }; int error = templateRender(file, template, vars, escapeXML); - free(url); + free(atom); + free(mbox); return error; } @@ -231,21 +230,24 @@ int htmlThreadOpen(FILE *file, const struct Envelope *envelope) { <h1>[subject]</h1> <nav> <ul> - <li><a href="[url].atom">follow</a></li> - <li><a href="[url].mbox">download</a></li> + <li><a href="[atom]">follow</a></li> + <li><a href="[mbox]">download</a></li> </ul> </nav> </header> <main class="thread"> ); - char *url = htmlThreadURL(envelope); + char *atom = htmlThreadURL(envelope, "atom"); + char *mbox = htmlThreadURL(envelope, "mbox"); struct Variable vars[] = { { "subject", envelope->subject }, - { "url", url }, + { "atom", atom }, + { "mbox", mbox }, {0}, }; int error = templateRender(file, template, vars, escapeXML); - free(url); + free(atom); + free(mbox); return error; } diff --git a/template.c b/template.c index afb9173..4f2d9dd 100644 --- a/template.c +++ b/template.c @@ -29,6 +29,25 @@ static int escapeNull(FILE *file, const char *str) { return (n ? 0 : -1); } +static const char SlashReplacement = ';'; + +int escapePath(FILE *file, const char *str) { + while (*str) { + if (*str == '/') { + str++; + int n = fprintf(file, "%c", SlashReplacement); + if (n < 0) return n; + } + size_t len = strcspn(str, "/"); + if (len) { + size_t n = fwrite(str, len, 1, file); + if (!n) return -1; + } + str += len; + } + return 0; +} + int escapeURL(FILE *file, const char *str) { static const char *Safe = { "$-_.+!*'()," @@ -44,7 +63,9 @@ int escapeURL(FILE *file, const char *str) { } str += len; if (*str) { - int n = fprintf(file, "%%%02X", *str++); + char ch = *str++; + if (ch == '/') ch = SlashReplacement; + int n = fprintf(file, "%%%02X", ch); if (n < 0) return n; } } @@ -53,12 +74,6 @@ int escapeURL(FILE *file, const char *str) { int escapeXML(FILE *file, const char *str) { while (*str) { - size_t len = strcspn(str, "\"&<"); - if (len) { - size_t n = fwrite(str, len, 1, file); - if (!n) return -1; - } - str += len; int n = 0; switch (*str) { break; case '"': str++; n = fprintf(file, """); @@ -66,6 +81,12 @@ int escapeXML(FILE *file, const char *str) { break; case '<': str++; n = fprintf(file, "<"); } if (n < 0) return n; + size_t len = strcspn(str, "\"&<"); + if (len) { + size_t n = fwrite(str, len, 1, file); + if (!n) return -1; + } + str += len; } return 0; } |