/* 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 . * * 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 #include #include "archive.h" static char *htmlMailto(struct Address addr) { struct Variable vars[] = { { "mailbox", addr.mailbox }, { "host", addr.host }, {0}, }; return templateString("mailto:[mailbox]@[host]", vars, escapeURL); } static int htmlAddress(FILE *file, struct Address addr, bool comma) { char *mailto = NULL; const char *template; if (addr.host) { mailto = htmlMailto(addr); template = Q([,][name]); } else if (addr.mailbox) { template = " [mailbox]:"; } else { template = ";"; } struct Variable vars[] = { { "mailto", mailto }, { "name", addressName(addr) }, { "mailbox", addr.mailbox }, { ",", (comma ? ", " : " ") }, {0}, }; int error = templateRender(file, template, vars, escapeXML); free(mailto); return error; } static int htmlAddressList(FILE *file, const char *name, struct AddressList list) { if (!list.len) return 0; const char *template = Q(
[name]: ); struct Variable vars[] = { { "name", name }, {0}, }; int error = templateRender(file, template, vars, escapeXML); if (error) return error; for (size_t i = 0; i < list.len; ++i) { error = htmlAddress( file, list.addrs[i], i > 0 && list.addrs[i - 1].host ); if (error) return error; } return templateRender(file, Q(
), NULL, NULL); } static char *htmlMbox(const char *messageID) { struct Variable vars[] = { { "messageID", messageID }, { "type", "mbox" }, {0}, }; return templateString("../" PATH_MESSAGE, vars, escapeURL); } static int htmlReplyCc(FILE *file, bool first, struct AddressList list) { for (size_t i = 0; i < list.len; ++i) { if (!list.addrs[i].mailbox || !list.addrs[i].host) continue; const char *template = "[,][mailbox]@[host]"; struct Variable vars[] = { { "mailbox", list.addrs[i].mailbox }, { "host", list.addrs[i].host }, { ",", (!first || i ? "," : "") }, {0}, }; int error = templateRender(file, template, vars, escapeURL); if (error) return error; } return 0; } static char *htmlReply(const struct Envelope *envelope) { char *buf; size_t len; FILE *file = open_memstream(&buf, &len); if (!file) err(EX_OSERR, "open_memstream"); const char *template = { "mailto:[mailbox]@[host]" "?subject=[re][subject]" "&In-Reply-To=[<][messageID][>]" "&cc=" }; struct Variable vars[] = { { "mailbox", envelope->replyTo.mailbox }, { "host", envelope->replyTo.host }, { "re", (strncmp(envelope->subject, "Re: ", 4) ? "Re: " : "") }, { "subject", envelope->subject }, { "messageID", envelope->messageID }, { "<", "<" }, { ">", ">" }, {0}, }; int error = 0 || templateRender(file, template, vars, escapeURL) || htmlReplyCc(file, true, envelope->to) || htmlReplyCc(file, false, envelope->cc) || fclose(file); if (error) err(EX_OSERR, "open_memstream"); return buf; } int htmlMessageNav(FILE *file, const struct Envelope *envelope) { char *mbox = htmlMbox(envelope->messageID); char *reply = htmlReply(envelope); const char *template = Q( ); struct Variable vars[] = { { "parent", envelope->inReplyTo }, { "mbox", mbox }, { "reply", reply }, {0}, }; int error = templateRender(file, template, vars, escapeXML); free(mbox); free(reply); return error; } int htmlMessageOpen(FILE *file, const struct Envelope *envelope, bool nested) { char *mailto = htmlMailto(envelope->from); const char *template = Q(

[subject]

From: [from]
); struct Variable vars[] = { { "messageID", envelope->messageID }, { "subject", envelope->subject }, { "mailto", mailto }, { "from", addressName(envelope->from) }, { "utc", iso8601(envelope->time).s }, { "date", envelope->date }, {0}, }; int error = 0 || templateRender(file, template, vars, escapeXML) || htmlAddressList(file, "To", envelope->to) || htmlAddressList(file, "Cc", envelope->cc) || (nested ? 0 : htmlMessageNav(file, envelope)) || templateRender(file, Q(
), NULL, NULL); free(mailto); return error; } static void compile(regex_t *regex, const char *pattern) { if (!regex->re_nsub) { int error = regcomp(regex, pattern, REG_EXTENDED); assert(!error); } } static void swap(char *a, char *b) { char ch = *a; *a = *b; *b = ch; } static int htmlMarkupURLs(FILE *file, char *buf) { static regex_t regex; compile(®ex, "(^|[[:space:]<])(https?:[^[:space:]>]+)"); int error; char *ptr; regmatch_t match[3]; for (ptr = buf; !regexec(®ex, ptr, 3, 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 = Q([url]); swap(&ptr[match[2].rm_eo], &nul); struct Variable vars[] = { { "url", &ptr[match[2].rm_so] }, {0}, }; error = templateRender(file, template, vars, escapeXML); if (error) return error; swap(&ptr[match[2].rm_eo], &nul); } return escapeXML(file, ptr); } static int htmlMarkupQuote(FILE *file, char *buf) { uint32_t level = 0; for (char *ch = buf; *ch == '>' || *ch == ' '; level += (*ch++ == '>')); const char *template = Q(); struct Variable vars[] = { { "level", u32(level).s }, {0}, }; return 0 || templateRender(file, template, vars, escapeXML) || htmlMarkupURLs(file, buf) || templateRender(file, Q(), NULL, NULL); } static int htmlMarkup(FILE *file, const char *content) { int error = 0; size_t cap = 0; char *buf = NULL; enum { Init, Patch, Diff } state = Init; while (*content) { size_t len = strcspn(content, "\n"); if (cap < len + 1) { cap = len + 1; buf = realloc(buf, cap); if (!buf) err(EX_OSERR, "realloc"); } memcpy(buf, content, len); buf[len] = '\0'; if (state == Init && !strcmp(buf, "---")) { state = Patch; } else if (state == Patch && !strncmp(buf, "diff", 4)) { state = Diff; } else if (!strcmp(buf, "-- ")) { state = Init; } if (state == Diff) { const char *template = Q( [line] ); struct Variable vars[] = { { "class", "head" }, { "line", buf }, {0}, }; if (buf[0] == '@') { vars[0].value = "hunk"; } else if (buf[0] == ' ') { vars[0].value = "ctx"; } else if (buf[0] == '-' && strncmp(buf, "---", 3)) { vars[0].value = "old"; } else if (buf[0] == '+' && strncmp(buf, "+++", 3)) { vars[0].value = "new"; } error = templateRender(file, template, vars, escapeXML); } else if (buf[0] == '>') { error = htmlMarkupQuote(file, buf); } else { error = htmlMarkupURLs(file, buf); } error = error || templateRender(file, "\n", NULL, NULL); if (error) break; content += len; if (*content) content++; } free(buf); return error; } int htmlInline(FILE *file, const struct BodyPart *part, const char *content) { const char *template = Q(
	);
	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;
	}
	struct Variable vars[] = {
		{ "contentID", part->contentID },
		{ "description", part->description },
		{ "language",  language },
		{0},
	};
	return 0
		|| templateRender(file, template, vars, escapeXML)
		|| htmlMarkup(file, content)
		|| templateRender(file, Q(
), NULL, NULL); } int htmlAttachmentOpen(FILE *file) { return templateRender(file, Q(
    ), NULL, NULL); } int htmlAttachment( FILE *file, const struct BodyPart *part, const struct Variable *path ) { char *url = templateString("../" PATH_ATTACHMENT, path, escapeURL); const char *name = paramGet(part->disposition.params, "filename"); if (!name) name = paramGet(part->params, "name"); const char *template = Q(
  • [name][type][/][subtype]
  • ); 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, Q(
), NULL, NULL); } int htmlMessageClose(FILE *file) { return templateRender(file, Q(
), NULL, NULL); } static int htmlStylesheet(FILE *file) { if (baseStylesheet) { const char *template = Q(); struct Variable vars[] = { { "href", baseStylesheet }, {0}, }; return templateRender(file, template, vars, escapeXML); } else { const char *template = Q(); struct Variable vars[] = { { "style", Stylesheet }, {0}, }; return templateRender(file, template, vars, NULL); } } static char *htmlThreadURL(const struct Envelope *envelope, const char *type) { struct Variable vars[] = { { "messageID", envelope->messageID }, { "type", type }, {0}, }; return templateString("../" PATH_THREAD, vars, escapeURL); } int htmlThreadHead(FILE *file, const struct Envelope *envelope) { char *atom = htmlThreadURL(envelope, "atom"); char *mbox = htmlThreadURL(envelope, "mbox"); const char *template = Q( [subject] ); struct Variable vars[] = { { "generator", GENERATOR_URL }, { "subject", envelope->subject }, { "atom", atom }, { "mbox", mbox }, {0}, }; int error = 0 || templateRender(file, template, vars, escapeXML) || htmlStylesheet(file); free(atom); free(mbox); return error; } int htmlThreadOpen(FILE *file, const struct Envelope *envelope) { char *atom = htmlThreadURL(envelope, "atom"); char *mbox = htmlThreadURL(envelope, "mbox"); const char *template = Q(

[subject]

); 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 uint32_t threadCount(struct List thread) { uint32_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; } static int htmlReplies(FILE *file, uint32_t replies) { if (!replies) return 0; const char *template = Q( [replies] repl[ies] ); struct Variable vars[] = { { "replies", u32(replies).s }, { "ies", (replies != 1 ? "ies" : "y") }, {0}, }; return templateRender(file, template, vars, escapeXML); } int htmlSubthreadOpen(FILE *file, struct List thread) { const char *template = Q(
); return 0 || templateRender(file, template, NULL, NULL) || htmlReplies(file, threadCount(thread)) || templateRender(file, Q(), NULL, NULL); } int htmlSubthreadClose(FILE *file) { return templateRender(file, Q(
), NULL, NULL); } static int htmlFooter(FILE *file) { const char *template = Q( ); struct Variable vars[] = { { "generator", GENERATOR_URL }, { "time", iso8601(time(NULL)).s }, {0}, }; return templateRender(file, template, vars, escapeXML); } int htmlThreadClose(FILE *file) { return 0 || templateRender(file, Q(
), NULL, NULL) || htmlFooter(file); } static char *htmlIndexURL(const char *name, const char *type) { struct Variable vars[] = { { "name", name }, { "type", type }, {0}, }; return templateString(PATH_INDEX, vars, escapeURL); } int htmlIndexHead(FILE *file, const char *name) { char *atom = htmlIndexURL(name, "atom"); const char *template = Q( [+name][name] - [-][title] ); struct Variable vars[] = { { "generator", GENERATOR_URL }, { "name", (strcmp(name, "index") ? name : NULL) }, { "title", baseTitle }, { "atom", atom }, {0}, }; int error = 0 || templateRender(file, template, vars, escapeXML) || htmlStylesheet(file); free(atom); return error; } static int htmlSearchNavItem(FILE *file, const char *name, bool current) { char *url = NULL; const char *template; if (current) { template = Q([name]) " "; } else { url = htmlIndexURL(name, "html"); template = Q([name]) " "; } struct Variable vars[] = { { "url", url }, { "name", name }, {0}, }; int error = templateRender(file, template, vars, escapeXML); free(url); return error; } static int htmlSearchNav(FILE *file, const char *name) { if (search.len < 2) return 0; int error = 0 || templateRender(file, Q(), NULL, NULL); } int htmlIndexOpen(FILE *file, const char *name) { char *atom = htmlIndexURL(name, "atom"); const char *template = Q(

[+name][name] - [-][title]

); const char *tail = Q(
    ); struct Variable vars[] = { { "name", (strcmp(name, "index") ? name : NULL) }, { "title", baseTitle }, { "atom", atom }, { "subscribe", baseSubscribe }, { "mailto", baseMailto }, {0}, }; int error = 0 || templateRender(file, template, vars, escapeXML) || htmlSearchNav(file, name) || templateRender(file, tail, NULL, NULL); free(atom); return error; } static char *htmlIndexThreadURL(const struct Envelope *envelope) { struct Variable vars[] = { { "messageID", envelope->messageID }, { "type", "html" }, {0}, }; return templateString(PATH_THREAD, vars, escapeURL); } int htmlIndexThread( FILE *file, const struct Envelope *envelope, struct List thread ) { char *url = htmlIndexThreadURL(envelope); const char *template = Q(
  1. [subject]

    From: [from]
    ); struct Variable vars[] = { { "url", url }, { "subject", envelope->subject }, { "from", addressName(envelope->from) }, { "utc", iso8601(envelope->time).s }, { "date", envelope->date }, {0}, }; int error = 0 || templateRender(file, template, vars, escapeXML) || htmlReplies(file, threadCount(thread) - 1) || templateRender(file, Q(
  2. ), NULL, NULL); free(url); return error; } int htmlIndexClose(FILE *file) { return 0 || templateRender(file, Q(
), NULL, NULL) || htmlFooter(file); }