commit - e234161f4be256ceb80d382f93edaa41e06407a8
commit + 8e1fb8d5023e6f5a977b55d5f8cacd7cdec9773f
blob - a069064c6c492975fc09a09360434fd1959ab16c
blob + 7147d48986f40aa0e63204363d6abd0b4eed8238
--- Makefile.am
+++ Makefile.am
iri.h \
keymap.c \
keymap.h \
+ mailcap.c \
+ mailcap.h \
mcache.c \
mcache.h \
mime.c \
blob - /dev/null
blob + ae2c7f285ff6bfbf07f821f6c78cef939c2b3d84 (mode 644)
--- /dev/null
+++ mailcap.c
+/*
+ * Copyright (c) 2024 Thomas Adam <thomas@xteddy.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * Handles reading mailmap files.
+ */
+
+#include "config.h"
+
+#include <ctype.h>
+#include <errno.h>
+#include <fnmatch.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "compat.h"
+#include "mailcap.h"
+
+#define DEFAULT_MAILCAP_ENTRY "*/*; xdg-open %s ; needsterminal"
+
+#define MAILCAP_NEEDSTERMINAL 0x1
+#define MAILCAP_COPIOUSOUTPUT 0x2
+
+#define str_unappend(ch) if (sps.off > 0 && (ch) != EOF) { sps.off--; }
+
+struct str_parse_state {
+ char *buf;
+ size_t len, off;
+ int esc;
+};
+static struct str_parse_state sps;
+
+static void str_append(char **, size_t *, char);
+static int str_getc(void);
+static char *str_tokenise(void);
+static int str_to_argv(char *, int *, char ***);
+static void str_trim_whitespace(char *);
+static void argv_free(int, char **);
+
+struct mailcaplist mailcaps = TAILQ_HEAD_INITIALIZER(mailcaps);
+
+static FILE *find_mailcap_file(void);
+static struct mailcap *mailcap_new(void);
+static void mailcap_expand_cmd(struct mailcap *, char *, char *);
+static int parse_mailcap_line(char *);
+static struct mailcap *mailcap_by_mimetype(const char *);
+
+/* FIXME: $MAILCAPS can override this, but we don't check for that. */
+static const char *default_mailcaps[] = {
+ "~/.mailcap",
+ "/etc/mailcap",
+ "/usr/etc/mailcap",
+ "/usr/local/etc/mailcap",
+ NULL,
+};
+
+enum mailcap_section {
+ MAILCAP_MIME = 0,
+ MAILCAP_CMD,
+ MAILCAP_FLAGS_1,
+ MAILCAP_FLAGS_2,
+ MAILCAP_END_OF_FIELDS,
+};
+
+static void
+str_trim_whitespace(char *s)
+{
+ char *t;
+
+ s += strspn(s, " \t\n");
+ t = s + strlen(s) - 1;
+
+ while (t >= s && isspace((unsigned char)*t))
+ *t-- = '\0';
+}
+
+static void
+str_append(char **buf, size_t *len, char add)
+{
+ size_t al = 1;
+
+ if (al > SIZE_MAX - 1 || *len > SIZE_MAX - 1 - al)
+ errx(1, "buffer is too big");
+
+ *buf = realloc(*buf, (*len) + 1 + al);
+ memcpy((*buf) + *len, &add, al);
+ (*len) += al;
+}
+
+static int
+str_getc(void)
+{
+ int ch;
+
+ if (sps.esc != 0) {
+ sps.esc--;
+ return ('\\');
+ }
+ for (;;) {
+ ch = EOF;
+ if (sps.off < sps.len)
+ ch = sps.buf[sps.off++];
+
+ if (ch == '\\') {
+ sps.esc++;
+ continue;
+ }
+ if (ch == '\n' && (sps.esc % 2) == 1) {
+ sps.esc--;
+ continue;
+ }
+
+ if (sps.esc != 0) {
+ str_unappend(ch);
+ sps.esc--;
+ return ('\\');
+ }
+ return (ch);
+ }
+}
+
+static char *
+str_tokenise(void)
+{
+ int ch;
+ char *buf;
+ size_t len;
+ enum { APPEND,
+ DOUBLE_QUOTES,
+ SINGLE_QUOTES,
+ } state = APPEND;
+
+ len = 0;
+ buf = calloc(1, sizeof *buf);
+
+ for (;;) {
+ ch = str_getc();
+ /* EOF or \n are always the end of the token. */
+ if (ch == EOF || (state == APPEND && ch == '\n'))
+ break;
+
+ /* Whitespace ends a token unless inside quotes. But if we've
+ * also been given:
+ *
+ * foo "" bar
+ *
+ * don't lose that.
+ */
+ if (((ch == ' ' || ch == '\t') && state == APPEND) &&
+ buf[0] != '\0') {
+ break;
+ }
+
+ if (ch == '\\' && state != SINGLE_QUOTES) {
+ ch = str_getc();
+ str_append(&buf, &len, ch);
+ continue;
+ }
+ switch (ch) {
+ case '\'':
+ if (state == APPEND) {
+ state = SINGLE_QUOTES;
+ continue;
+ }
+ if (state == SINGLE_QUOTES) {
+ state = APPEND;
+ continue;
+ }
+ break;
+ case '"':
+ if (state == APPEND) {
+ state = DOUBLE_QUOTES;
+ continue;
+ }
+ if (state == DOUBLE_QUOTES) {
+ state = APPEND;
+ continue;
+ }
+ break;
+ default:
+ /* Otherwise add the character to the buffer. */
+ str_append(&buf, &len, ch);
+ break;
+
+ }
+ }
+ str_unappend(ch);
+ buf[len] = '\0';
+
+ if (*buf == '\0' || state == SINGLE_QUOTES || state == DOUBLE_QUOTES) {
+ fprintf(stderr, "Unterminated string: <%s>, missing: %c\n",
+ buf, state == SINGLE_QUOTES ? '\'' :
+ state == DOUBLE_QUOTES ? '"' : ' ');
+ free(buf);
+ return (NULL);
+ }
+
+ return (buf);
+}
+
+static int
+str_to_argv(char *str, int *ret_argc, char ***ret_argv)
+{
+ char *token;
+ int ch, next;
+ char **argv = NULL;
+ int argc = 0;
+
+ if (str == NULL)
+ return -1;
+
+ free(sps.buf);
+ if ((sps.buf = strdup(str)) == NULL)
+ errx(1, "strdup");
+ sps.len = strlen(sps.buf);
+
+ for (;;) {
+ if (str[0] == '#') {
+ /* Skip comment. */
+ next = str_getc();
+ while (((next = str_getc()) != EOF))
+ ; /* Nothing. */
+ }
+
+ ch = str_getc();
+
+ if (ch == '\n' || ch == EOF)
+ goto out;
+ if (ch == ' ' || ch == '\t')
+ continue;
+
+ /* Tokenise the string according to quoting rules. Note that
+ * the string is stepped-back by one character to make the
+ * tokenisation easier, and not to kick-off the state of the
+ * parsing from this point.
+ */
+ str_unappend(ch);
+ if ((token = str_tokenise()) == NULL) {
+ argv_free(argc, argv);
+ return -1;
+ }
+
+ /* Add to argv. */
+ argv = reallocarray(argv, argc + 1, sizeof *argv);
+ if ((argv[argc++] = strdup(token)) == NULL)
+ errx(1, "strdup");
+ }
+out:
+ *ret_argv = argv;
+ *ret_argc = argc;
+
+ return 0;
+}
+
+void
+argv_free(int argc, char **argv)
+{
+ int i;
+
+ if (argc == 0)
+ return;
+
+ for (i = 0; i < argc; i++)
+ free(argv[i]);
+ free(argv);
+}
+
+static FILE *
+find_mailcap_file(void)
+{
+ FILE *fp;
+ char **entry = (char **)default_mailcaps;
+ char *home = NULL;
+ char *expanded = NULL;
+
+ for (; *entry != NULL; entry++) {
+ if (strncmp(*entry, "~/", 2) == 0) {
+ *entry += 2;
+ if ((home = getenv("HOME")) == NULL)
+ errx(1, "HOME not set");
+ asprintf(&expanded, "%s/%s", home, *entry);
+ } else
+ asprintf(&expanded, "%s", *entry);
+
+ fp = fopen(expanded, "r");
+ free(expanded);
+ if (fp != NULL)
+ return (fp);
+ }
+ return (NULL);
+}
+
+static struct mailcap *
+mailcap_new(void)
+{
+ struct mailcap *mc = NULL;
+
+ if ((mc = calloc(1, sizeof *mc)) == NULL)
+ errx(1, "calloc failed");
+
+ return (mc);
+}
+
+static int
+parse_mailcap_line(char *input)
+{
+ struct mailcap *mc;
+ int ms;
+ char *line = NULL;
+
+ mc = mailcap_new();
+
+ for (ms = MAILCAP_MIME; ms < MAILCAP_END_OF_FIELDS; ms++) {
+ if ((line = strsep(&input, ";")) == NULL)
+ break;
+
+ str_trim_whitespace(line);
+
+ switch (ms) {
+ case MAILCAP_MIME:
+ if ((mc->mime_type = strdup(line)) == NULL)
+ errx(1, "strdup");
+ break;
+ case MAILCAP_CMD:
+ if ((mc->cmd = strdup(line)) == NULL)
+ errx(1, "strdup");
+ break;
+ case MAILCAP_FLAGS_1:
+ case MAILCAP_FLAGS_2:
+ if (strcmp(line, "needsterminal") == 0)
+ mc->flags |= MAILCAP_NEEDSTERMINAL;
+ if (strcmp(line, "copiousoutput") == 0)
+ mc->flags |= MAILCAP_COPIOUSOUTPUT;
+ break;
+ }
+ }
+
+ if (line != NULL && *line != '\0') {
+ fprintf(stderr, "mailcap: trailing: %s: skipping...\n", line);
+ free(mc);
+ return (-1);
+ }
+ TAILQ_INSERT_TAIL(&mailcaps, mc, mailcaps);
+
+ return (0);
+}
+
+void
+mailcap_expand_cmd(struct mailcap *mc, char *mt, char *file)
+{
+ char **argv;
+ int argc = 0, ret;
+
+ if (mc->cmd == NULL)
+ return;
+
+ ret = str_to_argv(mc->cmd, &argc, &argv);
+
+ if (ret != 0 || argv == NULL)
+ return;
+
+ for (int z = 0; z < argc; z++) {
+ if (strcmp(argv[z], "%s") == 0) {
+ free(argv[z]);
+ if ((argv[z] = strdup(file)) == NULL)
+ errx(1, "strdup");
+ }
+
+ if (strcmp(argv[z], "%t") == 0) {
+ free(argv[z]);
+ if ((argv[z] = strdup(mt)) == NULL)
+ errx(1, "strdup");
+ }
+ }
+ argv[argc++] = NULL;
+
+ argv_free(mc->cmd_argc, mc->cmd_argv);
+
+ mc->cmd_argv = argv;
+ mc->cmd_argc = argc;
+}
+
+static struct mailcap *
+mailcap_by_mimetype(const char *mt)
+{
+ struct mailcap *mc;
+
+ TAILQ_FOREACH(mc, &mailcaps, mailcaps) {
+ if (fnmatch(mc->mime_type, mt, 0) == 0)
+ return (mc);
+ }
+ return (NULL);
+}
+
+void
+init_mailcap(void)
+{
+ FILE *f = NULL;
+ const char delims[3] = {'\\', '\\', '\0'};
+ char *buf, *copy;
+ size_t line = 0;
+
+ if ((f = find_mailcap_file()) == NULL)
+ goto add_default;
+
+ while ((buf = fparseln(f, NULL, &line, delims, 0)) != NULL) {
+ memset(&sps, 0, sizeof sps);
+ copy = buf;
+
+ str_trim_whitespace(copy);
+
+ if (*copy == '\0') {
+ free(buf);
+ continue;
+ }
+
+ if (parse_mailcap_line(copy) == -1) {
+ fprintf(stderr, "Error with entry: <<%s>>, line: %ld\n",
+ copy, line);
+ }
+ free(copy);
+ }
+ fclose(f);
+
+add_default:
+ if ((copy = strdup(DEFAULT_MAILCAP_ENTRY)) == NULL)
+ errx(1, "strdup");
+
+ /* Our own entry won't error. */
+ (void)parse_mailcap_line(copy);
+ free(copy);
+}
+
+struct mailcap *
+mailcap_cmd_from_mimetype(char *mime_type, char *filename)
+{
+ struct mailcap *mc = NULL;
+
+ if (mime_type == NULL || filename == NULL)
+ return (NULL);
+
+ if ((mc = mailcap_by_mimetype(mime_type)) != NULL)
+ mailcap_expand_cmd(mc, mime_type, filename);
+
+ return (mc);
+}
blob - /dev/null
blob + 442aa2d0d5c895e456d12ecac6ccd87f696f4544 (mode 644)
--- /dev/null
+++ mailcap.h
+/*
+ * Copyright (c) 2024 Thomas Adam <thomas@xteddy.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef MAILCAP_H
+#define MAILCAP_H
+
+#define DEFAULT_MIMETYPE "*/*"
+
+struct mailcap {
+ char *mime_type;
+ char *cmd;
+ char **cmd_argv;
+ int cmd_argc;
+ int flags;
+
+ TAILQ_ENTRY(mailcap) mailcaps;
+};
+
+extern TAILQ_HEAD(mailcaplist, mailcap) mailcaps;
+
+extern struct mailcaplist mailcaps;
+
+void init_mailcap(void);
+struct mailcap *mailcap_cmd_from_mimetype(char *, char *);
+
+#endif