NeoMutt  2025-12-11-435-g4ac674
Teaching an old dog new tricks
DOXYGEN
Loading...
Searching...
No Matches
mailcap.c
Go to the documentation of this file.
1
24
36
37#include "config.h"
38#include <stdbool.h>
39#include <stdio.h>
40#include <string.h>
41#include "mutt/lib.h"
42#include "config/lib.h"
43#include "core/lib.h"
44#include "mutt.h"
45#include "mailcap.h"
46#include "attach/lib.h"
47#include "body.h"
48#include "muttlib.h"
49#include "parameter.h"
50
70int mailcap_expand_command(struct Body *b, const char *filename,
71 const char *type, struct Buffer *command)
72{
73 int needspipe = true;
74 struct Buffer *buf = buf_pool_get();
75 struct Buffer *quoted = buf_pool_get();
76 struct Buffer *param = NULL;
77 struct Buffer *type2 = NULL;
78
79 const bool c_mailcap_sanitize = cs_subset_bool(NeoMutt->sub, "mailcap_sanitize");
80 const char *cptr = buf_string(command);
81 while (*cptr)
82 {
83 if (*cptr == '\\')
84 {
85 cptr++;
86 if (*cptr)
87 buf_addch(buf, *cptr++);
88 }
89 else if (*cptr == '%')
90 {
91 cptr++;
92 if (*cptr == '{')
93 {
94 const char *pvalue2 = NULL;
95
96 if (param)
97 buf_reset(param);
98 else
99 param = buf_pool_get();
100
101 /* Copy parameter name into param buffer */
102 cptr++;
103 while (*cptr && (*cptr != '}'))
104 buf_addch(param, *cptr++);
105
106 /* In send mode, use the current charset, since the message hasn't
107 * been converted yet. If noconv is set, then we assume the
108 * charset parameter has the correct value instead. */
109 if (mutt_istr_equal(buf_string(param), "charset") && b->charset && !b->noconv)
110 pvalue2 = b->charset;
111 else
112 pvalue2 = mutt_param_get(&b->parameter, buf_string(param));
113
114 /* Now copy the parameter value into param buffer */
115 if (c_mailcap_sanitize)
116 buf_sanitize_filename(param, NONULL(pvalue2), false);
117 else
118 buf_strcpy(param, pvalue2);
119
120 buf_quote_filename(quoted, buf_string(param), true);
121 buf_addstr(buf, buf_string(quoted));
122 }
123 else if ((*cptr == 's') && filename)
124 {
125 buf_quote_filename(quoted, filename, true);
126 buf_addstr(buf, buf_string(quoted));
127 needspipe = false;
128 }
129 else if (*cptr == 't')
130 {
131 if (!type2)
132 {
133 type2 = buf_pool_get();
134 if (c_mailcap_sanitize)
135 buf_sanitize_filename(type2, type, false);
136 else
137 buf_strcpy(type2, type);
138 }
139 buf_quote_filename(quoted, buf_string(type2), true);
140 buf_addstr(buf, buf_string(quoted));
141 }
142
143 if (*cptr)
144 cptr++;
145 }
146 else
147 {
148 buf_addch(buf, *cptr++);
149 }
150 }
151 buf_copy(command, buf);
152
153 buf_pool_release(&buf);
154 buf_pool_release(&quoted);
155 buf_pool_release(&param);
156 buf_pool_release(&type2);
157
158 return needspipe;
159}
160
167static char *get_field(char *s)
168{
169 if (!s)
170 return NULL;
171
172 char *ch = NULL;
173
174 while ((ch = strpbrk(s, ";\\")))
175 {
176 if (*ch == '\\')
177 {
178 s = ch + 1;
179 if (*s)
180 s++;
181 }
182 else
183 {
184 *ch = '\0';
185 ch = mutt_str_skip_email_wsp(ch + 1);
186 break;
187 }
188 }
190 return ch;
191}
192
203static int get_field_text(char *field, char **entry, const char *type,
204 const char *filename, int line)
205{
206 field = mutt_str_skip_whitespace(field);
207 if (*field == '=')
208 {
209 if (entry)
210 {
211 field++;
212 field = mutt_str_skip_whitespace(field);
213 mutt_str_replace(entry, field);
214 }
215 return 1;
216 }
217 else
218 {
219 mutt_error(_("Improperly formatted entry for type %s in \"%s\" line %d"),
220 type, filename, line);
221 return 0;
222 }
223}
224
235static bool rfc1524_mailcap_parse(struct Body *b, const char *filename, const char *type,
236 struct MailcapEntry *entry, enum MailcapLookup opt)
237{
238 char *buf = NULL;
239 bool found = false;
240 int line = 0;
241
242 /* rfc1524 mailcap file is of the format:
243 * base/type; command; extradefs
244 * type can be * for matching all
245 * base with no /type is an implicit wild
246 * command contains a %s for the filename to pass, default to pipe on stdin
247 * extradefs are of the form:
248 * def1="definition"; def2="define \;";
249 * line wraps with a \ at the end of the line
250 * # for comments */
251
252 /* find length of basetype */
253 char *ch = strchr(type, '/');
254 if (!ch)
255 return false;
256 const int btlen = ch - type;
257
258 FILE *fp = mutt_file_fopen(filename, "r");
259 if (fp)
260 {
261 size_t buflen;
262 while (!found && (buf = mutt_file_read_line(buf, &buflen, fp, &line, MUTT_RL_CONT)))
263 {
264 /* ignore comments */
265 if (*buf == '#')
266 continue;
267 mutt_debug(LL_DEBUG2, "mailcap entry: %s\n", buf);
268
269 /* check type */
270 ch = get_field(buf);
271 if (!mutt_istr_equal(buf, type) && (!mutt_istrn_equal(buf, type, btlen) ||
272 ((buf[btlen] != '\0') && /* implicit wild */
273 !mutt_str_equal(buf + btlen, "/*")))) /* wildsubtype */
274 {
275 continue;
276 }
277
278 /* next field is the viewcommand */
279 char *field = ch;
280 ch = get_field(ch);
281 if (entry)
282 entry->command = mutt_str_dup(field);
283
284 /* parse the optional fields */
285 found = true;
286 bool copiousoutput = false;
287 bool composecommand = false;
288 bool editcommand = false;
289 bool printcommand = false;
290
291 while (ch)
292 {
293 field = ch;
294 ch = get_field(ch);
295 mutt_debug(LL_DEBUG2, "field: %s\n", field);
296 size_t plen;
297
298 if (mutt_istr_equal(field, "needsterminal"))
299 {
300 if (entry)
301 entry->needsterminal = true;
302 }
303 else if (mutt_istr_equal(field, "copiousoutput"))
304 {
305 copiousoutput = true;
306 if (entry)
307 entry->copiousoutput = true;
308 }
309 else if ((plen = mutt_istr_startswith(field, "composetyped")))
310 {
311 /* this compare most occur before compose to match correctly */
312 if (get_field_text(field + plen, entry ? &entry->composetypecommand : NULL,
313 type, filename, line))
314 {
315 composecommand = true;
316 }
317 }
318 else if ((plen = mutt_istr_startswith(field, "compose")))
319 {
320 if (get_field_text(field + plen, entry ? &entry->composecommand : NULL,
321 type, filename, line))
322 {
323 composecommand = true;
324 }
325 }
326 else if ((plen = mutt_istr_startswith(field, "print")))
327 {
328 if (get_field_text(field + plen, entry ? &entry->printcommand : NULL,
329 type, filename, line))
330 {
331 printcommand = true;
332 }
333 }
334 else if ((plen = mutt_istr_startswith(field, "edit")))
335 {
336 if (get_field_text(field + plen, entry ? &entry->editcommand : NULL,
337 type, filename, line))
338 {
339 editcommand = true;
340 }
341 }
342 else if ((plen = mutt_istr_startswith(field, "nametemplate")))
343 {
344 get_field_text(field + plen, entry ? &entry->nametemplate : NULL,
345 type, filename, line);
346 }
347 else if ((plen = mutt_istr_startswith(field, "x-convert")))
348 {
349 get_field_text(field + plen, entry ? &entry->convert : NULL, type, filename, line);
350 }
351 else if ((plen = mutt_istr_startswith(field, "test")))
352 {
353 /* This routine executes the given test command to determine
354 * if this is the right entry. */
355 char *test_command = NULL;
356
357 if (get_field_text(field + plen, &test_command, type, filename, line) && test_command)
358 {
359 struct Buffer *command = buf_pool_get();
360 struct Buffer *afilename = buf_pool_get();
361 buf_strcpy(command, test_command);
362 const bool c_mailcap_sanitize = cs_subset_bool(NeoMutt->sub, "mailcap_sanitize");
363 if (c_mailcap_sanitize)
364 buf_sanitize_filename(afilename, NONULL(b->filename), true);
365 else
366 buf_strcpy(afilename, b->filename);
367 if (mailcap_expand_command(b, buf_string(afilename), type, command) == 1)
368 {
369 mutt_debug(LL_DEBUG1, "mailcap command needs a pipe: %s\n",
370 buf_string(command));
371 }
372
373 if (mutt_system(buf_string(command)))
374 {
375 /* a non-zero exit code means test failed */
376 found = false;
377 }
378 FREE(&test_command);
379 buf_pool_release(&command);
380 buf_pool_release(&afilename);
381 }
382 }
383 else if (mutt_istr_startswith(field, "x-neomutt-keep"))
384 {
385 if (entry)
386 entry->xneomuttkeep = true;
387 }
388 else if (mutt_istr_startswith(field, "x-neomutt-nowrap"))
389 {
390 if (entry)
391 entry->xneomuttnowrap = true;
392 b->nowrap = true;
393 }
394 } /* while (ch) */
395
396 if (opt == MUTT_MC_AUTOVIEW)
397 {
398 if (!copiousoutput)
399 found = false;
400 }
401 else if (opt == MUTT_MC_COMPOSE)
402 {
403 if (!composecommand)
404 found = false;
405 }
406 else if (opt == MUTT_MC_EDIT)
407 {
408 if (!editcommand)
409 found = false;
410 }
411 else if (opt == MUTT_MC_PRINT)
412 {
413 if (!printcommand)
414 found = false;
415 }
416
417 if (!found)
418 {
419 /* reset */
420 if (entry)
421 {
422 FREE(&entry->command);
423 FREE(&entry->composecommand);
424 FREE(&entry->composetypecommand);
425 FREE(&entry->editcommand);
426 FREE(&entry->printcommand);
427 FREE(&entry->nametemplate);
428 FREE(&entry->convert);
429 entry->needsterminal = false;
430 entry->copiousoutput = false;
431 entry->xneomuttkeep = false;
432 }
433 }
434 }
435 mutt_file_fclose(&fp);
436 }
437
438 FREE(&buf);
439 return found;
440}
441
447{
448 return MUTT_MEM_CALLOC(1, struct MailcapEntry);
449}
450
456{
457 if (!ptr || !*ptr)
458 return;
459
460 struct MailcapEntry *me = *ptr;
461
462 FREE(&me->command);
463 FREE(&me->testcommand);
464 FREE(&me->composecommand);
466 FREE(&me->editcommand);
467 FREE(&me->printcommand);
468 FREE(&me->nametemplate);
469 FREE(ptr);
470}
471
484bool mailcap_lookup(struct Body *b, char *type, size_t typelen,
485 struct MailcapEntry *entry, enum MailcapLookup opt)
486{
487 /* rfc1524 specifies that a path of mailcap files should be searched.
488 * joy. They say
489 * $HOME/.mailcap:/etc/mailcap:/usr/etc/mailcap:/usr/local/etc/mailcap, etc
490 * and overridden by the MAILCAPS environment variable, and, just to be nice,
491 * we'll make it specifiable in .neomuttrc */
492 const struct Slist *c_mailcap_path = cs_subset_slist(NeoMutt->sub, "mailcap_path");
493 if (!c_mailcap_path || (c_mailcap_path->count == 0))
494 {
495 /* L10N:
496 Mutt is trying to look up a mailcap value, but $mailcap_path is empty.
497 We added a reference to the MAILCAPS environment variable as a hint too.
498
499 Because the variable is automatically populated by Mutt, this
500 should only occur if the user deliberately runs in their shell:
501 export MAILCAPS=
502
503 or deliberately runs inside Mutt or their .muttrc:
504 set mailcap_path=""
505 -or-
506 unset mailcap_path
507 */
508 mutt_error(_("Neither mailcap_path nor MAILCAPS specified"));
509 return false;
510 }
511
512 mutt_check_lookup_list(b, type, typelen);
513
514 struct Buffer *path = buf_pool_get();
515 bool found = false;
516
517 struct ListNode *np = NULL;
518 STAILQ_FOREACH(np, &c_mailcap_path->head, entries)
519 {
520 buf_strcpy(path, np->data);
521 expand_path(path, false);
522
523 mutt_debug(LL_DEBUG2, "Checking mailcap file: %s\n", buf_string(path));
524 found = rfc1524_mailcap_parse(b, buf_string(path), type, entry, opt);
525 if (found)
526 break;
527 }
528
529 buf_pool_release(&path);
530
531 if (entry && !found)
532 mutt_error(_("mailcap entry for type %s not found"), type);
533
534 return found;
535}
536
553void mailcap_expand_filename(const char *nametemplate, const char *oldfile,
554 struct Buffer *newfile)
555{
556 int i, j, k;
557 char *s = NULL;
558 bool lmatch = false, rmatch = false;
559
560 buf_reset(newfile);
561
562 /* first, ignore leading path components */
563
564 if (nametemplate && (s = strrchr(nametemplate, '/')))
565 nametemplate = s + 1;
566
567 if (oldfile && (s = strrchr(oldfile, '/')))
568 oldfile = s + 1;
569
570 if (!nametemplate)
571 {
572 if (oldfile)
573 buf_strcpy(newfile, oldfile);
574 }
575 else if (!oldfile)
576 {
577 mutt_file_expand_fmt(newfile, nametemplate, "neomutt");
578 }
579 else /* oldfile && nametemplate */
580 {
581 /* first, compare everything left from the "%s"
582 * (if there is one). */
583
584 lmatch = true;
585 bool ps = false;
586 for (i = 0; nametemplate[i]; i++)
587 {
588 if ((nametemplate[i] == '%') && (nametemplate[i + 1] == 's'))
589 {
590 ps = true;
591 break;
592 }
593
594 /* note that the following will _not_ read beyond oldfile's end. */
595
596 if (lmatch && (nametemplate[i] != oldfile[i]))
597 lmatch = false;
598 }
599
600 if (ps)
601 {
602 /* If we had a "%s", check the rest. */
603
604 /* now, for the right part: compare everything right from
605 * the "%s" to the final part of oldfile.
606 *
607 * The logic here is as follows:
608 *
609 * - We start reading from the end.
610 * - There must be a match _right_ from the "%s",
611 * thus the i + 2.
612 * - If there was a left hand match, this stuff
613 * must not be counted again. That's done by the
614 * condition (j >= (lmatch ? i : 0)). */
615
616 rmatch = true;
617
618 for (j = mutt_str_len(oldfile) - 1, k = mutt_str_len(nametemplate) - 1;
619 (j >= (lmatch ? i : 0)) && (k >= (i + 2)); j--, k--)
620 {
621 if (nametemplate[k] != oldfile[j])
622 {
623 rmatch = false;
624 break;
625 }
626 }
627
628 /* Now, check if we had a full match. */
629
630 if (k >= i + 2)
631 rmatch = false;
632
633 struct Buffer *left = buf_pool_get();
634 struct Buffer *right = buf_pool_get();
635
636 if (!lmatch)
637 buf_strcpy_n(left, nametemplate, i);
638 if (!rmatch)
639 buf_strcpy(right, nametemplate + i + 2);
640 buf_printf(newfile, "%s%s%s", buf_string(left), oldfile, buf_string(right));
641
642 buf_pool_release(&left);
643 buf_pool_release(&right);
644 }
645 else
646 {
647 /* no "%s" in the name template. */
648 buf_strcpy(newfile, nametemplate);
649 }
650 }
651
652 mutt_adv_mktemp(newfile);
653}
GUI display the mailboxes in a side panel.
int buf_printf(struct Buffer *buf, const char *fmt,...)
Format a string overwriting a Buffer.
Definition buffer.c:161
void buf_reset(struct Buffer *buf)
Reset an existing Buffer.
Definition buffer.c:76
size_t buf_strcpy_n(struct Buffer *buf, const char *s, size_t len)
Copy a string into a Buffer.
Definition buffer.c:416
size_t buf_addch(struct Buffer *buf, char c)
Add a single character to a Buffer.
Definition buffer.c:241
size_t buf_addstr(struct Buffer *buf, const char *s)
Add a string to a Buffer.
Definition buffer.c:226
size_t buf_strcpy(struct Buffer *buf, const char *s)
Copy a string into a Buffer.
Definition buffer.c:395
size_t buf_copy(struct Buffer *dst, const struct Buffer *src)
Copy a Buffer's contents to another Buffer.
Definition buffer.c:601
static const char * buf_string(const struct Buffer *buf)
Convert a buffer to a const char * "string".
Definition buffer.h:96
const struct Slist * cs_subset_slist(const struct ConfigSubset *sub, const char *name)
Get a string-list config item by name.
Definition helpers.c:242
bool cs_subset_bool(const struct ConfigSubset *sub, const char *name)
Get a boolean config item by name.
Definition helpers.c:47
Convenience wrapper for the config headers.
Convenience wrapper for the core headers.
Representation of the body of an email.
void buf_quote_filename(struct Buffer *buf, const char *filename, bool add_outer)
Quote a filename to survive the shell's quoting rules.
Definition file.c:807
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:682
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:1361
#define MUTT_RL_CONT
-continuation
Definition file.h:41
#define mutt_file_fclose(FP)
Definition file.h:139
#define mutt_file_fopen(PATH, MODE)
Definition file.h:138
#define mutt_error(...)
Definition logging2.h:94
#define mutt_debug(LEVEL,...)
Definition logging2.h:91
@ LL_DEBUG2
Log at debug level 2.
Definition logging2.h:46
@ LL_DEBUG1
Log at debug level 1.
Definition logging2.h:45
void mailcap_entry_free(struct MailcapEntry **ptr)
Deallocate an struct MailcapEntry.
Definition mailcap.c:455
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:203
struct MailcapEntry * mailcap_entry_new(void)
Allocate memory for a new rfc1524 entry.
Definition mailcap.c:446
static char * get_field(char *s)
NUL terminate a RFC1524 field.
Definition mailcap.c:167
int mailcap_expand_command(struct Body *b, const char *filename, const char *type, struct Buffer *command)
Expand expandos in a command.
Definition mailcap.c:70
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:553
static bool rfc1524_mailcap_parse(struct Body *b, const char *filename, const char *type, struct MailcapEntry *entry, enum MailcapLookup opt)
Parse a mailcap entry.
Definition mailcap.c:235
bool mailcap_lookup(struct Body *b, char *type, size_t typelen, struct MailcapEntry *entry, enum MailcapLookup opt)
Find given type in the list of mailcap files.
Definition mailcap.c:484
RFC1524 Mailcap routines.
MailcapLookup
Mailcap actions.
Definition mailcap.h:56
@ MUTT_MC_PRINT
Mailcap print field.
Definition mailcap.h:60
@ MUTT_MC_EDIT
Mailcap edit field.
Definition mailcap.h:58
@ MUTT_MC_AUTOVIEW
Mailcap autoview field.
Definition mailcap.h:61
@ MUTT_MC_COMPOSE
Mailcap compose field.
Definition mailcap.h:59
#define FREE(x)
Free memory and set the pointer to NULL.
Definition memory.h:68
#define MUTT_MEM_CALLOC(n, type)
Definition memory.h:52
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:567
bool mutt_istr_equal(const char *a, const char *b)
Compare two strings, ignoring case.
Definition string.c:674
char * mutt_str_dup(const char *str)
Copy a string, safely.
Definition string.c:257
char * mutt_str_skip_email_wsp(const char *s)
Skip over whitespace as defined by RFC5322.
Definition string.c:610
bool mutt_str_equal(const char *a, const char *b)
Compare two strings.
Definition string.c:662
char * mutt_str_skip_whitespace(const char *p)
Find the first non-whitespace character in a string.
Definition string.c:553
size_t mutt_str_len(const char *a)
Calculate the length of a string, safely.
Definition string.c:500
size_t mutt_istr_startswith(const char *str, const char *prefix)
Check whether a string starts with a prefix, ignoring case.
Definition string.c:246
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:457
char * mutt_str_replace(char **p, const char *s)
Replace one string with another.
Definition string.c:284
Many unsorted constants and some structs.
int mutt_system(const char *cmd)
Run an external command.
Definition system.c:51
void mutt_check_lookup_list(struct Body *b, char *type, size_t len)
Update the mime type.
void expand_path(struct Buffer *buf, bool regex)
Create the canonical path.
Definition muttlib.c:121
void buf_sanitize_filename(struct Buffer *buf, const char *path, short slash)
Replace unsafe characters in a filename.
Definition muttlib.c:912
void mutt_adv_mktemp(struct Buffer *buf)
Create a temporary file.
Definition muttlib.c:83
Some miscellaneous functions.
char * mutt_param_get(const struct ParameterList *pl, const char *s)
Find a matching Parameter.
Definition parameter.c:85
Store attributes associated with a MIME part.
struct Buffer * buf_pool_get(void)
Get a Buffer from the pool.
Definition pool.c:91
void buf_pool_release(struct Buffer **ptr)
Return a Buffer to the pool.
Definition pool.c:111
#define STAILQ_FOREACH(var, head, field)
Definition queue.h:390
#define NONULL(x)
Definition string2.h:44
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:79
struct ParameterList parameter
Parameters of the content-type.
Definition body.h:63
bool nowrap
Do not wrap the output in the pager.
Definition body.h:89
char * filename
When sending a message, this is the file to which this structure refers.
Definition body.h:59
String manipulation buffer.
Definition buffer.h:36
A List node for strings.
Definition list.h:37
char * data
String.
Definition list.h:38
A mailcap entry.
Definition mailcap.h:37
char * composecommand
Compose command.
Definition mailcap.h:40
bool needsterminal
endwin() and system
Definition mailcap.h:46
char * testcommand
Test command.
Definition mailcap.h:39
char * nametemplate
Filename template.
Definition mailcap.h:44
char * printcommand
Print command.
Definition mailcap.h:43
char * composetypecommand
Compose type command.
Definition mailcap.h:41
char * editcommand
Edit command.
Definition mailcap.h:42
char * command
Command to run.
Definition mailcap.h:38
bool copiousoutput
needs pager, basically
Definition mailcap.h:47
bool xneomuttkeep
do not remove the file on command exit
Definition mailcap.h:48
char * convert
Conversion command.
Definition mailcap.h:45
bool xneomuttnowrap
do not wrap the output in the pager
Definition mailcap.h:49
Container for Accounts, Notifications.
Definition neomutt.h:41
struct ConfigSubset * sub
Inherited config items.
Definition neomutt.h:49
String list.
Definition slist.h:37
struct ListHead head
List containing values.
Definition slist.h:38
size_t count
Number of values in list.
Definition slist.h:39