NeoMutt  2025-12-11-219-g274730
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 "gui/lib.h"
46#include "lib.h"
47#include "parse/lib.h"
48
49// clang-format off
50typedef uint16_t ParseDateRangeFlags;
51#define MUTT_PDR_NO_FLAGS 0
52#define MUTT_PDR_MINUS (1 << 0)
53#define MUTT_PDR_PLUS (1 << 1)
54#define MUTT_PDR_WINDOW (1 << 2)
55#define MUTT_PDR_ABSOLUTE (1 << 3)
56#define MUTT_PDR_DONE (1 << 4)
57#define MUTT_PDR_ERROR (1 << 8)
58// clang-format on
59
60#define MUTT_PDR_ERRORDONE (MUTT_PDR_ERROR | MUTT_PDR_DONE)
61
65static bool eat_regex(struct Pattern *pat, PatternCompFlags flags,
66 struct Buffer *s, struct Buffer *err)
67{
68 struct Buffer *token = buf_pool_get();
69 bool rc = false;
70 const char *pexpr = s->dptr;
71 if ((parse_extract_token(token, s, TOKEN_PATTERN | TOKEN_COMMENT) != 0) ||
72 !token->data)
73 {
74 buf_printf(err, _("Error in expression: %s"), pexpr);
75 goto out;
76 }
77 if (buf_is_empty(token))
78 {
79 buf_addstr(err, _("Empty expression"));
80 goto out;
81 }
82
83 pat->p.regex = MUTT_MEM_CALLOC(1, regex_t);
84#ifdef USE_DEBUG_GRAPHVIZ
85 pat->raw_pattern = buf_strdup(token);
86#endif
87 uint16_t case_flags = mutt_mb_is_lower(token->data) ? REG_ICASE : 0;
88 int rc2 = REG_COMP(pat->p.regex, token->data, REG_NEWLINE | REG_NOSUB | case_flags);
89 if (rc2 != 0)
90 {
91 char errmsg[256] = { 0 };
92 regerror(rc2, pat->p.regex, errmsg, sizeof(errmsg));
93 buf_printf(err, "'%s': %s", token->data, errmsg);
94 FREE(&pat->p.regex);
95 goto out;
96 }
97
98 rc = true;
99
100out:
101 buf_pool_release(&token);
102 return rc;
103}
104
108static bool eat_string(struct Pattern *pat, PatternCompFlags flags,
109 struct Buffer *s, struct Buffer *err)
110{
111 struct Buffer *token = buf_pool_get();
112 bool rc = false;
113 const char *pexpr = s->dptr;
114 if ((parse_extract_token(token, s, TOKEN_PATTERN | TOKEN_COMMENT) != 0) ||
115 !token->data)
116 {
117 buf_printf(err, _("Error in expression: %s"), pexpr);
118 goto out;
119 }
120 if (buf_is_empty(token))
121 {
122 buf_addstr(err, _("Empty expression"));
123 goto out;
124 }
125
126 pat->string_match = true;
127 pat->p.str = buf_strdup(token);
128 pat->ign_case = mutt_mb_is_lower(token->data);
129
130 rc = true;
131
132out:
133 buf_pool_release(&token);
134 return rc;
135}
136
140static bool eat_group(struct Pattern *pat, PatternCompFlags flags,
141 struct Buffer *s, struct Buffer *err)
142{
143 struct Buffer *token = buf_pool_get();
144 bool rc = false;
145 const char *pexpr = s->dptr;
146 if ((parse_extract_token(token, s, TOKEN_PATTERN | TOKEN_COMMENT) != 0) ||
147 !token->data)
148 {
149 buf_printf(err, _("Error in expression: %s"), pexpr);
150 goto out;
151 }
152 if (buf_is_empty(token))
153 {
154 buf_addstr(err, _("Empty expression"));
155 goto out;
156 }
157
158 pat->group_match = true;
159 pat->p.group = groups_get_group(NeoMutt->groups, token->data);
160
161 rc = true;
162
163out:
164 buf_pool_release(&token);
165 return rc;
166}
167
172static bool add_query_msgid(char *line, int line_num, void *user_data)
173{
174 struct ListHead *msgid_list = (struct ListHead *) (user_data);
175 char *nows = mutt_str_skip_whitespace(line);
176 if (*nows == '\0')
177 return true;
179 mutt_list_insert_tail(msgid_list, mutt_str_dup(nows));
180 return true;
181}
182
192static bool eat_query(struct Pattern *pat, PatternCompFlags flags,
193 struct Buffer *s, struct Buffer *err, struct Mailbox *m)
194{
195 struct Buffer *cmd = buf_pool_get();
196 struct Buffer *tok = buf_pool_get();
197 bool rc = false;
198
199 FILE *fp = NULL;
200
201 const char *const c_external_search_command = cs_subset_string(NeoMutt->sub, "external_search_command");
202 if (!c_external_search_command)
203 {
204 buf_addstr(err, _("No search command defined"));
205 goto out;
206 }
207
208 char *pexpr = s->dptr;
209 if ((parse_extract_token(tok, s, TOKEN_PATTERN | TOKEN_COMMENT) != 0) || !tok->data)
210 {
211 buf_printf(err, _("Error in expression: %s"), pexpr);
212 goto out;
213 }
214 if (*tok->data == '\0')
215 {
216 buf_addstr(err, _("Empty expression"));
217 goto out;
218 }
219
220 buf_addstr(cmd, c_external_search_command);
221 buf_addch(cmd, ' ');
222
223 if (m)
224 {
225 char *escaped_folder = mutt_path_escape(mailbox_path(m));
226 mutt_debug(LL_DEBUG2, "escaped folder path: %s\n", escaped_folder);
227 buf_addch(cmd, '\'');
228 buf_addstr(cmd, escaped_folder);
229 buf_addch(cmd, '\'');
230 }
231 else
232 {
233 buf_addch(cmd, '/');
234 }
235 buf_addch(cmd, ' ');
236 buf_addstr(cmd, tok->data);
237
238 mutt_message(_("Running search command: %s ..."), cmd->data);
239 pat->is_multi = true;
241 pid_t pid = filter_create(cmd->data, NULL, &fp, NULL, NeoMutt->env);
242 if (pid < 0)
243 {
244 buf_printf(err, "unable to fork command: %s\n", cmd->data);
245 goto out;
246 }
247
249 mutt_file_fclose(&fp);
250 filter_wait(pid);
251
252 rc = true;
253
254out:
255 buf_pool_release(&cmd);
256 buf_pool_release(&tok);
257 return rc;
258}
259
272static const char *get_offset(struct tm *tm, const char *s, int sign)
273{
274 char *ps = NULL;
275 int offset = strtol(s, &ps, 0);
276 if (((sign < 0) && (offset > 0)) || ((sign > 0) && (offset < 0)))
277 offset = -offset;
278
279 switch (*ps)
280 {
281 case 'y':
282 tm->tm_year += offset;
283 break;
284 case 'm':
285 tm->tm_mon += offset;
286 break;
287 case 'w':
288 tm->tm_mday += 7 * offset;
289 break;
290 case 'd':
291 tm->tm_mday += offset;
292 break;
293 case 'H':
294 tm->tm_hour += offset;
295 break;
296 case 'M':
297 tm->tm_min += offset;
298 break;
299 case 'S':
300 tm->tm_sec += offset;
301 break;
302 default:
303 return s;
304 }
306 return ps + 1;
307}
308
326static const char *get_date(const char *s, struct tm *t, struct Buffer *err)
327{
328 char *p = NULL;
329 struct tm tm = mutt_date_localtime(mutt_date_now());
330 bool iso8601 = true;
331
332 for (int v = 0; v < 8; v++)
333 {
334 if (s[v] && (s[v] >= '0') && (s[v] <= '9'))
335 continue;
336
337 iso8601 = false;
338 break;
339 }
340
341 if (iso8601)
342 {
343 int year = 0;
344 int month = 0;
345 int mday = 0;
346 sscanf(s, "%4d%2d%2d", &year, &month, &mday);
347
348 t->tm_year = year;
349 if (t->tm_year > 1900)
350 t->tm_year -= 1900;
351 t->tm_mon = month - 1;
352 t->tm_mday = mday;
353
354 if ((t->tm_mday < 1) || (t->tm_mday > 31))
355 {
356 buf_printf(err, _("Invalid day of month: %s"), s);
357 return NULL;
358 }
359 if ((t->tm_mon < 0) || (t->tm_mon > 11))
360 {
361 buf_printf(err, _("Invalid month: %s"), s);
362 return NULL;
363 }
364
365 return (s + 8);
366 }
367
368 t->tm_mday = strtol(s, &p, 10);
369 if ((t->tm_mday < 1) || (t->tm_mday > 31))
370 {
371 buf_printf(err, _("Invalid day of month: %s"), s);
372 return NULL;
373 }
374 if (*p != '/')
375 {
376 /* fill in today's month and year */
377 t->tm_mon = tm.tm_mon;
378 t->tm_year = tm.tm_year;
379 return p;
380 }
381 p++;
382 t->tm_mon = strtol(p, &p, 10) - 1;
383 if ((t->tm_mon < 0) || (t->tm_mon > 11))
384 {
385 buf_printf(err, _("Invalid month: %s"), p);
386 return NULL;
387 }
388 if (*p != '/')
389 {
390 t->tm_year = tm.tm_year;
391 return p;
392 }
393 p++;
394 t->tm_year = strtol(p, &p, 10);
395 if (t->tm_year < 70) /* year 2000+ */
396 t->tm_year += 100;
397 else if (t->tm_year > 1900)
398 t->tm_year -= 1900;
399 return p;
400}
401
412static const char *parse_date_range(const char *pc, struct tm *min, struct tm *max,
413 bool have_min, struct tm *base_min, struct Buffer *err)
414{
416 while (*pc && ((flags & MUTT_PDR_DONE) == 0))
417 {
418 const char *pt = NULL;
419 char ch = *pc++;
420 SKIPWS(pc);
421 switch (ch)
422 {
423 case '-':
424 {
425 /* try a range of absolute date minus offset of Ndwmy */
426 pt = get_offset(min, pc, -1);
427 if (pc == pt)
428 {
429 if (flags == MUTT_PDR_NO_FLAGS)
430 { /* nothing yet and no offset parsed => absolute date? */
431 if (!get_date(pc, max, err))
432 {
433 flags |= (MUTT_PDR_ABSOLUTE | MUTT_PDR_ERRORDONE); /* done bad */
434 }
435 else
436 {
437 /* reestablish initial base minimum if not specified */
438 if (!have_min)
439 memcpy(min, base_min, sizeof(struct tm));
440 flags |= (MUTT_PDR_ABSOLUTE | MUTT_PDR_DONE); /* done good */
441 }
442 }
443 else
444 {
445 flags |= MUTT_PDR_ERRORDONE;
446 }
447 }
448 else
449 {
450 pc = pt;
451 if ((flags == MUTT_PDR_NO_FLAGS) && !have_min)
452 { /* the very first "-3d" without a previous absolute date */
453 max->tm_year = min->tm_year;
454 max->tm_mon = min->tm_mon;
455 max->tm_mday = min->tm_mday;
456 }
457 flags |= MUTT_PDR_MINUS;
458 }
459 break;
460 }
461 case '+':
462 { /* enlarge plus range */
463 pt = get_offset(max, pc, 1);
464 if (pc == pt)
465 {
466 flags |= MUTT_PDR_ERRORDONE;
467 }
468 else
469 {
470 pc = pt;
471 flags |= MUTT_PDR_PLUS;
472 }
473 break;
474 }
475 case '*':
476 { /* enlarge window in both directions */
477 pt = get_offset(min, pc, -1);
478 if (pc == pt)
479 {
480 flags |= MUTT_PDR_ERRORDONE;
481 }
482 else
483 {
484 pc = get_offset(max, pc, 1);
485 flags |= MUTT_PDR_WINDOW;
486 }
487 break;
488 }
489 default:
490 flags |= MUTT_PDR_ERRORDONE;
491 }
492 SKIPWS(pc);
493 }
494 if ((flags & MUTT_PDR_ERROR) && !(flags & MUTT_PDR_ABSOLUTE))
495 { /* get_date has its own error message, don't overwrite it here */
496 buf_printf(err, _("Invalid relative date: %s"), pc - 1);
497 }
498 return (flags & MUTT_PDR_ERROR) ? NULL : pc;
499}
500
506static void adjust_date_range(struct tm *min, struct tm *max)
507{
508 if ((min->tm_year > max->tm_year) ||
509 ((min->tm_year == max->tm_year) && (min->tm_mon > max->tm_mon)) ||
510 ((min->tm_year == max->tm_year) && (min->tm_mon == max->tm_mon) &&
511 (min->tm_mday > max->tm_mday)))
512 {
513 int tmp;
514
515 tmp = min->tm_year;
516 min->tm_year = max->tm_year;
517 max->tm_year = tmp;
518
519 tmp = min->tm_mon;
520 min->tm_mon = max->tm_mon;
521 max->tm_mon = tmp;
522
523 tmp = min->tm_mday;
524 min->tm_mday = max->tm_mday;
525 max->tm_mday = tmp;
526
527 min->tm_hour = 0;
528 min->tm_min = 0;
529 min->tm_sec = 0;
530 max->tm_hour = 23;
531 max->tm_min = 59;
532 max->tm_sec = 59;
533 }
534}
535
544bool eval_date_minmax(struct Pattern *pat, const char *s, struct Buffer *err)
545{
546 /* the '0' time is Jan 1, 1970 UTC, so in order to prevent a negative time
547 * when doing timezone conversion, we use Jan 2, 1970 UTC as the base here */
548 struct tm min = { 0 };
549 min.tm_mday = 2;
550 min.tm_year = 70;
551
552 /* Arbitrary year in the future. Don't set this too high or
553 * mutt_date_make_time() returns something larger than will fit in a time_t
554 * on some systems */
555 struct tm max = { 0 };
556 max.tm_year = 130;
557 max.tm_mon = 11;
558 max.tm_mday = 31;
559 max.tm_hour = 23;
560 max.tm_min = 59;
561 max.tm_sec = 59;
562
563 if (strchr("<>=", s[0]))
564 {
565 /* offset from current time
566 * <3d less than three days ago
567 * >3d more than three days ago
568 * =3d exactly three days ago */
569 struct tm *tm = NULL;
570 bool exact = false;
571
572 if (s[0] == '<')
573 {
575 tm = &min;
576 }
577 else
578 {
580 tm = &max;
581
582 if (s[0] == '=')
583 exact = true;
584 }
585
586 /* Reset the HMS unless we are relative matching using one of those
587 * offsets. */
588 char *offset_type = NULL;
589 strtol(s + 1, &offset_type, 0);
590 if (!(*offset_type && strchr("HMS", *offset_type)))
591 {
592 tm->tm_hour = 23;
593 tm->tm_min = 59;
594 tm->tm_sec = 59;
595 }
596
597 /* force negative offset */
598 get_offset(tm, s + 1, -1);
599
600 if (exact)
601 {
602 /* start at the beginning of the day in question */
603 memcpy(&min, &max, sizeof(max));
604 min.tm_hour = 0;
605 min.tm_sec = 0;
606 min.tm_min = 0;
607 }
608 }
609 else
610 {
611 const char *pc = s;
612
613 bool have_min = false;
614 bool until_now = false;
615 if (mutt_isdigit(*pc))
616 {
617 /* minimum date specified */
618 pc = get_date(pc, &min, err);
619 if (!pc)
620 {
621 return false;
622 }
623 have_min = true;
624 SKIPWS(pc);
625 if (*pc == '-')
626 {
627 const char *pt = pc + 1;
628 SKIPWS(pt);
629 until_now = (*pt == '\0');
630 }
631 }
632
633 if (!until_now)
634 { /* max date or relative range/window */
635
636 struct tm base_min = { 0 };
637
638 if (!have_min)
639 { /* save base minimum and set current date, e.g. for "-3d+1d" */
640 memcpy(&base_min, &min, sizeof(base_min));
642 min.tm_hour = 0;
643 min.tm_sec = 0;
644 min.tm_min = 0;
645 }
646
647 /* preset max date for relative offsets,
648 * if nothing follows we search for messages on a specific day */
649 max.tm_year = min.tm_year;
650 max.tm_mon = min.tm_mon;
651 max.tm_mday = min.tm_mday;
652
653 if (!parse_date_range(pc, &min, &max, have_min, &base_min, err))
654 { /* bail out on any parsing error */
655 return false;
656 }
657 }
658 }
659
660 /* Since we allow two dates to be specified we'll have to adjust that. */
661 adjust_date_range(&min, &max);
662
663 pat->min = mutt_date_make_time(&min, true);
664 pat->max = mutt_date_make_time(&max, true);
665
666 return true;
667}
668
672static bool eat_range(struct Pattern *pat, PatternCompFlags flags,
673 struct Buffer *s, struct Buffer *err)
674{
675 char *tmp = NULL;
676 bool do_exclusive = false;
677 bool skip_quote = false;
678
679 /* If simple_search is set to "~m %s", the range will have double quotes
680 * around it... */
681 if (*s->dptr == '"')
682 {
683 s->dptr++;
684 skip_quote = true;
685 }
686 if (*s->dptr == '<')
687 do_exclusive = true;
688 if ((*s->dptr != '-') && (*s->dptr != '<'))
689 {
690 /* range minimum */
691 if (*s->dptr == '>')
692 {
693 pat->max = MUTT_MAXRANGE;
694 pat->min = strtol(s->dptr + 1, &tmp, 0) + 1; /* exclusive range */
695 }
696 else
697 {
698 pat->min = strtol(s->dptr, &tmp, 0);
699 }
700 if (mutt_toupper(*tmp) == 'K') /* is there a prefix? */
701 {
702 pat->min *= 1024;
703 tmp++;
704 }
705 else if (mutt_toupper(*tmp) == 'M')
706 {
707 pat->min *= 1048576;
708 tmp++;
709 }
710 if (*s->dptr == '>')
711 {
712 s->dptr = tmp;
713 return true;
714 }
715 if (*tmp != '-')
716 {
717 /* exact value */
718 pat->max = pat->min;
719 s->dptr = tmp;
720 return true;
721 }
722 tmp++;
723 }
724 else
725 {
726 s->dptr++;
727 tmp = s->dptr;
728 }
729
730 if (mutt_isdigit(*tmp))
731 {
732 /* range maximum */
733 pat->max = strtol(tmp, &tmp, 0);
734 if (mutt_toupper(*tmp) == 'K')
735 {
736 pat->max *= 1024;
737 tmp++;
738 }
739 else if (mutt_toupper(*tmp) == 'M')
740 {
741 pat->max *= 1048576;
742 tmp++;
743 }
744 if (do_exclusive)
745 (pat->max)--;
746 }
747 else
748 {
749 pat->max = MUTT_MAXRANGE;
750 }
751
752 if (skip_quote && (*tmp == '"'))
753 tmp++;
754
755 SKIPWS(tmp);
756 s->dptr = tmp;
757 return true;
758}
759
763static bool eat_date(struct Pattern *pat, PatternCompFlags flags,
764 struct Buffer *s, struct Buffer *err)
765{
766 struct Buffer *tmp = buf_pool_get();
767 bool rc = false;
768
769 char *pexpr = s->dptr;
771 {
772 buf_printf(err, _("Error in expression: %s"), pexpr);
773 goto out;
774 }
775
776 if (buf_is_empty(tmp))
777 {
778 buf_addstr(err, _("Empty expression"));
779 goto out;
780 }
781
782 if (flags & MUTT_PC_PATTERN_DYNAMIC)
783 {
784 pat->dynamic = true;
785 pat->p.str = buf_strdup(tmp);
786 }
787
788 rc = eval_date_minmax(pat, tmp->data, err);
789
790out:
791 buf_pool_release(&tmp);
792 return rc;
793}
794
802static /* const */ char *find_matching_paren(/* const */ char *s)
803{
804 int level = 1;
805
806 for (; *s; s++)
807 {
808 if (*s == '(')
809 {
810 level++;
811 }
812 else if (*s == ')')
813 {
814 level--;
815 if (level == 0)
816 break;
817 }
818 }
819 return s;
820}
821
826void mutt_pattern_free(struct PatternList **pat)
827{
828 if (!pat || !*pat)
829 return;
830
831 struct Pattern *np = SLIST_FIRST(*pat);
832 struct Pattern *next = NULL;
833
834 while (np)
835 {
836 next = SLIST_NEXT(np, entries);
837
838 if (np->is_multi)
839 {
841 }
842 else if (np->string_match || np->dynamic)
843 {
844 FREE(&np->p.str);
845 }
846 else if (np->group_match)
847 {
848 np->p.group = NULL;
849 }
850 else if (np->p.regex)
851 {
852 regfree(np->p.regex);
853 FREE(&np->p.regex);
854 }
855
856#ifdef USE_DEBUG_GRAPHVIZ
857 FREE(&np->raw_pattern);
858#endif
860 FREE(&np);
861
862 np = next;
863 }
864
865 FREE(pat);
866}
867
872static struct Pattern *mutt_pattern_new(void)
873{
874 return MUTT_MEM_CALLOC(1, struct Pattern);
875}
876
881static struct PatternList *mutt_pattern_list_new(void)
882{
883 struct PatternList *h = MUTT_MEM_CALLOC(1, struct PatternList);
884 SLIST_INIT(h);
885 struct Pattern *p = mutt_pattern_new();
886 SLIST_INSERT_HEAD(h, p, entries);
887 return h;
888}
889
896static struct Pattern *attach_leaf(struct PatternList *list, struct Pattern *leaf)
897{
898 struct Pattern *last = NULL;
899 SLIST_FOREACH(last, list, entries)
900 {
901 // TODO - or we could use a doubly-linked list
902 if (!SLIST_NEXT(last, entries))
903 {
904 SLIST_NEXT(last, entries) = leaf;
905 break;
906 }
907 }
908 return leaf;
909}
910
918static struct Pattern *attach_new_root(struct PatternList **curlist)
919{
920 struct PatternList *root = mutt_pattern_list_new();
921 struct Pattern *leaf = SLIST_FIRST(root);
922 leaf->child = *curlist;
923 *curlist = root;
924 return leaf;
925}
926
934static struct Pattern *attach_new_leaf(struct PatternList **curlist)
935{
936 if (*curlist)
937 {
938 return attach_leaf(*curlist, mutt_pattern_new());
939 }
940 else
941 {
942 return attach_new_root(curlist);
943 }
944}
945
954struct PatternList *mutt_pattern_comp(struct MailboxView *mv, const char *s,
955 PatternCompFlags flags, struct Buffer *err)
956{
957 /* curlist when assigned will always point to a list containing at least one node
958 * with a Pattern value. */
959 struct PatternList *curlist = NULL;
960 bool pat_not = false;
961 bool all_addr = false;
962 bool pat_or = false;
963 bool implicit = true; /* used to detect logical AND operator */
964 bool is_alias = false;
965 const struct PatternFlags *entry = NULL;
966 char *p = NULL;
967 char *buf = NULL;
968 struct Mailbox *m = mv ? mv->mailbox : NULL;
969
970 if (!s || (s[0] == '\0'))
971 {
972 buf_strcpy(err, _("empty pattern"));
973 return NULL;
974 }
975
976 struct Buffer *ps = buf_pool_get();
977 buf_strcpy(ps, s);
978 buf_seek(ps, 0);
979
980 SKIPWS(ps->dptr);
981 while (*ps->dptr)
982 {
983 switch (*ps->dptr)
984 {
985 case '^':
986 ps->dptr++;
987 all_addr = !all_addr;
988 break;
989 case '!':
990 ps->dptr++;
991 pat_not = !pat_not;
992 break;
993 case '@':
994 ps->dptr++;
995 is_alias = !is_alias;
996 break;
997 case '|':
998 if (!pat_or)
999 {
1000 if (!curlist)
1001 {
1002 buf_printf(err, _("error in pattern at: %s"), ps->dptr);
1003 buf_pool_release(&ps);
1004 return NULL;
1005 }
1006
1007 struct Pattern *pat = SLIST_FIRST(curlist);
1008 if (SLIST_NEXT(pat, entries))
1009 {
1010 /* A & B | C == (A & B) | C */
1011 struct Pattern *root = attach_new_root(&curlist);
1012 root->op = MUTT_PAT_AND;
1013 }
1014
1015 pat_or = true;
1016 }
1017 ps->dptr++;
1018 implicit = false;
1019 pat_not = false;
1020 all_addr = false;
1021 is_alias = false;
1022 break;
1023 case '%':
1024 case '=':
1025 case '~':
1026 {
1027 if (ps->dptr[1] == '\0')
1028 {
1029 buf_printf(err, _("missing pattern: %s"), ps->dptr);
1030 goto cleanup;
1031 }
1032 short thread_op = 0;
1033 if (ps->dptr[1] == '(')
1034 thread_op = MUTT_PAT_THREAD;
1035 else if ((ps->dptr[1] == '<') && (ps->dptr[2] == '('))
1036 thread_op = MUTT_PAT_PARENT;
1037 else if ((ps->dptr[1] == '>') && (ps->dptr[2] == '('))
1038 thread_op = MUTT_PAT_CHILDREN;
1039 if (thread_op != 0)
1040 {
1041 ps->dptr++; /* skip ~ */
1042 if ((thread_op == MUTT_PAT_PARENT) || (thread_op == MUTT_PAT_CHILDREN))
1043 ps->dptr++;
1044 p = find_matching_paren(ps->dptr + 1);
1045 if (p[0] != ')')
1046 {
1047 buf_printf(err, _("mismatched parentheses: %s"), ps->dptr);
1048 goto cleanup;
1049 }
1050 struct Pattern *leaf = attach_new_leaf(&curlist);
1051 leaf->op = thread_op;
1052 leaf->pat_not = pat_not;
1053 leaf->all_addr = all_addr;
1054 leaf->is_alias = is_alias;
1055 pat_not = false;
1056 all_addr = false;
1057 is_alias = false;
1058 /* compile the sub-expression */
1059 buf = mutt_strn_dup(ps->dptr + 1, p - (ps->dptr + 1));
1060 leaf->child = mutt_pattern_comp(mv, buf, flags, err);
1061 if (!leaf->child)
1062 {
1063 FREE(&buf);
1064 goto cleanup;
1065 }
1066 FREE(&buf);
1067 ps->dptr = p + 1; /* restore location */
1068 break;
1069 }
1070 if (implicit && pat_or)
1071 {
1072 /* A | B & C == (A | B) & C */
1073 struct Pattern *root = attach_new_root(&curlist);
1074 root->op = MUTT_PAT_OR;
1075 pat_or = false;
1076 }
1077
1078 char prefix = ps->dptr[0];
1079 entry = lookup_tag(prefix, ps->dptr[1]);
1080 if (!entry)
1081 {
1082 buf_printf(err, _("%c%c: invalid pattern"), prefix, ps->dptr[1]);
1083 goto cleanup;
1084 }
1085 if (entry->flags && ((flags & entry->flags) == 0))
1086 {
1087 buf_printf(err, _("%c%c: not supported in this mode"), prefix, ps->dptr[1]);
1088 goto cleanup;
1089 }
1090
1091 struct Pattern *leaf = attach_new_leaf(&curlist);
1092 leaf->pat_not = pat_not;
1093 leaf->all_addr = all_addr;
1094 leaf->is_alias = is_alias;
1095 leaf->sendmode = (flags & MUTT_PC_SEND_MODE_SEARCH);
1096 leaf->op = entry->op;
1097 pat_not = false;
1098 all_addr = false;
1099 is_alias = false;
1100
1101 // Determine the actual eat_arg to use.
1102 // If the entry was found via fallback (entry->prefix is '~' but we used '=' or '%'),
1103 // override the eat_arg to use string or group parsing respectively.
1104 enum PatternEat eat_arg = entry->eat_arg;
1105 if ((entry->prefix == '~') && (prefix == '=') && (eat_arg == EAT_REGEX))
1106 eat_arg = EAT_STRING;
1107 else if ((entry->prefix == '~') && (prefix == '%') && (eat_arg == EAT_REGEX))
1108 eat_arg = EAT_GROUP;
1109
1110 ps->dptr++; /* move past the prefix (~, %, =) */
1111 ps->dptr++; /* eat the operator and any optional whitespace */
1112 SKIPWS(ps->dptr);
1113 if (eat_arg)
1114 {
1115 if (ps->dptr[0] == '\0')
1116 {
1117 buf_addstr(err, _("missing parameter"));
1118 goto cleanup;
1119 }
1120 switch (eat_arg)
1121 {
1122 case EAT_REGEX:
1123 if (!eat_regex(leaf, flags, ps, err))
1124 goto cleanup;
1125 break;
1126 case EAT_STRING:
1127 if (!eat_string(leaf, flags, ps, err))
1128 goto cleanup;
1129 break;
1130 case EAT_GROUP:
1131 if (!eat_group(leaf, flags, ps, err))
1132 goto cleanup;
1133 break;
1134 case EAT_DATE:
1135 if (!eat_date(leaf, flags, ps, err))
1136 goto cleanup;
1137 break;
1138 case EAT_RANGE:
1139 if (!eat_range(leaf, flags, ps, err))
1140 goto cleanup;
1141 break;
1142 case EAT_MESSAGE_RANGE:
1143 if (!eat_message_range(leaf, flags, ps, err, mv))
1144 goto cleanup;
1145 break;
1146 case EAT_QUERY:
1147 if (!eat_query(leaf, flags, ps, err, m))
1148 goto cleanup;
1149 break;
1150 default:
1151 break;
1152 }
1153 }
1154 implicit = true;
1155 break;
1156 }
1157
1158 case '(':
1159 {
1160 p = find_matching_paren(ps->dptr + 1);
1161 if (p[0] != ')')
1162 {
1163 buf_printf(err, _("mismatched parentheses: %s"), ps->dptr);
1164 goto cleanup;
1165 }
1166 /* compile the sub-expression */
1167 buf = mutt_strn_dup(ps->dptr + 1, p - (ps->dptr + 1));
1168 struct PatternList *sub = mutt_pattern_comp(mv, buf, flags, err);
1169 FREE(&buf);
1170 if (!sub)
1171 goto cleanup;
1172 struct Pattern *leaf = SLIST_FIRST(sub);
1173 if (curlist)
1174 {
1175 attach_leaf(curlist, leaf);
1176 FREE(&sub);
1177 }
1178 else
1179 {
1180 curlist = sub;
1181 }
1182 leaf->pat_not ^= pat_not;
1183 leaf->all_addr |= all_addr;
1184 leaf->is_alias |= is_alias;
1185 pat_not = false;
1186 all_addr = false;
1187 is_alias = false;
1188 ps->dptr = p + 1; /* restore location */
1189 break;
1190 }
1191
1192 default:
1193 buf_printf(err, _("error in pattern at: %s"), ps->dptr);
1194 goto cleanup;
1195 }
1196 SKIPWS(ps->dptr);
1197 }
1198 buf_pool_release(&ps);
1199
1200 if (!curlist)
1201 {
1202 buf_strcpy(err, _("empty pattern"));
1203 return NULL;
1204 }
1205
1206 if (SLIST_NEXT(SLIST_FIRST(curlist), entries))
1207 {
1208 struct Pattern *root = attach_new_root(&curlist);
1209 root->op = pat_or ? MUTT_PAT_OR : MUTT_PAT_AND;
1210 }
1211
1212 return curlist;
1213
1214cleanup:
1215 mutt_pattern_free(&curlist);
1216 buf_pool_release(&ps);
1217 return NULL;
1218}
struct Group * groups_get_group(struct HashTable *groups, const char *name)
Get a Group by its name.
Definition group.c:291
Email Address Handling.
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
#define MUTT_PDR_PLUS
Extend the range using '+'.
Definition compile.c:53
#define MUTT_PDR_NO_FLAGS
No flags are set.
Definition compile.c:51
bool eval_date_minmax(struct Pattern *pat, const char *s, struct Buffer *err)
Evaluate a date-range pattern against 'now'.
Definition compile.c:544
static struct Pattern * attach_new_root(struct PatternList **curlist)
Create a new Pattern as a parent for a List.
Definition compile.c:918
uint16_t ParseDateRangeFlags
Flags for parse_date_range(), e.g. MUTT_PDR_MINUS.
Definition compile.c:50
#define MUTT_PDR_ERROR
Invalid pattern.
Definition compile.c:57
#define MUTT_PDR_ABSOLUTE
Absolute pattern range.
Definition compile.c:55
static struct Pattern * attach_new_leaf(struct PatternList **curlist)
Attach a new Pattern to a List.
Definition compile.c:934
struct PatternList * mutt_pattern_comp(struct MailboxView *mv, const char *s, PatternCompFlags flags, struct Buffer *err)
Create a Pattern.
Definition compile.c:954
static struct Pattern * attach_leaf(struct PatternList *list, struct Pattern *leaf)
Attach a Pattern to a Pattern List.
Definition compile.c:896
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:412
static char * find_matching_paren(char *s)
Find the matching parenthesis.
Definition compile.c:802
static void adjust_date_range(struct tm *min, struct tm *max)
Put a date range in the correct order.
Definition compile.c:506
static struct Pattern * mutt_pattern_new(void)
Create a new Pattern.
Definition compile.c:872
static const char * get_offset(struct tm *tm, const char *s, int sign)
Calculate a symbolic offset.
Definition compile.c:272
#define MUTT_PDR_DONE
Pattern parse successfully.
Definition compile.c:56
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:326
static struct PatternList * mutt_pattern_list_new(void)
Create a new list containing a Pattern.
Definition compile.c:881
#define MUTT_PDR_ERRORDONE
Definition compile.c:60
void mutt_pattern_free(struct PatternList **pat)
Free a Pattern.
Definition compile.c:826
#define MUTT_PDR_MINUS
Pattern contains a range.
Definition compile.c:52
#define MUTT_PDR_WINDOW
Extend the range in both directions using '*'.
Definition compile.c:54
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:214
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
#define TOKEN_COMMENT
Don't reap comments.
Definition extract.h:51
#define TOKEN_PATTERN
~%=!| are terms (for patterns)
Definition extract.h:50
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:784
#define mutt_file_fclose(FP)
Definition file.h:139
#define MUTT_RL_NO_FLAGS
No flags are set.
Definition file.h:40
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:140
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:108
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:192
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:281
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:672
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:763
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:65
#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:172
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
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:220
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:209
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:567
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:553
Text parsing functions.
const struct PatternFlags * lookup_tag(char prefix, char tag)
Lookup a pattern modifier.
Definition flags.c:235
Match patterns to emails.
#define MUTT_PC_SEND_MODE_SEARCH
Allow send-mode body searching.
Definition lib.h:73
uint8_t PatternCompFlags
Flags for mutt_pattern_comp(), e.g. MUTT_PC_FULL_MSG.
Definition lib.h:69
@ MUTT_PAT_OR
Either pattern can match.
Definition lib.h:140
@ MUTT_PAT_CHILDREN
Pattern matches a child email.
Definition lib.h:143
@ MUTT_PAT_PARENT
Pattern matches parent.
Definition lib.h:142
@ MUTT_PAT_AND
Both patterns must match.
Definition lib.h:139
@ MUTT_PAT_THREAD
Pattern matches email thread.
Definition lib.h:141
#define MUTT_PC_PATTERN_DYNAMIC
Enable runtime date range evaluation.
Definition lib.h:72
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:50
#define SKIPWS(ch)
Definition string2.h:52
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:79
Container for Accounts, Notifications.
Definition neomutt.h:128
char ** env
Private copy of the environment variables.
Definition neomutt.h:143
struct HashTable * groups
Hash Table: "group-name" -> Group.
Definition neomutt.h:139
struct ConfigSubset * sub
Inherited config items.
Definition neomutt.h:134
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:79
bool group_match
Check a group of Addresses.
Definition lib.h:84
union Pattern::@006112053024257132210207314205210350156165326341 p
bool all_addr
All Addresses in the list must match.
Definition lib.h:82
struct Group * group
Address group if group_match is set.
Definition lib.h:95
struct PatternList * child
Arguments to logical operation.
Definition lib.h:92
long min
Minimum for range checks.
Definition lib.h:90
bool string_match
Check a string for a match.
Definition lib.h:83
regex_t * regex
Compiled regex, for non-pattern matching.
Definition lib.h:94
struct ListHead multi_cases
Multiple strings for ~I pattern.
Definition lib.h:97
char * str
String, if string_match is set.
Definition lib.h:96
bool is_alias
Is there an alias for this Address?
Definition lib.h:86
bool ign_case
Ignore case for local string_match searches.
Definition lib.h:85
long max
Maximum for range checks.
Definition lib.h:91
bool dynamic
Evaluate date ranges at run time.
Definition lib.h:87
short op
Operation, e.g. MUTT_PAT_SCORE.
Definition lib.h:80
bool sendmode
Evaluate searches in send-mode.
Definition lib.h:88
bool is_multi
Multiple case (only for ~I pattern now)
Definition lib.h:89
bool pat_not
Pattern should be inverted (not)
Definition lib.h:81