2 * Copyright (c) 2024 Thomas Adam <thomas@xteddy.org>
4 * Permission to use, copy, modify, and distribute this software for any
5 * purpose with or without fee is hereby granted, provided that the above
6 * copyright notice and this permission notice appear in all copies.
8 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18 * Handles reading mailmap files.
35 #define DEFAULT_MAILCAP_ENTRY "*/*; "DEFAULT_OPENER" %s"
37 #define str_unappend(ch) if (sps.off > 0 && (ch) != EOF) { sps.off--; }
39 struct str_parse_state {
44 static struct str_parse_state sps;
46 static void str_append(char **, size_t *, char);
47 static int str_getc(void);
48 static char *str_tokenise(void);
49 static int str_to_argv(char *, int *, char ***);
50 static char *str_trim_whitespace(char *);
51 static void argv_free(int, char **);
53 struct mailcaplist mailcaps = TAILQ_HEAD_INITIALIZER(mailcaps);
55 static FILE *find_mailcap_file(void);
56 static struct mailcap *mailcap_new(void);
57 static void mailcap_expand_cmd(struct mailcap *, char *, char *);
58 static int parse_mailcap_line(char *);
59 static struct mailcap *mailcap_by_mimetype(const char *);
61 /* FIXME: $MAILCAPS can override this, but we don't check for that. */
62 static const char *default_mailcaps[] = {
66 "/usr/local/etc/mailcap",
70 enum mailcap_section {
77 str_trim_whitespace(char *s)
81 s += strspn(s, " \t\n");
82 t = s + strlen(s) - 1;
84 while (t >= s && isspace((unsigned char)*t))
90 str_append(char **buf, size_t *len, char add)
94 if (al > SIZE_MAX - 1 || *len > SIZE_MAX - 1 - al)
95 errx(1, "buffer is too big");
97 *buf = xrealloc(*buf, (*len) + 1 + al);
98 memcpy((*buf) + *len, &add, al);
113 if (sps.off < sps.len)
114 ch = sps.buf[sps.off++];
120 if (ch == '\n' && (sps.esc % 2) == 1) {
147 buf = calloc(1, sizeof *buf);
151 /* EOF or \n are always the end of the token. */
152 if (ch == EOF || (state == APPEND && ch == '\n'))
155 /* Whitespace ends a token unless inside quotes. But if we've
162 if (((ch == ' ' || ch == '\t') && state == APPEND) &&
167 if (ch == '\\' && state != SINGLE_QUOTES) {
169 str_append(&buf, &len, ch);
174 if (state == APPEND) {
175 state = SINGLE_QUOTES;
178 if (state == SINGLE_QUOTES) {
184 if (state == APPEND) {
185 state = DOUBLE_QUOTES;
188 if (state == DOUBLE_QUOTES) {
194 /* Otherwise add the character to the buffer. */
195 str_append(&buf, &len, ch);
203 if (*buf == '\0' || state == SINGLE_QUOTES || state == DOUBLE_QUOTES) {
204 fprintf(stderr, "Unterminated string: <%s>, missing: %c\n",
205 buf, state == SINGLE_QUOTES ? '\'' :
206 state == DOUBLE_QUOTES ? '"' : ' ');
215 str_to_argv(char *str, int *ret_argc, char ***ret_argv)
226 memset(&sps, 0, sizeof(sps));
227 sps.buf = xstrdup(str);
228 sps.len = strlen(sps.buf);
234 while (((next = str_getc()) != EOF))
240 if (ch == '\n' || ch == EOF)
242 if (ch == ' ' || ch == '\t')
245 /* Tokenise the string according to quoting rules. Note that
246 * the string is stepped-back by one character to make the
247 * tokenisation easier, and not to kick-off the state of the
248 * parsing from this point.
251 if ((token = str_tokenise()) == NULL) {
252 argv_free(argc, argv);
257 argv = xreallocarray(argv, argc + 1, sizeof *argv);
258 argv[argc++] = xstrdup(token);
268 argv_free(int argc, char **argv)
275 for (i = 0; i < argc; i++)
281 find_mailcap_file(void)
284 char **entry = (char **)default_mailcaps;
286 char *expanded = NULL;
288 for (; *entry != NULL; entry++) {
289 if (strncmp(*entry, "~/", 2) == 0) {
291 if ((home = getenv("HOME")) == NULL)
292 errx(1, "HOME not set");
293 asprintf(&expanded, "%s/%s", home, *entry);
295 asprintf(&expanded, "%s", *entry);
297 fp = fopen(expanded, "r");
305 static struct mailcap *
308 struct mailcap *mc = NULL;
310 mc = xcalloc(1, sizeof *mc);
316 parse_mailcap_line(char *input)
324 while ((line = strsep(&input, ";")) != NULL) {
325 line = str_trim_whitespace(line);
329 mc->mime_type = xstrdup(line);
333 mc->cmd = xstrdup(line);
337 if (strcmp(line, "needsterminal") == 0)
338 mc->flags |= MAILCAP_NEEDSTERMINAL;
339 if (strcmp(line, "copiousoutput") == 0)
340 mc->flags |= MAILCAP_COPIOUSOUTPUT;
345 if (line != NULL && *line != '\0') {
346 fprintf(stderr, "mailcap: trailing: %s: skipping...\n", line);
350 TAILQ_INSERT_TAIL(&mailcaps, mc, mailcaps);
356 mailcap_expand_cmd(struct mailcap *mc, char *mt, char *file)
364 ret = str_to_argv(mc->cmd, &argc, &argv);
366 if (ret != 0 || argv == NULL)
369 for (int z = 0; z < argc; z++) {
370 if (strcmp(argv[z], "%s") == 0) {
372 argv[z] = xstrdup(file);
375 if (strcmp(argv[z], "%t") == 0) {
377 argv[z] = xstrdup(mt);
382 argv_free(mc->cmd_argc, mc->cmd_argv);
388 static struct mailcap *
389 mailcap_by_mimetype(const char *mt)
393 TAILQ_FOREACH(mc, &mailcaps, mailcaps) {
394 if (fnmatch(mc->mime_type, mt, 0) == 0)
406 if ((f = find_mailcap_file()) != NULL) {
411 copy = xstrdup(DEFAULT_MAILCAP_ENTRY);
413 /* Our own entry won't error. */
414 (void)parse_mailcap_line(copy);
419 mailcap_parse(FILE *f)
421 const char delims[3] = {'\\', '\\', '\0'};
425 while ((buf = fparseln(f, NULL, &line, delims, 0)) != NULL) {
426 memset(&sps, 0, sizeof sps);
429 copy = str_trim_whitespace(copy);
431 if (*copy == '#' || *copy == '\0') {
436 if (parse_mailcap_line(copy) == -1) {
437 fprintf(stderr, "Error with entry: <<%s>>, line: %ld\n",
445 mailcap_cmd_from_mimetype(char *mime_type, char *filename)
447 struct mailcap *mc = NULL;
449 if (mime_type == NULL || filename == NULL)
452 if ((mc = mailcap_by_mimetype(mime_type)) != NULL)
453 mailcap_expand_cmd(mc, mime_type, filename);