NeoMutt  2025-12-11-435-g4ac674
Teaching an old dog new tricks
DOXYGEN
Loading...
Searching...
No Matches
parse.c
Go to the documentation of this file.
1
22
28
29#include "config.h"
30#include <stdbool.h>
31#include <stdint.h>
32#include <stdio.h>
33#include "mutt/lib.h"
34#include "config/lib.h"
35#include "core/lib.h"
36#include "commands/lib.h"
37#include "compmbox/lib.h"
38#include "expando/lib.h"
39#include "index/lib.h"
40#include "parse/lib.h"
41#include "pattern/lib.h"
42#include "globals.h"
43#include "hook.h"
44#include "muttlib.h"
45
46extern const struct ExpandoDefinition IndexFormatDef[];
47
50
52struct HashTable *IdxFmtHooks = NULL;
53
56
64enum CommandResult parse_charset_hook(const struct Command *cmd, struct Buffer *line,
65 const struct ParseContext *pc, struct ParseError *pe)
66{
67 struct Buffer *err = pe->message;
68
69 if (!MoreArgs(line))
70 {
71 buf_printf(err, _("%s: too few arguments"), cmd->name);
72 return MUTT_CMD_WARNING;
73 }
74
75 struct Buffer *alias = buf_pool_get();
76 struct Buffer *charset = buf_pool_get();
78
79 if (parse_extract_token(alias, line, TOKEN_NO_FLAGS) < 0)
80 goto done;
81 if (parse_extract_token(charset, line, TOKEN_NO_FLAGS) < 0)
82 goto done;
83
84 const enum LookupType type = (cmd->id == CMD_ICONV_HOOK) ? MUTT_LOOKUP_ICONV :
86
87 if (buf_is_empty(alias) || buf_is_empty(charset))
88 {
89 buf_printf(err, _("%s: too few arguments"), cmd->name);
91 }
92 else if (MoreArgs(line))
93 {
94 buf_printf(err, _("%s: too many arguments"), cmd->name);
95 buf_reset(line); // clean up buffer to avoid a mess with further rcfile processing
97 }
98 else if (mutt_ch_lookup_add(type, buf_string(alias), buf_string(charset), err))
99 {
100 rc = MUTT_CMD_SUCCESS;
101 }
102
103done:
104 buf_pool_release(&alias);
105 buf_pool_release(&charset);
106
107 return rc;
108}
109
118enum CommandResult parse_global_hook(const struct Command *cmd, struct Buffer *line,
119 const struct ParseContext *pc, struct ParseError *pe)
120{
121 struct Buffer *err = pe->message;
122
123 struct Hook *hook = NULL;
125
126 struct Buffer *command = buf_pool_get();
127
128 // TOKEN_SPACE allows the command to contain whitespace, without quoting
129 parse_extract_token(command, line, TOKEN_SPACE);
130
131 if (buf_is_empty(command))
132 {
133 buf_printf(err, _("%s: too few arguments"), cmd->name);
134 rc = MUTT_CMD_WARNING;
135 goto cleanup;
136 }
137
138 if (MoreArgs(line))
139 {
140 buf_printf(err, _("%s: too many arguments"), cmd->name);
141 rc = MUTT_CMD_WARNING;
142 goto cleanup;
143 }
144
145 /* check to make sure that a matching Hook doesn't already exist */
146 TAILQ_FOREACH(hook, &Hooks, entries)
147 {
148 /* Ignore duplicate global Hooks */
149 if ((hook->id == cmd->id) && mutt_str_equal(hook->command, buf_string(command)))
150 {
151 rc = MUTT_CMD_SUCCESS;
152 goto cleanup;
153 }
154 }
155
156 hook = hook_new();
157 hook->id = cmd->id;
158 hook->command = buf_strdup(command);
160 hook->pattern = NULL;
161 hook->regex.pattern = NULL;
162 hook->regex.regex = NULL;
163 hook->regex.pat_not = false;
164 hook->expando = NULL;
165
166 TAILQ_INSERT_TAIL(&Hooks, hook, entries);
167 rc = MUTT_CMD_SUCCESS;
168
169cleanup:
170 buf_pool_release(&command);
171 return rc;
172}
173
183enum CommandResult parse_pattern_hook(const struct Command *cmd, struct Buffer *line,
184 const struct ParseContext *pc, struct ParseError *pe)
185{
186 struct Buffer *err = pe->message;
187
188 struct Hook *hook = NULL;
190 bool pat_not = false;
191 struct PatternList *pat = NULL;
192
193 struct Buffer *command = buf_pool_get();
194 struct Buffer *pattern = buf_pool_get();
195
196 if (*line->dptr == '!')
197 {
198 line->dptr++;
199 SKIPWS(line->dptr);
200 pat_not = true;
201 }
202
203 parse_extract_token(pattern, line, TOKEN_NO_FLAGS);
204
205 if (!MoreArgs(line))
206 {
207 buf_printf(err, _("%s: too few arguments"), cmd->name);
208 rc = MUTT_CMD_WARNING;
209 goto cleanup;
210 }
211
212 // TOKEN_SPACE allows the command to contain whitespace, without quoting
213 parse_extract_token(command, line, TOKEN_SPACE);
214
215 if (buf_is_empty(command))
216 {
217 buf_printf(err, _("%s: too few arguments"), cmd->name);
218 rc = MUTT_CMD_WARNING;
219 goto cleanup;
220 }
221
222 if (MoreArgs(line))
223 {
224 buf_printf(err, _("%s: too many arguments"), cmd->name);
225 rc = MUTT_CMD_WARNING;
226 goto cleanup;
227 }
228
229 const char *const c_default_hook = cs_subset_string(NeoMutt->sub, "default_hook");
230 if (c_default_hook)
231 {
232 mutt_check_simple(pattern, c_default_hook);
233 }
234
235 /* check to make sure that a matching hook doesn't already exist */
236 TAILQ_FOREACH(hook, &Hooks, entries)
237 {
238 if ((hook->id == cmd->id) && (hook->regex.pat_not == pat_not) &&
239 mutt_str_equal(buf_string(pattern), hook->regex.pattern))
240 {
241 /* these hooks allow multiple commands with the same pattern,
242 * so if we've already seen this pattern/command pair,
243 * just ignore it instead of creating a duplicate */
244 if (mutt_str_equal(hook->command, buf_string(command)))
245 {
246 rc = MUTT_CMD_SUCCESS;
247 goto cleanup;
248 }
249 }
250 }
251
252 PatternCompFlags comp_flags;
253 if (cmd->id == CMD_SEND2_HOOK)
254 comp_flags = MUTT_PC_SEND_MODE_SEARCH;
255 else if (cmd->id == CMD_SEND_HOOK)
256 comp_flags = MUTT_PC_NO_FLAGS;
257 else
258 comp_flags = MUTT_PC_FULL_MSG;
259
260 struct MailboxView *mv_cur = get_current_mailbox_view();
261 pat = mutt_pattern_comp(mv_cur, buf_string(pattern), comp_flags, err);
262 if (!pat)
263 goto cleanup;
264
265 hook = hook_new();
266 hook->id = cmd->id;
267 hook->command = buf_strdup(command);
269 hook->pattern = pat;
271 hook->regex.regex = NULL;
272 hook->regex.pat_not = pat_not;
273 hook->expando = NULL;
274
275 TAILQ_INSERT_TAIL(&Hooks, hook, entries);
276 rc = MUTT_CMD_SUCCESS;
277
278cleanup:
279 buf_pool_release(&command);
281 return rc;
282}
283
294 struct Buffer *pattern, bool pat_not, struct Buffer *err)
295{
296 struct Hook *hook = NULL;
297 struct PatternList *pat = NULL;
298
299 /* check to make sure that a matching hook doesn't already exist */
300 TAILQ_FOREACH(hook, &Hooks, entries)
301 {
302 if ((hook->id == id) && (hook->regex.pat_not == pat_not) &&
303 mutt_str_equal(buf_string(pattern), hook->regex.pattern))
304 {
305 // Update an existing hook
306 FREE(&hook->command);
307 hook->command = buf_strdup(mailbox);
308 FREE(&hook->source_file);
310
311 expando_free(&hook->expando);
312 hook->expando = expando_parse(buf_string(mailbox), IndexFormatDef, err);
313
314 return MUTT_CMD_SUCCESS;
315 }
316 }
317
318 PatternCompFlags comp_flags;
319 if (id == CMD_FCC_HOOK)
320 comp_flags = MUTT_PC_NO_FLAGS;
321 else
322 comp_flags = MUTT_PC_FULL_MSG;
323
324 struct MailboxView *mv_cur = get_current_mailbox_view();
325 pat = mutt_pattern_comp(mv_cur, buf_string(pattern), comp_flags, err);
326 if (!pat)
327 return MUTT_CMD_ERROR;
328
329 struct Expando *exp = expando_parse(buf_string(mailbox), IndexFormatDef, err);
330
331 hook = hook_new();
332 hook->id = id;
333 hook->command = buf_strdup(mailbox);
335 hook->pattern = pat;
336 hook->regex.pattern = buf_strdup(pattern);
337 hook->regex.regex = NULL;
338 hook->regex.pat_not = pat_not;
339 hook->expando = exp;
340
341 TAILQ_INSERT_TAIL(&Hooks, hook, entries);
342 return MUTT_CMD_SUCCESS;
343}
344
353enum CommandResult parse_mailbox_hook(const struct Command *cmd, struct Buffer *line,
354 const struct ParseContext *pc, struct ParseError *pe)
355{
356 struct Buffer *err = pe->message;
357
359 bool pat_not = false;
360
361 struct Buffer *pattern = buf_pool_get();
362 struct Buffer *mailbox = buf_pool_get();
363
364 if (*line->dptr == '!')
365 {
366 line->dptr++;
367 SKIPWS(line->dptr);
368 pat_not = true;
369 }
370
371 parse_extract_token(pattern, line, TOKEN_NO_FLAGS);
372
373 if (!MoreArgs(line))
374 {
375 buf_printf(err, _("%s: too few arguments"), cmd->name);
376 rc = MUTT_CMD_WARNING;
377 goto cleanup;
378 }
379
380 parse_extract_token(mailbox, line, TOKEN_NO_FLAGS);
381
382 if (buf_is_empty(mailbox))
383 {
384 buf_printf(err, _("%s: too few arguments"), cmd->name);
385 rc = MUTT_CMD_WARNING;
386 goto cleanup;
387 }
388
389 if (MoreArgs(line))
390 {
391 buf_printf(err, _("%s: too many arguments"), cmd->name);
392 rc = MUTT_CMD_WARNING;
393 goto cleanup;
394 }
395
396 const char *const c_default_hook = cs_subset_string(NeoMutt->sub, "default_hook");
397 if (c_default_hook)
398 {
399 mutt_check_simple(pattern, c_default_hook);
400 }
401
402 expand_path(mailbox, false);
403
404 if ((cmd->id == CMD_FCC_HOOK) || (cmd->id == CMD_FCC_SAVE_HOOK))
405 {
406 rc = add_mailbox_hook(CMD_FCC_HOOK, mailbox, pattern, pat_not, err);
407 if (rc != MUTT_CMD_SUCCESS)
408 goto cleanup;
409 }
410
411 if ((cmd->id == CMD_SAVE_HOOK) || (cmd->id == CMD_FCC_SAVE_HOOK))
412 {
413 rc = add_mailbox_hook(CMD_SAVE_HOOK, mailbox, pattern, pat_not, err);
414 if (rc != MUTT_CMD_SUCCESS)
415 goto cleanup;
416 }
417
418 rc = MUTT_CMD_SUCCESS;
419
420cleanup:
421 buf_pool_release(&pattern);
422 buf_pool_release(&mailbox);
423 return rc;
424}
425
432enum CommandResult parse_regex_hook(const struct Command *cmd, struct Buffer *line,
433 const struct ParseContext *pc, struct ParseError *pe)
434{
435 struct Buffer *err = pe->message;
436
437 struct Hook *hook = NULL;
439 bool pat_not = false;
440 regex_t *rx = NULL;
441
442 struct Buffer *regex = buf_pool_get();
443 struct Buffer *command = buf_pool_get();
444
445 if (*line->dptr == '!')
446 {
447 line->dptr++;
448 SKIPWS(line->dptr);
449 pat_not = true;
450 }
451
453
454 if (!MoreArgs(line))
455 {
456 buf_printf(err, _("%s: too few arguments"), cmd->name);
457 rc = MUTT_CMD_WARNING;
458 goto cleanup;
459 }
460
461 parse_extract_token(command, line, TOKEN_SPACE);
462
463 if (buf_is_empty(command))
464 {
465 buf_printf(err, _("%s: too few arguments"), cmd->name);
466 rc = MUTT_CMD_WARNING;
467 goto cleanup;
468 }
469
470 if (MoreArgs(line))
471 {
472 buf_printf(err, _("%s: too many arguments"), cmd->name);
473 rc = MUTT_CMD_WARNING;
474 goto cleanup;
475 }
476
477 /* check to make sure that a matching hook doesn't already exist */
478 TAILQ_FOREACH(hook, &Hooks, entries)
479 {
480 if ((hook->id == cmd->id) && (hook->regex.pat_not == pat_not) &&
481 mutt_str_equal(buf_string(regex), hook->regex.pattern))
482 {
483 // Ignore duplicate hooks
484 if (mutt_str_equal(hook->command, buf_string(command)))
485 {
486 rc = MUTT_CMD_SUCCESS;
487 goto cleanup;
488 }
489 }
490 }
491
492 /* Hooks not allowing full patterns: Check syntax of regex */
493 rx = MUTT_MEM_CALLOC(1, regex_t);
494 int rc2 = REG_COMP(rx, buf_string(regex), 0);
495 if (rc2 != 0)
496 {
497 regerror(rc2, rx, err->data, err->dsize);
498 FREE(&rx);
499 goto cleanup;
500 }
501
502 hook = hook_new();
503 hook->id = cmd->id;
504 hook->command = buf_strdup(command);
506 hook->pattern = NULL;
507 hook->regex.pattern = buf_strdup(regex);
508 hook->regex.regex = rx;
509 hook->regex.pat_not = pat_not;
510 hook->expando = NULL;
511
512 TAILQ_INSERT_TAIL(&Hooks, hook, entries);
513 rc = MUTT_CMD_SUCCESS;
514
515cleanup:
516 buf_pool_release(&regex);
517 buf_pool_release(&command);
518 return rc;
519}
520
527enum CommandResult parse_folder_hook(const struct Command *cmd, struct Buffer *line,
528 const struct ParseContext *pc, struct ParseError *pe)
529{
530 struct Buffer *err = pe->message;
531
532 struct Hook *hook = NULL;
534 bool pat_not = false;
535 bool use_regex = true;
536 regex_t *rx = NULL;
537
538 struct Buffer *regex = buf_pool_get();
539 struct Buffer *command = buf_pool_get();
540
541 if (*line->dptr == '!')
542 {
543 line->dptr++;
544 SKIPWS(line->dptr);
545 pat_not = true;
546 }
547
549 if (mutt_str_equal(buf_string(regex), "-noregex"))
550 {
551 use_regex = false;
552 if (!MoreArgs(line))
553 {
554 buf_printf(err, _("%s: too few arguments"), cmd->name);
555 rc = MUTT_CMD_WARNING;
556 goto cleanup;
557 }
559 }
560
561 if (!MoreArgs(line))
562 {
563 buf_printf(err, _("%s: too few arguments"), cmd->name);
564 rc = MUTT_CMD_WARNING;
565 goto cleanup;
566 }
567
568 parse_extract_token(command, line, TOKEN_SPACE);
569
570 if (buf_is_empty(command))
571 {
572 buf_printf(err, _("%s: too few arguments"), cmd->name);
573 rc = MUTT_CMD_WARNING;
574 goto cleanup;
575 }
576
577 if (MoreArgs(line))
578 {
579 buf_printf(err, _("%s: too many arguments"), cmd->name);
580 rc = MUTT_CMD_WARNING;
581 goto cleanup;
582 }
583
584 /* Accidentally using the ^ mailbox shortcut in the .neomuttrc is a
585 * common mistake */
586 if ((buf_at(regex, 0) == '^') && !CurrentFolder)
587 {
588 buf_strcpy(err, _("current mailbox shortcut '^' is unset"));
589 goto cleanup;
590 }
591
592 struct Buffer *tmp = buf_pool_get();
593 buf_copy(tmp, regex);
594 expand_path(tmp, use_regex);
595
596 /* Check for other mailbox shortcuts that expand to the empty string.
597 * This is likely a mistake too */
598 if (buf_is_empty(tmp) && !buf_is_empty(regex))
599 {
600 buf_strcpy(err, _("mailbox shortcut expanded to empty regex"));
601 buf_pool_release(&tmp);
602 goto cleanup;
603 }
604
605 if (use_regex)
606 {
607 buf_copy(regex, tmp);
608 }
609 else
610 {
612 }
613 buf_pool_release(&tmp);
614
615 /* check to make sure that a matching hook doesn't already exist */
616 TAILQ_FOREACH(hook, &Hooks, entries)
617 {
618 if ((hook->id == cmd->id) && (hook->regex.pat_not == pat_not) &&
619 mutt_str_equal(buf_string(regex), hook->regex.pattern))
620 {
621 // Ignore duplicate hooks
622 if (mutt_str_equal(hook->command, buf_string(command)))
623 {
624 rc = MUTT_CMD_SUCCESS;
625 goto cleanup;
626 }
627 }
628 }
629
630 /* Hooks not allowing full patterns: Check syntax of regex */
631 rx = MUTT_MEM_CALLOC(1, regex_t);
632 int rc2 = REG_COMP(rx, buf_string(regex), 0);
633 if (rc2 != 0)
634 {
635 regerror(rc2, rx, err->data, err->dsize);
636 FREE(&rx);
637 goto cleanup;
638 }
639
640 hook = hook_new();
641 hook->id = cmd->id;
642 hook->command = buf_strdup(command);
644 hook->pattern = NULL;
645 hook->regex.pattern = buf_strdup(regex);
646 hook->regex.regex = rx;
647 hook->regex.pat_not = pat_not;
648 hook->expando = NULL;
649
650 TAILQ_INSERT_TAIL(&Hooks, hook, entries);
651 rc = MUTT_CMD_SUCCESS;
652
653cleanup:
654 buf_pool_release(&regex);
655 buf_pool_release(&command);
656 return rc;
657}
658
666enum CommandResult parse_crypt_hook(const struct Command *cmd, struct Buffer *line,
667 const struct ParseContext *pc, struct ParseError *pe)
668{
669 struct Buffer *err = pe->message;
670
671 struct Hook *hook = NULL;
673 bool pat_not = false;
674 regex_t *rx = NULL;
675
676 struct Buffer *regex = buf_pool_get();
677 struct Buffer *keyid = buf_pool_get();
678
679 if (*line->dptr == '!')
680 {
681 line->dptr++;
682 SKIPWS(line->dptr);
683 pat_not = true;
684 }
685
687
688 if (!MoreArgs(line))
689 {
690 buf_printf(err, _("%s: too few arguments"), cmd->name);
691 rc = MUTT_CMD_WARNING;
692 goto cleanup;
693 }
694
696
697 if (buf_is_empty(keyid))
698 {
699 buf_printf(err, _("%s: too few arguments"), cmd->name);
700 rc = MUTT_CMD_WARNING;
701 goto cleanup;
702 }
703
704 if (MoreArgs(line))
705 {
706 buf_printf(err, _("%s: too many arguments"), cmd->name);
707 rc = MUTT_CMD_WARNING;
708 goto cleanup;
709 }
710
711 /* check to make sure that a matching hook doesn't already exist */
712 TAILQ_FOREACH(hook, &Hooks, entries)
713 {
714 if ((hook->id == cmd->id) && (hook->regex.pat_not == pat_not) &&
715 mutt_str_equal(buf_string(regex), hook->regex.pattern))
716 {
717 // Ignore duplicate hooks
718 if (mutt_str_equal(hook->command, buf_string(keyid)))
719 {
720 rc = MUTT_CMD_SUCCESS;
721 goto cleanup;
722 }
723 }
724 }
725
726 /* Hooks not allowing full patterns: Check syntax of regex */
727 rx = MUTT_MEM_CALLOC(1, regex_t);
728 int rc2 = REG_COMP(rx, buf_string(regex), REG_ICASE);
729 if (rc2 != 0)
730 {
731 regerror(rc2, rx, err->data, err->dsize);
732 FREE(&rx);
733 goto cleanup;
734 }
735
736 hook = hook_new();
737 hook->id = cmd->id;
738 hook->command = buf_strdup(keyid);
740 hook->pattern = NULL;
741 hook->regex.pattern = buf_strdup(regex);
742 hook->regex.regex = rx;
743 hook->regex.pat_not = pat_not;
744 hook->expando = NULL;
745
746 TAILQ_INSERT_TAIL(&Hooks, hook, entries);
747 rc = MUTT_CMD_SUCCESS;
748
749cleanup:
750 buf_pool_release(&regex);
751 buf_pool_release(&keyid);
752 return rc;
753}
754
761enum CommandResult parse_mbox_hook(const struct Command *cmd, struct Buffer *line,
762 const struct ParseContext *pc, struct ParseError *pe)
763{
764 struct Buffer *err = pe->message;
765
766 struct Hook *hook = NULL;
768 bool pat_not = false;
769 bool use_regex = true;
770 regex_t *rx = NULL;
771
772 struct Buffer *regex = buf_pool_get();
773 struct Buffer *command = buf_pool_get();
774
775 if (*line->dptr == '!')
776 {
777 line->dptr++;
778 SKIPWS(line->dptr);
779 pat_not = true;
780 }
781
783 if (mutt_str_equal(buf_string(regex), "-noregex"))
784 {
785 use_regex = false;
786 if (!MoreArgs(line))
787 {
788 buf_printf(err, _("%s: too few arguments"), cmd->name);
789 rc = MUTT_CMD_WARNING;
790 goto cleanup;
791 }
793 }
794
795 if (!MoreArgs(line))
796 {
797 buf_printf(err, _("%s: too few arguments"), cmd->name);
798 rc = MUTT_CMD_WARNING;
799 goto cleanup;
800 }
801
802 parse_extract_token(command, line, TOKEN_NO_FLAGS);
803
804 if (buf_is_empty(command))
805 {
806 buf_printf(err, _("%s: too few arguments"), cmd->name);
807 rc = MUTT_CMD_WARNING;
808 goto cleanup;
809 }
810
811 if (MoreArgs(line))
812 {
813 buf_printf(err, _("%s: too many arguments"), cmd->name);
814 rc = MUTT_CMD_WARNING;
815 goto cleanup;
816 }
817
818 /* Accidentally using the ^ mailbox shortcut in the .neomuttrc is a
819 * common mistake */
820 if ((buf_at(regex, 0) == '^') && !CurrentFolder)
821 {
822 buf_strcpy(err, _("current mailbox shortcut '^' is unset"));
823 goto cleanup;
824 }
825
826 struct Buffer *tmp = buf_pool_get();
827 buf_copy(tmp, regex);
828 expand_path(tmp, use_regex);
829
830 /* Check for other mailbox shortcuts that expand to the empty string.
831 * This is likely a mistake too */
832 if (buf_is_empty(tmp) && !buf_is_empty(regex))
833 {
834 buf_strcpy(err, _("mailbox shortcut expanded to empty regex"));
835 buf_pool_release(&tmp);
836 goto cleanup;
837 }
838
839 if (use_regex)
840 {
841 buf_copy(regex, tmp);
842 }
843 else
844 {
846 }
847 buf_pool_release(&tmp);
848
849 expand_path(command, false);
850
851 /* check to make sure that a matching hook doesn't already exist */
852 TAILQ_FOREACH(hook, &Hooks, entries)
853 {
854 if ((hook->id == cmd->id) && (hook->regex.pat_not == pat_not) &&
855 mutt_str_equal(buf_string(regex), hook->regex.pattern))
856 {
857 // Update an existing hook
858 FREE(&hook->command);
859 hook->command = buf_strdup(command);
860 FREE(&hook->source_file);
862
863 expando_free(&hook->expando);
864 hook->expando = expando_parse(buf_string(command), IndexFormatDef, err);
865
866 rc = MUTT_CMD_SUCCESS;
867 goto cleanup;
868 }
869 }
870
871 /* Hooks not allowing full patterns: Check syntax of regex */
872 rx = MUTT_MEM_CALLOC(1, regex_t);
873 int rc2 = REG_COMP(rx, buf_string(regex), 0);
874 if (rc2 != 0)
875 {
876 regerror(rc2, rx, err->data, err->dsize);
877 FREE(&rx);
878 goto cleanup;
879 }
880
881 struct Expando *exp = expando_parse(buf_string(command), IndexFormatDef, err);
882
883 hook = hook_new();
884 hook->id = cmd->id;
885 hook->command = buf_strdup(command);
887 hook->pattern = NULL;
888 hook->regex.pattern = buf_strdup(regex);
889 hook->regex.regex = rx;
890 hook->regex.pat_not = pat_not;
891 hook->expando = exp;
892
893 TAILQ_INSERT_TAIL(&Hooks, hook, entries);
894 rc = MUTT_CMD_SUCCESS;
895
896cleanup:
897 buf_pool_release(&regex);
898 buf_pool_release(&command);
899 return rc;
900}
901
910enum CommandResult parse_compress_hook(const struct Command *cmd, struct Buffer *line,
911 const struct ParseContext *pc, struct ParseError *pe)
912{
913 struct Buffer *err = pe->message;
914
915 struct Hook *hook = NULL;
917 bool pat_not = false;
918 regex_t *rx = NULL;
919
920 struct Buffer *regex = buf_pool_get();
921 struct Buffer *command = buf_pool_get();
922
923 if (*line->dptr == '!')
924 {
925 line->dptr++;
926 SKIPWS(line->dptr);
927 pat_not = true;
928 }
929
931
932 if (!MoreArgs(line))
933 {
934 buf_printf(err, _("%s: too few arguments"), cmd->name);
935 rc = MUTT_CMD_WARNING;
936 goto cleanup;
937 }
938
939 // TOKEN_SPACE allows the command to contain whitespace, without quoting
940 parse_extract_token(command, line, TOKEN_SPACE);
941
942 if (buf_is_empty(command))
943 {
944 buf_printf(err, _("%s: too few arguments"), cmd->name);
945 rc = MUTT_CMD_WARNING;
946 goto cleanup;
947 }
948
949 if (MoreArgs(line))
950 {
951 buf_printf(err, _("%s: too many arguments"), cmd->name);
952 rc = MUTT_CMD_WARNING;
953 goto cleanup;
954 }
955
956 if (mutt_comp_valid_command(buf_string(command)) == 0)
957 {
958 buf_strcpy(err, _("badly formatted command string"));
959 goto cleanup;
960 }
961
962 /* check to make sure that a matching hook doesn't already exist */
963 TAILQ_FOREACH(hook, &Hooks, entries)
964 {
965 if ((hook->id == cmd->id) && (hook->regex.pat_not == pat_not) &&
966 mutt_str_equal(buf_string(regex), hook->regex.pattern))
967 {
968 // Update an existing hook
969 FREE(&hook->command);
970 hook->command = buf_strdup(command);
971 FREE(&hook->source_file);
973
974 rc = MUTT_CMD_SUCCESS;
975 goto cleanup;
976 }
977 }
978
979 /* Hooks not allowing full patterns: Check syntax of regex */
980 rx = MUTT_MEM_CALLOC(1, regex_t);
981 int rc2 = REG_COMP(rx, buf_string(regex), 0);
982 if (rc2 != 0)
983 {
984 regerror(rc2, rx, err->data, err->dsize);
985 FREE(&rx);
986 goto cleanup;
987 }
988
989 hook = hook_new();
990 hook->id = cmd->id;
991 hook->command = buf_strdup(command);
993 hook->pattern = NULL;
994 hook->regex.pattern = buf_strdup(regex);
995 hook->regex.regex = rx;
996 hook->regex.pat_not = pat_not;
997 hook->expando = NULL;
998
999 TAILQ_INSERT_TAIL(&Hooks, hook, entries);
1000 rc = MUTT_CMD_SUCCESS;
1001
1002cleanup:
1003 buf_pool_release(&regex);
1004 buf_pool_release(&command);
1005 return rc;
1006}
1007
1015{
1016 struct Hook *h = NULL;
1017 struct Hook *tmp = NULL;
1018
1019 TAILQ_FOREACH_SAFE(h, &Hooks, entries, tmp)
1020 {
1021 if ((id == CMD_NONE) || (id == h->id))
1022 {
1023 TAILQ_REMOVE(&Hooks, h, entries);
1024 hook_free(&h);
1025 }
1026 }
1027}
1028
1032static void idxfmt_hashelem_free(int type, void *obj, intptr_t data)
1033{
1034 struct HookList *hl = obj;
1035 struct Hook *h = NULL;
1036 struct Hook *tmp = NULL;
1037
1038 TAILQ_FOREACH_SAFE(h, hl, entries, tmp)
1039 {
1040 TAILQ_REMOVE(hl, h, entries);
1041 hook_free(&h);
1042 }
1043
1044 FREE(&hl);
1045}
1046
1050static void delete_idxfmt_hooks(void)
1051{
1053}
1054
1061enum CommandResult parse_index_hook(const struct Command *cmd, struct Buffer *line,
1062 const struct ParseContext *pc, struct ParseError *pe)
1063{
1064 struct Buffer *err = pe->message;
1065
1066 if (!MoreArgs(line))
1067 {
1068 buf_printf(err, _("%s: too few arguments"), cmd->name);
1069 return MUTT_CMD_WARNING;
1070 }
1071
1072 enum CommandResult rc = MUTT_CMD_ERROR;
1073 bool pat_not = false;
1074
1075 struct Buffer *name = buf_pool_get();
1076 struct Buffer *pattern = buf_pool_get();
1077 struct Buffer *fmt = buf_pool_get();
1078 struct Expando *exp = NULL;
1079
1080 if (!IdxFmtHooks)
1081 {
1084 }
1085
1087 struct HookList *hl = mutt_hash_find(IdxFmtHooks, buf_string(name));
1088
1089 if (*line->dptr == '!')
1090 {
1091 line->dptr++;
1092 SKIPWS(line->dptr);
1093 pat_not = true;
1094 }
1095 parse_extract_token(pattern, line, TOKEN_NO_FLAGS);
1096
1097 if (!MoreArgs(line))
1098 {
1099 buf_printf(err, _("%s: too few arguments"), cmd->name);
1100 goto out;
1101 }
1103
1104 exp = expando_parse(buf_string(fmt), IndexFormatDef, err);
1105 if (!exp)
1106 goto out;
1107
1108 if (MoreArgs(line))
1109 {
1110 buf_printf(err, _("%s: too many arguments"), cmd->name);
1111 goto out;
1112 }
1113
1114 const char *const c_default_hook = cs_subset_string(NeoMutt->sub, "default_hook");
1115 if (c_default_hook)
1116 mutt_check_simple(pattern, c_default_hook);
1117
1118 /* check to make sure that a matching hook doesn't already exist */
1119 struct Hook *hook = NULL;
1120 if (hl)
1121 {
1122 TAILQ_FOREACH(hook, hl, entries)
1123 {
1124 // Update an existing hook
1125 if ((hook->regex.pat_not == pat_not) &&
1127 {
1128 expando_free(&hook->expando);
1129 hook->expando = exp;
1130 exp = NULL;
1131 rc = MUTT_CMD_SUCCESS;
1132 goto out;
1133 }
1134 }
1135 }
1136
1137 /* MUTT_PC_PATTERN_DYNAMIC sets so that date ranges are regenerated during
1138 * matching. This of course is slower, but index-format-hook is commonly
1139 * used for date ranges, and they need to be evaluated relative to "now", not
1140 * the hook compilation time. */
1141 struct MailboxView *mv_cur = get_current_mailbox_view();
1142 struct PatternList *pat = mutt_pattern_comp(mv_cur, buf_string(pattern),
1144 err);
1145 if (!pat)
1146 goto out;
1147
1148 hook = hook_new();
1149 hook->id = CMD_INDEX_FORMAT_HOOK;
1150 hook->command = NULL;
1152 hook->pattern = pat;
1153 hook->regex.pattern = buf_strdup(pattern);
1154 hook->regex.regex = NULL;
1155 hook->regex.pat_not = pat_not;
1156 hook->expando = exp;
1157 exp = NULL;
1158
1159 if (!hl)
1160 {
1161 hl = MUTT_MEM_CALLOC(1, struct HookList);
1162 TAILQ_INIT(hl);
1164 }
1165
1166 TAILQ_INSERT_TAIL(hl, hook, entries);
1167 rc = MUTT_CMD_SUCCESS;
1168
1169out:
1170 buf_pool_release(&name);
1171 buf_pool_release(&pattern);
1172 buf_pool_release(&fmt);
1173 expando_free(&exp);
1174
1175 return rc;
1176}
1177
1184enum CommandResult parse_unhook(const struct Command *cmd, struct Buffer *line,
1185 const struct ParseContext *pc, struct ParseError *pe)
1186{
1187 struct Buffer *err = pe->message;
1188
1189 if (!MoreArgs(line))
1190 {
1191 buf_printf(err, _("%s: too few arguments"), cmd->name);
1192 return MUTT_CMD_WARNING;
1193 }
1194
1195 struct Buffer *token = buf_pool_get();
1197
1198 while (MoreArgs(line))
1199 {
1200 parse_extract_token(token, line, TOKEN_NO_FLAGS);
1201 if (mutt_str_equal("*", buf_string(token)))
1202 {
1204 {
1205 buf_addstr(err, _("unhook: Can't do unhook * from within a hook"));
1206 goto done;
1207 }
1211 }
1212 else
1213 {
1214 const struct Command *hook = command_find_by_name(&NeoMutt->commands,
1215 buf_string(token));
1216 if (!hook)
1217 {
1218 buf_printf(err, _("unhook: unknown hook type: %s"), buf_string(token));
1219 rc = MUTT_CMD_ERROR;
1220 goto done;
1221 }
1222
1223 if ((hook->id == CMD_CHARSET_HOOK) || (hook->id == CMD_ICONV_HOOK))
1224 {
1226 rc = MUTT_CMD_SUCCESS;
1227 goto done;
1228 }
1229 if (CurrentHookId == hook->id)
1230 {
1231 buf_printf(err, _("unhook: Can't delete a %s from within a %s"),
1232 buf_string(token), buf_string(token));
1233 rc = MUTT_CMD_WARNING;
1234 goto done;
1235 }
1236 if (hook->id == CMD_INDEX_FORMAT_HOOK)
1238 else
1239 mutt_delete_hooks(hook->id);
1240 }
1241 }
1242
1243 rc = MUTT_CMD_SUCCESS;
1244
1245done:
1246 buf_pool_release(&token);
1247 return rc;
1248}
const struct ExpandoDefinition IndexFormatDef[]
Expando definitions.
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
bool buf_is_empty(const struct Buffer *buf)
Is the Buffer empty?
Definition buffer.c:291
char buf_at(const struct Buffer *buf, size_t offset)
Return the character at the given offset.
Definition buffer.c:668
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
char * buf_strdup(const struct Buffer *buf)
Copy a Buffer's string.
Definition buffer.c:571
static const char * buf_string(const struct Buffer *buf)
Convert a buffer to a const char * "string".
Definition buffer.h:96
CommandId
ID of Command.
Definition command.h:58
@ CMD_SEND_HOOK
:send-hook
Definition command.h:107
@ CMD_FCC_SAVE_HOOK
:fcc-save-hook
Definition command.h:76
@ CMD_ICONV_HOOK
:iconv-hook
Definition command.h:82
@ CMD_INDEX_FORMAT_HOOK
:index-format-hook
Definition command.h:86
@ CMD_FCC_HOOK
:fcc-hook
Definition command.h:75
@ CMD_SEND2_HOOK
:send2-hook
Definition command.h:106
@ CMD_CHARSET_HOOK
:charset-hook
Definition command.h:69
@ CMD_NONE
No Command.
Definition command.h:59
@ CMD_SAVE_HOOK
:save-hook
Definition command.h:104
CommandResult
Error codes for command_t parse functions.
Definition command.h:37
@ MUTT_CMD_SUCCESS
Success: Command worked.
Definition command.h:40
@ MUTT_CMD_ERROR
Error: Can't help the user.
Definition command.h:38
@ MUTT_CMD_WARNING
Warning: Help given to the user.
Definition command.h:39
const struct Command * command_find_by_name(const struct CommandArray *ca, const char *name)
Find a NeoMutt Command by its name.
Definition commands.c:171
NeoMutt Commands.
struct PatternList * mutt_pattern_comp(struct MailboxView *mv, const char *s, PatternCompFlags flags, struct Buffer *err)
Create a Pattern.
Definition compile.c:954
int mutt_comp_valid_command(const char *cmd)
Is this command string allowed?
Definition compress.c:383
Compressed mbox local mailbox type.
const char * cs_subset_string(const struct ConfigSubset *sub, const char *name)
Get a string config item by name.
Definition helpers.c:291
Convenience wrapper for the config headers.
Convenience wrapper for the core headers.
struct Expando * expando_parse(const char *str, const struct ExpandoDefinition *defs, struct Buffer *err)
Parse an Expando string.
Definition expando.c:81
void expando_free(struct Expando **ptr)
Free an Expando object.
Definition expando.c:61
Parse Expando string.
int parse_extract_token(struct Buffer *dest, struct Buffer *line, TokenFlags flags)
Extract one token from a string.
Definition extract.c:49
#define TOKEN_SPACE
Don't treat whitespace as a term.
Definition extract.h:48
#define MoreArgs(buf)
Definition extract.h:31
#define TOKEN_NO_FLAGS
No flags are set.
Definition extract.h:45
int mutt_file_sanitize_regex(struct Buffer *dest, const char *src)
Escape any regex-magic characters in a string.
Definition file.c:628
char * CurrentFolder
Currently selected mailbox.
Definition globals.c:38
Global variables.
enum CommandResult parse_pattern_hook(const struct Command *cmd, struct Buffer *line, const struct ParseContext *pc, struct ParseError *pe)
Parse pattern-based Hook commands - Implements Command::parse() -.
Definition parse.c:183
enum CommandResult parse_folder_hook(const struct Command *cmd, struct Buffer *line, const struct ParseContext *pc, struct ParseError *pe)
Parse folder hook command - Implements Command::parse() -.
Definition parse.c:527
enum CommandResult parse_compress_hook(const struct Command *cmd, struct Buffer *line, const struct ParseContext *pc, struct ParseError *pe)
Parse compress hook commands - Implements Command::parse() -.
Definition parse.c:910
enum CommandResult parse_regex_hook(const struct Command *cmd, struct Buffer *line, const struct ParseContext *pc, struct ParseError *pe)
Parse regex-based hook command - Implements Command::parse() -.
Definition parse.c:432
enum CommandResult parse_unhook(const struct Command *cmd, struct Buffer *line, const struct ParseContext *pc, struct ParseError *pe)
Parse the unhook command - Implements Command::parse() -.
Definition parse.c:1184
enum CommandResult parse_crypt_hook(const struct Command *cmd, struct Buffer *line, const struct ParseContext *pc, struct ParseError *pe)
Parse crypt hook commands - Implements Command::parse() -.
Definition parse.c:666
enum CommandResult parse_mailbox_hook(const struct Command *cmd, struct Buffer *line, const struct ParseContext *pc, struct ParseError *pe)
Parse mailbox pattern hook commands - Implements Command::parse() -.
Definition parse.c:353
enum CommandResult parse_charset_hook(const struct Command *cmd, struct Buffer *line, const struct ParseContext *pc, struct ParseError *pe)
Parse charset Hook commands - Implements Command::parse() -.
Definition parse.c:64
enum CommandResult parse_mbox_hook(const struct Command *cmd, struct Buffer *line, const struct ParseContext *pc, struct ParseError *pe)
Parse mbox hook command - Implements Command::parse() -.
Definition parse.c:761
enum CommandResult parse_global_hook(const struct Command *cmd, struct Buffer *line, const struct ParseContext *pc, struct ParseError *pe)
Parse global Hook commands - Implements Command::parse() -.
Definition parse.c:118
enum CommandResult parse_index_hook(const struct Command *cmd, struct Buffer *line, const struct ParseContext *pc, struct ParseError *pe)
Parse the index format hook command - Implements Command::parse() -.
Definition parse.c:1061
static void idxfmt_hashelem_free(int type, void *obj, intptr_t data)
Free our hash table data - Implements hash_hdata_free_t -.
Definition parse.c:1032
struct HashElem * mutt_hash_insert(struct HashTable *table, const char *strkey, void *data)
Add a new element to the Hash Table (with string keys)
Definition hash.c:337
void * mutt_hash_find(const struct HashTable *table, const char *strkey)
Find the HashElem data in a Hash Table element using a key.
Definition hash.c:364
struct HashTable * mutt_hash_new(size_t num_elems, HashFlags flags)
Create a new Hash Table (with string keys)
Definition hash.c:261
void mutt_hash_set_destructor(struct HashTable *table, hash_hdata_free_t fn, intptr_t fn_data)
Set the destructor for a Hash Table.
Definition hash.c:303
void mutt_hash_free(struct HashTable **ptr)
Free a hash table.
Definition hash.c:459
#define MUTT_HASH_STRDUP_KEYS
make a copy of the keys
Definition hash.h:113
void hook_free(struct Hook **ptr)
Free a Hook.
Definition hook.c:47
struct Hook * hook_new(void)
Create a Hook.
Definition hook.c:71
User-defined Hooks.
enum CommandId CurrentHookId
The ID of the Hook currently being executed, e.g. CMD_SAVE_HOOK.
Definition parse.c:55
struct HashTable * IdxFmtHooks
All Index Format hooks.
Definition parse.c:52
void mutt_delete_hooks(enum CommandId id)
Delete matching hooks.
Definition parse.c:1014
static void delete_idxfmt_hooks(void)
Delete all the index-format-hooks.
Definition parse.c:1050
enum CommandResult add_mailbox_hook(enum CommandId id, struct Buffer *mailbox, struct Buffer *pattern, bool pat_not, struct Buffer *err)
Add a Mailbox Hook.
Definition parse.c:293
struct HookList Hooks
All simple hooks, e.g. CMD_FOLDER_HOOK.
Definition parse.c:49
GUI manage the main index (list of emails)
struct MailboxView * get_current_mailbox_view(void)
Get the current Mailbox view.
Definition index.c:689
#define FREE(x)
Free memory and set the pointer to NULL.
Definition memory.h:68
#define MUTT_MEM_CALLOC(n, type)
Definition memory.h:52
void mutt_ch_lookup_remove(void)
Remove all the character set lookups.
Definition charset.c:527
bool mutt_ch_lookup_add(enum LookupType type, const char *pat, const char *replace, struct Buffer *err)
Add a new character set lookup.
Definition charset.c:495
LookupType
Types of character set lookups.
Definition charset.h:61
@ MUTT_LOOKUP_ICONV
Character set conversion.
Definition charset.h:63
@ MUTT_LOOKUP_CHARSET
Alias for another character set.
Definition charset.h:62
Convenience wrapper for the library headers.
#define _(a)
Definition message.h:28
bool mutt_str_equal(const char *a, const char *b)
Compare two strings.
Definition string.c:662
void expand_path(struct Buffer *buf, bool regex)
Create the canonical path.
Definition muttlib.c:121
Some miscellaneous functions.
Text parsing functions.
Match patterns to emails.
#define MUTT_PC_SEND_MODE_SEARCH
Allow send-mode body searching.
Definition lib.h:72
uint8_t PatternCompFlags
Flags for mutt_pattern_comp(), e.g. MUTT_PC_FULL_MSG.
Definition lib.h:68
#define MUTT_PC_FULL_MSG
Enable body and header matching.
Definition lib.h:70
void mutt_check_simple(struct Buffer *s, const char *simple)
Convert a simple search into a real request.
Definition pattern.c:107
#define MUTT_PC_NO_FLAGS
No flags are set.
Definition lib.h:69
#define MUTT_PC_PATTERN_DYNAMIC
Enable runtime date range evaluation.
Definition lib.h:71
struct Buffer * buf_pool_get(void)
Get a Buffer from the pool.
Definition pool.c:91
void buf_pool_release(struct Buffer **ptr)
Return a Buffer to the pool.
Definition pool.c:111
#define TAILQ_FOREACH(var, head, field)
Definition queue.h:782
#define TAILQ_FOREACH_SAFE(var, head, field, tvar)
Definition queue.h:792
#define TAILQ_INIT(head)
Definition queue.h:822
#define TAILQ_INSERT_TAIL(head, elm, field)
Definition queue.h:866
#define TAILQ_REMOVE(head, elm, field)
Definition queue.h:901
#define TAILQ_HEAD_INITIALIZER(head)
Definition queue.h:694
#define REG_COMP(preg, regex, cflags)
Compile a regular expression.
Definition regex3.h:49
char * mutt_get_sourced_cwd(void)
Get the current file path that is being parsed.
Definition source.c:314
#define SKIPWS(ch)
Definition string2.h:52
String manipulation buffer.
Definition buffer.h:36
char * dptr
Current read/write position.
Definition buffer.h:38
size_t dsize
Length of data.
Definition buffer.h:39
char * data
Pointer to data.
Definition buffer.h:37
const char * name
Name of the Command.
Definition command.h:159
enum CommandId id
ID of the Command.
Definition command.h:160
Definition of a format string.
Definition definition.h:43
Parsed Expando trees.
Definition expando.h:41
A Hash Table.
Definition hash.h:99
A list of user hooks.
Definition hook.h:33
struct PatternList * pattern
Used for fcc,save,send-hook.
Definition hook.h:38
struct Regex regex
Regular expression.
Definition hook.h:35
char * command
Filename, command or pattern to execute.
Definition hook.h:36
struct Expando * expando
Used for format hooks.
Definition hook.h:39
enum CommandId id
Hook CommandId, e.g. CMD_FOLDER_HOOK.
Definition hook.h:34
char * source_file
Used for relative-directory source.
Definition hook.h:37
View of a Mailbox.
Definition mview.h:40
struct Mailbox * mailbox
Current Mailbox.
Definition mview.h:51
char * pattern
Limit pattern string.
Definition mview.h:42
Container for Accounts, Notifications.
Definition neomutt.h:41
struct CommandArray commands
NeoMutt commands.
Definition neomutt.h:53
struct ConfigSubset * sub
Inherited config items.
Definition neomutt.h:49
Context for config parsing (history/backtrace)
Definition pcontext.h:34
Detailed error information from config parsing.
Definition perror.h:34
struct Buffer * message
Error message.
Definition perror.h:35
char * pattern
printable version
Definition regex3.h:86
bool pat_not
do not match
Definition regex3.h:88
regex_t * regex
compiled expression
Definition regex3.h:87