NeoMutt  2025-12-11-911-gd8d604
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 "module_data.h"
45#include "muttlib.h"
46
47extern const struct ExpandoDefinition IndexFormatDef[];
48
56enum CommandResult parse_charset_hook(const struct Command *cmd, struct Buffer *line,
57 const struct ParseContext *pc, struct ParseError *pe)
58{
59 struct Buffer *err = pe->message;
60
61 if (!MoreArgs(line))
62 {
63 buf_printf(err, _("%s: too few arguments"), cmd->name);
64 return MUTT_CMD_WARNING;
65 }
66
67 struct Buffer *alias = buf_pool_get();
68 struct Buffer *charset = buf_pool_get();
70
71 if (parse_extract_token(alias, line, TOKEN_NONE) < 0)
72 goto done;
73 if (parse_extract_token(charset, line, TOKEN_NONE) < 0)
74 goto done;
75
76 const enum LookupType type = (cmd->id == CMD_ICONV_HOOK) ? MUTT_LOOKUP_ICONV :
78
79 if (buf_is_empty(alias) || buf_is_empty(charset))
80 {
81 buf_printf(err, _("%s: too few arguments"), cmd->name);
83 }
84 else if (MoreArgs(line))
85 {
86 buf_printf(err, _("%s: too many arguments"), cmd->name);
87 buf_reset(line); // clean up buffer to avoid a mess with further rcfile processing
89 }
90 else if (mutt_ch_lookup_add(type, buf_string(alias), buf_string(charset), err))
91 {
93 }
94
95done:
96 buf_pool_release(&alias);
97 buf_pool_release(&charset);
98
99 return rc;
100}
101
110enum CommandResult parse_global_hook(const struct Command *cmd, struct Buffer *line,
111 const struct ParseContext *pc, struct ParseError *pe)
112{
113 struct Buffer *err = pe->message;
114
116 struct Hook *hook = NULL;
118
119 struct Buffer *command = buf_pool_get();
120
121 // TOKEN_SPACE allows the command to contain whitespace, without quoting
122 parse_extract_token(command, line, TOKEN_SPACE);
123
124 if (buf_is_empty(command))
125 {
126 buf_printf(err, _("%s: too few arguments"), cmd->name);
127 rc = MUTT_CMD_WARNING;
128 goto cleanup;
129 }
130
131 if (MoreArgs(line))
132 {
133 buf_printf(err, _("%s: too many arguments"), cmd->name);
134 rc = MUTT_CMD_WARNING;
135 goto cleanup;
136 }
137
138 /* check to make sure that a matching Hook doesn't already exist */
139 TAILQ_FOREACH(hook, &mod_data->hooks, entries)
140 {
141 /* Ignore duplicate global Hooks */
142 if ((hook->id == cmd->id) && mutt_str_equal(hook->command, buf_string(command)))
143 {
144 rc = MUTT_CMD_SUCCESS;
145 goto cleanup;
146 }
147 }
148
149 hook = hook_new();
150 hook->id = cmd->id;
151 hook->command = buf_strdup(command);
153 hook->pattern = NULL;
154 hook->regex.pattern = NULL;
155 hook->regex.regex = NULL;
156 hook->regex.pat_not = false;
157 hook->expando = NULL;
158
159 TAILQ_INSERT_TAIL(&mod_data->hooks, hook, entries);
160 rc = MUTT_CMD_SUCCESS;
161
162cleanup:
163 buf_pool_release(&command);
164 return rc;
165}
166
176enum CommandResult parse_pattern_hook(const struct Command *cmd, struct Buffer *line,
177 const struct ParseContext *pc, struct ParseError *pe)
178{
179 struct Buffer *err = pe->message;
180
182 struct Hook *hook = NULL;
184 bool pat_not = false;
185 struct PatternList *pat = NULL;
186
187 struct Buffer *command = buf_pool_get();
188 struct Buffer *pattern = buf_pool_get();
189
190 if (*line->dptr == '!')
191 {
192 line->dptr++;
193 SKIPWS(line->dptr);
194 pat_not = true;
195 }
196
197 parse_extract_token(pattern, line, TOKEN_NONE);
198
199 if (!MoreArgs(line))
200 {
201 buf_printf(err, _("%s: too few arguments"), cmd->name);
202 rc = MUTT_CMD_WARNING;
203 goto cleanup;
204 }
205
206 // TOKEN_SPACE allows the command to contain whitespace, without quoting
207 parse_extract_token(command, line, TOKEN_SPACE);
208
209 if (buf_is_empty(command))
210 {
211 buf_printf(err, _("%s: too few arguments"), cmd->name);
212 rc = MUTT_CMD_WARNING;
213 goto cleanup;
214 }
215
216 if (MoreArgs(line))
217 {
218 buf_printf(err, _("%s: too many arguments"), cmd->name);
219 rc = MUTT_CMD_WARNING;
220 goto cleanup;
221 }
222
223 const char *const c_default_hook = cs_subset_string(NeoMutt->sub, "default_hook");
224 if (c_default_hook)
225 {
226 mutt_check_simple(pattern, c_default_hook);
227 }
228
229 /* check to make sure that a matching hook doesn't already exist */
230 TAILQ_FOREACH(hook, &mod_data->hooks, entries)
231 {
232 if ((hook->id == cmd->id) && (hook->regex.pat_not == pat_not) &&
233 mutt_str_equal(buf_string(pattern), hook->regex.pattern))
234 {
235 /* these hooks allow multiple commands with the same pattern,
236 * so if we've already seen this pattern/command pair,
237 * just ignore it instead of creating a duplicate */
238 if (mutt_str_equal(hook->command, buf_string(command)))
239 {
240 rc = MUTT_CMD_SUCCESS;
241 goto cleanup;
242 }
243 }
244 }
245
246 PatternCompFlags comp_flags;
247 if (cmd->id == CMD_SEND2_HOOK)
248 comp_flags = MUTT_PC_SEND_MODE_SEARCH;
249 else if (cmd->id == CMD_SEND_HOOK)
250 comp_flags = MUTT_PC_NONE;
251 else
252 comp_flags = MUTT_PC_FULL_MSG;
253
254 struct MailboxView *mv_cur = get_current_mailbox_view();
255 pat = mutt_pattern_comp(mv_cur, buf_string(pattern), comp_flags, err);
256 if (!pat)
257 goto cleanup;
258
259 hook = hook_new();
260 hook->id = cmd->id;
261 hook->command = buf_strdup(command);
263 hook->pattern = pat;
265 hook->regex.regex = NULL;
266 hook->regex.pat_not = pat_not;
267 hook->expando = NULL;
268
269 TAILQ_INSERT_TAIL(&mod_data->hooks, hook, entries);
270 rc = MUTT_CMD_SUCCESS;
271
272cleanup:
273 buf_pool_release(&command);
275 return rc;
276}
277
288 struct Buffer *pattern, bool pat_not, struct Buffer *err)
289{
291 struct Hook *hook = NULL;
292 struct PatternList *pat = NULL;
293
294 /* check to make sure that a matching hook doesn't already exist */
295 TAILQ_FOREACH(hook, &mod_data->hooks, entries)
296 {
297 if ((hook->id == id) && (hook->regex.pat_not == pat_not) &&
298 mutt_str_equal(buf_string(pattern), hook->regex.pattern))
299 {
300 // Update an existing hook
301 FREE(&hook->command);
302 hook->command = buf_strdup(mailbox);
303 FREE(&hook->source_file);
305
306 expando_free(&hook->expando);
307 hook->expando = expando_parse(buf_string(mailbox), IndexFormatDef, err);
308
309 return MUTT_CMD_SUCCESS;
310 }
311 }
312
313 PatternCompFlags comp_flags;
314 if (id == CMD_FCC_HOOK)
315 comp_flags = MUTT_PC_NONE;
316 else
317 comp_flags = MUTT_PC_FULL_MSG;
318
319 struct MailboxView *mv_cur = get_current_mailbox_view();
320 pat = mutt_pattern_comp(mv_cur, buf_string(pattern), comp_flags, err);
321 if (!pat)
322 return MUTT_CMD_ERROR;
323
324 struct Expando *exp = expando_parse(buf_string(mailbox), IndexFormatDef, err);
325
326 hook = hook_new();
327 hook->id = id;
328 hook->command = buf_strdup(mailbox);
330 hook->pattern = pat;
331 hook->regex.pattern = buf_strdup(pattern);
332 hook->regex.regex = NULL;
333 hook->regex.pat_not = pat_not;
334 hook->expando = exp;
335
336 TAILQ_INSERT_TAIL(&mod_data->hooks, hook, entries);
337 return MUTT_CMD_SUCCESS;
338}
339
348enum CommandResult parse_mailbox_hook(const struct Command *cmd, struct Buffer *line,
349 const struct ParseContext *pc, struct ParseError *pe)
350{
351 struct Buffer *err = pe->message;
352
354 bool pat_not = false;
355
356 struct Buffer *pattern = buf_pool_get();
357 struct Buffer *mailbox = buf_pool_get();
358
359 if (*line->dptr == '!')
360 {
361 line->dptr++;
362 SKIPWS(line->dptr);
363 pat_not = true;
364 }
365
366 parse_extract_token(pattern, line, TOKEN_NONE);
367
368 if (!MoreArgs(line))
369 {
370 buf_printf(err, _("%s: too few arguments"), cmd->name);
371 rc = MUTT_CMD_WARNING;
372 goto cleanup;
373 }
374
375 parse_extract_token(mailbox, line, TOKEN_NONE);
376
377 if (buf_is_empty(mailbox))
378 {
379 buf_printf(err, _("%s: too few arguments"), cmd->name);
380 rc = MUTT_CMD_WARNING;
381 goto cleanup;
382 }
383
384 if (MoreArgs(line))
385 {
386 buf_printf(err, _("%s: too many arguments"), cmd->name);
387 rc = MUTT_CMD_WARNING;
388 goto cleanup;
389 }
390
391 const char *const c_default_hook = cs_subset_string(NeoMutt->sub, "default_hook");
392 if (c_default_hook)
393 {
394 mutt_check_simple(pattern, c_default_hook);
395 }
396
397 expand_path(mailbox, false);
398
399 if ((cmd->id == CMD_FCC_HOOK) || (cmd->id == CMD_FCC_SAVE_HOOK))
400 {
401 rc = add_mailbox_hook(CMD_FCC_HOOK, mailbox, pattern, pat_not, err);
402 if (rc != MUTT_CMD_SUCCESS)
403 goto cleanup;
404 }
405
406 if ((cmd->id == CMD_SAVE_HOOK) || (cmd->id == CMD_FCC_SAVE_HOOK))
407 {
408 rc = add_mailbox_hook(CMD_SAVE_HOOK, mailbox, pattern, pat_not, err);
409 if (rc != MUTT_CMD_SUCCESS)
410 goto cleanup;
411 }
412
413 rc = MUTT_CMD_SUCCESS;
414
415cleanup:
416 buf_pool_release(&pattern);
417 buf_pool_release(&mailbox);
418 return rc;
419}
420
427enum CommandResult parse_regex_hook(const struct Command *cmd, struct Buffer *line,
428 const struct ParseContext *pc, struct ParseError *pe)
429{
430 struct Buffer *err = pe->message;
431
433 struct Hook *hook = NULL;
435 bool pat_not = false;
436 regex_t *rx = NULL;
437
438 struct Buffer *regex = buf_pool_get();
439 struct Buffer *command = buf_pool_get();
440
441 if (*line->dptr == '!')
442 {
443 line->dptr++;
444 SKIPWS(line->dptr);
445 pat_not = true;
446 }
447
448 parse_extract_token(regex, line, TOKEN_NONE);
449
450 if (!MoreArgs(line))
451 {
452 buf_printf(err, _("%s: too few arguments"), cmd->name);
453 rc = MUTT_CMD_WARNING;
454 goto cleanup;
455 }
456
457 parse_extract_token(command, line, TOKEN_SPACE);
458
459 if (buf_is_empty(command))
460 {
461 buf_printf(err, _("%s: too few arguments"), cmd->name);
462 rc = MUTT_CMD_WARNING;
463 goto cleanup;
464 }
465
466 if (MoreArgs(line))
467 {
468 buf_printf(err, _("%s: too many arguments"), cmd->name);
469 rc = MUTT_CMD_WARNING;
470 goto cleanup;
471 }
472
473 /* check to make sure that a matching hook doesn't already exist */
474 TAILQ_FOREACH(hook, &mod_data->hooks, entries)
475 {
476 if ((hook->id == cmd->id) && (hook->regex.pat_not == pat_not) &&
477 mutt_str_equal(buf_string(regex), hook->regex.pattern))
478 {
479 // Ignore duplicate hooks
480 if (mutt_str_equal(hook->command, buf_string(command)))
481 {
482 rc = MUTT_CMD_SUCCESS;
483 goto cleanup;
484 }
485 }
486 }
487
488 /* Hooks not allowing full patterns: Check syntax of regex */
489 rx = MUTT_MEM_CALLOC(1, regex_t);
490 int rc2 = REG_COMP(rx, buf_string(regex), 0);
491 if (rc2 != 0)
492 {
493 regerror(rc2, rx, err->data, err->dsize);
494 buf_fix_dptr(err);
495 FREE(&rx);
496 goto cleanup;
497 }
498
499 hook = hook_new();
500 hook->id = cmd->id;
501 hook->command = buf_strdup(command);
503 hook->pattern = NULL;
504 hook->regex.pattern = buf_strdup(regex);
505 hook->regex.regex = rx;
506 hook->regex.pat_not = pat_not;
507 hook->expando = NULL;
508
509 TAILQ_INSERT_TAIL(&mod_data->hooks, hook, entries);
510 rc = MUTT_CMD_SUCCESS;
511
512cleanup:
513 buf_pool_release(&regex);
514 buf_pool_release(&command);
515 return rc;
516}
517
524enum CommandResult parse_folder_hook(const struct Command *cmd, struct Buffer *line,
525 const struct ParseContext *pc, struct ParseError *pe)
526{
527 struct Buffer *err = pe->message;
528
530 struct Hook *hook = NULL;
532 bool pat_not = false;
533 bool use_regex = true;
534 regex_t *rx = NULL;
535
536 struct Buffer *regex = buf_pool_get();
537 struct Buffer *command = buf_pool_get();
538
539 if (*line->dptr == '!')
540 {
541 line->dptr++;
542 SKIPWS(line->dptr);
543 pat_not = true;
544 }
545
546 parse_extract_token(regex, line, TOKEN_NONE);
547 if (mutt_str_equal(buf_string(regex), "-noregex"))
548 {
549 use_regex = false;
550 if (!MoreArgs(line))
551 {
552 buf_printf(err, _("%s: too few arguments"), cmd->name);
553 rc = MUTT_CMD_WARNING;
554 goto cleanup;
555 }
556 parse_extract_token(regex, line, TOKEN_NONE);
557 }
558
559 if (!MoreArgs(line))
560 {
561 buf_printf(err, _("%s: too few arguments"), cmd->name);
562 rc = MUTT_CMD_WARNING;
563 goto cleanup;
564 }
565
566 parse_extract_token(command, line, TOKEN_SPACE);
567
568 if (buf_is_empty(command))
569 {
570 buf_printf(err, _("%s: too few arguments"), cmd->name);
571 rc = MUTT_CMD_WARNING;
572 goto cleanup;
573 }
574
575 if (MoreArgs(line))
576 {
577 buf_printf(err, _("%s: too many arguments"), cmd->name);
578 rc = MUTT_CMD_WARNING;
579 goto cleanup;
580 }
581
582 /* Accidentally using the ^ mailbox shortcut in the .neomuttrc is a
583 * common mistake */
584 if ((buf_at(regex, 0) == '^') && !CurrentFolder)
585 {
586 buf_strcpy(err, _("current mailbox shortcut '^' is unset"));
587 goto cleanup;
588 }
589
590 struct Buffer *tmp = buf_pool_get();
591 buf_copy(tmp, regex);
592 expand_path(tmp, use_regex);
593
594 /* Check for other mailbox shortcuts that expand to the empty string.
595 * This is likely a mistake too */
596 if (buf_is_empty(tmp) && !buf_is_empty(regex))
597 {
598 buf_strcpy(err, _("mailbox shortcut expanded to empty regex"));
599 buf_pool_release(&tmp);
600 goto cleanup;
601 }
602
603 if (use_regex)
604 {
605 buf_copy(regex, tmp);
606 }
607 else
608 {
610 }
611 buf_pool_release(&tmp);
612
613 /* check to make sure that a matching hook doesn't already exist */
614 TAILQ_FOREACH(hook, &mod_data->hooks, entries)
615 {
616 if ((hook->id == cmd->id) && (hook->regex.pat_not == pat_not) &&
617 mutt_str_equal(buf_string(regex), hook->regex.pattern))
618 {
619 // Ignore duplicate hooks
620 if (mutt_str_equal(hook->command, buf_string(command)))
621 {
622 rc = MUTT_CMD_SUCCESS;
623 goto cleanup;
624 }
625 }
626 }
627
628 /* Hooks not allowing full patterns: Check syntax of regex */
629 rx = MUTT_MEM_CALLOC(1, regex_t);
630 int rc2 = REG_COMP(rx, buf_string(regex), 0);
631 if (rc2 != 0)
632 {
633 regerror(rc2, rx, err->data, err->dsize);
634 buf_fix_dptr(err);
635 FREE(&rx);
636 goto cleanup;
637 }
638
639 hook = hook_new();
640 hook->id = cmd->id;
641 hook->command = buf_strdup(command);
643 hook->pattern = NULL;
644 hook->regex.pattern = buf_strdup(regex);
645 hook->regex.regex = rx;
646 hook->regex.pat_not = pat_not;
647 hook->expando = NULL;
648
649 TAILQ_INSERT_TAIL(&mod_data->hooks, hook, entries);
650 rc = MUTT_CMD_SUCCESS;
651
652cleanup:
653 buf_pool_release(&regex);
654 buf_pool_release(&command);
655 return rc;
656}
657
665enum CommandResult parse_crypt_hook(const struct Command *cmd, struct Buffer *line,
666 const struct ParseContext *pc, struct ParseError *pe)
667{
668 struct Buffer *err = pe->message;
669
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
686 parse_extract_token(regex, line, TOKEN_NONE);
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
695 parse_extract_token(keyid, line, TOKEN_NONE);
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, &mod_data->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 buf_fix_dptr(err);
733 FREE(&rx);
734 goto cleanup;
735 }
736
737 hook = hook_new();
738 hook->id = cmd->id;
739 hook->command = buf_strdup(keyid);
741 hook->pattern = NULL;
742 hook->regex.pattern = buf_strdup(regex);
743 hook->regex.regex = rx;
744 hook->regex.pat_not = pat_not;
745 hook->expando = NULL;
746
747 TAILQ_INSERT_TAIL(&mod_data->hooks, hook, entries);
748 rc = MUTT_CMD_SUCCESS;
749
750cleanup:
751 buf_pool_release(&regex);
752 buf_pool_release(&keyid);
753 return rc;
754}
755
762enum CommandResult parse_mbox_hook(const struct Command *cmd, struct Buffer *line,
763 const struct ParseContext *pc, struct ParseError *pe)
764{
765 struct Buffer *err = pe->message;
766
768 struct Hook *hook = NULL;
770 bool pat_not = false;
771 bool use_regex = true;
772 regex_t *rx = NULL;
773
774 struct Buffer *regex = buf_pool_get();
775 struct Buffer *command = buf_pool_get();
776
777 if (*line->dptr == '!')
778 {
779 line->dptr++;
780 SKIPWS(line->dptr);
781 pat_not = true;
782 }
783
784 parse_extract_token(regex, line, TOKEN_NONE);
785 if (mutt_str_equal(buf_string(regex), "-noregex"))
786 {
787 use_regex = false;
788 if (!MoreArgs(line))
789 {
790 buf_printf(err, _("%s: too few arguments"), cmd->name);
791 rc = MUTT_CMD_WARNING;
792 goto cleanup;
793 }
794 parse_extract_token(regex, line, TOKEN_NONE);
795 }
796
797 if (!MoreArgs(line))
798 {
799 buf_printf(err, _("%s: too few arguments"), cmd->name);
800 rc = MUTT_CMD_WARNING;
801 goto cleanup;
802 }
803
804 parse_extract_token(command, line, TOKEN_NONE);
805
806 if (buf_is_empty(command))
807 {
808 buf_printf(err, _("%s: too few arguments"), cmd->name);
809 rc = MUTT_CMD_WARNING;
810 goto cleanup;
811 }
812
813 if (MoreArgs(line))
814 {
815 buf_printf(err, _("%s: too many arguments"), cmd->name);
816 rc = MUTT_CMD_WARNING;
817 goto cleanup;
818 }
819
820 /* Accidentally using the ^ mailbox shortcut in the .neomuttrc is a
821 * common mistake */
822 if ((buf_at(regex, 0) == '^') && !CurrentFolder)
823 {
824 buf_strcpy(err, _("current mailbox shortcut '^' is unset"));
825 goto cleanup;
826 }
827
828 struct Buffer *tmp = buf_pool_get();
829 buf_copy(tmp, regex);
830 expand_path(tmp, use_regex);
831
832 /* Check for other mailbox shortcuts that expand to the empty string.
833 * This is likely a mistake too */
834 if (buf_is_empty(tmp) && !buf_is_empty(regex))
835 {
836 buf_strcpy(err, _("mailbox shortcut expanded to empty regex"));
837 buf_pool_release(&tmp);
838 goto cleanup;
839 }
840
841 if (use_regex)
842 {
843 buf_copy(regex, tmp);
844 }
845 else
846 {
848 }
849 buf_pool_release(&tmp);
850
851 expand_path(command, false);
852
853 /* check to make sure that a matching hook doesn't already exist */
854 TAILQ_FOREACH(hook, &mod_data->hooks, entries)
855 {
856 if ((hook->id == cmd->id) && (hook->regex.pat_not == pat_not) &&
857 mutt_str_equal(buf_string(regex), hook->regex.pattern))
858 {
859 // Update an existing hook
860 FREE(&hook->command);
861 hook->command = buf_strdup(command);
862 FREE(&hook->source_file);
864
865 expando_free(&hook->expando);
866 hook->expando = expando_parse(buf_string(command), IndexFormatDef, err);
867
868 rc = MUTT_CMD_SUCCESS;
869 goto cleanup;
870 }
871 }
872
873 /* Hooks not allowing full patterns: Check syntax of regex */
874 rx = MUTT_MEM_CALLOC(1, regex_t);
875 int rc2 = REG_COMP(rx, buf_string(regex), 0);
876 if (rc2 != 0)
877 {
878 regerror(rc2, rx, err->data, err->dsize);
879 buf_fix_dptr(err);
880 FREE(&rx);
881 goto cleanup;
882 }
883
884 struct Expando *exp = expando_parse(buf_string(command), IndexFormatDef, err);
885
886 hook = hook_new();
887 hook->id = cmd->id;
888 hook->command = buf_strdup(command);
890 hook->pattern = NULL;
891 hook->regex.pattern = buf_strdup(regex);
892 hook->regex.regex = rx;
893 hook->regex.pat_not = pat_not;
894 hook->expando = exp;
895
896 TAILQ_INSERT_TAIL(&mod_data->hooks, hook, entries);
897 rc = MUTT_CMD_SUCCESS;
898
899cleanup:
900 buf_pool_release(&regex);
901 buf_pool_release(&command);
902 return rc;
903}
904
913enum CommandResult parse_compress_hook(const struct Command *cmd, struct Buffer *line,
914 const struct ParseContext *pc, struct ParseError *pe)
915{
916 struct Buffer *err = pe->message;
917
919 struct Hook *hook = NULL;
921 bool pat_not = false;
922 regex_t *rx = NULL;
923
924 struct Buffer *regex = buf_pool_get();
925 struct Buffer *command = buf_pool_get();
926
927 if (*line->dptr == '!')
928 {
929 line->dptr++;
930 SKIPWS(line->dptr);
931 pat_not = true;
932 }
933
934 parse_extract_token(regex, line, TOKEN_NONE);
935
936 if (!MoreArgs(line))
937 {
938 buf_printf(err, _("%s: too few arguments"), cmd->name);
939 rc = MUTT_CMD_WARNING;
940 goto cleanup;
941 }
942
943 // TOKEN_SPACE allows the command to contain whitespace, without quoting
944 parse_extract_token(command, line, TOKEN_SPACE);
945
946 if (buf_is_empty(command))
947 {
948 buf_printf(err, _("%s: too few arguments"), cmd->name);
949 rc = MUTT_CMD_WARNING;
950 goto cleanup;
951 }
952
953 if (MoreArgs(line))
954 {
955 buf_printf(err, _("%s: too many arguments"), cmd->name);
956 rc = MUTT_CMD_WARNING;
957 goto cleanup;
958 }
959
960 if (mutt_comp_valid_command(buf_string(command)) == 0)
961 {
962 buf_strcpy(err, _("badly formatted command string"));
963 goto cleanup;
964 }
965
966 /* check to make sure that a matching hook doesn't already exist */
967 TAILQ_FOREACH(hook, &mod_data->hooks, entries)
968 {
969 if ((hook->id == cmd->id) && (hook->regex.pat_not == pat_not) &&
970 mutt_str_equal(buf_string(regex), hook->regex.pattern))
971 {
972 // Update an existing hook
973 FREE(&hook->command);
974 hook->command = buf_strdup(command);
975 FREE(&hook->source_file);
977
978 rc = MUTT_CMD_SUCCESS;
979 goto cleanup;
980 }
981 }
982
983 /* Hooks not allowing full patterns: Check syntax of regex */
984 rx = MUTT_MEM_CALLOC(1, regex_t);
985 int rc2 = REG_COMP(rx, buf_string(regex), 0);
986 if (rc2 != 0)
987 {
988 regerror(rc2, rx, err->data, err->dsize);
989 buf_fix_dptr(err);
990 FREE(&rx);
991 goto cleanup;
992 }
993
994 hook = hook_new();
995 hook->id = cmd->id;
996 hook->command = buf_strdup(command);
998 hook->pattern = NULL;
999 hook->regex.pattern = buf_strdup(regex);
1000 hook->regex.regex = rx;
1001 hook->regex.pat_not = pat_not;
1002 hook->expando = NULL;
1003
1004 TAILQ_INSERT_TAIL(&mod_data->hooks, hook, entries);
1005 rc = MUTT_CMD_SUCCESS;
1006
1007cleanup:
1008 buf_pool_release(&regex);
1009 buf_pool_release(&command);
1010 return rc;
1011}
1012
1020void mutt_delete_hooks(struct HookList *hooks, enum CommandId id)
1021{
1022 struct Hook *h = NULL;
1023 struct Hook *tmp = NULL;
1024
1025 TAILQ_FOREACH_SAFE(h, hooks, entries, tmp)
1026 {
1027 if ((id == CMD_NONE) || (id == h->id))
1028 {
1029 TAILQ_REMOVE(hooks, h, entries);
1030 hook_free(&h);
1031 }
1032 }
1033}
1034
1038static void idxfmt_hashelem_free(int type, void *obj, intptr_t data)
1039{
1040 struct HookList *hl = obj;
1041 struct Hook *h = NULL;
1042 struct Hook *tmp = NULL;
1043
1044 TAILQ_FOREACH_SAFE(h, hl, entries, tmp)
1045 {
1046 TAILQ_REMOVE(hl, h, entries);
1047 hook_free(&h);
1048 }
1049
1050 FREE(&hl);
1051}
1052
1057void delete_idxfmt_hooks(struct HashTable **idx_fmt_hooks)
1058{
1059 mutt_hash_free(idx_fmt_hooks);
1060}
1061
1068enum CommandResult parse_index_hook(const struct Command *cmd, struct Buffer *line,
1069 const struct ParseContext *pc, struct ParseError *pe)
1070{
1071 struct Buffer *err = pe->message;
1072
1073 if (!MoreArgs(line))
1074 {
1075 buf_printf(err, _("%s: too few arguments"), cmd->name);
1076 return MUTT_CMD_WARNING;
1077 }
1078
1080 enum CommandResult rc = MUTT_CMD_ERROR;
1081 bool pat_not = false;
1082
1083 struct Buffer *name = buf_pool_get();
1084 struct Buffer *pattern = buf_pool_get();
1085 struct Buffer *fmt = buf_pool_get();
1086 struct Expando *exp = NULL;
1087
1088 if (!mod_data->idx_fmt_hooks)
1089 {
1092 }
1093
1094 parse_extract_token(name, line, TOKEN_NONE);
1095 struct HookList *hl = mutt_hash_find(mod_data->idx_fmt_hooks, buf_string(name));
1096
1097 if (*line->dptr == '!')
1098 {
1099 line->dptr++;
1100 SKIPWS(line->dptr);
1101 pat_not = true;
1102 }
1103 parse_extract_token(pattern, line, TOKEN_NONE);
1104
1105 if (!MoreArgs(line))
1106 {
1107 buf_printf(err, _("%s: too few arguments"), cmd->name);
1108 goto out;
1109 }
1110 parse_extract_token(fmt, line, TOKEN_NONE);
1111
1112 exp = expando_parse(buf_string(fmt), IndexFormatDef, err);
1113 if (!exp)
1114 goto out;
1115
1116 if (MoreArgs(line))
1117 {
1118 buf_printf(err, _("%s: too many arguments"), cmd->name);
1119 goto out;
1120 }
1121
1122 const char *const c_default_hook = cs_subset_string(NeoMutt->sub, "default_hook");
1123 if (c_default_hook)
1124 mutt_check_simple(pattern, c_default_hook);
1125
1126 /* check to make sure that a matching hook doesn't already exist */
1127 struct Hook *hook = NULL;
1128 if (hl)
1129 {
1130 TAILQ_FOREACH(hook, hl, entries)
1131 {
1132 // Update an existing hook
1133 if ((hook->regex.pat_not == pat_not) &&
1135 {
1136 expando_free(&hook->expando);
1137 hook->expando = exp;
1138 exp = NULL;
1139 rc = MUTT_CMD_SUCCESS;
1140 goto out;
1141 }
1142 }
1143 }
1144
1145 /* MUTT_PC_PATTERN_DYNAMIC sets so that date ranges are regenerated during
1146 * matching. This of course is slower, but index-format-hook is commonly
1147 * used for date ranges, and they need to be evaluated relative to "now", not
1148 * the hook compilation time. */
1149 struct MailboxView *mv_cur = get_current_mailbox_view();
1150 struct PatternList *pat = mutt_pattern_comp(mv_cur, buf_string(pattern),
1152 err);
1153 if (!pat)
1154 goto out;
1155
1156 hook = hook_new();
1157 hook->id = CMD_INDEX_FORMAT_HOOK;
1158 hook->command = NULL;
1160 hook->pattern = pat;
1161 hook->regex.pattern = buf_strdup(pattern);
1162 hook->regex.regex = NULL;
1163 hook->regex.pat_not = pat_not;
1164 hook->expando = exp;
1165 exp = NULL;
1166
1167 if (!hl)
1168 {
1169 hl = MUTT_MEM_CALLOC(1, struct HookList);
1170 TAILQ_INIT(hl);
1171 mutt_hash_insert(mod_data->idx_fmt_hooks, buf_string(name), hl);
1172 }
1173
1174 TAILQ_INSERT_TAIL(hl, hook, entries);
1175 rc = MUTT_CMD_SUCCESS;
1176
1177out:
1178 buf_pool_release(&name);
1179 buf_pool_release(&pattern);
1180 buf_pool_release(&fmt);
1181 expando_free(&exp);
1182
1183 return rc;
1184}
1185
1192enum CommandResult parse_unhook(const struct Command *cmd, struct Buffer *line,
1193 const struct ParseContext *pc, struct ParseError *pe)
1194{
1195 struct Buffer *err = pe->message;
1196
1197 if (!MoreArgs(line))
1198 {
1199 buf_printf(err, _("%s: too few arguments"), cmd->name);
1200 return MUTT_CMD_WARNING;
1201 }
1202
1204 struct Buffer *token = buf_pool_get();
1206
1207 while (MoreArgs(line))
1208 {
1209 parse_extract_token(token, line, TOKEN_NONE);
1210 if (mutt_str_equal("*", buf_string(token)))
1211 {
1212 if (mod_data->current_hook_id != CMD_NONE)
1213 {
1214 buf_addstr(err, _("unhook: Can't do unhook * from within a hook"));
1215 goto done;
1216 }
1217 mutt_delete_hooks(&mod_data->hooks, CMD_NONE);
1220 break;
1221 }
1222 else
1223 {
1224 const struct Command *hook = command_find_by_name(&NeoMutt->commands,
1225 buf_string(token));
1226 if (!hook)
1227 {
1228 buf_printf(err, _("unhook: unknown hook type: %s"), buf_string(token));
1229 rc = MUTT_CMD_ERROR;
1230 goto done;
1231 }
1232
1233 if ((hook->id == CMD_CHARSET_HOOK) || (hook->id == CMD_ICONV_HOOK))
1234 {
1236 }
1237 else if ((mod_data->current_hook_id == hook->id) ||
1238 ((hook->id == CMD_FCC_SAVE_HOOK) &&
1239 ((mod_data->current_hook_id == CMD_FCC_HOOK) ||
1240 (mod_data->current_hook_id == CMD_SAVE_HOOK))))
1241 {
1242 buf_printf(err, _("unhook: Can't delete a %s from within a %s"),
1243 buf_string(token), buf_string(token));
1244 rc = MUTT_CMD_WARNING;
1245 goto done;
1246 }
1247 else if (hook->id == CMD_INDEX_FORMAT_HOOK)
1248 {
1250 }
1251 else if (hook->id == CMD_FCC_SAVE_HOOK)
1252 {
1255 }
1256 else
1257 {
1258 mutt_delete_hooks(&mod_data->hooks, hook->id);
1259 }
1260 }
1261 }
1262
1263 rc = MUTT_CMD_SUCCESS;
1264
1265done:
1266 buf_pool_release(&token);
1267 return rc;
1268}
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
void buf_fix_dptr(struct Buffer *buf)
Move the dptr to end of the Buffer.
Definition buffer.c:182
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:61
@ CMD_SEND_HOOK
:send-hook
Definition command.h:110
@ CMD_FCC_SAVE_HOOK
:fcc-save-hook
Definition command.h:79
@ CMD_ICONV_HOOK
:iconv-hook
Definition command.h:85
@ CMD_INDEX_FORMAT_HOOK
:index-format-hook
Definition command.h:89
@ CMD_FCC_HOOK
:fcc-hook
Definition command.h:78
@ CMD_SEND2_HOOK
:send2-hook
Definition command.h:109
@ CMD_CHARSET_HOOK
:charset-hook
Definition command.h:72
@ CMD_NONE
No Command.
Definition command.h:62
@ CMD_SAVE_HOOK
:save-hook
Definition command.h:107
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:145
NeoMutt Commands.
struct PatternList * mutt_pattern_comp(struct MailboxView *mv, const char *s, PatternCompFlags flags, struct Buffer *err)
Create a Pattern.
Definition compile.c:964
int mutt_comp_valid_command(const char *cmd)
Is this command string allowed?
Definition compress.c:386
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 MoreArgs(buf)
Definition extract.h:31
@ TOKEN_SPACE
Don't treat whitespace as a term.
Definition extract.h:52
@ TOKEN_NONE
No flags are set.
Definition extract.h:49
int mutt_file_sanitize_regex(struct Buffer *dest, const char *src)
Escape any regex-magic characters in a string.
Definition file.c:624
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:176
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:524
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:913
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:427
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:1192
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:665
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:348
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:56
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:762
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:110
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:1068
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:1038
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
@ MUTT_HASH_STRDUP_KEYS
make a copy of the keys
Definition hash.h:117
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.
Hooks private Module data.
void mutt_delete_hooks(struct HookList *hooks, enum CommandId id)
Delete matching hooks.
Definition parse.c:1020
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:287
void delete_idxfmt_hooks(struct HashTable **idx_fmt_hooks)
Delete all the index-format-hooks.
Definition parse.c:1057
GUI manage the main index (list of emails)
struct MailboxView * get_current_mailbox_view(void)
Get the current Mailbox view.
Definition index.c:693
#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
@ MODULE_ID_HOOKS
ModuleHooks, Hook Commands
Definition module_api.h:70
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:665
void expand_path(struct Buffer *buf, bool regex)
Create the canonical path.
Definition muttlib.c:122
Some miscellaneous functions.
void * neomutt_get_module_data(struct NeoMutt *n, enum ModuleId id)
Get the private data for a Module.
Definition neomutt.c:663
Text parsing functions.
Match patterns to emails.
uint8_t PatternCompFlags
Definition lib.h:78
void mutt_check_simple(struct Buffer *s, const char *simple)
Convert a simple search into a real request.
Definition pattern.c:91
@ MUTT_PC_PATTERN_DYNAMIC
Enable runtime date range evaluation.
Definition lib.h:75
@ MUTT_PC_NONE
No flags are set.
Definition lib.h:73
@ MUTT_PC_SEND_MODE_SEARCH
Allow send-mode body searching.
Definition lib.h:76
@ MUTT_PC_FULL_MSG
Enable body and header matching.
Definition lib.h:74
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 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:315
#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:162
enum CommandId id
ID of the Command.
Definition command.h:163
Definition of a format string.
Definition definition.h:49
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
Hooks private Module data.
Definition module_data.h:33
struct HashTable * idx_fmt_hooks
All Index Format hooks.
Definition module_data.h:36
struct HookList hooks
All simple hooks, e.g. CMD_FOLDER_HOOK.
Definition module_data.h:35
int current_hook_id
The ID of the Hook currently being executed.
Definition module_data.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