NeoMutt  2025-12-11-911-gd8d604
Teaching an old dog new tricks
DOXYGEN
Loading...
Searching...
No Matches
compile.c
Go to the documentation of this file.
1
25
31
32#include "config.h"
33#include <stdbool.h>
34#include <stdint.h>
35#include <stdio.h>
36#include <stdlib.h>
37#include <string.h>
38#include <sys/types.h>
39#include <time.h>
40#include "private.h"
41#include "mutt/lib.h"
42#include "address/lib.h"
43#include "config/lib.h"
44#include "core/lib.h"
45#include "alias/lib.h"
46#include "gui/lib.h"
47#include "lib.h"
48#include "parse/lib.h"
49
54{
55 // clang-format off
57 MUTT_PDR_MINUS = 1U << 0,
58 MUTT_PDR_PLUS = 1U << 1,
59 MUTT_PDR_WINDOW = 1U << 2,
61 MUTT_PDR_DONE = 1U << 4,
62 MUTT_PDR_ERROR = 1U << 5,
63 // clang-format on
64};
65typedef uint8_t ParseDateRangeFlags;
66
67#define MUTT_PDR_ERRORDONE (MUTT_PDR_ERROR | MUTT_PDR_DONE)
68
72static bool eat_regex(struct Pattern *pat, PatternCompFlags flags,
73 struct Buffer *s, struct Buffer *err)
74{
75 struct Buffer *token = buf_pool_get();
76 bool rc = false;
77 const char *pexpr = s->dptr;
78 if ((parse_extract_token(token, s, TOKEN_PATTERN | TOKEN_COMMENT) != 0) ||
79 !token->data)
80 {
81 buf_printf(err, _("Error in expression: %s"), pexpr);
82 goto out;
83 }
84 if (buf_is_empty(token))
85 {
86 buf_addstr(err, _("Empty expression"));
87 goto out;
88 }
89
90 pat->p.regex = MUTT_MEM_CALLOC(1, regex_t);
91#ifdef USE_DEBUG_GRAPHVIZ
92 pat->raw_pattern = buf_strdup(token);
93#endif
94 uint16_t case_flags = mutt_mb_is_lower(token->data) ? REG_ICASE : 0;
95 int rc2 = REG_COMP(pat->p.regex, token->data, REG_NEWLINE | REG_NOSUB | case_flags);
96 if (rc2 != 0)
97 {
98 char errmsg[256] = { 0 };
99 regerror(rc2, pat->p.regex, errmsg, sizeof(errmsg));
100 buf_printf(err, "'%s': %s", token->data, errmsg);
101 FREE(&pat->p.regex);
102 goto out;
103 }
104
105 rc = true;
106
107out:
108 buf_pool_release(&token);
109 return rc;
110}
111
115static bool eat_string(struct Pattern *pat, PatternCompFlags flags,
116 struct Buffer *s, struct Buffer *err)
117{
118 struct Buffer *token = buf_pool_get();
119 bool rc = false;
120 const char *pexpr = s->dptr;
121 if ((parse_extract_token(token, s, TOKEN_PATTERN | TOKEN_COMMENT) != 0) ||
122 !token->data)
123 {
124 buf_printf(err, _("Error in expression: %s"), pexpr);
125 goto out;
126 }
127 if (buf_is_empty(token))
128 {
129 buf_addstr(err, _("Empty expression"));
130 goto out;
131 }
132
133 pat->string_match = true;
134 pat->p.str = buf_strdup(token);
135 pat->ign_case = mutt_mb_is_lower(token->data);
136
137 rc = true;
138
139out:
140 buf_pool_release(&token);
141 return rc;
142}
143
147static bool eat_group(struct Pattern *pat, PatternCompFlags flags,
148 struct Buffer *s, struct Buffer *err)
149{
150 struct Buffer *token = buf_pool_get();
151 bool rc = false;
152 const char *pexpr = s->dptr;
153 if ((parse_extract_token(token, s, TOKEN_PATTERN | TOKEN_COMMENT) != 0) ||
154 !token->data)
155 {
156 buf_printf(err, _("Error in expression: %s"), pexpr);
157 goto out;
158 }
159 if (buf_is_empty(token))
160 {
161 buf_addstr(err, _("Empty expression"));
162 goto out;
163 }
164
166 ASSERT(mod_data);
167
168 pat->group_match = true;
169 pat->p.group = groups_get_group(mod_data->groups, token->data);
170
171 rc = true;
172
173out:
174 buf_pool_release(&token);
175 return rc;
176}
177
182static bool add_query_msgid(char *line, int line_num, void *user_data)
183{
184 struct ListHead *msgid_list = (struct ListHead *) (user_data);
185 char *nows = mutt_str_skip_whitespace(line);
186 if (*nows == '\0')
187 return true;
189 mutt_list_insert_tail(msgid_list, mutt_str_dup(nows));
190 return true;
191}
192
202static bool eat_query(struct Pattern *pat, PatternCompFlags flags,
203 struct Buffer *s, struct Buffer *err, struct Mailbox *m)
204{
205 struct Buffer *cmd = buf_pool_get();
206 struct Buffer *tok = buf_pool_get();
207 bool rc = false;
208
209 FILE *fp = NULL;
210
211 const char *const c_external_search_command = cs_subset_string(NeoMutt->sub, "external_search_command");
212 if (!c_external_search_command)
213 {
214 buf_addstr(err, _("No search command defined"));
215 goto out;
216 }
217
218 char *pexpr = s->dptr;
219 if ((parse_extract_token(tok, s, TOKEN_PATTERN | TOKEN_COMMENT) != 0) || !tok->data)
220 {
221 buf_printf(err, _("Error in expression: %s"), pexpr);
222 goto out;
223 }
224 if (*tok->data == '\0')
225 {
226 buf_addstr(err, _("Empty expression"));
227 goto out;
228 }
229
230 buf_addstr(cmd, c_external_search_command);
231 buf_addch(cmd, ' ');
232
233 if (m)
234 {
235 char *escaped_folder = mutt_path_escape(mailbox_path(m));
236 mutt_debug(LL_DEBUG2, "escaped folder path: %s\n", escaped_folder);
237 buf_addch(cmd, '\'');
238 buf_addstr(cmd, escaped_folder);
239 buf_addch(cmd, '\'');
240 }
241 else
242 {
243 buf_addch(cmd, '/');
244 }
245 buf_addch(cmd, ' ');
246 buf_addstr(cmd, tok->data);
247
248 mutt_message(_("Running search command: %s ..."), cmd->data);
249 pat->is_multi = true;
251 pid_t pid = filter_create(cmd->data, NULL, &fp, NULL, NeoMutt->env);
252 if (pid < 0)
253 {
254 buf_printf(err, "unable to fork command: %s\n", cmd->data);
255 goto out;
256 }
257
259 mutt_file_fclose(&fp);
260 filter_wait(pid);
261
262 rc = true;
263
264out:
265 buf_pool_release(&cmd);
266 buf_pool_release(&tok);
267 return rc;
268}
269
282static const char *get_offset(struct tm *tm, const char *s, int sign)
283{
284 char *ps = NULL;
285 int offset = strtol(s, &ps, 0);
286 if (((sign < 0) && (offset > 0)) || ((sign > 0) && (offset < 0)))
287 offset = -offset;
288
289 switch (*ps)
290 {
291 case 'y':
292 tm->tm_year += offset;
293 break;
294 case 'm':
295 tm->tm_mon += offset;
296 break;
297 case 'w':
298 tm->tm_mday += 7 * offset;
299 break;
300 case 'd':
301 tm->tm_mday += offset;
302 break;
303 case 'H':
304 tm->tm_hour += offset;
305 break;
306 case 'M':
307 tm->tm_min += offset;
308 break;
309 case 'S':
310 tm->tm_sec += offset;
311 break;
312 default:
313 return s;
314 }
316 return ps + 1;
317}
318
336static const char *get_date(const char *s, struct tm *t, struct Buffer *err)
337{
338 char *p = NULL;
339 struct tm tm = mutt_date_localtime(mutt_date_now());
340 bool iso8601 = true;
341
342 for (int v = 0; v < 8; v++)
343 {
344 if (s[v] && (s[v] >= '0') && (s[v] <= '9'))
345 continue;
346
347 iso8601 = false;
348 break;
349 }
350
351 if (iso8601)
352 {
353 int year = 0;
354 int month = 0;
355 int mday = 0;
356 sscanf(s, "%4d%2d%2d", &year, &month, &mday);
357
358 t->tm_year = year;
359 if (t->tm_year > 1900)
360 t->tm_year -= 1900;
361 t->tm_mon = month - 1;
362 t->tm_mday = mday;
363
364 if ((t->tm_mday < 1) || (t->tm_mday > 31))
365 {
366 buf_printf(err, _("Invalid day of month: %s"), s);
367 return NULL;
368 }
369 if ((t->tm_mon < 0) || (t->tm_mon > 11))
370 {
371 buf_printf(err, _("Invalid month: %s"), s);
372 return NULL;
373 }
374
375 return (s + 8);
376 }
377
378 t->tm_mday = strtol(s, &p, 10);
379 if ((t->tm_mday < 1) || (t->tm_mday > 31))
380 {
381 buf_printf(err, _("Invalid day of month: %s"), s);
382 return NULL;
383 }
384 if (*p != '/')
385 {
386 /* fill in today's month and year */
387 t->tm_mon = tm.tm_mon;
388 t->tm_year = tm.tm_year;
389 return p;
390 }
391 p++;
392 t->tm_mon = strtol(p, &p, 10) - 1;
393 if ((t->tm_mon < 0) || (t->tm_mon > 11))
394 {
395 buf_printf(err, _("Invalid month: %s"), p);
396 return NULL;
397 }
398 if (*p != '/')
399 {
400 t->tm_year = tm.tm_year;
401 return p;
402 }
403 p++;
404 t->tm_year = strtol(p, &p, 10);
405 if (t->tm_year < 70) /* year 2000+ */
406 t->tm_year += 100;
407 else if (t->tm_year > 1900)
408 t->tm_year -= 1900;
409 return p;
410}
411
422static const char *parse_date_range(const char *pc, struct tm *min, struct tm *max,
423 bool have_min, struct tm *base_min, struct Buffer *err)
424{
426 while (*pc && ((flags & MUTT_PDR_DONE) == 0))
427 {
428 const char *pt = NULL;
429 char ch = *pc++;
430 SKIPWS(pc);
431 switch (ch)
432 {
433 case '-':
434 {
435 /* try a range of absolute date minus offset of Ndwmy */
436 pt = get_offset(min, pc, -1);
437 if (pc == pt)
438 {
439 if (flags == MUTT_PDR_NONE)
440 { /* nothing yet and no offset parsed => absolute date? */
441 if (!get_date(pc, max, err))
442 {
443 flags |= (MUTT_PDR_ABSOLUTE | MUTT_PDR_ERRORDONE); /* done bad */
444 }
445 else
446 {
447 /* reestablish initial base minimum if not specified */
448 if (!have_min)
449 memcpy(min, base_min, sizeof(struct tm));
450 flags |= (MUTT_PDR_ABSOLUTE | MUTT_PDR_DONE); /* done good */
451 }
452 }
453 else
454 {
455 flags |= MUTT_PDR_ERRORDONE;
456 }
457 }
458 else
459 {
460 pc = pt;
461 if ((flags == MUTT_PDR_NONE) && !have_min)
462 { /* the very first "-3d" without a previous absolute date */
463 max->tm_year = min->tm_year;
464 max->tm_mon = min->tm_mon;
465 max->tm_mday = min->tm_mday;
466 }
467 flags |= MUTT_PDR_MINUS;
468 }
469 break;
470 }
471 case '+':
472 { /* enlarge plus range */
473 pt = get_offset(max, pc, 1);
474 if (pc == pt)
475 {
476 flags |= MUTT_PDR_ERRORDONE;
477 }
478 else
479 {
480 pc = pt;
481 flags |= MUTT_PDR_PLUS;
482 }
483 break;
484 }
485 case '*':
486 { /* enlarge window in both directions */
487 pt = get_offset(min, pc, -1);
488 if (pc == pt)
489 {
490 flags |= MUTT_PDR_ERRORDONE;
491 }
492 else
493 {
494 pc = get_offset(max, pc, 1);
495 flags |= MUTT_PDR_WINDOW;
496 }
497 break;
498 }
499 default:
500 flags |= MUTT_PDR_ERRORDONE;
501 }
502 SKIPWS(pc);
503 }
504 if ((flags & MUTT_PDR_ERROR) && !(flags & MUTT_PDR_ABSOLUTE))
505 { /* get_date has its own error message, don't overwrite it here */
506 buf_printf(err, _("Invalid relative date: %s"), pc - 1);
507 }
508 return (flags & MUTT_PDR_ERROR) ? NULL : pc;
509}
510
516static void adjust_date_range(struct tm *min, struct tm *max)
517{
518 if ((min->tm_year > max->tm_year) ||
519 ((min->tm_year == max->tm_year) && (min->tm_mon > max->tm_mon)) ||
520 ((min->tm_year == max->tm_year) && (min->tm_mon == max->tm_mon) &&
521 (min->tm_mday > max->tm_mday)))
522 {
523 int tmp;
524
525 tmp = min->tm_year;
526 min->tm_year = max->tm_year;
527 max->tm_year = tmp;
528
529 tmp = min->tm_mon;
530 min->tm_mon = max->tm_mon;
531 max->tm_mon = tmp;
532
533 tmp = min->tm_mday;
534 min->tm_mday = max->tm_mday;
535 max->tm_mday = tmp;
536
537 min->tm_hour = 0;
538 min->tm_min = 0;
539 min->tm_sec = 0;
540 max->tm_hour = 23;
541 max->tm_min = 59;
542 max->tm_sec = 59;
543 }
544}
545
554bool eval_date_minmax(struct Pattern *pat, const char *s, struct Buffer *err)
555{
556 /* the '0' time is Jan 1, 1970 UTC, so in order to prevent a negative time
557 * when doing timezone conversion, we use Jan 2, 1970 UTC as the base here */
558 struct tm min = { 0 };
559 min.tm_mday = 2;
560 min.tm_year = 70;
561
562 /* Arbitrary year in the future. Don't set this too high or
563 * mutt_date_make_time() returns something larger than will fit in a time_t
564 * on some systems */
565 struct tm max = { 0 };
566 max.tm_year = 137;
567 max.tm_mon = 11;
568 max.tm_mday = 31;
569 max.tm_hour = 23;
570 max.tm_min = 59;
571 max.tm_sec = 59;
572
573 if (strchr("<>=", s[0]))
574 {
575 /* offset from current time
576 * <3d less than three days ago
577 * >3d more than three days ago
578 * =3d exactly three days ago */
579 struct tm *tm = NULL;
580 bool exact = false;
581
582 if (s[0] == '<')
583 {
585 tm = &min;
586 }
587 else
588 {
590 tm = &max;
591
592 if (s[0] == '=')
593 exact = true;
594 }
595
596 /* Reset the HMS unless we are relative matching using one of those
597 * offsets. */
598 char *offset_type = NULL;
599 strtol(s + 1, &offset_type, 0);
600 if (!(*offset_type && strchr("HMS", *offset_type)))
601 {
602 tm->tm_hour = 23;
603 tm->tm_min = 59;
604 tm->tm_sec = 59;
605 }
606
607 /* force negative offset */
608 get_offset(tm, s + 1, -1);
609
610 if (exact)
611 {
612 /* start at the beginning of the day in question */
613 memcpy(&min, &max, sizeof(max));
614 min.tm_hour = 0;
615 min.tm_sec = 0;
616 min.tm_min = 0;
617 }
618 }
619 else
620 {
621 const char *pc = s;
622
623 bool have_min = false;
624 bool until_now = false;
625 if (mutt_isdigit(*pc))
626 {
627 /* minimum date specified */
628 pc = get_date(pc, &min, err);
629 if (!pc)
630 {
631 return false;
632 }
633 have_min = true;
634 SKIPWS(pc);
635 if (*pc == '-')
636 {
637 const char *pt = pc + 1;
638 SKIPWS(pt);
639 until_now = (*pt == '\0');
640 }
641 }
642
643 if (!until_now)
644 { /* max date or relative range/window */
645
646 struct tm base_min = { 0 };
647
648 if (!have_min)
649 { /* save base minimum and set current date, e.g. for "-3d+1d" */
650 memcpy(&base_min, &min, sizeof(base_min));
652 min.tm_hour = 0;
653 min.tm_sec = 0;
654 min.tm_min = 0;
655 }
656
657 /* preset max date for relative offsets,
658 * if nothing follows we search for messages on a specific day */
659 max.tm_year = min.tm_year;
660 max.tm_mon = min.tm_mon;
661 max.tm_mday = min.tm_mday;
662
663 if (!parse_date_range(pc, &min, &max, have_min, &base_min, err))
664 { /* bail out on any parsing error */
665 return false;
666 }
667 }
668 }
669
670 /* Since we allow two dates to be specified we'll have to adjust that. */
671 adjust_date_range(&min, &max);
672
673 pat->min = mutt_date_make_time(&min, true);
674 pat->max = mutt_date_make_time(&max, true);
675
676 return true;
677}
678
682static bool eat_range(struct Pattern *pat, PatternCompFlags flags,
683 struct Buffer *s, struct Buffer *err)
684{
685 char *tmp = NULL;
686 bool do_exclusive = false;
687 bool skip_quote = false;
688
689 /* If simple_search is set to "~m %s", the range will have double quotes
690 * around it... */
691 if (*s->dptr == '"')
692 {
693 s->dptr++;
694 skip_quote = true;
695 }
696 if (*s->dptr == '<')
697 do_exclusive = true;
698 if ((*s->dptr != '-') && (*s->dptr != '<'))
699 {
700 /* range minimum */
701 if (*s->dptr == '>')
702 {
703 pat->max = MUTT_MAXRANGE;
704 pat->min = strtol(s->dptr + 1, &tmp, 0) + 1; /* exclusive range */
705 }
706 else
707 {
708 pat->min = strtol(s->dptr, &tmp, 0);
709 }
710 if (mutt_toupper(*tmp) == 'K') /* is there a prefix? */
711 {
712 pat->min *= 1024;
713 tmp++;
714 }
715 else if (mutt_toupper(*tmp) == 'M')
716 {
717 pat->min *= 1048576;
718 tmp++;
719 }
720 if (*s->dptr == '>')
721 {
722 s->dptr = tmp;
723 return true;
724 }
725 if (*tmp != '-')
726 {
727 /* exact value */
728 pat->max = pat->min;
729 s->dptr = tmp;
730 return true;
731 }
732 tmp++;
733 }
734 else
735 {
736 s->dptr++;
737 tmp = s->dptr;
738 }
739
740 if (mutt_isdigit(*tmp))
741 {
742 /* range maximum */
743 pat->max = strtol(tmp, &tmp, 0);
744 if (mutt_toupper(*tmp) == 'K')
745 {
746 pat->max *= 1024;
747 tmp++;
748 }
749 else if (mutt_toupper(*tmp) == 'M')
750 {
751 pat->max *= 1048576;
752 tmp++;
753 }
754 if (do_exclusive)
755 (pat->max)--;
756 }
757 else
758 {
759 pat->max = MUTT_MAXRANGE;
760 }
761
762 if (skip_quote && (*tmp == '"'))
763 tmp++;
764
765 SKIPWS(tmp);
766 s->dptr = tmp;
767 return true;
768}
769
773static bool eat_date(struct Pattern *pat, PatternCompFlags flags,
774 struct Buffer *s, struct Buffer *err)
775{
776 struct Buffer *tmp = buf_pool_get();
777 bool rc = false;
778
779 char *pexpr = s->dptr;
781 {
782 buf_printf(err, _("Error in expression: %s"), pexpr);
783 goto out;
784 }
785
786 if (buf_is_empty(tmp))
787 {
788 buf_addstr(err, _("Empty expression"));
789 goto out;
790 }
791
792 if (flags & MUTT_PC_PATTERN_DYNAMIC)
793 {
794 pat->dynamic = true;
795 pat->p.str = buf_strdup(tmp);
796 }
797
798 rc = eval_date_minmax(pat, tmp->data, err);
799
800out:
801 buf_pool_release(&tmp);
802 return rc;
803}
804
812static /* const */ char *find_matching_paren(/* const */ char *s)
813{
814 int level = 1;
815
816 for (; *s; s++)
817 {
818 if (*s == '(')
819 {
820 level++;
821 }
822 else if (*s == ')')
823 {
824 level--;
825 if (level == 0)
826 break;
827 }
828 }
829 return s;
830}
831
836void mutt_pattern_free(struct PatternList **pat)
837{
838 if (!pat || !*pat)
839 return;
840
841 struct Pattern *np = SLIST_FIRST(*pat);
842 struct Pattern *next = NULL;
843
844 while (np)
845 {
846 next = SLIST_NEXT(np, entries);
847
848 if (np->is_multi)
849 {
851 }
852 else if (np->string_match || np->dynamic)
853 {
854 FREE(&np->p.str);
855 }
856 else if (np->group_match)
857 {
858 np->p.group = NULL;
859 }
860 else if (np->p.regex)
861 {
862 regfree(np->p.regex);
863 FREE(&np->p.regex);
864 }
865
866#ifdef USE_DEBUG_GRAPHVIZ
867 FREE(&np->raw_pattern);
868#endif
870 FREE(&np);
871
872 np = next;
873 }
874
875 FREE(pat);
876}
877
882static struct Pattern *mutt_pattern_new(void)
883{
884 return MUTT_MEM_CALLOC(1, struct Pattern);
885}
886
891static struct PatternList *mutt_pattern_list_new(void)
892{
893 struct PatternList *h = MUTT_MEM_CALLOC(1, struct PatternList);
894 SLIST_INIT(h);
895 struct Pattern *p = mutt_pattern_new();
896 SLIST_INSERT_HEAD(h, p, entries);
897 return h;
898}
899
906static struct Pattern *attach_leaf(struct PatternList *list, struct Pattern *leaf)
907{
908 struct Pattern *last = NULL;
909 SLIST_FOREACH(last, list, entries)
910 {
911 // TODO - or we could use a doubly-linked list
912 if (!SLIST_NEXT(last, entries))
913 {
914 SLIST_NEXT(last, entries) = leaf;
915 break;
916 }
917 }
918 return leaf;
919}
920
928static struct Pattern *attach_new_root(struct PatternList **curlist)
929{
930 struct PatternList *root = mutt_pattern_list_new();
931 struct Pattern *leaf = SLIST_FIRST(root);
932 leaf->child = *curlist;
933 *curlist = root;
934 return leaf;
935}
936
944static struct Pattern *attach_new_leaf(struct PatternList **curlist)
945{
946 if (*curlist)
947 {
948 return attach_leaf(*curlist, mutt_pattern_new());
949 }
950 else
951 {
952 return attach_new_root(curlist);
953 }
954}
955
964struct PatternList *mutt_pattern_comp(struct MailboxView *mv, const char *s,
965 PatternCompFlags flags, struct Buffer *err)
966{
967 /* curlist when assigned will always point to a list containing at least one node
968 * with a Pattern value. */
969 struct PatternList *curlist = NULL;
970 bool pat_not = false;
971 bool all_addr = false;
972 bool pat_or = false;
973 bool implicit = true; /* used to detect logical AND operator */
974 bool is_alias = false;
975 const struct PatternFlags *entry = NULL;
976 char *p = NULL;
977 char *buf = NULL;
978 struct Mailbox *m = mv ? mv->mailbox : NULL;
979
980 if (!s || (s[0] == '\0'))
981 {
982 buf_strcpy(err, _("empty pattern"));
983 return NULL;
984 }
985
986 struct Buffer *ps = buf_pool_get();
987 buf_strcpy(ps, s);
988 buf_seek(ps, 0);
989
990 SKIPWS(ps->dptr);
991 while (*ps->dptr)
992 {
993 switch (*ps->dptr)
994 {
995 case '^':
996 ps->dptr++;
997 all_addr = !all_addr;
998 break;
999 case '!':
1000 ps->dptr++;
1001 pat_not = !pat_not;
1002 break;
1003 case '@':
1004 ps->dptr++;
1005 is_alias = !is_alias;
1006 break;
1007 case '|':
1008 if (!pat_or)
1009 {
1010 if (!curlist)
1011 {
1012 buf_printf(err, _("error in pattern at: %s"), ps->dptr);
1013 buf_pool_release(&ps);
1014 return NULL;
1015 }
1016
1017 struct Pattern *pat = SLIST_FIRST(curlist);
1018 if (SLIST_NEXT(pat, entries))
1019 {
1020 /* A & B | C == (A & B) | C */
1021 struct Pattern *root = attach_new_root(&curlist);
1022 root->op = MUTT_PAT_AND;
1023 }
1024
1025 pat_or = true;
1026 }
1027 ps->dptr++;
1028 implicit = false;
1029 pat_not = false;
1030 all_addr = false;
1031 is_alias = false;
1032 break;
1033 case '%':
1034 case '=':
1035 case '~':
1036 {
1037 if (ps->dptr[1] == '\0')
1038 {
1039 buf_printf(err, _("missing pattern: %s"), ps->dptr);
1040 goto cleanup;
1041 }
1042 short thread_op = 0;
1043 if (ps->dptr[1] == '(')
1044 thread_op = MUTT_PAT_THREAD;
1045 else if ((ps->dptr[1] == '<') && (ps->dptr[2] == '('))
1046 thread_op = MUTT_PAT_PARENT;
1047 else if ((ps->dptr[1] == '>') && (ps->dptr[2] == '('))
1048 thread_op = MUTT_PAT_CHILDREN;
1049 if (thread_op != 0)
1050 {
1051 ps->dptr++; /* skip ~ */
1052 if ((thread_op == MUTT_PAT_PARENT) || (thread_op == MUTT_PAT_CHILDREN))
1053 ps->dptr++;
1054 p = find_matching_paren(ps->dptr + 1);
1055 if (p[0] != ')')
1056 {
1057 buf_printf(err, _("mismatched parentheses: %s"), ps->dptr);
1058 goto cleanup;
1059 }
1060 struct Pattern *leaf = attach_new_leaf(&curlist);
1061 leaf->op = thread_op;
1062 leaf->pat_not = pat_not;
1063 leaf->all_addr = all_addr;
1064 leaf->is_alias = is_alias;
1065 pat_not = false;
1066 all_addr = false;
1067 is_alias = false;
1068 /* compile the sub-expression */
1069 buf = mutt_strn_dup(ps->dptr + 1, p - (ps->dptr + 1));
1070 leaf->child = mutt_pattern_comp(mv, buf, flags, err);
1071 if (!leaf->child)
1072 {
1073 FREE(&buf);
1074 goto cleanup;
1075 }
1076 FREE(&buf);
1077 ps->dptr = p + 1; /* restore location */
1078 break;
1079 }
1080 if (implicit && pat_or)
1081 {
1082 /* A | B & C == (A | B) & C */
1083 struct Pattern *root = attach_new_root(&curlist);
1084 root->op = MUTT_PAT_OR;
1085 pat_or = false;
1086 }
1087
1088 char prefix = ps->dptr[0];
1089 entry = lookup_tag(prefix, ps->dptr[1]);
1090 if (!entry)
1091 {
1092 buf_printf(err, _("%c%c: invalid pattern"), prefix, ps->dptr[1]);
1093 goto cleanup;
1094 }
1095 if (entry->flags && ((flags & entry->flags) == 0))
1096 {
1097 buf_printf(err, _("%c%c: not supported in this mode"), prefix, ps->dptr[1]);
1098 goto cleanup;
1099 }
1100
1101 struct Pattern *leaf = attach_new_leaf(&curlist);
1102 leaf->pat_not = pat_not;
1103 leaf->all_addr = all_addr;
1104 leaf->is_alias = is_alias;
1105 leaf->sendmode = (flags & MUTT_PC_SEND_MODE_SEARCH);
1106 leaf->op = entry->op;
1107 pat_not = false;
1108 all_addr = false;
1109 is_alias = false;
1110
1111 // Determine the actual eat_arg to use.
1112 // If the entry was found via fallback (entry->prefix is '~' but we used '=' or '%'),
1113 // override the eat_arg to use string or group parsing respectively.
1114 enum PatternEat eat_arg = entry->eat_arg;
1115 if ((entry->prefix == '~') && (prefix == '=') && (eat_arg == EAT_REGEX))
1116 eat_arg = EAT_STRING;
1117 else if ((entry->prefix == '~') && (prefix == '%') && (eat_arg == EAT_REGEX))
1118 eat_arg = EAT_GROUP;
1119
1120 ps->dptr++; /* move past the prefix (~, %, =) */
1121 ps->dptr++; /* eat the operator and any optional whitespace */
1122 SKIPWS(ps->dptr);
1123 if (eat_arg)
1124 {
1125 if (ps->dptr[0] == '\0')
1126 {
1127 buf_addstr(err, _("missing parameter"));
1128 goto cleanup;
1129 }
1130 switch (eat_arg)
1131 {
1132 case EAT_REGEX:
1133 if (!eat_regex(leaf, flags, ps, err))
1134 goto cleanup;
1135 break;
1136 case EAT_STRING:
1137 if (!eat_string(leaf, flags, ps, err))
1138 goto cleanup;
1139 break;
1140 case EAT_GROUP:
1141 if (!eat_group(leaf, flags, ps, err))
1142 goto cleanup;
1143 break;
1144 case EAT_DATE:
1145 if (!eat_date(leaf, flags, ps, err))
1146 goto cleanup;
1147 break;
1148 case EAT_RANGE:
1149 if (!eat_range(leaf, flags, ps, err))
1150 goto cleanup;
1151 break;
1152 case EAT_MESSAGE_RANGE:
1153 if (!eat_message_range(leaf, flags, ps, err, mv))
1154 goto cleanup;
1155 break;
1156 case EAT_QUERY:
1157 if (!eat_query(leaf, flags, ps, err, m))
1158 goto cleanup;
1159 break;
1160 default:
1161 break;
1162 }
1163 }
1164 implicit = true;
1165 break;
1166 }
1167
1168 case '(':
1169 {
1170 p = find_matching_paren(ps->dptr + 1);
1171 if (p[0] != ')')
1172 {
1173 buf_printf(err, _("mismatched parentheses: %s"), ps->dptr);
1174 goto cleanup;
1175 }
1176 /* compile the sub-expression */
1177 buf = mutt_strn_dup(ps->dptr + 1, p - (ps->dptr + 1));
1178 struct PatternList *sub = mutt_pattern_comp(mv, buf, flags, err);
1179 FREE(&buf);
1180 if (!sub)
1181 goto cleanup;
1182 struct Pattern *leaf = SLIST_FIRST(sub);
1183 if (curlist)
1184 {
1185 attach_leaf(curlist, leaf);
1186 FREE(&sub);
1187 }
1188 else
1189 {
1190 curlist = sub;
1191 }
1192 leaf->pat_not ^= pat_not;
1193 leaf->all_addr |= all_addr;
1194 leaf->is_alias |= is_alias;
1195 pat_not = false;
1196 all_addr = false;
1197 is_alias = false;
1198 ps->dptr = p + 1; /* restore location */
1199 break;
1200 }
1201
1202 default:
1203 buf_printf(err, _("error in pattern at: %s"), ps->dptr);
1204 goto cleanup;
1205 }
1206 SKIPWS(ps->dptr);
1207 }
1208 buf_pool_release(&ps);
1209
1210 if (!curlist)
1211 {
1212 buf_strcpy(err, _("empty pattern"));
1213 return NULL;
1214 }
1215
1216 if (SLIST_NEXT(SLIST_FIRST(curlist), entries))
1217 {
1218 struct Pattern *root = attach_new_root(&curlist);
1219 root->op = pat_or ? MUTT_PAT_OR : MUTT_PAT_AND;
1220 }
1221
1222 return curlist;
1223
1224cleanup:
1225 mutt_pattern_free(&curlist);
1226 buf_pool_release(&ps);
1227 return NULL;
1228}
struct Group * groups_get_group(struct HashTable *groups, const char *name)
Get a Group by its name.
Definition group.c:291
Email Address Handling.
Email Aliases.
int buf_printf(struct Buffer *buf, const char *fmt,...)
Format a string overwriting a Buffer.
Definition buffer.c:161
void buf_seek(struct Buffer *buf, size_t offset)
Set current read/write position to offset from beginning.
Definition buffer.c:622
bool buf_is_empty(const struct Buffer *buf)
Is the Buffer empty?
Definition buffer.c:291
size_t buf_addch(struct Buffer *buf, char c)
Add a single character to a Buffer.
Definition buffer.c:241
size_t buf_addstr(struct Buffer *buf, const char *s)
Add a string to a Buffer.
Definition buffer.c:226
size_t buf_strcpy(struct Buffer *buf, const char *s)
Copy a string into a Buffer.
Definition buffer.c:395
char * buf_strdup(const struct Buffer *buf)
Copy a Buffer's string.
Definition buffer.c:571
uint8_t ParseDateRangeFlags
Definition compile.c:65
bool eval_date_minmax(struct Pattern *pat, const char *s, struct Buffer *err)
Evaluate a date-range pattern against 'now'.
Definition compile.c:554
static struct Pattern * attach_new_root(struct PatternList **curlist)
Create a new Pattern as a parent for a List.
Definition compile.c:928
static struct Pattern * attach_new_leaf(struct PatternList **curlist)
Attach a new Pattern to a List.
Definition compile.c:944
struct PatternList * mutt_pattern_comp(struct MailboxView *mv, const char *s, PatternCompFlags flags, struct Buffer *err)
Create a Pattern.
Definition compile.c:964
static struct Pattern * attach_leaf(struct PatternList *list, struct Pattern *leaf)
Attach a Pattern to a Pattern List.
Definition compile.c:906
static const char * parse_date_range(const char *pc, struct tm *min, struct tm *max, bool have_min, struct tm *base_min, struct Buffer *err)
Parse a date range.
Definition compile.c:422
static char * find_matching_paren(char *s)
Find the matching parenthesis.
Definition compile.c:812
static void adjust_date_range(struct tm *min, struct tm *max)
Put a date range in the correct order.
Definition compile.c:516
static struct Pattern * mutt_pattern_new(void)
Create a new Pattern.
Definition compile.c:882
static const char * get_offset(struct tm *tm, const char *s, int sign)
Calculate a symbolic offset.
Definition compile.c:282
static const char * get_date(const char *s, struct tm *t, struct Buffer *err)
Parse a (partial) date in dd/mm/yyyy format.
Definition compile.c:336
static struct PatternList * mutt_pattern_list_new(void)
Create a new list containing a Pattern.
Definition compile.c:891
#define MUTT_PDR_ERRORDONE
Definition compile.c:67
ParseDateRangeFlag
Flags for parse_date_range(), e.g.
Definition compile.c:54
@ MUTT_PDR_MINUS
Pattern contains a range.
Definition compile.c:57
@ MUTT_PDR_NONE
No flags are set.
Definition compile.c:56
@ MUTT_PDR_WINDOW
Extend the range in both directions using '*'.
Definition compile.c:59
@ MUTT_PDR_DONE
Pattern parse successfully.
Definition compile.c:61
@ MUTT_PDR_PLUS
Extend the range using '+'.
Definition compile.c:58
@ MUTT_PDR_ERROR
Invalid pattern.
Definition compile.c:62
@ MUTT_PDR_ABSOLUTE
Absolute pattern range.
Definition compile.c:60
void mutt_pattern_free(struct PatternList **pat)
Free a Pattern.
Definition compile.c:836
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.
static const char * mailbox_path(const struct Mailbox *m)
Get the Mailbox's path string.
Definition mailbox.h:216
int mutt_toupper(int arg)
Wrapper for toupper(3)
Definition ctype.c:140
bool mutt_isdigit(int arg)
Wrapper for isdigit(3)
Definition ctype.c:66
int parse_extract_token(struct Buffer *dest, struct Buffer *line, TokenFlags flags)
Extract one token from a string.
Definition extract.c:49
@ TOKEN_COMMENT
Don't reap comments.
Definition extract.h:55
@ TOKEN_PATTERN
~%=!| are terms (for patterns)
Definition extract.h:54
bool mutt_file_map_lines(mutt_file_map_t func, void *user_data, FILE *fp, ReadLineFlags flags)
Process lines of text read from a file pointer.
Definition file.c:780
#define mutt_file_fclose(FP)
Definition file.h:144
@ MUTT_RL_NONE
No flags are set.
Definition file.h:43
static bool eat_group(struct Pattern *pat, PatternCompFlags flags, struct Buffer *s, struct Buffer *err)
Parse a group name - Implements eat_arg_t -.
Definition compile.c:147
static bool eat_string(struct Pattern *pat, PatternCompFlags flags, struct Buffer *s, struct Buffer *err)
Parse a plain string - Implements eat_arg_t -.
Definition compile.c:115
static bool eat_query(struct Pattern *pat, PatternCompFlags flags, struct Buffer *s, struct Buffer *err, struct Mailbox *m)
Parse a query for an external search program - Implements eat_arg_t -.
Definition compile.c:202
bool eat_message_range(struct Pattern *pat, PatternCompFlags flags, struct Buffer *s, struct Buffer *err, struct MailboxView *mv)
Parse a range of message numbers - Implements eat_arg_t -.
Definition message.c:283
static bool eat_range(struct Pattern *pat, PatternCompFlags flags, struct Buffer *s, struct Buffer *err)
Parse a number range - Implements eat_arg_t -.
Definition compile.c:682
static bool eat_date(struct Pattern *pat, PatternCompFlags flags, struct Buffer *s, struct Buffer *err)
Parse a date pattern - Implements eat_arg_t -.
Definition compile.c:773
static bool eat_regex(struct Pattern *pat, PatternCompFlags flags, struct Buffer *s, struct Buffer *err)
Parse a regex - Implements eat_arg_t -.
Definition compile.c:72
#define mutt_message(...)
Definition logging2.h:93
#define mutt_debug(LEVEL,...)
Definition logging2.h:91
static bool add_query_msgid(char *line, int line_num, void *user_data)
Parse a Message-ID and add it to a list - Implements mutt_file_map_t -.
Definition compile.c:182
Convenience wrapper for the gui headers.
struct ListNode * mutt_list_insert_tail(struct ListHead *h, char *s)
Append a string to the end of a List.
Definition list.c:65
void mutt_list_clear(struct ListHead *h)
Free a list, but NOT its strings.
Definition list.c:166
void mutt_list_free(struct ListHead *h)
Free a List AND its strings.
Definition list.c:123
@ LL_DEBUG2
Log at debug level 2.
Definition logging2.h:46
bool mutt_mb_is_lower(const char *s)
Does a multi-byte string contain only lowercase characters?
Definition mbyte.c:355
#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_ALIAS
ModuleAlias, Alias
Definition module_api.h:48
struct tm mutt_date_localtime(time_t t)
Converts calendar time to a broken-down time structure expressed in user timezone.
Definition date.c:907
time_t mutt_date_make_time(struct tm *t, bool local)
Convert struct tm to time_t
Definition date.c:243
time_t mutt_date_now(void)
Return the number of seconds since the Unix epoch.
Definition date.c:457
void mutt_date_normalize_time(struct tm *tm)
Fix the contents of a struct tm.
Definition date.c:311
int filter_wait(pid_t pid)
Wait for the exit of a process and return its status.
Definition filter.c:228
pid_t filter_create(const char *cmd, FILE **fp_in, FILE **fp_out, FILE **fp_err, char **envlist)
Set up filter program.
Definition filter.c:217
Convenience wrapper for the library headers.
#define _(a)
Definition message.h:28
char * mutt_path_escape(const char *src)
Escapes single quotes in a path for a command string.
Definition path.c:433
char * mutt_strn_dup(const char *begin, size_t len)
Duplicate a sub-string.
Definition string.c:384
void mutt_str_remove_trailing_ws(char *s)
Trim trailing whitespace from a string.
Definition string.c:570
char * mutt_str_dup(const char *str)
Copy a string, safely.
Definition string.c:257
char * mutt_str_skip_whitespace(const char *p)
Find the first non-whitespace character in a string.
Definition string.c:556
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.
const struct PatternFlags * lookup_tag(char prefix, char tag)
Lookup a pattern modifier.
Definition flags.c:235
Match patterns to emails.
uint8_t PatternCompFlags
Definition lib.h:78
@ MUTT_PAT_OR
Either pattern can match.
Definition lib.h:151
@ MUTT_PAT_CHILDREN
Pattern matches a child email.
Definition lib.h:154
@ MUTT_PAT_PARENT
Pattern matches parent.
Definition lib.h:153
@ MUTT_PAT_AND
Both patterns must match.
Definition lib.h:150
@ MUTT_PAT_THREAD
Pattern matches email thread.
Definition lib.h:152
@ MUTT_PC_PATTERN_DYNAMIC
Enable runtime date range evaluation.
Definition lib.h:75
@ MUTT_PC_SEND_MODE_SEARCH
Allow send-mode body searching.
Definition lib.h:76
Shared constants/structs that are private to libpattern.
PatternEat
Function to process pattern arguments.
Definition private.h:51
@ EAT_STRING
Process a plain string.
Definition private.h:59
@ EAT_RANGE
Process a number (range)
Definition private.h:57
@ EAT_GROUP
Process a group name.
Definition private.h:54
@ EAT_MESSAGE_RANGE
Process a message number (range)
Definition private.h:55
@ EAT_DATE
Process a date (range)
Definition private.h:53
@ EAT_QUERY
Process a query string.
Definition private.h:56
@ EAT_REGEX
Process a regex.
Definition private.h:58
#define MUTT_MAXRANGE
Definition private.h:149
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 SLIST_FOREACH(var, head, field)
Definition queue.h:229
#define SLIST_INIT(head)
Definition queue.h:254
#define SLIST_INSERT_HEAD(head, elm, field)
Definition queue.h:263
#define SLIST_NEXT(elm, field)
Definition queue.h:268
#define SLIST_FIRST(head)
Definition queue.h:227
#define REG_COMP(preg, regex, cflags)
Compile a regular expression.
Definition regex3.h:49
#define ASSERT(COND)
Definition signal2.h:59
#define SKIPWS(ch)
Definition string2.h:52
Alias private Module data.
Definition module_data.h:33
struct HashTable * groups
Hash Table: "group-name" -> Group.
Definition module_data.h:45
String manipulation buffer.
Definition buffer.h:36
char * dptr
Current read/write position.
Definition buffer.h:38
char * data
Pointer to data.
Definition buffer.h:37
View of a Mailbox.
Definition mview.h:40
struct Mailbox * mailbox
Current Mailbox.
Definition mview.h:51
A mailbox.
Definition mailbox.h:81
Container for Accounts, Notifications.
Definition neomutt.h:41
char ** env
Private copy of the environment variables.
Definition neomutt.h:57
struct ConfigSubset * sub
Inherited config items.
Definition neomutt.h:49
Mapping between user character and internal constant.
Definition private.h:66
enum PatternEat eat_arg
Type of function needed to parse flag, e.g. EAT_DATE.
Definition private.h:72
PatternCompFlags flags
Pattern flags, e.g. MUTT_PC_FULL_MSG.
Definition private.h:70
int op
Operation to perform, e.g. MUTT_PAT_SCORE.
Definition private.h:69
char prefix
Prefix for this pattern, e.g. '~', '', or '='.
Definition private.h:67
A simple (non-regex) pattern.
Definition lib.h:84
bool group_match
Check a group of Addresses.
Definition lib.h:89
union Pattern::@006112053024257132210207314205210350156165326341 p
bool all_addr
All Addresses in the list must match.
Definition lib.h:87
struct Group * group
Address group if group_match is set.
Definition lib.h:100
struct PatternList * child
Arguments to logical operation.
Definition lib.h:97
long min
Minimum for range checks.
Definition lib.h:95
bool string_match
Check a string for a match.
Definition lib.h:88
regex_t * regex
Compiled regex, for non-pattern matching.
Definition lib.h:99
struct ListHead multi_cases
Multiple strings for ~I pattern.
Definition lib.h:102
char * str
String, if string_match is set.
Definition lib.h:101
bool is_alias
Is there an alias for this Address?
Definition lib.h:91
bool ign_case
Ignore case for local string_match searches.
Definition lib.h:90
long max
Maximum for range checks.
Definition lib.h:96
bool dynamic
Evaluate date ranges at run time.
Definition lib.h:92
short op
Operation, e.g. MUTT_PAT_SCORE.
Definition lib.h:85
bool sendmode
Evaluate searches in send-mode.
Definition lib.h:93
bool is_multi
Multiple case (only for ~I pattern now)
Definition lib.h:94
bool pat_not
Pattern should be inverted (not)
Definition lib.h:86