1 /**
2 * @file
3 * Parse and execute user-defined hooks
4 *
5 * @authors
6 * Copyright (C) 1996-2002,2004,2007 Michael R. Elkins <me@mutt.org>, and others
7 * Copyright (C) 2019 Pietro Cerutti <gahr@gahr.ch>
8 *
9 * @copyright
10 * This program is free software: you can redistribute it and/or modify it under
11 * the terms of the GNU General Public License as published by the Free Software
12 * Foundation, either version 2 of the License, or (at your option) any later
13 * version.
14 *
15 * This program is distributed in the hope that it will be useful, but WITHOUT
16 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
17 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
18 * details.
19 *
20 * You should have received a copy of the GNU General Public License along with
21 * this program. If not, see <http://www.gnu.org/licenses/>.
22 */
23
24 /**
25 * @page neo_hook Parse and execute user-defined hooks
26 *
27 * Parse and execute user-defined hooks
28 */
29
30 #include "config.h"
31 #include <limits.h>
32 #include <stdbool.h>
33 #include <stdio.h>
34 #include <string.h>
35 #include <unistd.h>
36 #include "mutt/lib.h"
37 #include "address/lib.h"
38 #include "config/lib.h"
39 #include "email/lib.h"
40 #include "core/lib.h"
41 #include "alias/lib.h"
42 #include "mutt.h"
43 #include "hook.h"
44 #include "attach/lib.h"
45 #include "ncrypt/lib.h"
46 #include "pattern/lib.h"
47 #include "context.h"
48 #include "format_flags.h"
49 #include "hdrline.h"
50 #include "init.h"
51 #include "mutt_globals.h"
52 #include "muttlib.h"
53 #include "mx.h"
54 #ifdef USE_COMP_MBOX
55 #include "compmbox/lib.h"
56 #endif
57
58 /**
59 * struct Hook - A list of user hooks
60 */
61 struct Hook
62 {
63 HookFlags type; ///< Hook type
64 struct Regex regex; ///< Regular expression
65 char *command; ///< Filename, command or pattern to execute
66 struct PatternList *pattern; ///< Used for fcc,save,send-hook
67 TAILQ_ENTRY(Hook) entries; ///< Linked list
68 };
69 TAILQ_HEAD(HookList, Hook);
70
71 static struct HookList Hooks = TAILQ_HEAD_INITIALIZER(Hooks);
72
73 static struct HashTable *IdxFmtHooks = NULL;
74 static HookFlags current_hook_type = MUTT_HOOK_NO_FLAGS;
75
76 /**
77 * mutt_parse_hook - Parse the 'hook' family of commands - Implements Command::parse() - @ingroup command_parse
78 *
79 * This is used by 'account-hook', 'append-hook' and many more.
80 */
mutt_parse_hook(struct Buffer * buf,struct Buffer * s,intptr_t data,struct Buffer * err)81 enum CommandResult mutt_parse_hook(struct Buffer *buf, struct Buffer *s,
82 intptr_t data, struct Buffer *err)
83 {
84 struct Hook *hook = NULL;
85 int rc = MUTT_CMD_ERROR;
86 bool pat_not = false;
87 bool use_regex = true;
88 regex_t *rx = NULL;
89 struct PatternList *pat = NULL;
90 const bool folder_or_mbox = (data & (MUTT_FOLDER_HOOK | MUTT_MBOX_HOOK));
91
92 struct Buffer *cmd = mutt_buffer_pool_get();
93 struct Buffer *pattern = mutt_buffer_pool_get();
94
95 if (~data & MUTT_GLOBAL_HOOK) /* NOT a global hook */
96 {
97 if (*s->dptr == '!')
98 {
99 s->dptr++;
100 SKIPWS(s->dptr);
101 pat_not = true;
102 }
103
104 mutt_extract_token(pattern, s, MUTT_TOKEN_NO_FLAGS);
105 if (folder_or_mbox &&
106 mutt_str_equal(mutt_buffer_string(pattern), "-noregex"))
107 {
108 use_regex = false;
109 if (!MoreArgs(s))
110 {
111 mutt_buffer_printf(err, _("%s: too few arguments"), buf->data);
112 rc = MUTT_CMD_WARNING;
113 goto cleanup;
114 }
115 mutt_extract_token(pattern, s, MUTT_TOKEN_NO_FLAGS);
116 }
117
118 if (!MoreArgs(s))
119 {
120 mutt_buffer_printf(err, _("%s: too few arguments"), buf->data);
121 rc = MUTT_CMD_WARNING;
122 goto cleanup;
123 }
124 }
125
126 mutt_extract_token(cmd, s,
127 (data & (MUTT_FOLDER_HOOK | MUTT_SEND_HOOK | MUTT_SEND2_HOOK |
128 MUTT_ACCOUNT_HOOK | MUTT_REPLY_HOOK)) ?
129 MUTT_TOKEN_SPACE :
130 MUTT_TOKEN_NO_FLAGS);
131
132 if (mutt_buffer_is_empty(cmd))
133 {
134 mutt_buffer_printf(err, _("%s: too few arguments"), buf->data);
135 rc = MUTT_CMD_WARNING;
136 goto cleanup;
137 }
138
139 if (MoreArgs(s))
140 {
141 mutt_buffer_printf(err, _("%s: too many arguments"), buf->data);
142 rc = MUTT_CMD_WARNING;
143 goto cleanup;
144 }
145
146 const char *const c_default_hook =
147 cs_subset_string(NeoMutt->sub, "default_hook");
148 if (folder_or_mbox)
149 {
150 /* Accidentally using the ^ mailbox shortcut in the .neomuttrc is a
151 * common mistake */
152 if ((pattern->data[0] == '^') && !CurrentFolder)
153 {
154 mutt_buffer_strcpy(err, _("current mailbox shortcut '^' is unset"));
155 goto cleanup;
156 }
157
158 struct Buffer *tmp = mutt_buffer_pool_get();
159 mutt_buffer_copy(tmp, pattern);
160 mutt_buffer_expand_path_regex(tmp, use_regex);
161
162 /* Check for other mailbox shortcuts that expand to the empty string.
163 * This is likely a mistake too */
164 if (mutt_buffer_is_empty(tmp) && !mutt_buffer_is_empty(pattern))
165 {
166 mutt_buffer_strcpy(err, _("mailbox shortcut expanded to empty regex"));
167 mutt_buffer_pool_release(&tmp);
168 goto cleanup;
169 }
170
171 if (use_regex)
172 {
173 mutt_buffer_copy(pattern, tmp);
174 }
175 else
176 {
177 mutt_file_sanitize_regex(pattern, mutt_buffer_string(tmp));
178 }
179 mutt_buffer_pool_release(&tmp);
180 }
181 #ifdef USE_COMP_MBOX
182 else if (data & (MUTT_APPEND_HOOK | MUTT_OPEN_HOOK | MUTT_CLOSE_HOOK))
183 {
184 if (mutt_comp_valid_command(mutt_buffer_string(cmd)) == 0)
185 {
186 mutt_buffer_strcpy(err, _("badly formatted command string"));
187 goto cleanup;
188 }
189 }
190 #endif
191 else if (c_default_hook && (~data & MUTT_GLOBAL_HOOK) &&
192 !(data & (MUTT_CHARSET_HOOK | MUTT_ICONV_HOOK | MUTT_ACCOUNT_HOOK)) &&
193 (!WithCrypto || !(data & MUTT_CRYPT_HOOK)))
194 {
195 /* At this stage remain only message-hooks, reply-hooks, send-hooks,
196 * send2-hooks, save-hooks, and fcc-hooks: All those allowing full
197 * patterns. If given a simple regex, we expand $default_hook. */
198 mutt_check_simple(pattern, c_default_hook);
199 }
200
201 if (data & (MUTT_MBOX_HOOK | MUTT_SAVE_HOOK | MUTT_FCC_HOOK))
202 {
203 mutt_buffer_expand_path(cmd);
204 }
205
206 /* check to make sure that a matching hook doesn't already exist */
207 TAILQ_FOREACH(hook, &Hooks, entries)
208 {
209 if (data & MUTT_GLOBAL_HOOK)
210 {
211 /* Ignore duplicate global hooks */
212 if (mutt_str_equal(hook->command, mutt_buffer_string(cmd)))
213 {
214 rc = MUTT_CMD_SUCCESS;
215 goto cleanup;
216 }
217 }
218 else if ((hook->type == data) && (hook->regex.pat_not == pat_not) &&
219 mutt_str_equal(mutt_buffer_string(pattern), hook->regex.pattern))
220 {
221 if (data & (MUTT_FOLDER_HOOK | MUTT_SEND_HOOK | MUTT_SEND2_HOOK | MUTT_MESSAGE_HOOK |
222 MUTT_ACCOUNT_HOOK | MUTT_REPLY_HOOK | MUTT_CRYPT_HOOK |
223 MUTT_TIMEOUT_HOOK | MUTT_STARTUP_HOOK | MUTT_SHUTDOWN_HOOK))
224 {
225 /* these hooks allow multiple commands with the same
226 * pattern, so if we've already seen this pattern/command pair, just
227 * ignore it instead of creating a duplicate */
228 if (mutt_str_equal(hook->command, mutt_buffer_string(cmd)))
229 {
230 rc = MUTT_CMD_SUCCESS;
231 goto cleanup;
232 }
233 }
234 else
235 {
236 /* other hooks only allow one command per pattern, so update the
237 * entry with the new command. this currently does not change the
238 * order of execution of the hooks, which i think is desirable since
239 * a common action to perform is to change the default (.) entry
240 * based upon some other information. */
241 FREE(&hook->command);
242 hook->command = mutt_buffer_strdup(cmd);
243 rc = MUTT_CMD_SUCCESS;
244 goto cleanup;
245 }
246 }
247 }
248
249 if (data & (MUTT_CHARSET_HOOK | MUTT_ICONV_HOOK))
250 {
251 /* These are managed separately by the charset code */
252 enum LookupType type = (data & MUTT_CHARSET_HOOK) ? MUTT_LOOKUP_CHARSET : MUTT_LOOKUP_ICONV;
253 if (mutt_ch_lookup_add(type, mutt_buffer_string(pattern), mutt_buffer_string(cmd), err))
254 rc = MUTT_CMD_SUCCESS;
255 goto cleanup;
256 }
257 else if (data & (MUTT_SEND_HOOK | MUTT_SEND2_HOOK | MUTT_SAVE_HOOK |
258 MUTT_FCC_HOOK | MUTT_MESSAGE_HOOK | MUTT_REPLY_HOOK))
259 {
260 PatternCompFlags comp_flags;
261
262 if (data & (MUTT_SEND2_HOOK))
263 comp_flags = MUTT_PC_SEND_MODE_SEARCH;
264 else if (data & (MUTT_SEND_HOOK | MUTT_FCC_HOOK))
265 comp_flags = MUTT_PC_NO_FLAGS;
266 else
267 comp_flags = MUTT_PC_FULL_MSG;
268
269 pat = mutt_pattern_comp(ctx_mailbox(Context), Context ? Context->menu : NULL,
270 mutt_buffer_string(pattern), comp_flags, err);
271 if (!pat)
272 goto cleanup;
273 }
274 else if (~data & MUTT_GLOBAL_HOOK) /* NOT a global hook */
275 {
276 /* Hooks not allowing full patterns: Check syntax of regex */
277 rx = mutt_mem_malloc(sizeof(regex_t));
278 int rc2 = REG_COMP(rx, NONULL(mutt_buffer_string(pattern)),
279 ((data & MUTT_CRYPT_HOOK) ? REG_ICASE : 0));
280 if (rc2 != 0)
281 {
282 regerror(rc2, rx, err->data, err->dsize);
283 FREE(&rx);
284 goto cleanup;
285 }
286 }
287
288 hook = mutt_mem_calloc(1, sizeof(struct Hook));
289 hook->type = data;
290 hook->command = mutt_buffer_strdup(cmd);
291 hook->pattern = pat;
292 hook->regex.pattern = mutt_buffer_strdup(pattern);
293 hook->regex.regex = rx;
294 hook->regex.pat_not = pat_not;
295 TAILQ_INSERT_TAIL(&Hooks, hook, entries);
296 rc = MUTT_CMD_SUCCESS;
297
298 cleanup:
299 mutt_buffer_pool_release(&cmd);
300 mutt_buffer_pool_release(&pattern);
301 return rc;
302 }
303
304 /**
305 * delete_hook - Delete a Hook
306 * @param h Hook to delete
307 */
delete_hook(struct Hook * h)308 static void delete_hook(struct Hook *h)
309 {
310 FREE(&h->command);
311 FREE(&h->regex.pattern);
312 if (h->regex.regex)
313 {
314 regfree(h->regex.regex);
315 FREE(&h->regex.regex);
316 }
317 mutt_pattern_free(&h->pattern);
318 FREE(&h);
319 }
320
321 /**
322 * mutt_delete_hooks - Delete matching hooks
323 * @param type Hook type to delete, see #HookFlags
324 *
325 * If 0 is passed, all the hooks will be deleted.
326 */
mutt_delete_hooks(HookFlags type)327 void mutt_delete_hooks(HookFlags type)
328 {
329 struct Hook *h = NULL;
330 struct Hook *tmp = NULL;
331
332 TAILQ_FOREACH_SAFE(h, &Hooks, entries, tmp)
333 {
334 if ((type == MUTT_HOOK_NO_FLAGS) || (type == h->type))
335 {
336 TAILQ_REMOVE(&Hooks, h, entries);
337 delete_hook(h);
338 }
339 }
340 }
341
342 /**
343 * idxfmt_hashelem_free - Delete an index-format-hook from the Hash Table - Implements ::hash_hdata_free_t - @ingroup hash_hdata_free_api
344 */
idxfmt_hashelem_free(int type,void * obj,intptr_t data)345 static void idxfmt_hashelem_free(int type, void *obj, intptr_t data)
346 {
347 struct HookList *hl = obj;
348 struct Hook *h = NULL;
349 struct Hook *tmp = NULL;
350
351 TAILQ_FOREACH_SAFE(h, hl, entries, tmp)
352 {
353 TAILQ_REMOVE(hl, h, entries);
354 delete_hook(h);
355 }
356
357 FREE(&hl);
358 }
359
360 /**
361 * delete_idxfmt_hooks - Delete all the index-format-hooks
362 */
delete_idxfmt_hooks(void)363 static void delete_idxfmt_hooks(void)
364 {
365 mutt_hash_free(&IdxFmtHooks);
366 }
367
368 /**
369 * mutt_parse_idxfmt_hook - Parse the 'index-format-hook' command - Implements Command::parse() - @ingroup command_parse
370 */
mutt_parse_idxfmt_hook(struct Buffer * buf,struct Buffer * s,intptr_t data,struct Buffer * err)371 enum CommandResult mutt_parse_idxfmt_hook(struct Buffer *buf, struct Buffer *s,
372 intptr_t data, struct Buffer *err)
373 {
374 enum CommandResult rc = MUTT_CMD_ERROR;
375 bool pat_not = false;
376
377 struct Buffer *name = mutt_buffer_pool_get();
378 struct Buffer *pattern = mutt_buffer_pool_get();
379 struct Buffer *fmtstring = mutt_buffer_pool_get();
380
381 if (!IdxFmtHooks)
382 {
383 IdxFmtHooks = mutt_hash_new(30, MUTT_HASH_STRDUP_KEYS);
384 mutt_hash_set_destructor(IdxFmtHooks, idxfmt_hashelem_free, 0);
385 }
386
387 if (!MoreArgs(s))
388 {
389 mutt_buffer_printf(err, _("%s: too few arguments"), buf->data);
390 goto out;
391 }
392 mutt_extract_token(name, s, MUTT_TOKEN_NO_FLAGS);
393 struct HookList *hl = mutt_hash_find(IdxFmtHooks, mutt_buffer_string(name));
394
395 if (*s->dptr == '!')
396 {
397 s->dptr++;
398 SKIPWS(s->dptr);
399 pat_not = true;
400 }
401 mutt_extract_token(pattern, s, MUTT_TOKEN_NO_FLAGS);
402
403 if (!MoreArgs(s))
404 {
405 mutt_buffer_printf(err, _("%s: too few arguments"), buf->data);
406 goto out;
407 }
408 mutt_extract_token(fmtstring, s, MUTT_TOKEN_NO_FLAGS);
409
410 if (MoreArgs(s))
411 {
412 mutt_buffer_printf(err, _("%s: too many arguments"), buf->data);
413 goto out;
414 }
415
416 const char *const c_default_hook =
417 cs_subset_string(NeoMutt->sub, "default_hook");
418 if (c_default_hook)
419 mutt_check_simple(pattern, c_default_hook);
420
421 /* check to make sure that a matching hook doesn't already exist */
422 struct Hook *hook = NULL;
423 if (hl)
424 {
425 TAILQ_FOREACH(hook, hl, entries)
426 {
427 if ((hook->regex.pat_not == pat_not) &&
428 mutt_str_equal(mutt_buffer_string(pattern), hook->regex.pattern))
429 {
430 mutt_str_replace(&hook->command, mutt_buffer_string(fmtstring));
431 rc = MUTT_CMD_SUCCESS;
432 goto out;
433 }
434 }
435 }
436
437 /* MUTT_PC_PATTERN_DYNAMIC sets so that date ranges are regenerated during
438 * matching. This of course is slower, but index-format-hook is commonly
439 * used for date ranges, and they need to be evaluated relative to "now", not
440 * the hook compilation time. */
441 struct PatternList *pat =
442 mutt_pattern_comp(ctx_mailbox(Context), Context ? Context->menu : NULL,
443 mutt_buffer_string(pattern),
444 MUTT_PC_FULL_MSG | MUTT_PC_PATTERN_DYNAMIC, err);
445 if (!pat)
446 goto out;
447
448 hook = mutt_mem_calloc(1, sizeof(struct Hook));
449 hook->type = MUTT_IDXFMTHOOK;
450 hook->command = mutt_buffer_strdup(fmtstring);
451 hook->pattern = pat;
452 hook->regex.pattern = mutt_buffer_strdup(pattern);
453 hook->regex.regex = NULL;
454 hook->regex.pat_not = pat_not;
455
456 if (!hl)
457 {
458 hl = mutt_mem_calloc(1, sizeof(*hl));
459 TAILQ_INIT(hl);
460 mutt_hash_insert(IdxFmtHooks, mutt_buffer_string(name), hl);
461 }
462
463 TAILQ_INSERT_TAIL(hl, hook, entries);
464 rc = MUTT_CMD_SUCCESS;
465
466 out:
467 mutt_buffer_pool_release(&name);
468 mutt_buffer_pool_release(&pattern);
469 mutt_buffer_pool_release(&fmtstring);
470
471 return rc;
472 }
473
474 /**
475 * mutt_parse_unhook - Parse the 'unhook' command - Implements Command::parse() - @ingroup command_parse
476 */
mutt_parse_unhook(struct Buffer * buf,struct Buffer * s,intptr_t data,struct Buffer * err)477 enum CommandResult mutt_parse_unhook(struct Buffer *buf, struct Buffer *s,
478 intptr_t data, struct Buffer *err)
479 {
480 while (MoreArgs(s))
481 {
482 mutt_extract_token(buf, s, MUTT_TOKEN_NO_FLAGS);
483 if (mutt_str_equal("*", buf->data))
484 {
485 if (current_hook_type != MUTT_TOKEN_NO_FLAGS)
486 {
487 mutt_buffer_printf(err, "%s", _("unhook: Can't do unhook * from within a hook"));
488 return MUTT_CMD_WARNING;
489 }
490 mutt_delete_hooks(MUTT_HOOK_NO_FLAGS);
491 delete_idxfmt_hooks();
492 mutt_ch_lookup_remove();
493 }
494 else
495 {
496 HookFlags type = mutt_get_hook_type(buf->data);
497
498 if (type == MUTT_HOOK_NO_FLAGS)
499 {
500 mutt_buffer_printf(err, _("unhook: unknown hook type: %s"), buf->data);
501 return MUTT_CMD_ERROR;
502 }
503 if (type & (MUTT_CHARSET_HOOK | MUTT_ICONV_HOOK))
504 {
505 mutt_ch_lookup_remove();
506 return MUTT_CMD_SUCCESS;
507 }
508 if (current_hook_type == type)
509 {
510 mutt_buffer_printf(err, _("unhook: Can't delete a %s from within a %s"),
511 buf->data, buf->data);
512 return MUTT_CMD_WARNING;
513 }
514 if (type == MUTT_IDXFMTHOOK)
515 delete_idxfmt_hooks();
516 else
517 mutt_delete_hooks(type);
518 }
519 }
520 return MUTT_CMD_SUCCESS;
521 }
522
523 /**
524 * mutt_folder_hook - Perform a folder hook
525 * @param path Path to potentially match
526 * @param desc Description to potentially match
527 */
mutt_folder_hook(const char * path,const char * desc)528 void mutt_folder_hook(const char *path, const char *desc)
529 {
530 if (!path && !desc)
531 return;
532
533 struct Hook *hook = NULL;
534 struct Buffer *err = mutt_buffer_pool_get();
535
536 current_hook_type = MUTT_FOLDER_HOOK;
537
538 TAILQ_FOREACH(hook, &Hooks, entries)
539 {
540 if (!hook->command)
541 continue;
542
543 if (!(hook->type & MUTT_FOLDER_HOOK))
544 continue;
545
546 const char *match = NULL;
547 if (mutt_regex_match(&hook->regex, path))
548 match = path;
549 else if (mutt_regex_match(&hook->regex, desc))
550 match = desc;
551
552 if (match)
553 {
554 mutt_debug(LL_DEBUG1, "folder-hook '%s' matches '%s'\n", hook->regex.pattern, match);
555 mutt_debug(LL_DEBUG5, " %s\n", hook->command);
556 if (mutt_parse_rc_line(hook->command, err) == MUTT_CMD_ERROR)
557 {
558 mutt_error("%s", mutt_buffer_string(err));
559 break;
560 }
561 }
562 }
563 mutt_buffer_pool_release(&err);
564
565 current_hook_type = MUTT_HOOK_NO_FLAGS;
566 }
567
568 /**
569 * mutt_find_hook - Find a matching hook
570 * @param type Hook type, see #HookFlags
571 * @param pat Pattern to match
572 * @retval ptr Command string
573 *
574 * @note The returned string must not be freed.
575 */
mutt_find_hook(HookFlags type,const char * pat)576 char *mutt_find_hook(HookFlags type, const char *pat)
577 {
578 struct Hook *tmp = NULL;
579
580 TAILQ_FOREACH(tmp, &Hooks, entries)
581 {
582 if (tmp->type & type)
583 {
584 if (mutt_regex_match(&tmp->regex, pat))
585 return tmp->command;
586 }
587 }
588 return NULL;
589 }
590
591 /**
592 * mutt_message_hook - Perform a message hook
593 * @param m Mailbox Context
594 * @param e Email
595 * @param type Hook type, see #HookFlags
596 */
mutt_message_hook(struct Mailbox * m,struct Email * e,HookFlags type)597 void mutt_message_hook(struct Mailbox *m, struct Email *e, HookFlags type)
598 {
599 struct Hook *hook = NULL;
600 struct PatternCache cache = { 0 };
601 struct Buffer *err = mutt_buffer_pool_get();
602
603 current_hook_type = type;
604
605 TAILQ_FOREACH(hook, &Hooks, entries)
606 {
607 if (!hook->command)
608 continue;
609
610 if (hook->type & type)
611 {
612 if ((mutt_pattern_exec(SLIST_FIRST(hook->pattern), 0, m, e, &cache) > 0) ^
613 hook->regex.pat_not)
614 {
615 if (mutt_parse_rc_line(hook->command, err) == MUTT_CMD_ERROR)
616 {
617 mutt_error("%s", mutt_buffer_string(err));
618 current_hook_type = MUTT_HOOK_NO_FLAGS;
619 mutt_buffer_pool_release(&err);
620
621 return;
622 }
623 /* Executing arbitrary commands could affect the pattern results,
624 * so the cache has to be wiped */
625 memset(&cache, 0, sizeof(cache));
626 }
627 }
628 }
629 mutt_buffer_pool_release(&err);
630
631 current_hook_type = MUTT_HOOK_NO_FLAGS;
632 }
633
634 /**
635 * addr_hook - Perform an address hook (get a path)
636 * @param path Buffer for path
637 * @param pathlen Length of buffer
638 * @param type Hook type, see #HookFlags
639 * @param m Mailbox
640 * @param e Email
641 * @retval 0 Success
642 * @retval -1 Failure
643 */
addr_hook(char * path,size_t pathlen,HookFlags type,struct Mailbox * m,struct Email * e)644 static int addr_hook(char *path, size_t pathlen, HookFlags type,
645 struct Mailbox *m, struct Email *e)
646 {
647 struct Hook *hook = NULL;
648 struct PatternCache cache = { 0 };
649
650 /* determine if a matching hook exists */
651 TAILQ_FOREACH(hook, &Hooks, entries)
652 {
653 if (!hook->command)
654 continue;
655
656 if (hook->type & type)
657 {
658 if ((mutt_pattern_exec(SLIST_FIRST(hook->pattern), 0, m, e, &cache) > 0) ^
659 hook->regex.pat_not)
660 {
661 mutt_make_string(path, pathlen, 0, hook->command, m, -1, e, MUTT_FORMAT_PLAIN, NULL);
662 return 0;
663 }
664 }
665 }
666
667 return -1;
668 }
669
670 /**
671 * mutt_default_save - Find the default save path for an email
672 * @param path Buffer for the path
673 * @param pathlen Length of buffer
674 * @param e Email
675 */
mutt_default_save(char * path,size_t pathlen,struct Email * e)676 void mutt_default_save(char *path, size_t pathlen, struct Email *e)
677 {
678 *path = '\0';
679 if (addr_hook(path, pathlen, MUTT_SAVE_HOOK, ctx_mailbox(Context), e) == 0)
680 return;
681
682 struct Envelope *env = e->env;
683 const struct Address *from = TAILQ_FIRST(&env->from);
684 const struct Address *reply_to = TAILQ_FIRST(&env->reply_to);
685 const struct Address *to = TAILQ_FIRST(&env->to);
686 const struct Address *cc = TAILQ_FIRST(&env->cc);
687 const struct Address *addr = NULL;
688 bool from_me = mutt_addr_is_user(from);
689
690 if (!from_me && reply_to && reply_to->mailbox)
691 addr = reply_to;
692 else if (!from_me && from && from->mailbox)
693 addr = from;
694 else if (to && to->mailbox)
695 addr = to;
696 else if (cc && cc->mailbox)
697 addr = cc;
698 else
699 addr = NULL;
700 if (addr)
701 {
702 struct Buffer *tmp = mutt_buffer_pool_get();
703 mutt_safe_path(tmp, addr);
704 snprintf(path, pathlen, "=%s", mutt_buffer_string(tmp));
705 mutt_buffer_pool_release(&tmp);
706 }
707 }
708
709 /**
710 * mutt_select_fcc - Select the FCC path for an email
711 * @param path Buffer for the path
712 * @param e Email
713 */
mutt_select_fcc(struct Buffer * path,struct Email * e)714 void mutt_select_fcc(struct Buffer *path, struct Email *e)
715 {
716 mutt_buffer_alloc(path, PATH_MAX);
717
718 if (addr_hook(path->data, path->dsize, MUTT_FCC_HOOK, NULL, e) != 0)
719 {
720 const struct Address *to = TAILQ_FIRST(&e->env->to);
721 const struct Address *cc = TAILQ_FIRST(&e->env->cc);
722 const struct Address *bcc = TAILQ_FIRST(&e->env->bcc);
723 const bool c_save_name = cs_subset_bool(NeoMutt->sub, "save_name");
724 const bool c_force_name = cs_subset_bool(NeoMutt->sub, "force_name");
725 const char *const c_record = cs_subset_string(NeoMutt->sub, "record");
726 if ((c_save_name || c_force_name) && (to || cc || bcc))
727 {
728 const struct Address *addr = to ? to : (cc ? cc : bcc);
729 struct Buffer *buf = mutt_buffer_pool_get();
730 mutt_safe_path(buf, addr);
731 const char *const c_folder = cs_subset_string(NeoMutt->sub, "folder");
732 mutt_buffer_concat_path(path, NONULL(c_folder), mutt_buffer_string(buf));
733 mutt_buffer_pool_release(&buf);
734 if (!c_force_name && (mx_access(mutt_buffer_string(path), W_OK) != 0))
735 mutt_buffer_strcpy(path, c_record);
736 }
737 else
738 mutt_buffer_strcpy(path, c_record);
739 }
740 else
741 mutt_buffer_fix_dptr(path);
742
743 mutt_buffer_pretty_mailbox(path);
744 }
745
746 /**
747 * list_hook - Find hook strings matching
748 * @param[out] matches List of hook strings
749 * @param[in] match String to match
750 * @param[in] hook Hook type, see #HookFlags
751 */
list_hook(struct ListHead * matches,const char * match,HookFlags hook)752 static void list_hook(struct ListHead *matches, const char *match, HookFlags hook)
753 {
754 struct Hook *tmp = NULL;
755
756 TAILQ_FOREACH(tmp, &Hooks, entries)
757 {
758 if ((tmp->type & hook) && mutt_regex_match(&tmp->regex, match))
759 {
760 mutt_list_insert_tail(matches, mutt_str_dup(tmp->command));
761 }
762 }
763 }
764
765 /**
766 * mutt_crypt_hook - Find crypto hooks for an Address
767 * @param[out] list List of keys
768 * @param[in] addr Address to match
769 *
770 * The crypt-hook associates keys with addresses.
771 */
mutt_crypt_hook(struct ListHead * list,struct Address * addr)772 void mutt_crypt_hook(struct ListHead *list, struct Address *addr)
773 {
774 list_hook(list, addr->mailbox, MUTT_CRYPT_HOOK);
775 }
776
777 /**
778 * mutt_account_hook - Perform an account hook
779 * @param url Account URL to match
780 */
mutt_account_hook(const char * url)781 void mutt_account_hook(const char *url)
782 {
783 /* parsing commands with URLs in an account hook can cause a recursive
784 * call. We just skip processing if this occurs. Typically such commands
785 * belong in a folder-hook -- perhaps we should warn the user. */
786 static bool inhook = false;
787 if (inhook)
788 return;
789
790 struct Hook *hook = NULL;
791 struct Buffer *err = mutt_buffer_pool_get();
792
793 TAILQ_FOREACH(hook, &Hooks, entries)
794 {
795 if (!(hook->command && (hook->type & MUTT_ACCOUNT_HOOK)))
796 continue;
797
798 if (mutt_regex_match(&hook->regex, url))
799 {
800 inhook = true;
801 mutt_debug(LL_DEBUG1, "account-hook '%s' matches '%s'\n", hook->regex.pattern, url);
802 mutt_debug(LL_DEBUG5, " %s\n", hook->command);
803
804 if (mutt_parse_rc_line(hook->command, err) == MUTT_CMD_ERROR)
805 {
806 mutt_error("%s", mutt_buffer_string(err));
807 mutt_buffer_pool_release(&err);
808
809 inhook = false;
810 goto done;
811 }
812
813 inhook = false;
814 }
815 }
816 done:
817 mutt_buffer_pool_release(&err);
818 }
819
820 /**
821 * mutt_timeout_hook - Execute any timeout hooks
822 *
823 * The user can configure hooks to be run on timeout.
824 * This function finds all the matching hooks and executes them.
825 */
mutt_timeout_hook(void)826 void mutt_timeout_hook(void)
827 {
828 struct Hook *hook = NULL;
829 struct Buffer err;
830 char buf[256];
831
832 mutt_buffer_init(&err);
833 err.data = buf;
834 err.dsize = sizeof(buf);
835
836 TAILQ_FOREACH(hook, &Hooks, entries)
837 {
838 if (!(hook->command && (hook->type & MUTT_TIMEOUT_HOOK)))
839 continue;
840
841 if (mutt_parse_rc_line(hook->command, &err) == MUTT_CMD_ERROR)
842 {
843 mutt_error("%s", err.data);
844 mutt_buffer_reset(&err);
845
846 /* The hooks should be independent of each other, so even though this on
847 * failed, we'll carry on with the others. */
848 }
849 }
850
851 /* Delete temporary attachment files */
852 mutt_unlink_temp_attachments();
853 }
854
855 /**
856 * mutt_startup_shutdown_hook - Execute any startup/shutdown hooks
857 * @param type Hook type: #MUTT_STARTUP_HOOK or #MUTT_SHUTDOWN_HOOK
858 *
859 * The user can configure hooks to be run on startup/shutdown.
860 * This function finds all the matching hooks and executes them.
861 */
mutt_startup_shutdown_hook(HookFlags type)862 void mutt_startup_shutdown_hook(HookFlags type)
863 {
864 struct Hook *hook = NULL;
865 struct Buffer err = mutt_buffer_make(0);
866 char buf[256];
867
868 err.data = buf;
869 err.dsize = sizeof(buf);
870
871 TAILQ_FOREACH(hook, &Hooks, entries)
872 {
873 if (!(hook->command && (hook->type & type)))
874 continue;
875
876 if (mutt_parse_rc_line(hook->command, &err) == MUTT_CMD_ERROR)
877 {
878 mutt_error("%s", err.data);
879 mutt_buffer_reset(&err);
880 }
881 }
882 }
883
884 /**
885 * mutt_idxfmt_hook - Get index-format-hook format string
886 * @param name Hook name
887 * @param m Mailbox
888 * @param e Email
889 * @retval ptr printf(3)-like format string
890 * @retval NULL No matching hook
891 */
mutt_idxfmt_hook(const char * name,struct Mailbox * m,struct Email * e)892 const char *mutt_idxfmt_hook(const char *name, struct Mailbox *m, struct Email *e)
893 {
894 if (!IdxFmtHooks)
895 return NULL;
896
897 struct HookList *hl = mutt_hash_find(IdxFmtHooks, name);
898 if (!hl)
899 return NULL;
900
901 current_hook_type = MUTT_IDXFMTHOOK;
902
903 struct PatternCache cache = { 0 };
904 const char *fmtstring = NULL;
905 struct Hook *hook = NULL;
906
907 TAILQ_FOREACH(hook, hl, entries)
908 {
909 struct Pattern *pat = SLIST_FIRST(hook->pattern);
910 if ((mutt_pattern_exec(pat, 0, m, e, &cache) > 0) ^ hook->regex.pat_not)
911 {
912 fmtstring = hook->command;
913 break;
914 }
915 }
916
917 current_hook_type = MUTT_HOOK_NO_FLAGS;
918
919 return fmtstring;
920 }
921