diff options
-rw-r--r-- | archive.c | 1 | ||||
-rw-r--r-- | archive.h | 15 | ||||
-rw-r--r-- | bubger.1 | 15 | ||||
-rw-r--r-- | export.c | 83 | ||||
-rw-r--r-- | html.c | 27 |
5 files changed, 126 insertions, 15 deletions
diff --git a/archive.c b/archive.c index 7edc6d6..64b234e 100644 --- a/archive.c +++ b/archive.c @@ -181,6 +181,7 @@ int main(int argc, char *argv[]) { errx(EX_TEMPFAIL, "no messages matching %s", search); } createDir("UID"); + createDir("attachment"); createDir("message"); createDir("thread"); threads = resp.data; diff --git a/archive.h b/archive.h index d0bc00d..06fd435 100644 --- a/archive.h +++ b/archive.h @@ -156,15 +156,19 @@ 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]; + 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[PATH_MAX]; + static char buf[NAME_MAX + 1]; strlcpy(buf, messageID, sizeof(buf)); for (char *ptr = buf; (ptr = strchr(ptr, '/')); ++ptr) { *ptr = ';'; @@ -173,13 +177,13 @@ static inline const char *pathSafe(const char *messageID) { } static inline const char *pathMessage(const char *messageID, const char *type) { - static char buf[PATH_MAX]; + 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]; + static char buf[PATH_MAX + 1]; snprintf(buf, sizeof(buf), "thread/%s.%s", pathSafe(messageID), type); return buf; } @@ -203,6 +207,9 @@ int atomFeedClose(FILE *file); 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 +); int htmlMessageClose(FILE *file); int htmlThreadHead(FILE *file, const struct Envelope *envelope); int htmlThreadOpen(FILE *file, const struct Envelope *envelope); diff --git a/bubger.1 b/bubger.1 index 57d734f..ed402a1 100644 --- a/bubger.1 +++ b/bubger.1 @@ -1,4 +1,4 @@ -.Dd April 16, 2020 +.Dd April 17, 2020 .Dt BUBGER 1 .Os . @@ -112,6 +112,8 @@ Stores the mailbox UID validity. Stores the next UID of the mailbox. .It Pa UID/*.atom , Pa UID/*.html , Pa UID/*.mbox Cached Atom, HTML and mboxrd fragments for each message. +.It Pa attachment/*/*/* +Attached files. .It Pa message/*.mbox Rendered mboxrd files for each message. .It Pa thread/*.atom , Pa thread/*.html , Pa thread/*.mbox @@ -179,6 +181,17 @@ Rendered Atom, HTML and mboxrd files for each thread. .Re .It .Rs +.%A R. Troost +.%A S. Dorner +.%A K. Moore +.%T The Content-Disposition Header Field +.%I IETF +.%N RFC 2183 +.%D August 1997 +.%U https://tools.ietf.org/html/rfc2183 +.Re +.It +.Rs .%A T. Berners-Lee .%A L. Masinter .%A M. McCahill diff --git a/export.c b/export.c index 21f5662..9aa3126 100644 --- a/export.c +++ b/export.c @@ -15,11 +15,14 @@ */ #include <err.h> +#include <errno.h> #include <inttypes.h> +#include <limits.h> #include <stdint.h> #include <stdio.h> #include <stdlib.h> #include <strings.h> +#include <sys/stat.h> #include <sysexits.h> #include <unistd.h> @@ -115,8 +118,58 @@ static void exportAtom( if (error) err(EX_IOERR, "%s", path); } +static struct Attachment exportAttachment( + 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 path[PATH_MAX + 1] = "attachment"; + for (int i = 0; i < 2; ++i) { + strlcat(path, "/", sizeof(path)); + strlcat(path, attach.path[i], sizeof(path)); + int error = mkdir(path, 0775); + if (error && errno != EEXIST) err(EX_CANTCREAT, "%s", path); + } + strlcat(path, "/", sizeof(path)); + strlcat(path, attach.path[2], sizeof(path)); + + FILE *file = fopen(path, "w"); + if (!file) err(EX_CANTCREAT, "%s", path); + + int error = 0 + || decodeToFile(file, part, dataCheck(body, String).string) + || fclose(file); + if (error) err(EX_IOERR, "%s", path); + + return attach; +} + static int exportHTMLBody( - FILE *file, struct List *section, + FILE *file, const struct Envelope *envelope, struct List *section, const struct BodyPart *part, struct Data body ) { int error = 0; @@ -124,36 +177,46 @@ static int exportHTMLBody( for (size_t i = part->parts.len - 1; i < part->parts.len; --i) { if (!isInline(&part->parts.ptr[i])) continue; return exportHTMLBody( - file, section, &part->parts.ptr[i], - dataCheck(body, List).list.ptr[i] + file, envelope, section, + &part->parts.ptr[i], dataCheck(body, List).list.ptr[i] ); } return exportHTMLBody( - file, section, &part->parts.ptr[part->parts.len - 1], + file, envelope, section, + &part->parts.ptr[part->parts.len - 1], dataCheck(body, List).list.ptr[part->parts.len - 1] ); + } else if (part->multipart) { for (size_t i = 0; i < part->parts.len; ++i) { struct Data num = { .type = Number, .number = 1 + i }; listPush(section, num); error = exportHTMLBody( - file, section, &part->parts.ptr[i], - dataCheck(body, List).list.ptr[i] + file, envelope, section, + &part->parts.ptr[i], dataCheck(body, List).list.ptr[i] ); if (error) return error; section->len--; } - } else if (part->message.envelope) { + + } else if (part->message.structure) { + const struct BodyPart *structure = part->message.structure; error = 0 || htmlMessageOpen(file, part->message.envelope) - || exportHTMLBody(file, section, part->message.structure, body) + || exportHTMLBody(file, envelope, section, structure, body) || htmlMessageClose(file); + } else if (isInline(part)) { char *content = decodeToString(part, dataCheck(body, String).string); error = htmlInline(file, part, content); free(content); + } else { - // TODO: Write out attachment. + // TODO: Open and close attachment lists. + struct Attachment attach = exportAttachment( + envelope, *section, part, body + ); + error = htmlAttachment(file, part, &attach); } return error; } @@ -170,7 +233,7 @@ static void exportHTML( if (error) err(EX_IOERR, "%s", path); struct List section = {0}; - error = exportHTMLBody(file, §ion, structure, body); + error = exportHTMLBody(file, envelope, §ion, structure, body); if (error) err(EX_IOERR, "%s", path); listFree(section); diff --git a/html.c b/html.c index 9e37db8..2d24ce2 100644 --- a/html.c +++ b/html.c @@ -164,6 +164,33 @@ 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 +) { + const char *template = TEMPLATE( + <a class="attachment" href="[url]">[name]</a> + ); + char *url = htmlAttachmentURL(attach); + struct Variable vars[] = { + { "url", url }, + { "name", attach->path[2] }, // FIXME: Show intended name or type. + {0}, + }; + int error = templateRender(file, template, vars, escapeXML); + free(url); + return error; +} + int htmlMessageClose(FILE *file) { return templateRender(file, TEMPLATE(</article>), NULL, NULL); } |