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