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