NeoMutt  2023-05-17-56-ga67199
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 = 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") && a->charset && !a->noconv)
107 pvalue2 = a->charset;
108 else
109 pvalue2 = mutt_param_get(&a->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 *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_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(a->filename), true);
362 else
363 buf_strcpy(afilename, NONULL(a->filename));
364 mailcap_expand_command(a, buf_string(afilename), type, command);
365 if (mutt_system(buf_string(command)))
366 {
367 /* a non-zero exit code means test failed */
368 found = false;
369 }
370 FREE(&test_command);
371 buf_pool_release(&command);
372 buf_pool_release(&afilename);
373 }
374 }
375 else if (mutt_istr_startswith(field, "x-neomutt-keep"))
376 {
377 if (entry)
378 entry->xneomuttkeep = true;
379 }
380 else if (mutt_istr_startswith(field, "x-neomutt-nowrap"))
381 {
382 if (entry)
383 entry->xneomuttnowrap = true;
384 a->nowrap = true;
385 }
386 } /* while (ch) */
387
388 if (opt == MUTT_MC_AUTOVIEW)
389 {
390 if (!copiousoutput)
391 found = false;
392 }
393 else if (opt == MUTT_MC_COMPOSE)
394 {
395 if (!composecommand)
396 found = false;
397 }
398 else if (opt == MUTT_MC_EDIT)
399 {
400 if (!editcommand)
401 found = false;
402 }
403 else if (opt == MUTT_MC_PRINT)
404 {
405 if (!printcommand)
406 found = false;
407 }
408
409 if (!found)
410 {
411 /* reset */
412 if (entry)
413 {
414 FREE(&entry->command);
415 FREE(&entry->composecommand);
416 FREE(&entry->composetypecommand);
417 FREE(&entry->editcommand);
418 FREE(&entry->printcommand);
419 FREE(&entry->nametemplate);
420 FREE(&entry->convert);
421 entry->needsterminal = false;
422 entry->copiousoutput = false;
423 entry->xneomuttkeep = false;
424 }
425 }
426 } /* while (!found && (buf = mutt_file_read_line ())) */
427 mutt_file_fclose(&fp);
428 } /* if ((fp = fopen ())) */
429 FREE(&buf);
430 return found;
431}
432
438{
439 return mutt_mem_calloc(1, sizeof(struct MailcapEntry));
440}
441
447{
448 if (!ptr || !*ptr)
449 return;
450
451 struct MailcapEntry *me = *ptr;
452
453 FREE(&me->command);
454 FREE(&me->testcommand);
455 FREE(&me->composecommand);
457 FREE(&me->editcommand);
458 FREE(&me->printcommand);
459 FREE(&me->nametemplate);
460 FREE(ptr);
461}
462
475bool mailcap_lookup(struct Body *a, char *type, size_t typelen,
476 struct MailcapEntry *entry, enum MailcapLookup opt)
477{
478 /* rfc1524 specifies that a path of mailcap files should be searched.
479 * joy. They say
480 * $HOME/.mailcap:/etc/mailcap:/usr/etc/mailcap:/usr/local/etc/mailcap, etc
481 * and overridden by the MAILCAPS environment variable, and, just to be nice,
482 * we'll make it specifiable in .neomuttrc */
483 const struct Slist *c_mailcap_path = cs_subset_slist(NeoMutt->sub, "mailcap_path");
484 if (!c_mailcap_path || (c_mailcap_path->count == 0))
485 {
486 /* L10N:
487 Mutt is trying to look up a mailcap value, but $mailcap_path is empty.
488 We added a reference to the MAILCAPS environment variable as a hint too.
489
490 Because the variable is automatically populated by Mutt, this
491 should only occur if the user deliberately runs in their shell:
492 export MAILCAPS=
493
494 or deliberately runs inside Mutt or their .muttrc:
495 set mailcap_path=""
496 -or-
497 unset mailcap_path
498 */
499 mutt_error(_("Neither mailcap_path nor MAILCAPS specified"));
500 return false;
501 }
502
503 mutt_check_lookup_list(a, type, typelen);
504
505 struct Buffer *path = buf_pool_get();
506 bool found = false;
507
508 struct ListNode *np = NULL;
509 STAILQ_FOREACH(np, &c_mailcap_path->head, entries)
510 {
511 buf_strcpy(path, np->data);
512 buf_expand_path(path);
513
514 mutt_debug(LL_DEBUG2, "Checking mailcap file: %s\n", buf_string(path));
515 found = rfc1524_mailcap_parse(a, buf_string(path), type, entry, opt);
516 if (found)
517 break;
518 }
519
520 buf_pool_release(&path);
521
522 if (entry && !found)
523 mutt_error(_("mailcap entry for type %s not found"), type);
524
525 return found;
526}
527
544void mailcap_expand_filename(const char *nametemplate, const char *oldfile,
545 struct Buffer *newfile)
546{
547 int i, j, k;
548 char *s = NULL;
549 bool lmatch = false, rmatch = false;
550
551 buf_reset(newfile);
552
553 /* first, ignore leading path components */
554
555 if (nametemplate && (s = strrchr(nametemplate, '/')))
556 nametemplate = s + 1;
557
558 if (oldfile && (s = strrchr(oldfile, '/')))
559 oldfile = s + 1;
560
561 if (!nametemplate)
562 {
563 if (oldfile)
564 buf_strcpy(newfile, oldfile);
565 }
566 else if (!oldfile)
567 {
568 mutt_file_expand_fmt(newfile, nametemplate, "neomutt");
569 }
570 else /* oldfile && nametemplate */
571 {
572 /* first, compare everything left from the "%s"
573 * (if there is one). */
574
575 lmatch = true;
576 bool ps = false;
577 for (i = 0; nametemplate[i]; i++)
578 {
579 if ((nametemplate[i] == '%') && (nametemplate[i + 1] == 's'))
580 {
581 ps = true;
582 break;
583 }
584
585 /* note that the following will _not_ read beyond oldfile's end. */
586
587 if (lmatch && (nametemplate[i] != oldfile[i]))
588 lmatch = false;
589 }
590
591 if (ps)
592 {
593 /* If we had a "%s", check the rest. */
594
595 /* now, for the right part: compare everything right from
596 * the "%s" to the final part of oldfile.
597 *
598 * The logic here is as follows:
599 *
600 * - We start reading from the end.
601 * - There must be a match _right_ from the "%s",
602 * thus the i + 2.
603 * - If there was a left hand match, this stuff
604 * must not be counted again. That's done by the
605 * condition (j >= (lmatch ? i : 0)). */
606
607 rmatch = true;
608
609 for (j = mutt_str_len(oldfile) - 1, k = mutt_str_len(nametemplate) - 1;
610 (j >= (lmatch ? i : 0)) && (k >= (i + 2)); j--, k--)
611 {
612 if (nametemplate[k] != oldfile[j])
613 {
614 rmatch = false;
615 break;
616 }
617 }
618
619 /* Now, check if we had a full match. */
620
621 if (k >= i + 2)
622 rmatch = false;
623
624 struct Buffer *left = buf_pool_get();
625 struct Buffer *right = buf_pool_get();
626
627 if (!lmatch)
628 buf_strcpy_n(left, nametemplate, i);
629 if (!rmatch)
630 buf_strcpy(right, nametemplate + i + 2);
631 buf_printf(newfile, "%s%s%s", buf_string(left), oldfile, buf_string(right));
632
633 buf_pool_release(&left);
634 buf_pool_release(&right);
635 }
636 else
637 {
638 /* no "%s" in the name template. */
639 buf_strcpy(newfile, nametemplate);
640 }
641 }
642
643 mutt_adv_mktemp(newfile);
644}
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:416
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:401
size_t buf_copy(struct Buffer *dst, const struct Buffer *src)
Copy a Buffer's contents to another Buffer.
Definition: buffer.c:566
static const char * buf_string(const struct Buffer *buf)
Convert a buffer to a const char * "string".
Definition: buffer.h:90
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.
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:911
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:738
int mutt_file_fclose(FILE **fp)
Close a FILE handle (and NULL the pointer)
Definition: file.c:150
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:1490
#define MUTT_RL_CONT
-continuation
Definition: file.h:40
#define mutt_error(...)
Definition: logging2.h:90
#define mutt_debug(LEVEL,...)
Definition: logging2.h:87
@ LL_DEBUG2
Log at debug level 2.
Definition: logging2.h:44
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:475
void mailcap_entry_free(struct MailcapEntry **ptr)
Deallocate an struct MailcapEntry.
Definition: mailcap.c:446
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
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:437
static char * get_field(char *s)
NUL terminate a RFC1524 field.
Definition: mailcap.c:164
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:544
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: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:342
void buf_sanitize_filename(struct Buffer *buf, const char *path, short slash)
Replace unsafe characters in a filename.
Definition: muttlib.c:1625
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:333
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:52
#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