Commit Diff


commit - e234161f4be256ceb80d382f93edaa41e06407a8
commit + 8e1fb8d5023e6f5a977b55d5f8cacd7cdec9773f
blob - a069064c6c492975fc09a09360434fd1959ab16c
blob + 7147d48986f40aa0e63204363d6abd0b4eed8238
--- Makefile.am
+++ Makefile.am
@@ -35,6 +35,8 @@ telescope_SOURCES =	bufio.c			\
 			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
@@ -0,0 +1,461 @@
+/*
+ * 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
@@ -0,0 +1,39 @@
+/*
+ * 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