NeoMutt  2023-11-03-85-g512e01
Teaching an old dog new tricks
DOXYGEN
Loading...
Searching...
No Matches
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 *b, const char *filename,
68 const char *type, struct Buffer *command)
69{
70 int needspipe = true;
71 struct Buffer *buf = buf_pool_get();
72 struct Buffer *quoted = buf_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 = buf_string(command);
78 while (*cptr)
79 {
80 if (*cptr == '\\')
81 {
82 cptr++;
83 if (*cptr)
84 buf_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 buf_reset(param);
95 else
96 param = buf_pool_get();
97
98 /* Copy parameter name into param buffer */
99 cptr++;
100 while (*cptr && (*cptr != '}'))
101 buf_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(buf_string(param), "charset") && b->charset && !b->noconv)
107 pvalue2 = b->charset;
108 else
109 pvalue2 = mutt_param_get(&b->parameter, buf_string(param));
110
111 /* Now copy the parameter value into param buffer */
112 if (c_mailcap_sanitize)
113 buf_sanitize_filename(param, NONULL(pvalue2), false);
114 else
115 buf_strcpy(param, NONULL(pvalue2));
116
117 buf_quote_filename(quoted, buf_string(param), true);
118 buf_addstr(buf, buf_string(quoted));
119 }
120 else if ((*cptr == 's') && filename)
121 {
122 buf_quote_filename(quoted, filename, true);
123 buf_addstr(buf, buf_string(quoted));
124 needspipe = false;
125 }
126 else if (*cptr == 't')
127 {
128 if (!type2)
129 {
130 type2 = buf_pool_get();
131 if (c_mailcap_sanitize)
132 buf_sanitize_filename(type2, type, false);
133 else
134 buf_strcpy(type2, type);
135 }
136 buf_quote_filename(quoted, buf_string(type2), true);
137 buf_addstr(buf, buf_string(quoted));
138 }
139
140 if (*cptr)
141 cptr++;
142 }
143 else
144 {
145 buf_addch(buf, *cptr++);
146 }
147 }
148 buf_copy(command, buf);
149
150 buf_pool_release(&buf);
151 buf_pool_release(&quoted);
152 buf_pool_release(&param);
153 buf_pool_release(&type2);
154
155 return needspipe;
156}
157
164static 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
200static 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
232static bool rfc1524_mailcap_parse(struct Body *b, 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_RL_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_istr_equal(buf, type) && (!mutt_istrn_equal(buf, type, btlen) ||
269 ((buf[btlen] != '\0') && /* implicit wild */
270 !mutt_str_equal(buf + btlen, "/*")))) /* wildsubtype */
271 {
272 continue;
273 }
274
275 /* next field is the viewcommand */
276 char *field = ch;
277 ch = get_field(ch);
278 if (entry)
279 entry->command = mutt_str_dup(field);
280
281 /* parse the optional fields */
282 found = true;
283 bool copiousoutput = false;
284 bool composecommand = false;
285 bool editcommand = false;
286 bool printcommand = false;
287
288 while (ch)
289 {
290 field = ch;
291 ch = get_field(ch);
292 mutt_debug(LL_DEBUG2, "field: %s\n", field);
293 size_t plen;
294
295 if (mutt_istr_equal(field, "needsterminal"))
296 {
297 if (entry)
298 entry->needsterminal = true;
299 }
300 else if (mutt_istr_equal(field, "copiousoutput"))
301 {
302 copiousoutput = true;
303 if (entry)
304 entry->copiousoutput = true;
305 }
306 else if ((plen = mutt_istr_startswith(field, "composetyped")))
307 {
308 /* this compare most occur before compose to match correctly */
309 if (get_field_text(field + plen, entry ? &entry->composetypecommand : NULL,
310 type, filename, line))
311 {
312 composecommand = true;
313 }
314 }
315 else if ((plen = mutt_istr_startswith(field, "compose")))
316 {
317 if (get_field_text(field + plen, entry ? &entry->composecommand : NULL,
318 type, filename, line))
319 {
320 composecommand = true;
321 }
322 }
323 else if ((plen = mutt_istr_startswith(field, "print")))
324 {
325 if (get_field_text(field + plen, entry ? &entry->printcommand : NULL,
326 type, filename, line))
327 {
328 printcommand = true;
329 }
330 }
331 else if ((plen = mutt_istr_startswith(field, "edit")))
332 {
333 if (get_field_text(field + plen, entry ? &entry->editcommand : NULL,
334 type, filename, line))
335 {
336 editcommand = true;
337 }
338 }
339 else if ((plen = mutt_istr_startswith(field, "nametemplate")))
340 {
341 get_field_text(field + plen, entry ? &entry->nametemplate : NULL,
342 type, filename, line);
343 }
344 else if ((plen = mutt_istr_startswith(field, "x-convert")))
345 {
346 get_field_text(field + plen, entry ? &entry->convert : NULL, type, filename, line);
347 }
348 else if ((plen = mutt_istr_startswith(field, "test")))
349 {
350 /* This routine executes the given test command to determine
351 * if this is the right entry. */
352 char *test_command = NULL;
353
354 if (get_field_text(field + plen, &test_command, type, filename, line) && test_command)
355 {
356 struct Buffer *command = buf_pool_get();
357 struct Buffer *afilename = buf_pool_get();
358 buf_strcpy(command, test_command);
359 const bool c_mailcap_sanitize = cs_subset_bool(NeoMutt->sub, "mailcap_sanitize");
360 if (c_mailcap_sanitize)
361 buf_sanitize_filename(afilename, NONULL(b->filename), true);
362 else
363 buf_strcpy(afilename, NONULL(b->filename));
364 if (mailcap_expand_command(b, buf_string(afilename), type, command) == 1)
365 {
366 mutt_debug(LL_DEBUG1, "mailcap command needs a pipe: %s\n",
367 buf_string(command));
368 }
369
370 if (mutt_system(buf_string(command)))
371 {
372 /* a non-zero exit code means test failed */
373 found = false;
374 }
375 FREE(&test_command);
376 buf_pool_release(&command);
377 buf_pool_release(&afilename);
378 }
379 }
380 else if (mutt_istr_startswith(field, "x-neomutt-keep"))
381 {
382 if (entry)
383 entry->xneomuttkeep = true;
384 }
385 else if (mutt_istr_startswith(field, "x-neomutt-nowrap"))
386 {
387 if (entry)
388 entry->xneomuttnowrap = true;
389 b->nowrap = true;
390 }
391 } /* while (ch) */
392
393 if (opt == MUTT_MC_AUTOVIEW)
394 {
395 if (!copiousoutput)
396 found = false;
397 }
398 else if (opt == MUTT_MC_COMPOSE)
399 {
400 if (!composecommand)
401 found = false;
402 }
403 else if (opt == MUTT_MC_EDIT)
404 {
405 if (!editcommand)
406 found = false;
407 }
408 else if (opt == MUTT_MC_PRINT)
409 {
410 if (!printcommand)
411 found = false;
412 }
413
414 if (!found)
415 {
416 /* reset */
417 if (entry)
418 {
419 FREE(&entry->command);
420 FREE(&entry->composecommand);
421 FREE(&entry->composetypecommand);
422 FREE(&entry->editcommand);
423 FREE(&entry->printcommand);
424 FREE(&entry->nametemplate);
425 FREE(&entry->convert);
426 entry->needsterminal = false;
427 entry->copiousoutput = false;
428 entry->xneomuttkeep = false;
429 }
430 }
431 } /* while (!found && (buf = mutt_file_read_line ())) */
432 mutt_file_fclose(&fp);
433 } /* if ((fp = fopen ())) */
434 FREE(&buf);
435 return found;
436}
437
443{
444 return mutt_mem_calloc(1, sizeof(struct MailcapEntry));
445}
446
452{
453 if (!ptr || !*ptr)
454 return;
455
456 struct MailcapEntry *me = *ptr;
457
458 FREE(&me->command);
459 FREE(&me->testcommand);
460 FREE(&me->composecommand);
462 FREE(&me->editcommand);
463 FREE(&me->printcommand);
464 FREE(&me->nametemplate);
465 FREE(ptr);
466}
467
480bool mailcap_lookup(struct Body *b, char *type, size_t typelen,
481 struct MailcapEntry *entry, enum MailcapLookup opt)
482{
483 /* rfc1524 specifies that a path of mailcap files should be searched.
484 * joy. They say
485 * $HOME/.mailcap:/etc/mailcap:/usr/etc/mailcap:/usr/local/etc/mailcap, etc
486 * and overridden by the MAILCAPS environment variable, and, just to be nice,
487 * we'll make it specifiable in .neomuttrc */
488 const struct Slist *c_mailcap_path = cs_subset_slist(NeoMutt->sub, "mailcap_path");
489 if (!c_mailcap_path || (c_mailcap_path->count == 0))
490 {
491 /* L10N:
492 Mutt is trying to look up a mailcap value, but $mailcap_path is empty.
493 We added a reference to the MAILCAPS environment variable as a hint too.
494
495 Because the variable is automatically populated by Mutt, this
496 should only occur if the user deliberately runs in their shell:
497 export MAILCAPS=
498
499 or deliberately runs inside Mutt or their .muttrc:
500 set mailcap_path=""
501 -or-
502 unset mailcap_path
503 */
504 mutt_error(_("Neither mailcap_path nor MAILCAPS specified"));
505 return false;
506 }
507
508 mutt_check_lookup_list(b, type, typelen);
509
510 struct Buffer *path = buf_pool_get();
511 bool found = false;
512
513 struct ListNode *np = NULL;
514 STAILQ_FOREACH(np, &c_mailcap_path->head, entries)
515 {
516 buf_strcpy(path, np->data);
517 buf_expand_path(path);
518
519 mutt_debug(LL_DEBUG2, "Checking mailcap file: %s\n", buf_string(path));
520 found = rfc1524_mailcap_parse(b, buf_string(path), type, entry, opt);
521 if (found)
522 break;
523 }
524
525 buf_pool_release(&path);
526
527 if (entry && !found)
528 mutt_error(_("mailcap entry for type %s not found"), type);
529
530 return found;
531}
532
549void mailcap_expand_filename(const char *nametemplate, const char *oldfile,
550 struct Buffer *newfile)
551{
552 int i, j, k;
553 char *s = NULL;
554 bool lmatch = false, rmatch = false;
555
556 buf_reset(newfile);
557
558 /* first, ignore leading path components */
559
560 if (nametemplate && (s = strrchr(nametemplate, '/')))
561 nametemplate = s + 1;
562
563 if (oldfile && (s = strrchr(oldfile, '/')))
564 oldfile = s + 1;
565
566 if (!nametemplate)
567 {
568 if (oldfile)
569 buf_strcpy(newfile, oldfile);
570 }
571 else if (!oldfile)
572 {
573 mutt_file_expand_fmt(newfile, nametemplate, "neomutt");
574 }
575 else /* oldfile && nametemplate */
576 {
577 /* first, compare everything left from the "%s"
578 * (if there is one). */
579
580 lmatch = true;
581 bool ps = false;
582 for (i = 0; nametemplate[i]; i++)
583 {
584 if ((nametemplate[i] == '%') && (nametemplate[i + 1] == 's'))
585 {
586 ps = true;
587 break;
588 }
589
590 /* note that the following will _not_ read beyond oldfile's end. */
591
592 if (lmatch && (nametemplate[i] != oldfile[i]))
593 lmatch = false;
594 }
595
596 if (ps)
597 {
598 /* If we had a "%s", check the rest. */
599
600 /* now, for the right part: compare everything right from
601 * the "%s" to the final part of oldfile.
602 *
603 * The logic here is as follows:
604 *
605 * - We start reading from the end.
606 * - There must be a match _right_ from the "%s",
607 * thus the i + 2.
608 * - If there was a left hand match, this stuff
609 * must not be counted again. That's done by the
610 * condition (j >= (lmatch ? i : 0)). */
611
612 rmatch = true;
613
614 for (j = mutt_str_len(oldfile) - 1, k = mutt_str_len(nametemplate) - 1;
615 (j >= (lmatch ? i : 0)) && (k >= (i + 2)); j--, k--)
616 {
617 if (nametemplate[k] != oldfile[j])
618 {
619 rmatch = false;
620 break;
621 }
622 }
623
624 /* Now, check if we had a full match. */
625
626 if (k >= i + 2)
627 rmatch = false;
628
629 struct Buffer *left = buf_pool_get();
630 struct Buffer *right = buf_pool_get();
631
632 if (!lmatch)
633 buf_strcpy_n(left, nametemplate, i);
634 if (!rmatch)
635 buf_strcpy(right, nametemplate + i + 2);
636 buf_printf(newfile, "%s%s%s", buf_string(left), oldfile, buf_string(right));
637
638 buf_pool_release(&left);
639 buf_pool_release(&right);
640 }
641 else
642 {
643 /* no "%s" in the name template. */
644 buf_strcpy(newfile, nametemplate);
645 }
646 }
647
648 mutt_adv_mktemp(newfile);
649}
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:173
void buf_reset(struct Buffer *buf)
Reset an existing Buffer.
Definition: buffer.c:88
size_t buf_strcpy_n(struct Buffer *buf, const char *s, size_t len)
Copy a string into a Buffer.
Definition: buffer.c:422
size_t buf_addch(struct Buffer *buf, char c)
Add a single character to a Buffer.
Definition: buffer.c:253
size_t buf_addstr(struct Buffer *buf, const char *s)
Add a string to a Buffer.
Definition: buffer.c:238
size_t buf_strcpy(struct Buffer *buf, const char *s)
Copy a string into a Buffer.
Definition: buffer.c:407
size_t buf_copy(struct Buffer *dst, const struct Buffer *src)
Copy a Buffer's contents to another Buffer.
Definition: buffer.c:572
static const char * buf_string(const struct Buffer *buf)
Convert a buffer to a const char * "string".
Definition: buffer.h:93
const struct Slist * cs_subset_slist(const struct ConfigSubset *sub, const char *name)
Get a string-list config item by name.
Definition: helpers.c:243
bool cs_subset_bool(const struct ConfigSubset *sub, const char *name)
Get a boolean config item by name.
Definition: helpers.c:48
Convenience wrapper for the config headers.
Convenience wrapper for the core headers.
Structs that make up 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:936
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:763
int mutt_file_fclose(FILE **fp)
Close a FILE handle (and NULL the pointer)
Definition: file.c:152
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:1512
#define MUTT_RL_CONT
-continuation
Definition: file.h:40
#define mutt_error(...)
Definition: logging2.h:92
#define mutt_debug(LEVEL,...)
Definition: logging2.h:89
@ LL_DEBUG2
Log at debug level 2.
Definition: logging2.h:44
@ LL_DEBUG1
Log at debug level 1.
Definition: logging2.h:43
void mailcap_entry_free(struct MailcapEntry **ptr)
Deallocate an struct MailcapEntry.
Definition: mailcap.c:451
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
struct MailcapEntry * mailcap_entry_new(void)
Allocate memory for a new rfc1524 entry.
Definition: mailcap.c:442
static char * get_field(char *s)
NUL terminate a RFC1524 field.
Definition: mailcap.c:164
int mailcap_expand_command(struct Body *b, 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:549
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:232
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:480
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:45
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:637
bool mutt_istr_equal(const char *a, const char *b)
Compare two strings, ignoring case.
Definition: string.c:810
char * mutt_str_dup(const char *str)
Copy a string, safely.
Definition: string.c:251
char * mutt_str_skip_email_wsp(const char *s)
Skip over whitespace as defined by RFC5322.
Definition: string.c:680
bool mutt_str_equal(const char *a, const char *b)
Compare two strings.
Definition: string.c:798
char * mutt_str_skip_whitespace(const char *p)
Find the first non-whitespace character in a string.
Definition: string.c:623
size_t mutt_str_len(const char *a)
Calculate the length of a string, safely.
Definition: string.c:568
size_t mutt_istr_startswith(const char *str, const char *prefix)
Check whether a string starts with a prefix, ignoring case.
Definition: string.c:240
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:525
char * mutt_str_replace(char **p, const char *s)
Replace one string with another.
Definition: string.c:327
void mutt_check_lookup_list(struct Body *b, char *type, size_t len)
Update the mime type.
Definition: mutt_attach.c:339
void buf_sanitize_filename(struct Buffer *buf, const char *path, short slash)
Replace unsafe characters in a filename.
Definition: muttlib.c:1619
void mutt_adv_mktemp(struct Buffer *buf)
Create a temporary file.
Definition: muttlib.c:85
void buf_expand_path(struct Buffer *buf)
Create the canonical path.
Definition: muttlib.c:329
Some miscellaneous functions.
char * mutt_param_get(const struct ParameterList *pl, const char *s)
Find a matching Parameter.
Definition: parameter.c:84
struct Buffer * buf_pool_get(void)
Get a Buffer from the pool.
Definition: pool.c:81
void buf_pool_release(struct Buffer **ptr)
Return a Buffer to the pool.
Definition: pool.c:94
Prototypes for many functions.
int mutt_system(const char *cmd)
Run an external command.
Definition: system.c:50
#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:41
struct ConfigSubset * sub
Inherited config items.
Definition: neomutt.h:45
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