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