/* 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 #include "archive.h" static int htmlAddress(FILE *file, const char *class, struct Address addr) { const char *template; if (addr.host) { template = TEMPLATE(
  • [name]
  • ); } else if (addr.mailbox) { template = TEMPLATE(
  • [mailbox]
      ); } else { template = TEMPLATE(
  • ); } struct Variable vars[] = { { "class", class }, { "name", addressName(addr) }, { "mailbox", addr.mailbox }, {0}, }; return templateRender(file, template, vars, escapeXML); } static int htmlAddressList(FILE *file, const char *class, struct AddressList list) { if (!list.len) return 0; const char *template = TEMPLATE(
      ); struct Variable vars[] = { { "class", class }, {0}, }; int error = templateRender(file, template, vars, escapeXML); if (error) return error; for (size_t i = 0; i < list.len; ++i) { error = htmlAddress(file, class, list.addrs[i]); if (error) return error; } return templateRender(file, TEMPLATE(
    ), NULL, NULL); } static char *htmlReply(const struct Envelope *envelope) { const char *template = { "mailto:[mailbox]@[host]" "?cc=[cc]" "&subject=[re][subject]" "&In-Reply-To=[<][messageID][>]" }; struct Variable vars[] = { { "mailbox", envelope->replyTo.mailbox }, { "host", envelope->replyTo.host }, { "cc", baseMailto }, { "re", (strncmp(envelope->subject, "Re: ", 4) ? "Re: " : "") }, { "subject", envelope->subject }, { "messageID", envelope->messageID }, { "<", "<" }, { ">", ">" }, {0}, }; return templateURL(template, vars); } static char *htmlFragment(const char *messageID) { struct Variable vars[] = { { "messageID", messageID }, {0}, }; return templateURL("#[messageID]", vars); } static char *htmlMbox(const char *messageID) { struct Variable vars[] = { { "messageID", messageID }, { "type", "mbox" }, {0}, }; return templateURL("../" PATH_MESSAGE, vars); } static int htmlNavItem(FILE *file, const char *name, const char *base, const char *url) { const char *template = TEMPLATE(
  • [name]
  • ); struct Variable vars[] = { { "name", name }, { "base", base }, { "url", url }, {0}, }; return templateRender(file, template, vars, escapeXML); } int htmlMessageNav(FILE *file, const struct Envelope *envelope) { int error = templateRender(file, TEMPLATE(), NULL, NULL); } static const char *htmlUTC(time_t time) { static char buf[sizeof("0000-00-00T00:00:00Z")]; strftime(buf, sizeof(buf), "%FT%TZ", gmtime(&time)); return buf; } int htmlMessageOpen(FILE *file, const struct Envelope *envelope) { const char *template = TEMPLATE(

    [subject]

    [from]
    ); char *fragment = htmlFragment(envelope->messageID); char *reply = htmlReply(envelope); struct Variable vars[] = { { "messageID", envelope->messageID }, { "fragment", fragment }, { "subject", envelope->subject }, { "reply", reply }, { "from", addressName(envelope->from) }, { "utc", htmlUTC(envelope->time) }, { "date", envelope->date }, {0}, }; int error = 0 || templateRender(file, template, vars, escapeXML) || htmlAddressList(file, "to", envelope->to) || htmlAddressList(file, "cc", envelope->cc) || htmlMessageNav(file, envelope) || templateRender(file, TEMPLATE(
    ), NULL, NULL); free(reply); free(fragment); return error; } static int htmlInlineAttrs(FILE *file, const struct BodyPart *part) { const char *template = " " TEMPLATE([attr]="[value]"); if (part->contentID) { struct Variable vars[] = { { "attr", "id" }, { "value", part->contentID }, {0}, }; int error = templateRender(file, template, vars, escapeXML); if (error) return error; } if (part->description) { struct Variable vars[] = { { "attr", "title" }, { "value", part->description }, {0}, }; int error = templateRender(file, template, vars, escapeXML); if (error) return error; } const char *language = NULL; if (part->language.type == String) language = part->language.string; if (part->language.type == List && part->language.list.len == 1) { language = dataCheck(part->language.list.ptr[0], String).string; } if (language) { struct Variable vars[] = { { "attr", "lang" }, { "value", language }, {0}, }; int error = templateRender(file, template, vars, escapeXML); if (error) return error; } return 0; } static void swap(char *a, char *b) { char ch = *a; *a = *b; *b = ch; } static int htmlMarkupURLs(FILE *file, char *buf) { static const char *Pattern = "(^|[[:space:]<])(https?:[^[:space:]>]+)(.|$)"; static regex_t regex; if (!regex.re_nsub) { int error = regcomp(®ex, Pattern, REG_EXTENDED); assert(!error); } int error; char *ptr; regmatch_t match[4]; for (ptr = buf; !regexec(®ex, ptr, 4, match, 0); ptr += match[2].rm_eo) { char nul = '\0'; swap(&ptr[match[2].rm_so], &nul); error = escapeXML(file, ptr); if (error) return error; swap(&ptr[match[2].rm_so], &nul); const char *template = TEMPLATE([url]); swap(&ptr[match[3].rm_so], &nul); struct Variable vars[] = { { "url", &ptr[match[2].rm_so] }, {0}, }; error = templateRender(file, template, vars, escapeXML); if (error) return error; swap(&ptr[match[3].rm_so], &nul); } return escapeXML(file, ptr); } static int htmlMarkupQuote(FILE *file, char *buf) { int error; size_t level = 0; for (char *ch = buf; *ch == '>' || *ch == ' '; level += (*ch++ == '>')); for (size_t i = 0; i < level; ++i) { error = templateRender(file, TEMPLATE(), NULL, NULL); if (error) return error; } error = htmlMarkupURLs(file, buf); if (error) return error; for (size_t i = 0; i < level; ++i) { error = templateRender(file, TEMPLATE(), NULL, NULL); if (error) return error; } return 0; } static int htmlMarkup(FILE *file, const char *content) { int error = 0; size_t cap = 0; char *buf = NULL; bool patch = false; for (const char *nl; (nl = strchr(content, '\n')); content = &nl[1]) { size_t len = nl - content; if (cap < len + 1) { cap = len + 1; buf = realloc(buf, cap); if (!buf) err(EX_OSERR, "realloc"); } memcpy(buf, content, len); buf[len] = '\0'; struct Variable vars[] = { { "line", buf }, {0}, }; if (!strcmp(buf, "---")) { patch = true; } else if (patch && !strcmp(buf, "-- ")) { patch = false; } static const char *Pattern = "^(diff|index|---|[+]{3}|@@) "; static regex_t regex; if (!regex.re_nsub) { error = regcomp(®ex, Pattern, REG_EXTENDED); assert(!error); } if (patch && !regexec(®ex, buf, 0, NULL, 0)) { error = templateRender( file, TEMPLATE([line]), vars, escapeXML ); } else if (patch && buf[0] == '-' && strcmp(buf, "---")) { error = templateRender( file, TEMPLATE([line]), vars, escapeXML ); } else if (patch && buf[0] == '+') { error = templateRender( file, TEMPLATE([line]), vars, escapeXML ); } else if (patch) { error = escapeXML(file, buf); } else if (buf[0] == '>') { error = htmlMarkupQuote(file, buf); } else { error = htmlMarkupURLs(file, buf); } if (error) break; error = templateRender(file, "\n", NULL, NULL); if (error) break; } free(buf); return error; } int htmlInline(FILE *file, const struct BodyPart *part, const char *content) { return 0 || templateRender(file, TEMPLATE(), NULL, NULL) || htmlMarkup(file, content) || templateRender(file, TEMPLATE(), NULL, NULL); } int htmlAttachmentOpen(FILE *file) { return templateRender(file, TEMPLATE(
      ), NULL, NULL); } int htmlAttachment( FILE *file, const struct BodyPart *part, const struct Variable *path ) { const char *template = TEMPLATE(
    • [name][type][/][subtype]
    • ); char *url = templateURL("../" PATH_ATTACHMENT, path); const char *name = paramGet(part->disposition.params, "filename"); if (!name) name = paramGet(part->params, "name"); struct Variable vars[] = { { "url", url }, { "name", (name ? name : "") }, { "type", (name ? "" : part->type) }, { "/", (name ? "" : "/") }, { "subtype", (name ? "" : part->subtype) }, {0}, }; int error = templateRender(file, template, vars, escapeXML); free(url); return error; } int htmlAttachmentClose(FILE *file) { return templateRender(file, TEMPLATE(
    ), NULL, NULL); } int htmlMessageClose(FILE *file) { return templateRender(file, TEMPLATE(
    ), NULL, NULL); } static char *htmlThreadURL(const struct Envelope *envelope, const char *type) { struct Variable vars[] = { { "messageID", envelope->messageID }, { "type", type }, {0}, }; return templateURL("../" PATH_THREAD, vars); } int htmlThreadHead(FILE *file, const struct Envelope *envelope) { const char *template = TEMPLATE( [subject] · [title] ); char *atom = htmlThreadURL(envelope, "atom"); char *mbox = htmlThreadURL(envelope, "mbox"); struct Variable vars[] = { { "generator", GENERATOR_URL }, { "subject", envelope->subject }, { "title", baseTitle }, { "atom", atom }, { "mbox", mbox }, {0}, }; int error = templateRender(file, template, vars, escapeXML); free(atom); free(mbox); return error; } int htmlThreadOpen(FILE *file, const struct Envelope *envelope) { const char *template = TEMPLATE(

    [subject]

    ); char *atom = htmlThreadURL(envelope, "atom"); char *mbox = htmlThreadURL(envelope, "mbox"); struct Variable vars[] = { { "subject", envelope->subject }, { "atom", atom }, { "mbox", mbox }, {0}, }; int error = templateRender(file, template, vars, escapeXML); free(atom); free(mbox); return error; } static size_t threadCount(struct List thread) { size_t count = 0; for (size_t i = 0; i < thread.len; ++i) { if (thread.ptr[i].type == List) { count += threadCount(thread.ptr[i].list); } else { count++; } } return count; } int htmlSubthreadOpen(FILE *file, struct List thread) { const char *template = { TEMPLATE(
    ) TEMPLATE ( [replies] repl[ies] ) TEMPLATE( ) }; size_t count = threadCount(thread); char replies[32]; snprintf(replies, sizeof(replies), "%zu", count); struct Variable vars[] = { { "replies", replies }, { "ies", (count > 1 ? "ies" : "y") }, {0}, }; return templateRender(file, template, vars, escapeXML); } int htmlSubthreadClose(FILE *file) { return templateRender(file, TEMPLATE(
    ), NULL, NULL); } static int htmlFooter(FILE *file) { const char *template = TEMPLATE( ); struct Variable vars[] = { { "generator", GENERATOR_URL }, { "time", htmlUTC(time(NULL)) }, {0}, }; return templateRender(file, template, vars, escapeXML); } int htmlThreadClose(FILE *file) { return 0 || templateRender(file, TEMPLATE(
    ), NULL, NULL) || htmlFooter(file); } int htmlIndexHead(FILE *file) { const char *template = TEMPLATE( [title] ); struct Variable vars[] = { { "generator", GENERATOR_URL }, { "title", baseTitle }, {0}, }; return templateRender(file, template, vars, escapeXML); } static int htmlIndexNav(FILE *file) { int error = 0 || templateRender(file, TEMPLATE(), NULL, NULL); } int htmlIndexOpen(FILE *file) { const char *head = TEMPLATE(

    [title]

    ); struct Variable vars[] = { { "title", baseTitle }, {0}, }; const char *tail = TEMPLATE(
      ); return 0 || templateRender(file, head, vars, escapeXML) || htmlIndexNav(file) || templateRender(file, tail, NULL, NULL); } static char *htmlIndexURL(const struct Envelope *envelope) { struct Variable vars[] = { { "messageID", envelope->messageID }, { "type", "html" }, {0}, }; return templateURL(PATH_THREAD, vars); } int htmlIndexThread( FILE *file, const struct Envelope *envelope, struct List thread ) { const char *template = TEMPLATE(
    1. [subject]

      [from]
      [replies] repl[ies]
    2. ); char *url = htmlIndexURL(envelope); size_t count = threadCount(thread) - 1; char replies[32]; snprintf(replies, sizeof(replies), "%zu", count); struct Variable vars[] = { { "url", url }, { "subject", envelope->subject }, { "from", addressName(envelope->from) }, { "utc", htmlUTC(envelope->time) }, { "date", envelope->date }, { "replies", replies }, { "ies", (count == 1 ? "y" : "ies") }, {0}, }; int error = templateRender(file, template, vars, escapeXML); free(url); return error; } int htmlIndexClose(FILE *file) { return 0 || templateRender(file, TEMPLATE(
    ), NULL, NULL) || htmlFooter(file); }