NeoMutt  2020-06-26-250-g349c94
Teaching an old dog new tricks
DOXYGEN
mailcap.c
Go to the documentation of this file.
1 
35 #include "config.h"
36 #include <stdbool.h>
37 #include <stdio.h>
38 #include <string.h>
39 #include "mutt/lib.h"
40 #include "email/lib.h"
41 #include "mailcap.h"
42 #include "mutt_attach.h"
43 #include "mutt_globals.h"
44 #include "muttlib.h"
45 #include "protos.h"
46 
47 /* These Config Variables are only used in rfc1524.c */
49 
69 int mailcap_expand_command(struct Body *a, const char *filename,
70  const char *type, struct Buffer *command)
71 {
72  int needspipe = true;
73  struct Buffer *buf = mutt_buffer_pool_get();
74  struct Buffer *quoted = mutt_buffer_pool_get();
75  struct Buffer *param = NULL;
76  struct Buffer *type2 = NULL;
77 
78  const char *cptr = mutt_b2s(command);
79  while (*cptr)
80  {
81  if (*cptr == '\\')
82  {
83  cptr++;
84  if (*cptr)
85  mutt_buffer_addch(buf, *cptr++);
86  }
87  else if (*cptr == '%')
88  {
89  cptr++;
90  if (*cptr == '{')
91  {
92  const char *pvalue2 = NULL;
93 
94  if (param)
95  mutt_buffer_reset(param);
96  else
97  param = mutt_buffer_pool_get();
98 
99  /* Copy parameter name into param buffer */
100  cptr++;
101  while (*cptr && (*cptr != '}'))
102  mutt_buffer_addch(param, *cptr++);
103 
104  /* In send mode, use the current charset, since the message hasn't
105  * been converted yet. If noconv is set, then we assume the
106  * charset parameter has the correct value instead. */
107  if (mutt_istr_equal(mutt_b2s(param), "charset") && a->charset && !a->noconv)
108  pvalue2 = a->charset;
109  else
110  pvalue2 = mutt_param_get(&a->parameter, mutt_b2s(param));
111 
112  /* Now copy the parameter value into param buffer */
113  if (C_MailcapSanitize)
114  mutt_buffer_sanitize_filename(param, NONULL(pvalue2), false);
115  else
116  mutt_buffer_strcpy(param, NONULL(pvalue2));
117 
118  mutt_buffer_quote_filename(quoted, mutt_b2s(param), true);
119  mutt_buffer_addstr(buf, mutt_b2s(quoted));
120  }
121  else if ((*cptr == 's') && filename)
122  {
123  mutt_buffer_quote_filename(quoted, filename, true);
124  mutt_buffer_addstr(buf, mutt_b2s(quoted));
125  needspipe = false;
126  }
127  else if (*cptr == 't')
128  {
129  if (!type2)
130  {
131  type2 = mutt_buffer_pool_get();
132  if (C_MailcapSanitize)
133  mutt_buffer_sanitize_filename(type2, type, false);
134  else
135  mutt_buffer_strcpy(type2, type);
136  }
137  mutt_buffer_quote_filename(quoted, mutt_b2s(type2), true);
138  mutt_buffer_addstr(buf, mutt_b2s(quoted));
139  }
140 
141  if (*cptr)
142  cptr++;
143  }
144  else
145  mutt_buffer_addch(buf, *cptr++);
146  }
147  mutt_buffer_copy(command, buf);
148 
150  mutt_buffer_pool_release(&quoted);
151  mutt_buffer_pool_release(&param);
152  mutt_buffer_pool_release(&type2);
153 
154  return needspipe;
155 }
156 
163 static char *get_field(char *s)
164 {
165  if (!s)
166  return NULL;
167 
168  char *ch = NULL;
169 
170  while ((ch = strpbrk(s, ";\\")))
171  {
172  if (*ch == '\\')
173  {
174  s = ch + 1;
175  if (*s)
176  s++;
177  }
178  else
179  {
180  *ch = '\0';
181  ch = mutt_str_skip_email_wsp(ch + 1);
182  break;
183  }
184  }
186  return ch;
187 }
188 
199 static int get_field_text(char *field, char **entry, const char *type,
200  const char *filename, int line)
201 {
202  field = mutt_str_skip_whitespace(field);
203  if (*field == '=')
204  {
205  if (entry)
206  {
207  field++;
208  field = mutt_str_skip_whitespace(field);
209  mutt_str_replace(entry, field);
210  }
211  return 1;
212  }
213  else
214  {
215  mutt_error(_("Improperly formatted entry for type %s in \"%s\" line %d"),
216  type, filename, line);
217  return 0;
218  }
219 }
220 
231 static bool rfc1524_mailcap_parse(struct Body *a, const char *filename, const char *type,
232  struct MailcapEntry *entry, enum MailcapLookup opt)
233 {
234  char *buf = NULL;
235  bool found = false;
236  int line = 0;
237 
238  /* rfc1524 mailcap file is of the format:
239  * base/type; command; extradefs
240  * type can be * for matching all
241  * base with no /type is an implicit wild
242  * command contains a %s for the filename to pass, default to pipe on stdin
243  * extradefs are of the form:
244  * def1="definition"; def2="define \;";
245  * line wraps with a \ at the end of the line
246  * # for comments */
247 
248  /* find length of basetype */
249  char *ch = strchr(type, '/');
250  if (!ch)
251  return false;
252  const int btlen = ch - type;
253 
254  FILE *fp = fopen(filename, "r");
255  if (fp)
256  {
257  size_t buflen;
258  while (!found && (buf = mutt_file_read_line(buf, &buflen, fp, &line, MUTT_CONT)))
259  {
260  /* ignore comments */
261  if (*buf == '#')
262  continue;
263  mutt_debug(LL_DEBUG2, "mailcap entry: %s\n", buf);
264 
265  /* check type */
266  ch = get_field(buf);
267  if (!mutt_istr_equal(buf, type) && (!mutt_istrn_equal(buf, type, btlen) ||
268  ((buf[btlen] != '\0') && /* implicit wild */
269  !mutt_str_equal(buf + btlen, "/*")))) /* wildsubtype */
270  {
271  continue;
272  }
273 
274  /* next field is the viewcommand */
275  char *field = ch;
276  ch = get_field(ch);
277  if (entry)
278  entry->command = mutt_str_dup(field);
279 
280  /* parse the optional fields */
281  found = true;
282  bool copiousoutput = false;
283  bool composecommand = false;
284  bool editcommand = false;
285  bool printcommand = false;
286 
287  while (ch)
288  {
289  field = ch;
290  ch = get_field(ch);
291  mutt_debug(LL_DEBUG2, "field: %s\n", field);
292  size_t plen;
293 
294  if (mutt_istr_equal(field, "needsterminal"))
295  {
296  if (entry)
297  entry->needsterminal = true;
298  }
299  else if (mutt_istr_equal(field, "copiousoutput"))
300  {
301  copiousoutput = true;
302  if (entry)
303  entry->copiousoutput = true;
304  }
305  else if ((plen = mutt_istr_startswith(field, "composetyped")))
306  {
307  /* this compare most occur before compose to match correctly */
308  if (get_field_text(field + plen, entry ? &entry->composetypecommand : NULL,
309  type, filename, line))
310  {
311  composecommand = true;
312  }
313  }
314  else if ((plen = mutt_istr_startswith(field, "compose")))
315  {
316  if (get_field_text(field + plen, entry ? &entry->composecommand : NULL,
317  type, filename, line))
318  {
319  composecommand = true;
320  }
321  }
322  else if ((plen = mutt_istr_startswith(field, "print")))
323  {
324  if (get_field_text(field + plen, entry ? &entry->printcommand : NULL,
325  type, filename, line))
326  {
327  printcommand = true;
328  }
329  }
330  else if ((plen = mutt_istr_startswith(field, "edit")))
331  {
332  if (get_field_text(field + plen, entry ? &entry->editcommand : NULL,
333  type, filename, line))
334  editcommand = true;
335  }
336  else if ((plen = mutt_istr_startswith(field, "nametemplate")))
337  {
338  get_field_text(field + plen, entry ? &entry->nametemplate : NULL,
339  type, filename, line);
340  }
341  else if ((plen = mutt_istr_startswith(field, "x-convert")))
342  {
343  get_field_text(field + plen, entry ? &entry->convert : NULL, type, filename, line);
344  }
345  else if ((plen = mutt_istr_startswith(field, "test")))
346  {
347  /* This routine executes the given test command to determine
348  * if this is the right entry. */
349  char *test_command = NULL;
350 
351  if (get_field_text(field + plen, &test_command, type, filename, line) && test_command)
352  {
353  struct Buffer *command = mutt_buffer_pool_get();
354  struct Buffer *afilename = mutt_buffer_pool_get();
355  mutt_buffer_strcpy(command, test_command);
356  if (C_MailcapSanitize)
357  mutt_buffer_sanitize_filename(afilename, NONULL(a->filename), true);
358  else
359  mutt_buffer_strcpy(afilename, NONULL(a->filename));
360  mailcap_expand_command(a, mutt_b2s(afilename), type, command);
361  if (mutt_system(mutt_b2s(command)))
362  {
363  /* a non-zero exit code means test failed */
364  found = false;
365  }
366  FREE(&test_command);
367  mutt_buffer_pool_release(&command);
368  mutt_buffer_pool_release(&afilename);
369  }
370  }
371  else if (mutt_istr_startswith(field, "x-neomutt-keep"))
372  {
373  if (entry)
374  entry->xneomuttkeep = true;
375  }
376  } /* while (ch) */
377 
378  if (opt == MUTT_MC_AUTOVIEW)
379  {
380  if (!copiousoutput)
381  found = false;
382  }
383  else if (opt == MUTT_MC_COMPOSE)
384  {
385  if (!composecommand)
386  found = false;
387  }
388  else if (opt == MUTT_MC_EDIT)
389  {
390  if (!editcommand)
391  found = false;
392  }
393  else if (opt == MUTT_MC_PRINT)
394  {
395  if (!printcommand)
396  found = false;
397  }
398 
399  if (!found)
400  {
401  /* reset */
402  if (entry)
403  {
404  FREE(&entry->command);
405  FREE(&entry->composecommand);
406  FREE(&entry->composetypecommand);
407  FREE(&entry->editcommand);
408  FREE(&entry->printcommand);
409  FREE(&entry->nametemplate);
410  FREE(&entry->convert);
411  entry->needsterminal = false;
412  entry->copiousoutput = false;
413  entry->xneomuttkeep = false;
414  }
415  }
416  } /* while (!found && (buf = mutt_file_read_line ())) */
417  mutt_file_fclose(&fp);
418  } /* if ((fp = fopen ())) */
419  FREE(&buf);
420  return found;
421 }
422 
428 {
429  return mutt_mem_calloc(1, sizeof(struct MailcapEntry));
430 }
431 
437 {
438  if (!ptr || !*ptr)
439  return;
440 
441  struct MailcapEntry *me = *ptr;
442 
443  FREE(&me->command);
444  FREE(&me->testcommand);
445  FREE(&me->composecommand);
446  FREE(&me->composetypecommand);
447  FREE(&me->editcommand);
448  FREE(&me->printcommand);
449  FREE(&me->nametemplate);
450  FREE(ptr);
451 }
452 
465 bool mailcap_lookup(struct Body *a, char *type, size_t typelen,
466  struct MailcapEntry *entry, enum MailcapLookup opt)
467 {
468  /* rfc1524 specifies that a path of mailcap files should be searched.
469  * joy. They say
470  * $HOME/.mailcap:/etc/mailcap:/usr/etc/mailcap:/usr/local/etc/mailcap, etc
471  * and overridden by the MAILCAPS environment variable, and, just to be nice,
472  * we'll make it specifiable in .neomuttrc */
473  if (!C_MailcapPath || (C_MailcapPath->count == 0))
474  {
475  /* L10N:
476  Mutt is trying to look up a mailcap value, but $mailcap_path is empty.
477  We added a reference to the MAILCAPS environment variable as a hint too.
478 
479  Because the variable is automatically populated by Mutt, this
480  should only occur if the user deliberately runs in their shell:
481  export MAILCAPS=
482 
483  or deliberately runs inside Mutt or their .muttrc:
484  set mailcap_path=""
485  -or-
486  unset mailcap_path
487  */
488  mutt_error(_("Neither mailcap_path nor MAILCAPS specified"));
489  return false;
490  }
491 
492  mutt_check_lookup_list(a, type, typelen);
493 
494  struct Buffer *path = mutt_buffer_pool_get();
495  bool found = false;
496 
497  struct ListNode *np = NULL;
498  STAILQ_FOREACH(np, &C_MailcapPath->head, entries)
499  {
500  mutt_buffer_strcpy(path, np->data);
502 
503  mutt_debug(LL_DEBUG2, "Checking mailcap file: %s\n", mutt_b2s(path));
504  found = rfc1524_mailcap_parse(a, mutt_b2s(path), type, entry, opt);
505  if (found)
506  break;
507  }
508 
510 
511  if (entry && !found)
512  mutt_error(_("mailcap entry for type %s not found"), type);
513 
514  return found;
515 }
516 
533 void mailcap_expand_filename(const char *nametemplate, const char *oldfile,
534  struct Buffer *newfile)
535 {
536  int i, j, k;
537  char *s = NULL;
538  bool lmatch = false, rmatch = false;
539 
540  mutt_buffer_reset(newfile);
541 
542  /* first, ignore leading path components */
543 
544  if (nametemplate && (s = strrchr(nametemplate, '/')))
545  nametemplate = s + 1;
546 
547  if (oldfile && (s = strrchr(oldfile, '/')))
548  oldfile = s + 1;
549 
550  if (!nametemplate)
551  {
552  if (oldfile)
553  mutt_buffer_strcpy(newfile, oldfile);
554  }
555  else if (!oldfile)
556  {
557  mutt_file_expand_fmt(newfile, nametemplate, "neomutt");
558  }
559  else /* oldfile && nametemplate */
560  {
561  /* first, compare everything left from the "%s"
562  * (if there is one). */
563 
564  lmatch = true;
565  bool ps = false;
566  for (i = 0; nametemplate[i]; i++)
567  {
568  if ((nametemplate[i] == '%') && (nametemplate[i + 1] == 's'))
569  {
570  ps = true;
571  break;
572  }
573 
574  /* note that the following will _not_ read beyond oldfile's end. */
575 
576  if (lmatch && (nametemplate[i] != oldfile[i]))
577  lmatch = false;
578  }
579 
580  if (ps)
581  {
582  /* If we had a "%s", check the rest. */
583 
584  /* now, for the right part: compare everything right from
585  * the "%s" to the final part of oldfile.
586  *
587  * The logic here is as follows:
588  *
589  * - We start reading from the end.
590  * - There must be a match _right_ from the "%s",
591  * thus the i + 2.
592  * - If there was a left hand match, this stuff
593  * must not be counted again. That's done by the
594  * condition (j >= (lmatch ? i : 0)). */
595 
596  rmatch = true;
597 
598  for (j = mutt_str_len(oldfile) - 1, k = mutt_str_len(nametemplate) - 1;
599  (j >= (lmatch ? i : 0)) && (k >= (i + 2)); j--, k--)
600  {
601  if (nametemplate[k] != oldfile[j])
602  {
603  rmatch = false;
604  break;
605  }
606  }
607 
608  /* Now, check if we had a full match. */
609 
610  if (k >= i + 2)
611  rmatch = false;
612 
613  struct Buffer *left = mutt_buffer_pool_get();
614  struct Buffer *right = mutt_buffer_pool_get();
615 
616  if (!lmatch)
617  mutt_buffer_strcpy_n(left, nametemplate, i);
618  if (!rmatch)
619  mutt_buffer_strcpy(right, nametemplate + i + 2);
620  mutt_buffer_printf(newfile, "%s%s%s", mutt_b2s(left), oldfile, mutt_b2s(right));
621 
623  mutt_buffer_pool_release(&right);
624  }
625  else
626  {
627  /* no "%s" in the name template. */
628  mutt_buffer_strcpy(newfile, nametemplate);
629  }
630  }
631 
632  mutt_adv_mktemp(newfile);
633 }
bool mutt_str_equal(const char *a, const char *b)
Compare two strings.
Definition: string.c:876
bool xneomuttkeep
do not remove the file on command exit
Definition: mailcap.h:50
A mailcap entry.
Definition: mailcap.h:38
void mutt_buffer_quote_filename(struct Buffer *buf, const char *filename, bool add_outer)
Quote a filename to survive the shell&#39;s quoting rules.
Definition: file.c:836
struct MailcapEntry * mailcap_entry_new(void)
Allocate memory for a new rfc1524 entry.
Definition: mailcap.c:427
char * mutt_str_skip_whitespace(const char *p)
Find the first non-whitespace character in a string.
Definition: string.c:691
char * filename
when sending a message, this is the file to which this structure refers
Definition: body.h:46
#define NONULL(x)
Definition: string2.h:37
void * mutt_mem_calloc(size_t nmemb, size_t size)
Allocate zeroed memory on the heap.
Definition: memory.c:50
void mutt_buffer_reset(struct Buffer *buf)
Reset an existing Buffer.
Definition: buffer.c:79
struct Buffer * mutt_buffer_pool_get(void)
Get a Buffer from the pool.
Definition: pool.c:101
char * convert
Definition: mailcap.h:47
Structs that make up an email.
size_t mutt_buffer_copy(struct Buffer *dst, const struct Buffer *src)
Copy a Buffer&#39;s contents to another Buffer.
Definition: buffer.c:445
Mailcap autoview field.
Definition: mailcap.h:62
char * composetypecommand
Definition: mailcap.h:43
static size_t plen
Length of cached packet.
Definition: pgppacket.c:39
void mutt_buffer_pool_release(struct Buffer **pbuf)
Free a Buffer from the pool.
Definition: pool.c:112
bool noconv
Don&#39;t do character set conversion.
Definition: body.h:73
RFC1524 Mailcap routines.
String manipulation buffer.
Definition: buffer.h:33
char * mutt_str_dup(const char *str)
Copy a string, safely.
Definition: string.c:375
#define _(a)
Definition: message.h:28
char * mutt_file_read_line(char *line, size_t *size, FILE *fp, int *line_num, int flags)
Read a line from a file.
Definition: file.c:667
WHERE struct Slist * C_MailcapPath
Config: Colon-separated list of mailcap files.
Definition: mutt_globals.h:96
int mutt_buffer_printf(struct Buffer *buf, const char *fmt,...)
Format a string overwriting a Buffer.
Definition: buffer.c:160
#define MUTT_CONT
-continuation
Definition: file.h:37
The body of an email.
Definition: body.h:34
bool mailcap_lookup(struct Body *a, char *type, size_t typelen, struct MailcapEntry *entry, enum MailcapLookup opt)
Find given type in the list of mailcap files.
Definition: mailcap.c:465
int mailcap_expand_command(struct Body *a, const char *filename, const char *type, struct Buffer *command)
Expand expandos in a command.
Definition: mailcap.c:69
void mailcap_expand_filename(const char *nametemplate, const char *oldfile, struct Buffer *newfile)
Expand a new filename from a template or existing filename.
Definition: mailcap.c:533
bool mutt_istrn_equal(const char *a, const char *b, size_t l)
Check for equality of two strings ignoring case (to a maximum), safely.
Definition: string.c:626
Some miscellaneous functions.
Mailcap compose field.
Definition: mailcap.h:60
Log at debug level 2.
Definition: logging.h:41
Mailcap print field.
Definition: mailcap.h:61
bool copiousoutput
needs pager, basically
Definition: mailcap.h:49
struct ListHead head
Definition: slist.h:45
int mutt_file_fclose(FILE **fp)
Close a FILE handle (and NULL the pointer)
Definition: file.c:153
char * editcommand
Definition: mailcap.h:44
size_t mutt_buffer_addstr(struct Buffer *buf, const char *s)
Add a string to a Buffer.
Definition: buffer.c:225
bool mutt_istr_equal(const char *a, const char *b)
Compare two strings, ignoring case.
Definition: string.c:888
char * command
Definition: mailcap.h:40
void mutt_str_remove_trailing_ws(char *s)
Trim trailing whitespace from a string.
Definition: string.c:705
size_t count
Definition: slist.h:46
#define mutt_b2s(buf)
Definition: buffer.h:41
Prototypes for many functions.
static int get_field_text(char *field, char **entry, const char *type, const char *filename, int line)
Get the matching text from a mailcap.
Definition: mailcap.c:199
void mailcap_entry_free(struct MailcapEntry **ptr)
Deallocate an struct MailcapEntry.
Definition: mailcap.c:436
size_t mutt_buffer_strcpy_n(struct Buffer *buf, const char *s, size_t len)
Copy a string into a Buffer.
Definition: buffer.c:327
void mutt_file_expand_fmt(struct Buffer *dest, const char *fmt, const char *src)
Replace s in a string with a filename.
Definition: file.c:1447
char * charset
Send mode: charset of attached file as stored on disk.
Definition: body.h:49
static bool rfc1524_mailcap_parse(struct Body *a, const char *filename, const char *type, struct MailcapEntry *entry, enum MailcapLookup opt)
Parse a mailcap entry.
Definition: mailcap.c:231
char * mutt_str_skip_email_wsp(const char *s)
Skip over whitespace as defined by RFC5322.
Definition: string.c:748
Handling of email attachments.
size_t mutt_istr_startswith(const char *str, const char *prefix)
Check whether a string starts with a prefix, ignoring case.
Definition: string.c:177
bool C_MailcapSanitize
Config: Restrict the possible characters in mailcap expandos.
Definition: mailcap.c:48
#define STAILQ_FOREACH(var, head, field)
Definition: queue.h:349
size_t mutt_buffer_addch(struct Buffer *buf, char c)
Add a single character to a Buffer.
Definition: buffer.c:240
size_t mutt_buffer_strcpy(struct Buffer *buf, const char *s)
Copy a string into a Buffer.
Definition: buffer.c:312
char * printcommand
Definition: mailcap.h:45
char * nametemplate
Definition: mailcap.h:46
size_t mutt_str_len(const char *a)
Calculate the length of a string, safely.
Definition: string.c:636
char * data
String.
Definition: list.h:36
void mutt_buffer_expand_path(struct Buffer *buf)
Create the canonical path.
Definition: muttlib.c:323
void mutt_check_lookup_list(struct Body *b, char *type, size_t len)
Update the mime type.
Definition: mutt_attach.c:333
void mutt_buffer_sanitize_filename(struct Buffer *buf, const char *path, short slash)
Replace unsafe characters in a filename.
Definition: muttlib.c:1637
char * mutt_str_replace(char **p, const char *s)
Replace one string with another.
Definition: string.c:451
#define mutt_error(...)
Definition: logging.h:84
MailcapLookup
Mailcap actions.
Definition: mailcap.h:56
void mutt_adv_mktemp(struct Buffer *buf)
Create a temporary file.
Definition: muttlib.c:90
char * composecommand
Definition: mailcap.h:42
#define FREE(x)
Definition: memory.h:40
char * mutt_param_get(const struct ParameterList *pl, const char *s)
Find a matching Parameter.
Definition: parameter.c:84
Hundreds of global variables to back the user variables.
#define mutt_debug(LEVEL,...)
Definition: logging.h:81
int const char int line
Definition: acutest.h:617
Mailcap edit field.
Definition: mailcap.h:59
Convenience wrapper for the library headers.
A List node for strings.
Definition: list.h:34
struct ParameterList parameter
parameters of the content-type
Definition: body.h:39
bool needsterminal
endwin() and system
Definition: mailcap.h:48
int mutt_system(const char *cmd)
Run an external command.
Definition: system.c:51
static char * get_field(char *s)
NUL terminate a RFC1524 field.
Definition: mailcap.c:163
char * testcommand
Definition: mailcap.h:41