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