NeoMutt  2025-09-05-7-geaa2bd
Teaching an old dog new tricks
DOXYGEN
Loading...
Searching...
No Matches
compile.c
Go to the documentation of this file.
1
31#include "config.h"
32#include <stdbool.h>
33#include <stdint.h>
34#include <stdio.h>
35#include <stdlib.h>
36#include <string.h>
37#include <sys/types.h>
38#include <time.h>
39#include "private.h"
40#include "mutt/lib.h"
41#include "address/lib.h"
42#include "config/lib.h"
43#include "core/lib.h"
44#include "lib.h"
45#include "parse/lib.h"
46#include "mview.h"
47
48struct Menu;
49
50// clang-format off
51typedef uint16_t ParseDateRangeFlags;
52#define MUTT_PDR_NO_FLAGS 0
53#define MUTT_PDR_MINUS (1 << 0)
54#define MUTT_PDR_PLUS (1 << 1)
55#define MUTT_PDR_WINDOW (1 << 2)
56#define MUTT_PDR_ABSOLUTE (1 << 3)
57#define MUTT_PDR_DONE (1 << 4)
58#define MUTT_PDR_ERROR (1 << 8)
59// clang-format on
60
61#define MUTT_PDR_ERRORDONE (MUTT_PDR_ERROR | MUTT_PDR_DONE)
62
66static bool eat_regex(struct Pattern *pat, PatternCompFlags flags,
67 struct Buffer *s, struct Buffer *err)
68{
69 struct Buffer *buf = buf_pool_get();
70 bool rc = false;
71 char *pexpr = s->dptr;
72 if ((parse_extract_token(buf, s, TOKEN_PATTERN | TOKEN_COMMENT) != 0) || !buf->data)
73 {
74 buf_printf(err, _("Error in expression: %s"), pexpr);
75 goto out;
76 }
77 if (buf_is_empty(buf))
78 {
79 buf_addstr(err, _("Empty expression"));
80 goto out;
81 }
82
83 if (pat->string_match)
84 {
85 pat->p.str = mutt_str_dup(buf->data);
86 pat->ign_case = mutt_mb_is_lower(buf->data);
87 }
88 else if (pat->group_match)
89 {
90 pat->p.group = mutt_pattern_group(buf->data);
91 }
92 else
93 {
94 pat->p.regex = MUTT_MEM_CALLOC(1, regex_t);
95#ifdef USE_DEBUG_GRAPHVIZ
96 pat->raw_pattern = mutt_str_dup(buf->data);
97#endif
98 uint16_t case_flags = mutt_mb_is_lower(buf->data) ? REG_ICASE : 0;
99 int rc2 = REG_COMP(pat->p.regex, buf->data, REG_NEWLINE | REG_NOSUB | case_flags);
100 if (rc2 != 0)
101 {
102 char errmsg[256] = { 0 };
103 regerror(rc2, pat->p.regex, errmsg, sizeof(errmsg));
104 buf_printf(err, "'%s': %s", buf->data, errmsg);
105 FREE(&pat->p.regex);
106 goto out;
107 }
108 }
109
110 rc = true;
111
112out:
113 buf_pool_release(&buf);
114 return rc;
115}
116
121static bool add_query_msgid(char *line, int line_num, void *user_data)
122{
123 struct ListHead *msgid_list = (struct ListHead *) (user_data);
124 char *nows = mutt_str_skip_whitespace(line);
125 if (*nows == '\0')
126 return true;
128 mutt_list_insert_tail(msgid_list, mutt_str_dup(nows));
129 return true;
130}
131
141static bool eat_query(struct Pattern *pat, PatternCompFlags flags,
142 struct Buffer *s, struct Buffer *err, struct Mailbox *m)
143{
144 struct Buffer *cmd_buf = buf_pool_get();
145 struct Buffer *tok_buf = buf_pool_get();
146 bool rc = false;
147
148 FILE *fp = NULL;
149
150 const char *const c_external_search_command = cs_subset_string(NeoMutt->sub, "external_search_command");
151 if (!c_external_search_command)
152 {
153 buf_addstr(err, _("No search command defined"));
154 goto out;
155 }
156
157 char *pexpr = s->dptr;
158 if ((parse_extract_token(tok_buf, s, TOKEN_PATTERN | TOKEN_COMMENT) != 0) ||
159 !tok_buf->data)
160 {
161 buf_printf(err, _("Error in expression: %s"), pexpr);
162 goto out;
163 }
164 if (*tok_buf->data == '\0')
165 {
166 buf_addstr(err, _("Empty expression"));
167 goto out;
168 }
169
170 buf_addstr(cmd_buf, c_external_search_command);
171 buf_addch(cmd_buf, ' ');
172
173 if (m)
174 {
175 char *escaped_folder = mutt_path_escape(mailbox_path(m));
176 mutt_debug(LL_DEBUG2, "escaped folder path: %s\n", escaped_folder);
177 buf_addch(cmd_buf, '\'');
178 buf_addstr(cmd_buf, escaped_folder);
179 buf_addch(cmd_buf, '\'');
180 }
181 else
182 {
183 buf_addch(cmd_buf, '/');
184 }
185 buf_addch(cmd_buf, ' ');
186 buf_addstr(cmd_buf, tok_buf->data);
187
188 mutt_message(_("Running search command: %s ..."), cmd_buf->data);
189 pat->is_multi = true;
191 pid_t pid = filter_create(cmd_buf->data, NULL, &fp, NULL, NeoMutt->env);
192 if (pid < 0)
193 {
194 buf_printf(err, "unable to fork command: %s\n", cmd_buf->data);
195 goto out;
196 }
197
199 mutt_file_fclose(&fp);
200 filter_wait(pid);
201
202 rc = true;
203
204out:
205 buf_pool_release(&cmd_buf);
206 buf_pool_release(&tok_buf);
207 return rc;
208}
209
222static const char *get_offset(struct tm *tm, const char *s, int sign)
223{
224 char *ps = NULL;
225 int offset = strtol(s, &ps, 0);
226 if (((sign < 0) && (offset > 0)) || ((sign > 0) && (offset < 0)))
227 offset = -offset;
228
229 switch (*ps)
230 {
231 case 'y':
232 tm->tm_year += offset;
233 break;
234 case 'm':
235 tm->tm_mon += offset;
236 break;
237 case 'w':
238 tm->tm_mday += 7 * offset;
239 break;
240 case 'd':
241 tm->tm_mday += offset;
242 break;
243 case 'H':
244 tm->tm_hour += offset;
245 break;
246 case 'M':
247 tm->tm_min += offset;
248 break;
249 case 'S':
250 tm->tm_sec += offset;
251 break;
252 default:
253 return s;
254 }
256 return ps + 1;
257}
258
276static const char *get_date(const char *s, struct tm *t, struct Buffer *err)
277{
278 char *p = NULL;
279 struct tm tm = mutt_date_localtime(mutt_date_now());
280 bool iso8601 = true;
281
282 for (int v = 0; v < 8; v++)
283 {
284 if (s[v] && (s[v] >= '0') && (s[v] <= '9'))
285 continue;
286
287 iso8601 = false;
288 break;
289 }
290
291 if (iso8601)
292 {
293 int year = 0;
294 int month = 0;
295 int mday = 0;
296 sscanf(s, "%4d%2d%2d", &year, &month, &mday);
297
298 t->tm_year = year;
299 if (t->tm_year > 1900)
300 t->tm_year -= 1900;
301 t->tm_mon = month - 1;
302 t->tm_mday = mday;
303
304 if ((t->tm_mday < 1) || (t->tm_mday > 31))
305 {
306 buf_printf(err, _("Invalid day of month: %s"), s);
307 return NULL;
308 }
309 if ((t->tm_mon < 0) || (t->tm_mon > 11))
310 {
311 buf_printf(err, _("Invalid month: %s"), s);
312 return NULL;
313 }
314
315 return (s + 8);
316 }
317
318 t->tm_mday = strtol(s, &p, 10);
319 if ((t->tm_mday < 1) || (t->tm_mday > 31))
320 {
321 buf_printf(err, _("Invalid day of month: %s"), s);
322 return NULL;
323 }
324 if (*p != '/')
325 {
326 /* fill in today's month and year */
327 t->tm_mon = tm.tm_mon;
328 t->tm_year = tm.tm_year;
329 return p;
330 }
331 p++;
332 t->tm_mon = strtol(p, &p, 10) - 1;
333 if ((t->tm_mon < 0) || (t->tm_mon > 11))
334 {
335 buf_printf(err, _("Invalid month: %s"), p);
336 return NULL;
337 }
338 if (*p != '/')
339 {
340 t->tm_year = tm.tm_year;
341 return p;
342 }
343 p++;
344 t->tm_year = strtol(p, &p, 10);
345 if (t->tm_year < 70) /* year 2000+ */
346 t->tm_year += 100;
347 else if (t->tm_year > 1900)
348 t->tm_year -= 1900;
349 return p;
350}
351
362static const char *parse_date_range(const char *pc, struct tm *min, struct tm *max,
363 bool have_min, struct tm *base_min, struct Buffer *err)
364{
366 while (*pc && ((flags & MUTT_PDR_DONE) == 0))
367 {
368 const char *pt = NULL;
369 char ch = *pc++;
370 SKIPWS(pc);
371 switch (ch)
372 {
373 case '-':
374 {
375 /* try a range of absolute date minus offset of Ndwmy */
376 pt = get_offset(min, pc, -1);
377 if (pc == pt)
378 {
379 if (flags == MUTT_PDR_NO_FLAGS)
380 { /* nothing yet and no offset parsed => absolute date? */
381 if (!get_date(pc, max, err))
382 {
383 flags |= (MUTT_PDR_ABSOLUTE | MUTT_PDR_ERRORDONE); /* done bad */
384 }
385 else
386 {
387 /* reestablish initial base minimum if not specified */
388 if (!have_min)
389 memcpy(min, base_min, sizeof(struct tm));
390 flags |= (MUTT_PDR_ABSOLUTE | MUTT_PDR_DONE); /* done good */
391 }
392 }
393 else
394 {
395 flags |= MUTT_PDR_ERRORDONE;
396 }
397 }
398 else
399 {
400 pc = pt;
401 if ((flags == MUTT_PDR_NO_FLAGS) && !have_min)
402 { /* the very first "-3d" without a previous absolute date */
403 max->tm_year = min->tm_year;
404 max->tm_mon = min->tm_mon;
405 max->tm_mday = min->tm_mday;
406 }
407 flags |= MUTT_PDR_MINUS;
408 }
409 break;
410 }
411 case '+':
412 { /* enlarge plus range */
413 pt = get_offset(max, pc, 1);
414 if (pc == pt)
415 {
416 flags |= MUTT_PDR_ERRORDONE;
417 }
418 else
419 {
420 pc = pt;
421 flags |= MUTT_PDR_PLUS;
422 }
423 break;
424 }
425 case '*':
426 { /* enlarge window in both directions */
427 pt = get_offset(min, pc, -1);
428 if (pc == pt)
429 {
430 flags |= MUTT_PDR_ERRORDONE;
431 }
432 else
433 {
434 pc = get_offset(max, pc, 1);
435 flags |= MUTT_PDR_WINDOW;
436 }
437 break;
438 }
439 default:
440 flags |= MUTT_PDR_ERRORDONE;
441 }
442 SKIPWS(pc);
443 }
444 if ((flags & MUTT_PDR_ERROR) && !(flags & MUTT_PDR_ABSOLUTE))
445 { /* get_date has its own error message, don't overwrite it here */
446 buf_printf(err, _("Invalid relative date: %s"), pc - 1);
447 }
448 return (flags & MUTT_PDR_ERROR) ? NULL : pc;
449}
450
456static void adjust_date_range(struct tm *min, struct tm *max)
457{
458 if ((min->tm_year > max->tm_year) ||
459 ((min->tm_year == max->tm_year) && (min->tm_mon > max->tm_mon)) ||
460 ((min->tm_year == max->tm_year) && (min->tm_mon == max->tm_mon) &&
461 (min->tm_mday > max->tm_mday)))
462 {
463 int tmp;
464
465 tmp = min->tm_year;
466 min->tm_year = max->tm_year;
467 max->tm_year = tmp;
468
469 tmp = min->tm_mon;
470 min->tm_mon = max->tm_mon;
471 max->tm_mon = tmp;
472
473 tmp = min->tm_mday;
474 min->tm_mday = max->tm_mday;
475 max->tm_mday = tmp;
476
477 min->tm_hour = 0;
478 min->tm_min = 0;
479 min->tm_sec = 0;
480 max->tm_hour = 23;
481 max->tm_min = 59;
482 max->tm_sec = 59;
483 }
484}
485
494bool eval_date_minmax(struct Pattern *pat, const char *s, struct Buffer *err)
495{
496 /* the '0' time is Jan 1, 1970 UTC, so in order to prevent a negative time
497 * when doing timezone conversion, we use Jan 2, 1970 UTC as the base here */
498 struct tm min = { 0 };
499 min.tm_mday = 2;
500 min.tm_year = 70;
501
502 /* Arbitrary year in the future. Don't set this too high or
503 * mutt_date_make_time() returns something larger than will fit in a time_t
504 * on some systems */
505 struct tm max = { 0 };
506 max.tm_year = 130;
507 max.tm_mon = 11;
508 max.tm_mday = 31;
509 max.tm_hour = 23;
510 max.tm_min = 59;
511 max.tm_sec = 59;
512
513 if (strchr("<>=", s[0]))
514 {
515 /* offset from current time
516 * <3d less than three days ago
517 * >3d more than three days ago
518 * =3d exactly three days ago */
519 struct tm *tm = NULL;
520 bool exact = false;
521
522 if (s[0] == '<')
523 {
525 tm = &min;
526 }
527 else
528 {
530 tm = &max;
531
532 if (s[0] == '=')
533 exact = true;
534 }
535
536 /* Reset the HMS unless we are relative matching using one of those
537 * offsets. */
538 char *offset_type = NULL;
539 strtol(s + 1, &offset_type, 0);
540 if (!(*offset_type && strchr("HMS", *offset_type)))
541 {
542 tm->tm_hour = 23;
543 tm->tm_min = 59;
544 tm->tm_sec = 59;
545 }
546
547 /* force negative offset */
548 get_offset(tm, s + 1, -1);
549
550 if (exact)
551 {
552 /* start at the beginning of the day in question */
553 memcpy(&min, &max, sizeof(max));
554 min.tm_hour = 0;
555 min.tm_sec = 0;
556 min.tm_min = 0;
557 }
558 }
559 else
560 {
561 const char *pc = s;
562
563 bool have_min = false;
564 bool until_now = false;
565 if (mutt_isdigit(*pc))
566 {
567 /* minimum date specified */
568 pc = get_date(pc, &min, err);
569 if (!pc)
570 {
571 return false;
572 }
573 have_min = true;
574 SKIPWS(pc);
575 if (*pc == '-')
576 {
577 const char *pt = pc + 1;
578 SKIPWS(pt);
579 until_now = (*pt == '\0');
580 }
581 }
582
583 if (!until_now)
584 { /* max date or relative range/window */
585
586 struct tm base_min = { 0 };
587
588 if (!have_min)
589 { /* save base minimum and set current date, e.g. for "-3d+1d" */
590 memcpy(&base_min, &min, sizeof(base_min));
592 min.tm_hour = 0;
593 min.tm_sec = 0;
594 min.tm_min = 0;
595 }
596
597 /* preset max date for relative offsets,
598 * if nothing follows we search for messages on a specific day */
599 max.tm_year = min.tm_year;
600 max.tm_mon = min.tm_mon;
601 max.tm_mday = min.tm_mday;
602
603 if (!parse_date_range(pc, &min, &max, have_min, &base_min, err))
604 { /* bail out on any parsing error */
605 return false;
606 }
607 }
608 }
609
610 /* Since we allow two dates to be specified we'll have to adjust that. */
611 adjust_date_range(&min, &max);
612
613 pat->min = mutt_date_make_time(&min, true);
614 pat->max = mutt_date_make_time(&max, true);
615
616 return true;
617}
618
622static bool eat_range(struct Pattern *pat, PatternCompFlags flags,
623 struct Buffer *s, struct Buffer *err)
624{
625 char *tmp = NULL;
626 bool do_exclusive = false;
627 bool skip_quote = false;
628
629 /* If simple_search is set to "~m %s", the range will have double quotes
630 * around it... */
631 if (*s->dptr == '"')
632 {
633 s->dptr++;
634 skip_quote = true;
635 }
636 if (*s->dptr == '<')
637 do_exclusive = true;
638 if ((*s->dptr != '-') && (*s->dptr != '<'))
639 {
640 /* range minimum */
641 if (*s->dptr == '>')
642 {
643 pat->max = MUTT_MAXRANGE;
644 pat->min = strtol(s->dptr + 1, &tmp, 0) + 1; /* exclusive range */
645 }
646 else
647 {
648 pat->min = strtol(s->dptr, &tmp, 0);
649 }
650 if (mutt_toupper(*tmp) == 'K') /* is there a prefix? */
651 {
652 pat->min *= 1024;
653 tmp++;
654 }
655 else if (mutt_toupper(*tmp) == 'M')
656 {
657 pat->min *= 1048576;
658 tmp++;
659 }
660 if (*s->dptr == '>')
661 {
662 s->dptr = tmp;
663 return true;
664 }
665 if (*tmp != '-')
666 {
667 /* exact value */
668 pat->max = pat->min;
669 s->dptr = tmp;
670 return true;
671 }
672 tmp++;
673 }
674 else
675 {
676 s->dptr++;
677 tmp = s->dptr;
678 }
679
680 if (mutt_isdigit(*tmp))
681 {
682 /* range maximum */
683 pat->max = strtol(tmp, &tmp, 0);
684 if (mutt_toupper(*tmp) == 'K')
685 {
686 pat->max *= 1024;
687 tmp++;
688 }
689 else if (mutt_toupper(*tmp) == 'M')
690 {
691 pat->max *= 1048576;
692 tmp++;
693 }
694 if (do_exclusive)
695 (pat->max)--;
696 }
697 else
698 {
699 pat->max = MUTT_MAXRANGE;
700 }
701
702 if (skip_quote && (*tmp == '"'))
703 tmp++;
704
705 SKIPWS(tmp);
706 s->dptr = tmp;
707 return true;
708}
709
713static bool eat_date(struct Pattern *pat, PatternCompFlags flags,
714 struct Buffer *s, struct Buffer *err)
715{
716 struct Buffer *tmp = buf_pool_get();
717 bool rc = false;
718
719 char *pexpr = s->dptr;
721 {
722 buf_printf(err, _("Error in expression: %s"), pexpr);
723 goto out;
724 }
725
726 if (buf_is_empty(tmp))
727 {
728 buf_addstr(err, _("Empty expression"));
729 goto out;
730 }
731
732 if (flags & MUTT_PC_PATTERN_DYNAMIC)
733 {
734 pat->dynamic = true;
735 pat->p.str = mutt_str_dup(tmp->data);
736 }
737
738 rc = eval_date_minmax(pat, tmp->data, err);
739
740out:
741 buf_pool_release(&tmp);
742 return rc;
743}
744
752static /* const */ char *find_matching_paren(/* const */ char *s)
753{
754 int level = 1;
755
756 for (; *s; s++)
757 {
758 if (*s == '(')
759 {
760 level++;
761 }
762 else if (*s == ')')
763 {
764 level--;
765 if (level == 0)
766 break;
767 }
768 }
769 return s;
770}
771
776void mutt_pattern_free(struct PatternList **pat)
777{
778 if (!pat || !*pat)
779 return;
780
781 struct Pattern *np = SLIST_FIRST(*pat);
782 struct Pattern *next = NULL;
783
784 while (np)
785 {
786 next = SLIST_NEXT(np, entries);
787
788 if (np->is_multi)
789 {
791 }
792 else if (np->string_match || np->dynamic)
793 {
794 FREE(&np->p.str);
795 }
796 else if (np->group_match)
797 {
798 np->p.group = NULL;
799 }
800 else if (np->p.regex)
801 {
802 regfree(np->p.regex);
803 FREE(&np->p.regex);
804 }
805
806#ifdef USE_DEBUG_GRAPHVIZ
807 FREE(&np->raw_pattern);
808#endif
810 FREE(&np);
811
812 np = next;
813 }
814
815 FREE(pat);
816}
817
822static struct Pattern *mutt_pattern_new(void)
823{
824 return MUTT_MEM_CALLOC(1, struct Pattern);
825}
826
831static struct PatternList *mutt_pattern_list_new(void)
832{
833 struct PatternList *h = MUTT_MEM_CALLOC(1, struct PatternList);
834 SLIST_INIT(h);
835 struct Pattern *p = mutt_pattern_new();
836 SLIST_INSERT_HEAD(h, p, entries);
837 return h;
838}
839
846static struct Pattern *attach_leaf(struct PatternList *list, struct Pattern *leaf)
847{
848 struct Pattern *last = NULL;
849 SLIST_FOREACH(last, list, entries)
850 {
851 // TODO - or we could use a doubly-linked list
852 if (!SLIST_NEXT(last, entries))
853 {
854 SLIST_NEXT(last, entries) = leaf;
855 break;
856 }
857 }
858 return leaf;
859}
860
868static struct Pattern *attach_new_root(struct PatternList **curlist)
869{
870 struct PatternList *root = mutt_pattern_list_new();
871 struct Pattern *leaf = SLIST_FIRST(root);
872 leaf->child = *curlist;
873 *curlist = root;
874 return leaf;
875}
876
884static struct Pattern *attach_new_leaf(struct PatternList **curlist)
885{
886 if (*curlist)
887 {
888 return attach_leaf(*curlist, mutt_pattern_new());
889 }
890 else
891 {
892 return attach_new_root(curlist);
893 }
894}
895
905struct PatternList *mutt_pattern_comp(struct MailboxView *mv, struct Menu *menu,
906 const char *s, PatternCompFlags flags,
907 struct Buffer *err)
908{
909 /* curlist when assigned will always point to a list containing at least one node
910 * with a Pattern value. */
911 struct PatternList *curlist = NULL;
912 bool pat_not = false;
913 bool all_addr = false;
914 bool pat_or = false;
915 bool implicit = true; /* used to detect logical AND operator */
916 bool is_alias = false;
917 const struct PatternFlags *entry = NULL;
918 char *p = NULL;
919 char *buf = NULL;
920 struct Mailbox *m = mv ? mv->mailbox : NULL;
921
922 if (!s || (s[0] == '\0'))
923 {
924 buf_strcpy(err, _("empty pattern"));
925 return NULL;
926 }
927
928 struct Buffer *ps = buf_pool_get();
929 buf_strcpy(ps, s);
930 buf_seek(ps, 0);
931
932 SKIPWS(ps->dptr);
933 while (*ps->dptr)
934 {
935 switch (*ps->dptr)
936 {
937 case '^':
938 ps->dptr++;
939 all_addr = !all_addr;
940 break;
941 case '!':
942 ps->dptr++;
943 pat_not = !pat_not;
944 break;
945 case '@':
946 ps->dptr++;
947 is_alias = !is_alias;
948 break;
949 case '|':
950 if (!pat_or)
951 {
952 if (!curlist)
953 {
954 buf_printf(err, _("error in pattern at: %s"), ps->dptr);
955 buf_pool_release(&ps);
956 return NULL;
957 }
958
959 struct Pattern *pat = SLIST_FIRST(curlist);
960 if (SLIST_NEXT(pat, entries))
961 {
962 /* A & B | C == (A & B) | C */
963 struct Pattern *root = attach_new_root(&curlist);
964 root->op = MUTT_PAT_AND;
965 }
966
967 pat_or = true;
968 }
969 ps->dptr++;
970 implicit = false;
971 pat_not = false;
972 all_addr = false;
973 is_alias = false;
974 break;
975 case '%':
976 case '=':
977 case '~':
978 {
979 if (ps->dptr[1] == '\0')
980 {
981 buf_printf(err, _("missing pattern: %s"), ps->dptr);
982 goto cleanup;
983 }
984 short thread_op = 0;
985 if (ps->dptr[1] == '(')
986 thread_op = MUTT_PAT_THREAD;
987 else if ((ps->dptr[1] == '<') && (ps->dptr[2] == '('))
988 thread_op = MUTT_PAT_PARENT;
989 else if ((ps->dptr[1] == '>') && (ps->dptr[2] == '('))
990 thread_op = MUTT_PAT_CHILDREN;
991 if (thread_op != 0)
992 {
993 ps->dptr++; /* skip ~ */
994 if ((thread_op == MUTT_PAT_PARENT) || (thread_op == MUTT_PAT_CHILDREN))
995 ps->dptr++;
996 p = find_matching_paren(ps->dptr + 1);
997 if (p[0] != ')')
998 {
999 buf_printf(err, _("mismatched parentheses: %s"), ps->dptr);
1000 goto cleanup;
1001 }
1002 struct Pattern *leaf = attach_new_leaf(&curlist);
1003 leaf->op = thread_op;
1004 leaf->pat_not = pat_not;
1005 leaf->all_addr = all_addr;
1006 leaf->is_alias = is_alias;
1007 pat_not = false;
1008 all_addr = false;
1009 is_alias = false;
1010 /* compile the sub-expression */
1011 buf = mutt_strn_dup(ps->dptr + 1, p - (ps->dptr + 1));
1012 leaf->child = mutt_pattern_comp(mv, menu, buf, flags, err);
1013 if (!leaf->child)
1014 {
1015 FREE(&buf);
1016 goto cleanup;
1017 }
1018 FREE(&buf);
1019 ps->dptr = p + 1; /* restore location */
1020 break;
1021 }
1022 if (implicit && pat_or)
1023 {
1024 /* A | B & C == (A | B) & C */
1025 struct Pattern *root = attach_new_root(&curlist);
1026 root->op = MUTT_PAT_OR;
1027 pat_or = false;
1028 }
1029
1030 entry = lookup_tag(ps->dptr[1]);
1031 if (!entry)
1032 {
1033 buf_printf(err, _("%c: invalid pattern modifier"), *ps->dptr);
1034 goto cleanup;
1035 }
1036 if (entry->flags && ((flags & entry->flags) == 0))
1037 {
1038 buf_printf(err, _("%c: not supported in this mode"), *ps->dptr);
1039 goto cleanup;
1040 }
1041
1042 struct Pattern *leaf = attach_new_leaf(&curlist);
1043 leaf->pat_not = pat_not;
1044 leaf->all_addr = all_addr;
1045 leaf->is_alias = is_alias;
1046 leaf->string_match = (ps->dptr[0] == '=');
1047 leaf->group_match = (ps->dptr[0] == '%');
1048 leaf->sendmode = (flags & MUTT_PC_SEND_MODE_SEARCH);
1049 leaf->op = entry->op;
1050 pat_not = false;
1051 all_addr = false;
1052 is_alias = false;
1053
1054 ps->dptr++; /* move past the ~ */
1055 ps->dptr++; /* eat the operator and any optional whitespace */
1056 SKIPWS(ps->dptr);
1057 if (entry->eat_arg)
1058 {
1059 if (ps->dptr[0] == '\0')
1060 {
1061 buf_addstr(err, _("missing parameter"));
1062 goto cleanup;
1063 }
1064 switch (entry->eat_arg)
1065 {
1066 case EAT_REGEX:
1067 if (!eat_regex(leaf, flags, ps, err))
1068 goto cleanup;
1069 break;
1070 case EAT_DATE:
1071 if (!eat_date(leaf, flags, ps, err))
1072 goto cleanup;
1073 break;
1074 case EAT_RANGE:
1075 if (!eat_range(leaf, flags, ps, err))
1076 goto cleanup;
1077 break;
1078 case EAT_MESSAGE_RANGE:
1079 if (!eat_message_range(leaf, flags, ps, err, mv))
1080 goto cleanup;
1081 break;
1082 case EAT_QUERY:
1083 if (!eat_query(leaf, flags, ps, err, m))
1084 goto cleanup;
1085 break;
1086 default:
1087 break;
1088 }
1089 }
1090 implicit = true;
1091 break;
1092 }
1093
1094 case '(':
1095 {
1096 p = find_matching_paren(ps->dptr + 1);
1097 if (p[0] != ')')
1098 {
1099 buf_printf(err, _("mismatched parentheses: %s"), ps->dptr);
1100 goto cleanup;
1101 }
1102 /* compile the sub-expression */
1103 buf = mutt_strn_dup(ps->dptr + 1, p - (ps->dptr + 1));
1104 struct PatternList *sub = mutt_pattern_comp(mv, menu, buf, flags, err);
1105 FREE(&buf);
1106 if (!sub)
1107 goto cleanup;
1108 struct Pattern *leaf = SLIST_FIRST(sub);
1109 if (curlist)
1110 {
1111 attach_leaf(curlist, leaf);
1112 FREE(&sub);
1113 }
1114 else
1115 {
1116 curlist = sub;
1117 }
1118 leaf->pat_not ^= pat_not;
1119 leaf->all_addr |= all_addr;
1120 leaf->is_alias |= is_alias;
1121 pat_not = false;
1122 all_addr = false;
1123 is_alias = false;
1124 ps->dptr = p + 1; /* restore location */
1125 break;
1126 }
1127
1128 default:
1129 buf_printf(err, _("error in pattern at: %s"), ps->dptr);
1130 goto cleanup;
1131 }
1132 SKIPWS(ps->dptr);
1133 }
1134 buf_pool_release(&ps);
1135
1136 if (!curlist)
1137 {
1138 buf_strcpy(err, _("empty pattern"));
1139 return NULL;
1140 }
1141
1142 if (SLIST_NEXT(SLIST_FIRST(curlist), entries))
1143 {
1144 struct Pattern *root = attach_new_root(&curlist);
1145 root->op = pat_or ? MUTT_PAT_OR : MUTT_PAT_AND;
1146 }
1147
1148 return curlist;
1149
1150cleanup:
1151 mutt_pattern_free(&curlist);
1152 buf_pool_release(&ps);
1153 return NULL;
1154}
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
#define MUTT_PDR_PLUS
Extend the range using '+'.
Definition: compile.c:54
#define MUTT_PDR_NO_FLAGS
No flags are set.
Definition: compile.c:52
bool eval_date_minmax(struct Pattern *pat, const char *s, struct Buffer *err)
Evaluate a date-range pattern against 'now'.
Definition: compile.c:494
static struct Pattern * attach_new_root(struct PatternList **curlist)
Create a new Pattern as a parent for a List.
Definition: compile.c:868
uint16_t ParseDateRangeFlags
Flags for parse_date_range(), e.g. MUTT_PDR_MINUS.
Definition: compile.c:51
#define MUTT_PDR_ERROR
Invalid pattern.
Definition: compile.c:58
#define MUTT_PDR_ABSOLUTE
Absolute pattern range.
Definition: compile.c:56
static struct Pattern * attach_new_leaf(struct PatternList **curlist)
Attach a new Pattern to a List.
Definition: compile.c:884
static struct Pattern * attach_leaf(struct PatternList *list, struct Pattern *leaf)
Attach a Pattern to a Pattern List.
Definition: compile.c:846
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:362
static char * find_matching_paren(char *s)
Find the matching parenthesis.
Definition: compile.c:752
static void adjust_date_range(struct tm *min, struct tm *max)
Put a date range in the correct order.
Definition: compile.c:456
static struct Pattern * mutt_pattern_new(void)
Create a new Pattern.
Definition: compile.c:822
static const char * get_offset(struct tm *tm, const char *s, int sign)
Calculate a symbolic offset.
Definition: compile.c:222
#define MUTT_PDR_DONE
Pattern parse successfully.
Definition: compile.c:57
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:276
static struct PatternList * mutt_pattern_list_new(void)
Create a new list containing a Pattern.
Definition: compile.c:831
struct PatternList * mutt_pattern_comp(struct MailboxView *mv, struct Menu *menu, const char *s, PatternCompFlags flags, struct Buffer *err)
Create a Pattern.
Definition: compile.c:905
#define MUTT_PDR_ERRORDONE
Definition: compile.c:61
void mutt_pattern_free(struct PatternList **pat)
Free a Pattern.
Definition: compile.c:776
#define MUTT_PDR_MINUS
Pattern contains a range.
Definition: compile.c:53
#define MUTT_PDR_WINDOW
Extend the range in both directions using '*'.
Definition: compile.c:55
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:223
int mutt_toupper(int arg)
Wrapper for toupper(3)
Definition: ctype.c:139
bool mutt_isdigit(int arg)
Wrapper for isdigit(3)
Definition: ctype.c:65
int parse_extract_token(struct Buffer *dest, struct Buffer *tok, TokenFlags flags)
Extract one token from a string.
Definition: extract.c:48
#define TOKEN_COMMENT
Don't reap comments.
Definition: extract.h:50
#define TOKEN_PATTERN
~%=!| are terms (for patterns)
Definition: extract.h:49
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:787
#define mutt_file_fclose(FP)
Definition: file.h:139
#define MUTT_RL_NO_FLAGS
No flags are set.
Definition: file.h:40
struct Group * mutt_pattern_group(const char *pat)
Match a pattern to a Group.
Definition: group.c:117
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:141
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:622
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:713
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:66
#define mutt_message(...)
Definition: logging2.h:92
#define mutt_debug(LEVEL,...)
Definition: logging2.h:90
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:121
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:45
bool mutt_mb_is_lower(const char *s)
Does a multi-byte string contain only lowercase characters?
Definition: mbyte.c:354
#define FREE(x)
Definition: memory.h:62
#define MUTT_MEM_CALLOC(n, type)
Definition: memory.h:47
struct tm mutt_date_localtime(time_t t)
Converts calendar time to a broken-down time structure expressed in user timezone.
Definition: date.c:906
time_t mutt_date_make_time(struct tm *t, bool local)
Convert struct tm to time_t
Definition: date.c:242
time_t mutt_date_now(void)
Return the number of seconds since the Unix epoch.
Definition: date.c:456
void mutt_date_normalize_time(struct tm *tm)
Fix the contents of a struct tm.
Definition: date.c:310
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:381
void mutt_str_remove_trailing_ws(char *s)
Trim trailing whitespace from a string.
Definition: string.c:564
char * mutt_str_dup(const char *str)
Copy a string, safely.
Definition: string.c:254
char * mutt_str_skip_whitespace(const char *p)
Find the first non-whitespace character in a string.
Definition: string.c:550
View of a Mailbox.
Text parsing functions.
const struct PatternFlags * lookup_tag(char tag)
Lookup a pattern modifier.
Definition: flags.c:198
#define MUTT_PC_SEND_MODE_SEARCH
Allow send-mode body searching.
Definition: lib.h:71
uint8_t PatternCompFlags
Flags for mutt_pattern_comp(), e.g. MUTT_PC_FULL_MSG.
Definition: lib.h:67
@ MUTT_PAT_OR
Either pattern can match.
Definition: lib.h:138
@ MUTT_PAT_CHILDREN
Pattern matches a child email.
Definition: lib.h:141
@ MUTT_PAT_PARENT
Pattern matches parent.
Definition: lib.h:140
@ MUTT_PAT_AND
Both patterns must match.
Definition: lib.h:137
@ MUTT_PAT_THREAD
Pattern matches email thread.
Definition: lib.h:139
#define MUTT_PC_PATTERN_DYNAMIC
Enable runtime date range evaluation.
Definition: lib.h:70
@ EAT_RANGE
Process a number (range)
Definition: private.h:55
@ EAT_MESSAGE_RANGE
Process a message number (range)
Definition: private.h:56
@ EAT_DATE
Process a date (range)
Definition: private.h:54
@ EAT_QUERY
Process a query string.
Definition: private.h:57
@ EAT_REGEX
Process a regex.
Definition: private.h:53
#define MUTT_MAXRANGE
Definition: private.h:141
struct Buffer * buf_pool_get(void)
Get a Buffer from the pool.
Definition: pool.c:82
void buf_pool_release(struct Buffer **ptr)
Return a Buffer to the pool.
Definition: pool.c:96
#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
GUI display the mailboxes in a side panel.
Key value store.
#define SKIPWS(ch)
Definition: string2.h:44
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
Definition: lib.h:79
Container for Accounts, Notifications.
Definition: neomutt.h:43
char ** env
Private copy of the environment variables.
Definition: neomutt.h:55
struct ConfigSubset * sub
Inherited config items.
Definition: neomutt.h:47
Mapping between user character and internal constant.
Definition: private.h:64
enum PatternEat eat_arg
Type of function needed to parse flag, e.g. EAT_DATE.
Definition: private.h:69
PatternCompFlags flags
Pattern flags, e.g. MUTT_PC_FULL_MSG.
Definition: private.h:67
int op
Operation to perform, e.g. MUTT_PAT_SCORE.
Definition: private.h:66
A simple (non-regex) pattern.
Definition: lib.h:77
bool group_match
Check a group of Addresses.
Definition: lib.h:82
union Pattern::@1 p
bool all_addr
All Addresses in the list must match.
Definition: lib.h:80
struct Group * group
Address group if group_match is set.
Definition: lib.h:93
struct PatternList * child
Arguments to logical operation.
Definition: lib.h:90
long min
Minimum for range checks.
Definition: lib.h:88
bool string_match
Check a string for a match.
Definition: lib.h:81
regex_t * regex
Compiled regex, for non-pattern matching.
Definition: lib.h:92
struct ListHead multi_cases
Multiple strings for ~I pattern.
Definition: lib.h:95
char * str
String, if string_match is set.
Definition: lib.h:94
bool is_alias
Is there an alias for this Address?
Definition: lib.h:84
bool ign_case
Ignore case for local string_match searches.
Definition: lib.h:83
long max
Maximum for range checks.
Definition: lib.h:89
bool dynamic
Evaluate date ranges at run time.
Definition: lib.h:85
short op
Operation, e.g. MUTT_PAT_SCORE.
Definition: lib.h:78
bool sendmode
Evaluate searches in send-mode.
Definition: lib.h:86
bool is_multi
Multiple case (only for ~I pattern now)
Definition: lib.h:87
bool pat_not
Pattern should be inverted (not)
Definition: lib.h:79