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