NeoMutt  2020-04-24
Teaching an old dog new tricks
DOXYGEN
pattern.c
Go to the documentation of this file.
1 
30 #include "config.h"
31 #include <stddef.h>
32 #include <ctype.h>
33 #include <stdarg.h>
34 #include <stdbool.h>
35 #include <stdio.h>
36 #include <stdlib.h>
37 #include <string.h>
38 #include <time.h>
39 #include <unistd.h>
40 #include "mutt/lib.h"
41 #include "address/lib.h"
42 #include "email/lib.h"
43 #include "core/lib.h"
44 #include "gui/lib.h"
45 #include "mutt.h"
46 #include "pattern.h"
47 #include "alias.h"
48 #include "context.h"
49 #include "copy.h"
50 #include "globals.h"
51 #include "handler.h"
52 #include "hdrline.h"
53 #include "init.h"
54 #include "mutt_logging.h"
55 #include "mutt_menu.h"
56 #include "mutt_parse.h"
57 #include "muttlib.h"
58 #include "mx.h"
59 #include "opcodes.h"
60 #include "options.h"
61 #include "progress.h"
62 #include "protos.h"
63 #include "sendlib.h"
64 #include "state.h"
65 #include "ncrypt/lib.h"
66 #ifndef USE_FMEMOPEN
67 #include <sys/stat.h>
68 #endif
69 #ifdef USE_IMAP
70 #include "imap/lib.h"
71 #endif
72 
73 /* These Config Variables are only used in pattern.c */
75 
76 // clang-format off
77 /* The regexes in a modern format */
78 #define RANGE_NUM_RX "([[:digit:]]+|0x[[:xdigit:]]+)[MmKk]?"
79 #define RANGE_REL_SLOT_RX "[[:blank:]]*([.^$]|-?" RANGE_NUM_RX ")?[[:blank:]]*"
80 #define RANGE_REL_RX "^" RANGE_REL_SLOT_RX "," RANGE_REL_SLOT_RX
81 
82 /* Almost the same, but no negative numbers allowed */
83 #define RANGE_ABS_SLOT_RX "[[:blank:]]*([.^$]|" RANGE_NUM_RX ")?[[:blank:]]*"
84 #define RANGE_ABS_RX "^" RANGE_ABS_SLOT_RX "-" RANGE_ABS_SLOT_RX
85 
86 /* First group is intentionally empty */
87 #define RANGE_LT_RX "^()[[:blank:]]*(<[[:blank:]]*" RANGE_NUM_RX ")[[:blank:]]*"
88 #define RANGE_GT_RX "^()[[:blank:]]*(>[[:blank:]]*" RANGE_NUM_RX ")[[:blank:]]*"
89 
90 /* Single group for min and max */
91 #define RANGE_BARE_RX "^[[:blank:]]*([.^$]|" RANGE_NUM_RX ")[[:blank:]]*"
92 #define RANGE_RX_GROUPS 5
93 
94 #define KILO 1024
95 #define MEGA 1048576
96 #define EMSG(e) (((e)->msgno) + 1)
97 
98 #define MUTT_MAXRANGE -1
99 
100 typedef uint16_t ParseDateRangeFlags;
101 #define MUTT_PDR_NO_FLAGS 0
102 #define MUTT_PDR_MINUS (1 << 0)
103 #define MUTT_PDR_PLUS (1 << 1)
104 #define MUTT_PDR_WINDOW (1 << 2)
105 #define MUTT_PDR_ABSOLUTE (1 << 3)
106 #define MUTT_PDR_DONE (1 << 4)
107 #define MUTT_PDR_ERROR (1 << 8)
108 
109 #define MUTT_PDR_ERRORDONE (MUTT_PDR_ERROR | MUTT_PDR_DONE)
110 
111 #define RANGE_DOT '.'
112 #define RANGE_CIRCUM '^'
113 #define RANGE_DOLLAR '$'
114 #define RANGE_LT '<'
115 #define RANGE_GT '>'
116 // clang-format on
117 
122 {
126 };
127 
132 {
133  const char *raw;
134  int lgrp;
135  int rgrp;
136  bool ready;
137  regex_t cooked;
138 };
139 
144 {
150  /* add new ones HERE */
152 };
153 
158 {
161 };
162 
167 {
168  int tag;
169  int op;
170  int flags;
171 
180  bool (*eat_arg)(struct Pattern *pat, int flags, struct Buffer *s, struct Buffer *err);
181 };
182 
183 // clang-format off
189 static struct RangeRegex range_regexes[] = {
190  [RANGE_K_REL] = { RANGE_REL_RX, 1, 3, 0, { 0 } },
191  [RANGE_K_ABS] = { RANGE_ABS_RX, 1, 3, 0, { 0 } },
192  [RANGE_K_LT] = { RANGE_LT_RX, 1, 2, 0, { 0 } },
193  [RANGE_K_GT] = { RANGE_GT_RX, 2, 1, 0, { 0 } },
194  [RANGE_K_BARE] = { RANGE_BARE_RX, 1, 1, 0, { 0 } },
195 };
196 // clang-format on
197 
198 static struct PatternList *SearchPattern = NULL;
199 static char LastSearch[256] = { 0 };
200 static char LastSearchExpn[1024] = { 0 };
201 
207 typedef bool (*addr_predicate_t)(const struct Address *a);
208 
212 static bool eat_regex(struct Pattern *pat, int flags, struct Buffer *s, struct Buffer *err)
213 {
214  struct Buffer buf;
215 
216  mutt_buffer_init(&buf);
217  char *pexpr = s->dptr;
218  if ((mutt_extract_token(&buf, s, MUTT_TOKEN_PATTERN | MUTT_TOKEN_COMMENT) != 0) || !buf.data)
219  {
220  mutt_buffer_printf(err, _("Error in expression: %s"), pexpr);
221  FREE(&buf.data);
222  return false;
223  }
224  if (buf.data[0] == '\0')
225  {
226  mutt_buffer_printf(err, "%s", _("Empty expression"));
227  FREE(&buf.data);
228  return false;
229  }
230 
231  if (pat->string_match)
232  {
233  pat->p.str = mutt_str_strdup(buf.data);
234  pat->ign_case = mutt_mb_is_lower(buf.data);
235  FREE(&buf.data);
236  }
237  else if (pat->group_match)
238  {
239  pat->p.group = mutt_pattern_group(buf.data);
240  FREE(&buf.data);
241  }
242  else
243  {
244  pat->p.regex = mutt_mem_malloc(sizeof(regex_t));
245  int case_flags = mutt_mb_is_lower(buf.data) ? REG_ICASE : 0;
246  int rc = REG_COMP(pat->p.regex, buf.data, REG_NEWLINE | REG_NOSUB | case_flags);
247  if (rc != 0)
248  {
249  char errmsg[256];
250  regerror(rc, pat->p.regex, errmsg, sizeof(errmsg));
251  mutt_buffer_add_printf(err, "'%s': %s", buf.data, errmsg);
252  FREE(&buf.data);
253  FREE(&pat->p.regex);
254  return false;
255  }
256  FREE(&buf.data);
257  }
258 
259  return true;
260 }
261 
266 static bool add_query_msgid(char *line, int line_num, void *user_data)
267 {
268  struct ListHead *msgid_list = (struct ListHead *) (user_data);
269  char *nows = mutt_str_skip_whitespace(line);
270  if (!*nows)
271  return true;
273  mutt_list_insert_tail(msgid_list, mutt_str_strdup(nows));
274  return true;
275 }
276 
280 static bool eat_query(struct Pattern *pat, int flags, struct Buffer *s, struct Buffer *err)
281 {
282  struct Buffer cmd_buf;
283  struct Buffer tok_buf;
284  FILE *fp = NULL;
285 
287  {
288  mutt_buffer_printf(err, "%s", _("No search command defined"));
289  return false;
290  }
291 
292  mutt_buffer_init(&tok_buf);
293  char *pexpr = s->dptr;
294  if ((mutt_extract_token(&tok_buf, s, MUTT_TOKEN_PATTERN | MUTT_TOKEN_COMMENT) != 0) ||
295  !tok_buf.data)
296  {
297  mutt_buffer_printf(err, _("Error in expression: %s"), pexpr);
298  return false;
299  }
300  if (*tok_buf.data == '\0')
301  {
302  mutt_buffer_printf(err, "%s", _("Empty expression"));
303  FREE(&tok_buf.data);
304  return false;
305  }
306 
307  mutt_buffer_init(&cmd_buf);
309  mutt_buffer_addch(&cmd_buf, ' ');
310  if (!Context || !Context->mailbox)
311  {
312  mutt_buffer_addch(&cmd_buf, '/');
313  }
314  else
315  {
316  char *escaped_folder = mutt_path_escape(mailbox_path(Context->mailbox));
317  mutt_debug(LL_DEBUG2, "escaped folder path: %s\n", escaped_folder);
318  mutt_buffer_addch(&cmd_buf, '\'');
319  mutt_buffer_addstr(&cmd_buf, escaped_folder);
320  mutt_buffer_addch(&cmd_buf, '\'');
321  }
322  mutt_buffer_addch(&cmd_buf, ' ');
323  mutt_buffer_addstr(&cmd_buf, tok_buf.data);
324  FREE(&tok_buf.data);
325 
326  mutt_message(_("Running search command: %s ..."), cmd_buf.data);
327  pat->is_multi = true;
329  pid_t pid = filter_create(cmd_buf.data, NULL, &fp, NULL);
330  if (pid < 0)
331  {
332  mutt_buffer_printf(err, "unable to fork command: %s\n", cmd_buf.data);
333  FREE(&cmd_buf.data);
334  return false;
335  }
336 
338  mutt_file_fclose(&fp);
339  filter_wait(pid);
340  FREE(&cmd_buf.data);
341  return true;
342 }
343 
356 static const char *get_offset(struct tm *tm, const char *s, int sign)
357 {
358  char *ps = NULL;
359  int offset = strtol(s, &ps, 0);
360  if (((sign < 0) && (offset > 0)) || ((sign > 0) && (offset < 0)))
361  offset = -offset;
362 
363  switch (*ps)
364  {
365  case 'y':
366  tm->tm_year += offset;
367  break;
368  case 'm':
369  tm->tm_mon += offset;
370  break;
371  case 'w':
372  tm->tm_mday += 7 * offset;
373  break;
374  case 'd':
375  tm->tm_mday += offset;
376  break;
377  case 'H':
378  tm->tm_hour += offset;
379  break;
380  case 'M':
381  tm->tm_min += offset;
382  break;
383  case 'S':
384  tm->tm_sec += offset;
385  break;
386  default:
387  return s;
388  }
390  return ps + 1;
391 }
392 
410 static const char *get_date(const char *s, struct tm *t, struct Buffer *err)
411 {
412  char *p = NULL;
413  struct tm tm = mutt_date_localtime(MUTT_DATE_NOW);
414  bool iso8601 = true;
415 
416  for (int v = 0; v < 8; v++)
417  {
418  if (s[v] && (s[v] >= '0') && (s[v] <= '9'))
419  continue;
420 
421  iso8601 = false;
422  break;
423  }
424 
425  if (iso8601)
426  {
427  int year = 0;
428  int month = 0;
429  int mday = 0;
430  sscanf(s, "%4d%2d%2d", &year, &month, &mday);
431 
432  t->tm_year = year;
433  if (t->tm_year > 1900)
434  t->tm_year -= 1900;
435  t->tm_mon = month - 1;
436  t->tm_mday = mday;
437 
438  if ((t->tm_mday < 1) || (t->tm_mday > 31))
439  {
440  snprintf(err->data, err->dsize, _("Invalid day of month: %s"), s);
441  return NULL;
442  }
443  if ((t->tm_mon < 0) || (t->tm_mon > 11))
444  {
445  snprintf(err->data, err->dsize, _("Invalid month: %s"), s);
446  return NULL;
447  }
448 
449  return (s + 8);
450  }
451 
452  t->tm_mday = strtol(s, &p, 10);
453  if ((t->tm_mday < 1) || (t->tm_mday > 31))
454  {
455  mutt_buffer_printf(err, _("Invalid day of month: %s"), s);
456  return NULL;
457  }
458  if (*p != '/')
459  {
460  /* fill in today's month and year */
461  t->tm_mon = tm.tm_mon;
462  t->tm_year = tm.tm_year;
463  return p;
464  }
465  p++;
466  t->tm_mon = strtol(p, &p, 10) - 1;
467  if ((t->tm_mon < 0) || (t->tm_mon > 11))
468  {
469  mutt_buffer_printf(err, _("Invalid month: %s"), p);
470  return NULL;
471  }
472  if (*p != '/')
473  {
474  t->tm_year = tm.tm_year;
475  return p;
476  }
477  p++;
478  t->tm_year = strtol(p, &p, 10);
479  if (t->tm_year < 70) /* year 2000+ */
480  t->tm_year += 100;
481  else if (t->tm_year > 1900)
482  t->tm_year -= 1900;
483  return p;
484 }
485 
496 static const char *parse_date_range(const char *pc, struct tm *min, struct tm *max,
497  bool have_min, struct tm *base_min, struct Buffer *err)
498 {
500  while (*pc && ((flags & MUTT_PDR_DONE) == 0))
501  {
502  const char *pt = NULL;
503  char ch = *pc++;
504  SKIPWS(pc);
505  switch (ch)
506  {
507  case '-':
508  {
509  /* try a range of absolute date minus offset of Ndwmy */
510  pt = get_offset(min, pc, -1);
511  if (pc == pt)
512  {
513  if (flags == MUTT_PDR_NO_FLAGS)
514  { /* nothing yet and no offset parsed => absolute date? */
515  if (!get_date(pc, max, err))
516  flags |= (MUTT_PDR_ABSOLUTE | MUTT_PDR_ERRORDONE); /* done bad */
517  else
518  {
519  /* reestablish initial base minimum if not specified */
520  if (!have_min)
521  memcpy(min, base_min, sizeof(struct tm));
522  flags |= (MUTT_PDR_ABSOLUTE | MUTT_PDR_DONE); /* done good */
523  }
524  }
525  else
526  flags |= MUTT_PDR_ERRORDONE;
527  }
528  else
529  {
530  pc = pt;
531  if ((flags == MUTT_PDR_NO_FLAGS) && !have_min)
532  { /* the very first "-3d" without a previous absolute date */
533  max->tm_year = min->tm_year;
534  max->tm_mon = min->tm_mon;
535  max->tm_mday = min->tm_mday;
536  }
537  flags |= MUTT_PDR_MINUS;
538  }
539  break;
540  }
541  case '+':
542  { /* enlarge plus range */
543  pt = get_offset(max, pc, 1);
544  if (pc == pt)
545  flags |= MUTT_PDR_ERRORDONE;
546  else
547  {
548  pc = pt;
549  flags |= MUTT_PDR_PLUS;
550  }
551  break;
552  }
553  case '*':
554  { /* enlarge window in both directions */
555  pt = get_offset(min, pc, -1);
556  if (pc == pt)
557  flags |= MUTT_PDR_ERRORDONE;
558  else
559  {
560  pc = get_offset(max, pc, 1);
561  flags |= MUTT_PDR_WINDOW;
562  }
563  break;
564  }
565  default:
566  flags |= MUTT_PDR_ERRORDONE;
567  }
568  SKIPWS(pc);
569  }
570  if ((flags & MUTT_PDR_ERROR) && !(flags & MUTT_PDR_ABSOLUTE))
571  { /* get_date has its own error message, don't overwrite it here */
572  mutt_buffer_printf(err, _("Invalid relative date: %s"), pc - 1);
573  }
574  return (flags & MUTT_PDR_ERROR) ? NULL : pc;
575 }
576 
582 static void adjust_date_range(struct tm *min, struct tm *max)
583 {
584  if ((min->tm_year > max->tm_year) ||
585  ((min->tm_year == max->tm_year) && (min->tm_mon > max->tm_mon)) ||
586  ((min->tm_year == max->tm_year) && (min->tm_mon == max->tm_mon) &&
587  (min->tm_mday > max->tm_mday)))
588  {
589  int tmp;
590 
591  tmp = min->tm_year;
592  min->tm_year = max->tm_year;
593  max->tm_year = tmp;
594 
595  tmp = min->tm_mon;
596  min->tm_mon = max->tm_mon;
597  max->tm_mon = tmp;
598 
599  tmp = min->tm_mday;
600  min->tm_mday = max->tm_mday;
601  max->tm_mday = tmp;
602 
603  min->tm_hour = 0;
604  min->tm_min = 0;
605  min->tm_sec = 0;
606  max->tm_hour = 23;
607  max->tm_min = 59;
608  max->tm_sec = 59;
609  }
610 }
611 
620 static bool eval_date_minmax(struct Pattern *pat, const char *s, struct Buffer *err)
621 {
622  /* the '0' time is Jan 1, 1970 UTC, so in order to prevent a negative time
623  * when doing timezone conversion, we use Jan 2, 1970 UTC as the base here */
624  struct tm min = { 0 };
625  min.tm_mday = 2;
626  min.tm_year = 70;
627 
628  /* Arbitrary year in the future. Don't set this too high or
629  * mutt_date_make_time() returns something larger than will fit in a time_t
630  * on some systems */
631  struct tm max = { 0 };
632  max.tm_year = 130;
633  max.tm_mon = 11;
634  max.tm_mday = 31;
635  max.tm_hour = 23;
636  max.tm_min = 59;
637  max.tm_sec = 59;
638 
639  if (strchr("<>=", s[0]))
640  {
641  /* offset from current time
642  * <3d less than three days ago
643  * >3d more than three days ago
644  * =3d exactly three days ago */
645  struct tm *tm = NULL;
646  bool exact = false;
647 
648  if (s[0] == '<')
649  {
651  tm = &min;
652  }
653  else
654  {
656  tm = &max;
657 
658  if (s[0] == '=')
659  exact = true;
660  }
661 
662  /* Reset the HMS unless we are relative matching using one of those
663  * offsets. */
664  char *offset_type = NULL;
665  strtol(s + 1, &offset_type, 0);
666  if (!(*offset_type && strchr("HMS", *offset_type)))
667  {
668  tm->tm_hour = 23;
669  tm->tm_min = 59;
670  tm->tm_sec = 59;
671  }
672 
673  /* force negative offset */
674  get_offset(tm, s + 1, -1);
675 
676  if (exact)
677  {
678  /* start at the beginning of the day in question */
679  memcpy(&min, &max, sizeof(max));
680  min.tm_hour = 0;
681  min.tm_sec = 0;
682  min.tm_min = 0;
683  }
684  }
685  else
686  {
687  const char *pc = s;
688 
689  bool have_min = false;
690  bool until_now = false;
691  if (isdigit((unsigned char) *pc))
692  {
693  /* minimum date specified */
694  pc = get_date(pc, &min, err);
695  if (!pc)
696  {
697  return false;
698  }
699  have_min = true;
700  SKIPWS(pc);
701  if (*pc == '-')
702  {
703  const char *pt = pc + 1;
704  SKIPWS(pt);
705  until_now = (*pt == '\0');
706  }
707  }
708 
709  if (!until_now)
710  { /* max date or relative range/window */
711 
712  struct tm base_min;
713 
714  if (!have_min)
715  { /* save base minimum and set current date, e.g. for "-3d+1d" */
716  memcpy(&base_min, &min, sizeof(base_min));
718  min.tm_hour = 0;
719  min.tm_sec = 0;
720  min.tm_min = 0;
721  }
722 
723  /* preset max date for relative offsets,
724  * if nothing follows we search for messages on a specific day */
725  max.tm_year = min.tm_year;
726  max.tm_mon = min.tm_mon;
727  max.tm_mday = min.tm_mday;
728 
729  if (!parse_date_range(pc, &min, &max, have_min, &base_min, err))
730  { /* bail out on any parsing error */
731  return false;
732  }
733  }
734  }
735 
736  /* Since we allow two dates to be specified we'll have to adjust that. */
737  adjust_date_range(&min, &max);
738 
739  pat->min = mutt_date_make_time(&min, true);
740  pat->max = mutt_date_make_time(&max, true);
741 
742  return true;
743 }
744 
748 static bool eat_range(struct Pattern *pat, int flags, struct Buffer *s, struct Buffer *err)
749 {
750  char *tmp = NULL;
751  bool do_exclusive = false;
752  bool skip_quote = false;
753 
754  /* If simple_search is set to "~m %s", the range will have double quotes
755  * around it... */
756  if (*s->dptr == '"')
757  {
758  s->dptr++;
759  skip_quote = true;
760  }
761  if (*s->dptr == '<')
762  do_exclusive = true;
763  if ((*s->dptr != '-') && (*s->dptr != '<'))
764  {
765  /* range minimum */
766  if (*s->dptr == '>')
767  {
768  pat->max = MUTT_MAXRANGE;
769  pat->min = strtol(s->dptr + 1, &tmp, 0) + 1; /* exclusive range */
770  }
771  else
772  pat->min = strtol(s->dptr, &tmp, 0);
773  if (toupper((unsigned char) *tmp) == 'K') /* is there a prefix? */
774  {
775  pat->min *= 1024;
776  tmp++;
777  }
778  else if (toupper((unsigned char) *tmp) == 'M')
779  {
780  pat->min *= 1048576;
781  tmp++;
782  }
783  if (*s->dptr == '>')
784  {
785  s->dptr = tmp;
786  return true;
787  }
788  if (*tmp != '-')
789  {
790  /* exact value */
791  pat->max = pat->min;
792  s->dptr = tmp;
793  return true;
794  }
795  tmp++;
796  }
797  else
798  {
799  s->dptr++;
800  tmp = s->dptr;
801  }
802 
803  if (isdigit((unsigned char) *tmp))
804  {
805  /* range maximum */
806  pat->max = strtol(tmp, &tmp, 0);
807  if (toupper((unsigned char) *tmp) == 'K')
808  {
809  pat->max *= 1024;
810  tmp++;
811  }
812  else if (toupper((unsigned char) *tmp) == 'M')
813  {
814  pat->max *= 1048576;
815  tmp++;
816  }
817  if (do_exclusive)
818  (pat->max)--;
819  }
820  else
821  pat->max = MUTT_MAXRANGE;
822 
823  if (skip_quote && (*tmp == '"'))
824  tmp++;
825 
826  SKIPWS(tmp);
827  s->dptr = tmp;
828  return true;
829 }
830 
838 static int report_regerror(int regerr, regex_t *preg, struct Buffer *err)
839 {
840  size_t ds = err->dsize;
841 
842  if (regerror(regerr, preg, err->data, ds) > ds)
843  mutt_debug(LL_DEBUG2, "warning: buffer too small for regerror\n");
844  /* The return value is fixed, exists only to shorten code at callsite */
845  return RANGE_E_SYNTAX;
846 }
847 
857 static bool is_context_available(struct Buffer *s, regmatch_t pmatch[],
858  int kind, struct Buffer *err)
859 {
860  const char *context_req_chars[] = {
861  [RANGE_K_REL] = ".0123456789",
862  [RANGE_K_ABS] = ".",
863  [RANGE_K_LT] = "",
864  [RANGE_K_GT] = "",
865  [RANGE_K_BARE] = ".",
866  };
867 
868  /* First decide if we're going to need the context at all.
869  * Relative patterns need it if they contain a dot or a number.
870  * Absolute patterns only need it if they contain a dot. */
871  char *context_loc = strpbrk(s->dptr + pmatch[0].rm_so, context_req_chars[kind]);
872  if (!context_loc || (context_loc >= &s->dptr[pmatch[0].rm_eo]))
873  return true;
874 
875  /* We need a current message. Do we actually have one? */
876  if (Context && Context->menu)
877  return true;
878 
879  /* Nope. */
880  mutt_buffer_strcpy(err, _("No current message"));
881  return false;
882 }
883 
892 static int scan_range_num(struct Buffer *s, regmatch_t pmatch[], int group, int kind)
893 {
894  int num = (int) strtol(&s->dptr[pmatch[group].rm_so], NULL, 0);
895  unsigned char c = (unsigned char) (s->dptr[pmatch[group].rm_eo - 1]);
896  if (toupper(c) == 'K')
897  num *= KILO;
898  else if (toupper(c) == 'M')
899  num *= MEGA;
900  switch (kind)
901  {
902  case RANGE_K_REL:
903  {
905  return num + EMSG(e);
906  }
907  case RANGE_K_LT:
908  return num - 1;
909  case RANGE_K_GT:
910  return num + 1;
911  default:
912  return num;
913  }
914 }
915 
925 static int scan_range_slot(struct Buffer *s, regmatch_t pmatch[], int grp, int side, int kind)
926 {
927  /* This means the left or right subpattern was empty, e.g. ",." */
928  if ((pmatch[grp].rm_so == -1) || (pmatch[grp].rm_so == pmatch[grp].rm_eo))
929  {
930  if (side == RANGE_S_LEFT)
931  return 1;
932  if (side == RANGE_S_RIGHT)
933  return Context->mailbox->msg_count;
934  }
935  /* We have something, so determine what */
936  unsigned char c = (unsigned char) (s->dptr[pmatch[grp].rm_so]);
937  switch (c)
938  {
939  case RANGE_CIRCUM:
940  return 1;
941  case RANGE_DOLLAR:
942  return Context->mailbox->msg_count;
943  case RANGE_DOT:
944  {
946  return EMSG(e);
947  }
948  case RANGE_LT:
949  case RANGE_GT:
950  return scan_range_num(s, pmatch, grp + 1, kind);
951  default:
952  /* Only other possibility: a number */
953  return scan_range_num(s, pmatch, grp, kind);
954  }
955 }
956 
961 static void order_range(struct Pattern *pat)
962 {
963  if (pat->min <= pat->max)
964  return;
965  int num = pat->min;
966  pat->min = pat->max;
967  pat->max = num;
968 }
969 
978 static int eat_range_by_regex(struct Pattern *pat, struct Buffer *s, int kind,
979  struct Buffer *err)
980 {
981  int regerr;
982  regmatch_t pmatch[RANGE_RX_GROUPS];
983  struct RangeRegex *pspec = &range_regexes[kind];
984 
985  /* First time through, compile the big regex */
986  if (!pspec->ready)
987  {
988  regerr = regcomp(&pspec->cooked, pspec->raw, REG_EXTENDED);
989  if (regerr != 0)
990  return report_regerror(regerr, &pspec->cooked, err);
991  pspec->ready = true;
992  }
993 
994  /* Match the pattern buffer against the compiled regex.
995  * No match means syntax error. */
996  regerr = regexec(&pspec->cooked, s->dptr, RANGE_RX_GROUPS, pmatch, 0);
997  if (regerr != 0)
998  return report_regerror(regerr, &pspec->cooked, err);
999 
1000  if (!is_context_available(s, pmatch, kind, err))
1001  return RANGE_E_CTX;
1002 
1003  /* Snarf the contents of the two sides of the range. */
1004  pat->min = scan_range_slot(s, pmatch, pspec->lgrp, RANGE_S_LEFT, kind);
1005  pat->max = scan_range_slot(s, pmatch, pspec->rgrp, RANGE_S_RIGHT, kind);
1006  mutt_debug(LL_DEBUG1, "pat->min=%d pat->max=%d\n", pat->min, pat->max);
1007 
1008  /* Special case for a bare 0. */
1009  if ((kind == RANGE_K_BARE) && (pat->min == 0) && (pat->max == 0))
1010  {
1011  if (!Context->menu)
1012  {
1013  mutt_buffer_strcpy(err, _("No current message"));
1014  return RANGE_E_CTX;
1015  }
1017  pat->max = EMSG(e);
1018  pat->min = pat->max;
1019  }
1020 
1021  /* Since we don't enforce order, we must swap bounds if they're backward */
1022  order_range(pat);
1023 
1024  /* Slide pointer past the entire match. */
1025  s->dptr += pmatch[0].rm_eo;
1026  return RANGE_E_OK;
1027 }
1028 
1032 static bool eat_message_range(struct Pattern *pat, int flags, struct Buffer *s,
1033  struct Buffer *err)
1034 {
1035  bool skip_quote = false;
1036 
1037  /* We need a Context for pretty much anything. */
1038  if (!Context)
1039  {
1040  mutt_buffer_strcpy(err, _("No Context"));
1041  return false;
1042  }
1043 
1044  /* If simple_search is set to "~m %s", the range will have double quotes
1045  * around it... */
1046  if (*s->dptr == '"')
1047  {
1048  s->dptr++;
1049  skip_quote = true;
1050  }
1051 
1052  for (int i_kind = 0; i_kind != RANGE_K_INVALID; i_kind++)
1053  {
1054  switch (eat_range_by_regex(pat, s, i_kind, err))
1055  {
1056  case RANGE_E_CTX:
1057  /* This means it matched syntactically but lacked context.
1058  * No point in continuing. */
1059  break;
1060  case RANGE_E_SYNTAX:
1061  /* Try another syntax, then */
1062  continue;
1063  case RANGE_E_OK:
1064  if (skip_quote && (*s->dptr == '"'))
1065  s->dptr++;
1066  SKIPWS(s->dptr);
1067  return true;
1068  }
1069  }
1070  return false;
1071 }
1072 
1076 static bool eat_date(struct Pattern *pat, int flags, struct Buffer *s, struct Buffer *err)
1077 {
1078  struct Buffer *tmp = mutt_buffer_pool_get();
1079  bool rc = false;
1080 
1081  char *pexpr = s->dptr;
1083  {
1084  snprintf(err->data, err->dsize, _("Error in expression: %s"), pexpr);
1085  goto out;
1086  }
1087 
1088  if (mutt_buffer_is_empty(tmp))
1089  {
1090  snprintf(err->data, err->dsize, "%s", _("Empty expression"));
1091  goto out;
1092  }
1093 
1094  if (flags & MUTT_PC_PATTERN_DYNAMIC)
1095  {
1096  pat->dynamic = true;
1097  pat->p.str = mutt_str_strdup(tmp->data);
1098  }
1099 
1100  rc = eval_date_minmax(pat, tmp->data, err);
1101 
1102 out:
1104 
1105  return rc;
1106 }
1107 
1115 static bool patmatch(const struct Pattern *pat, const char *buf)
1116 {
1117  if (pat->is_multi)
1118  return (mutt_list_find(&pat->p.multi_cases, buf) != NULL);
1119  if (pat->string_match)
1120  return pat->ign_case ? strcasestr(buf, pat->p.str) : strstr(buf, pat->p.str);
1121  if (pat->group_match)
1122  return mutt_group_match(pat->p.group, buf);
1123  return (regexec(pat->p.regex, buf, 0, NULL, 0) == 0);
1124 }
1125 
1134 static bool msg_search(struct Mailbox *m, struct Pattern *pat, int msgno)
1135 {
1136  bool match = false;
1137  struct Message *msg = mx_msg_open(m, msgno);
1138  if (!msg)
1139  {
1140  return match;
1141  }
1142 
1143  FILE *fp = NULL;
1144  long len = 0;
1145  struct Email *e = m->emails[msgno];
1146 #ifdef USE_FMEMOPEN
1147  char *temp = NULL;
1148  size_t tempsize = 0;
1149 #else
1150  struct stat st;
1151 #endif
1152 
1153  if (C_ThoroughSearch)
1154  {
1155  /* decode the header / body */
1156  struct State s = { 0 };
1157  s.fp_in = msg->fp;
1158  s.flags = MUTT_CHARCONV;
1159 #ifdef USE_FMEMOPEN
1160  s.fp_out = open_memstream(&temp, &tempsize);
1161  if (!s.fp_out)
1162  {
1163  mutt_perror(_("Error opening 'memory stream'"));
1164  return false;
1165  }
1166 #else
1167  s.fp_out = mutt_file_mkstemp();
1168  if (!s.fp_out)
1169  {
1170  mutt_perror(_("Can't create temporary file"));
1171  return false;
1172  }
1173 #endif
1174 
1175  if (pat->op != MUTT_PAT_BODY)
1176  mutt_copy_header(msg->fp, e, s.fp_out, CH_FROM | CH_DECODE, NULL, 0);
1177 
1178  if (pat->op != MUTT_PAT_HEADER)
1179  {
1181 
1182  if ((WithCrypto != 0) && (e->security & SEC_ENCRYPT) &&
1184  {
1185  mx_msg_close(m, &msg);
1186  if (s.fp_out)
1187  {
1189 #ifdef USE_FMEMOPEN
1190  FREE(&temp);
1191 #endif
1192  }
1193  return false;
1194  }
1195 
1196  fseeko(msg->fp, e->offset, SEEK_SET);
1197  mutt_body_handler(e->content, &s);
1198  }
1199 
1200 #ifdef USE_FMEMOPEN
1202  len = tempsize;
1203 
1204  if (tempsize != 0)
1205  {
1206  fp = fmemopen(temp, tempsize, "r");
1207  if (!fp)
1208  {
1209  mutt_perror(_("Error re-opening 'memory stream'"));
1210  FREE(&temp);
1211  return false;
1212  }
1213  }
1214  else
1215  { /* fmemopen can't handle empty buffers */
1216  fp = mutt_file_fopen("/dev/null", "r");
1217  if (!fp)
1218  {
1219  mutt_perror(_("Error opening /dev/null"));
1220  return false;
1221  }
1222  }
1223 #else
1224  fp = s.fp_out;
1225  fflush(fp);
1226  fseek(fp, 0, SEEK_SET);
1227  fstat(fileno(fp), &st);
1228  len = (long) st.st_size;
1229 #endif
1230  }
1231  else
1232  {
1233  /* raw header / body */
1234  fp = msg->fp;
1235  if (pat->op != MUTT_PAT_BODY)
1236  {
1237  fseeko(fp, e->offset, SEEK_SET);
1238  len = e->content->offset - e->offset;
1239  }
1240  if (pat->op != MUTT_PAT_HEADER)
1241  {
1242  if (pat->op == MUTT_PAT_BODY)
1243  fseeko(fp, e->content->offset, SEEK_SET);
1244  len += e->content->length;
1245  }
1246  }
1247 
1248  size_t blen = 256;
1249  char *buf = mutt_mem_malloc(blen);
1250 
1251  /* search the file "fp" */
1252  while (len > 0)
1253  {
1254  if (pat->op == MUTT_PAT_HEADER)
1255  {
1256  buf = mutt_rfc822_read_line(fp, buf, &blen);
1257  if (*buf == '\0')
1258  break;
1259  }
1260  else if (!fgets(buf, blen - 1, fp))
1261  break; /* don't loop forever */
1262  if (patmatch(pat, buf))
1263  {
1264  match = true;
1265  break;
1266  }
1267  len -= mutt_str_strlen(buf);
1268  }
1269 
1270  FREE(&buf);
1271 
1272  mx_msg_close(m, &msg);
1273 
1274  if (C_ThoroughSearch)
1275  mutt_file_fclose(&fp);
1276 
1277 #ifdef USE_FMEMOPEN
1278  FREE(&temp);
1279 #endif
1280  return match;
1281 }
1282 
1283 // clang-format off
1287 static const struct PatternFlags Flags[] = {
1288  { 'A', MUTT_ALL, 0, NULL },
1291  { 'c', MUTT_PAT_CC, 0, eat_regex },
1292  { 'C', MUTT_PAT_RECIPIENT, 0, eat_regex },
1293  { 'd', MUTT_PAT_DATE, 0, eat_date },
1294  { 'D', MUTT_DELETED, 0, NULL },
1295  { 'e', MUTT_PAT_SENDER, 0, eat_regex },
1296  { 'E', MUTT_EXPIRED, 0, NULL },
1297  { 'f', MUTT_PAT_FROM, 0, eat_regex },
1298  { 'F', MUTT_FLAG, 0, NULL },
1299  { 'g', MUTT_PAT_CRYPT_SIGN, 0, NULL },
1300  { 'G', MUTT_PAT_CRYPT_ENCRYPT, 0, NULL },
1302  { 'H', MUTT_PAT_HORMEL, 0, eat_regex },
1303  { 'i', MUTT_PAT_ID, 0, eat_regex },
1304  { 'I', MUTT_PAT_ID_EXTERNAL, 0, eat_query },
1305  { 'k', MUTT_PAT_PGP_KEY, 0, NULL },
1306  { 'l', MUTT_PAT_LIST, 0, NULL },
1307  { 'L', MUTT_PAT_ADDRESS, 0, eat_regex },
1308  { 'm', MUTT_PAT_MESSAGE, 0, eat_message_range },
1309  { 'M', MUTT_PAT_MIMETYPE, MUTT_PC_FULL_MSG, eat_regex },
1310  { 'n', MUTT_PAT_SCORE, 0, eat_range },
1311  { 'N', MUTT_NEW, 0, NULL },
1312  { 'O', MUTT_OLD, 0, NULL },
1313  { 'p', MUTT_PAT_PERSONAL_RECIP, 0, NULL },
1314  { 'P', MUTT_PAT_PERSONAL_FROM, 0, NULL },
1315  { 'Q', MUTT_REPLIED, 0, NULL },
1316  { 'r', MUTT_PAT_DATE_RECEIVED, 0, eat_date },
1317  { 'R', MUTT_READ, 0, NULL },
1318  { 's', MUTT_PAT_SUBJECT, 0, eat_regex },
1319  { 'S', MUTT_SUPERSEDED, 0, NULL },
1320  { 't', MUTT_PAT_TO, 0, eat_regex },
1321  { 'T', MUTT_TAG, 0, NULL },
1322  { 'u', MUTT_PAT_SUBSCRIBED_LIST, 0, NULL },
1323  { 'U', MUTT_UNREAD, 0, NULL },
1324  { 'v', MUTT_PAT_COLLAPSED, 0, NULL },
1325  { 'V', MUTT_PAT_CRYPT_VERIFIED, 0, NULL },
1326 #ifdef USE_NNTP
1327  { 'w', MUTT_PAT_NEWSGROUPS, 0, eat_regex },
1328 #endif
1329  { 'x', MUTT_PAT_REFERENCE, 0, eat_regex },
1330  { 'X', MUTT_PAT_MIMEATTACH, 0, eat_range },
1331  { 'y', MUTT_PAT_XLABEL, 0, eat_regex },
1332  { 'Y', MUTT_PAT_DRIVER_TAGS, 0, eat_regex },
1333  { 'z', MUTT_PAT_SIZE, 0, eat_range },
1334  { '=', MUTT_PAT_DUPLICATED, 0, NULL },
1335  { '$', MUTT_PAT_UNREFERENCED, 0, NULL },
1336  { '#', MUTT_PAT_BROKEN, 0, NULL },
1337  { '/', MUTT_PAT_SERVERSEARCH, 0, eat_regex },
1338  { 0, 0, 0, NULL },
1339 };
1340 // clang-format on
1341 
1347 static const struct PatternFlags *lookup_tag(char tag)
1348 {
1349  for (int i = 0; Flags[i].tag; i++)
1350  if (Flags[i].tag == tag)
1351  return &Flags[i];
1352  return NULL;
1353 }
1354 
1361 static /* const */ char *find_matching_paren(/* const */ char *s)
1362 {
1363  int level = 1;
1364 
1365  for (; *s; s++)
1366  {
1367  if (*s == '(')
1368  level++;
1369  else if (*s == ')')
1370  {
1371  level--;
1372  if (level == 0)
1373  break;
1374  }
1375  }
1376  return s;
1377 }
1378 
1383 void mutt_pattern_free(struct PatternList **pat)
1384 {
1385  if (!pat || !*pat)
1386  return;
1387 
1388  struct Pattern *np = SLIST_FIRST(*pat), *next = NULL;
1389 
1390  while (np)
1391  {
1392  next = SLIST_NEXT(np, entries);
1393 
1394  if (np->is_multi)
1396  else if (np->string_match || np->dynamic)
1397  FREE(&np->p.str);
1398  else if (np->group_match)
1399  np->p.group = NULL;
1400  else if (np->p.regex)
1401  {
1402  regfree(np->p.regex);
1403  FREE(&np->p.regex);
1404  }
1405 
1406  mutt_pattern_free(&np->child);
1407  FREE(&np);
1408 
1409  np = next;
1410  }
1411 
1412  FREE(pat);
1413 }
1414 
1419 static struct PatternList *mutt_pattern_node_new(void)
1420 {
1421  struct PatternList *h = mutt_mem_calloc(1, sizeof(struct PatternList));
1422  SLIST_INIT(h);
1423  struct Pattern *p = mutt_mem_calloc(1, sizeof(struct Pattern));
1424  SLIST_INSERT_HEAD(h, p, entries);
1425  return h;
1426 }
1427 
1435 struct PatternList *mutt_pattern_comp(const char *s, PatternCompFlags flags, struct Buffer *err)
1436 {
1437  /* curlist when assigned will always point to a list containing at least one node
1438  * with a Pattern value. */
1439  struct PatternList *curlist = NULL;
1440  struct PatternList *tmp = NULL, *tmp2 = NULL;
1441  struct PatternList *last = NULL;
1442  bool pat_not = false;
1443  bool all_addr = false;
1444  bool pat_or = false;
1445  bool implicit = true; /* used to detect logical AND operator */
1446  bool is_alias = false;
1447  short thread_op;
1448  const struct PatternFlags *entry = NULL;
1449  char *p = NULL;
1450  char *buf = NULL;
1451  struct Buffer ps;
1452 
1453  mutt_buffer_init(&ps);
1454  ps.dptr = (char *) s;
1455  ps.dsize = mutt_str_strlen(s);
1456 
1457  while (*ps.dptr)
1458  {
1459  SKIPWS(ps.dptr);
1460  switch (*ps.dptr)
1461  {
1462  case '^':
1463  ps.dptr++;
1464  all_addr = !all_addr;
1465  break;
1466  case '!':
1467  ps.dptr++;
1468  pat_not = !pat_not;
1469  break;
1470  case '@':
1471  ps.dptr++;
1472  is_alias = !is_alias;
1473  break;
1474  case '|':
1475  if (!pat_or)
1476  {
1477  if (!curlist)
1478  {
1479  mutt_buffer_printf(err, _("error in pattern at: %s"), ps.dptr);
1480  return NULL;
1481  }
1482 
1483  struct Pattern *pat = SLIST_FIRST(curlist);
1484 
1485  if (SLIST_NEXT(pat, entries))
1486  {
1487  /* A & B | C == (A & B) | C */
1488  tmp = mutt_pattern_node_new();
1489  pat = SLIST_FIRST(tmp);
1490  pat->op = MUTT_PAT_AND;
1491  pat->child = curlist;
1492 
1493  curlist = tmp;
1494  last = curlist;
1495  }
1496 
1497  pat_or = true;
1498  }
1499  ps.dptr++;
1500  implicit = false;
1501  pat_not = false;
1502  all_addr = false;
1503  is_alias = false;
1504  break;
1505  case '%':
1506  case '=':
1507  case '~':
1508  {
1509  struct Pattern *pat = NULL;
1510  if (ps.dptr[1] == '\0')
1511  {
1512  mutt_buffer_printf(err, _("missing pattern: %s"), ps.dptr);
1513  goto cleanup;
1514  }
1515  thread_op = 0;
1516  if (ps.dptr[1] == '(')
1517  thread_op = MUTT_PAT_THREAD;
1518  else if ((ps.dptr[1] == '<') && (ps.dptr[2] == '('))
1519  thread_op = MUTT_PAT_PARENT;
1520  else if ((ps.dptr[1] == '>') && (ps.dptr[2] == '('))
1521  thread_op = MUTT_PAT_CHILDREN;
1522  if (thread_op)
1523  {
1524  ps.dptr++; /* skip ~ */
1525  if ((thread_op == MUTT_PAT_PARENT) || (thread_op == MUTT_PAT_CHILDREN))
1526  ps.dptr++;
1527  p = find_matching_paren(ps.dptr + 1);
1528  if (p[0] != ')')
1529  {
1530  mutt_buffer_printf(err, _("mismatched parentheses: %s"), ps.dptr);
1531  goto cleanup;
1532  }
1533  tmp = mutt_pattern_node_new();
1534  pat = SLIST_FIRST(tmp);
1535  pat->op = thread_op;
1536  if (last)
1537  SLIST_NEXT(SLIST_FIRST(last), entries) = pat;
1538  else
1539  curlist = tmp;
1540  last = tmp;
1541  pat->pat_not ^= pat_not;
1542  pat->all_addr |= all_addr;
1543  pat->is_alias |= is_alias;
1544  pat_not = false;
1545  all_addr = false;
1546  is_alias = false;
1547  /* compile the sub-expression */
1548  buf = mutt_str_substr_dup(ps.dptr + 1, p);
1549  tmp2 = mutt_pattern_comp(buf, flags, err);
1550  if (!tmp2)
1551  {
1552  FREE(&buf);
1553  goto cleanup;
1554  }
1555  FREE(&buf);
1556  pat->child = tmp2;
1557  ps.dptr = p + 1; /* restore location */
1558  break;
1559  }
1560  if (implicit && pat_or)
1561  {
1562  /* A | B & C == (A | B) & C */
1563  tmp = mutt_pattern_node_new();
1564  pat = SLIST_FIRST(tmp);
1565  pat->op = MUTT_PAT_OR;
1566  pat->child = curlist;
1567  curlist = tmp;
1568  last = tmp;
1569  pat_or = false;
1570  }
1571 
1572  tmp = mutt_pattern_node_new();
1573  pat = SLIST_FIRST(tmp);
1574  pat->pat_not = pat_not;
1575  pat->all_addr = all_addr;
1576  pat->is_alias = is_alias;
1577  pat->string_match = (ps.dptr[0] == '=');
1578  pat->group_match = (ps.dptr[0] == '%');
1579  pat_not = false;
1580  all_addr = false;
1581  is_alias = false;
1582 
1583  if (last)
1584  SLIST_NEXT(SLIST_FIRST(last), entries) = pat;
1585  else
1586  curlist = tmp;
1587  if (curlist != last)
1588  FREE(&last);
1589  last = tmp;
1590 
1591  ps.dptr++; /* move past the ~ */
1592  entry = lookup_tag(*ps.dptr);
1593  if (!entry)
1594  {
1595  mutt_buffer_printf(err, _("%c: invalid pattern modifier"), *ps.dptr);
1596  goto cleanup;
1597  }
1598  if (entry->flags && ((flags & entry->flags) == 0))
1599  {
1600  mutt_buffer_printf(err, _("%c: not supported in this mode"), *ps.dptr);
1601  goto cleanup;
1602  }
1603  if (flags & MUTT_PC_SEND_MODE_SEARCH)
1604  pat->sendmode = true;
1605 
1606  pat->op = entry->op;
1607 
1608  ps.dptr++; /* eat the operator and any optional whitespace */
1609  SKIPWS(ps.dptr);
1610 
1611  if (entry->eat_arg)
1612  {
1613  if (ps.dptr[0] == '\0')
1614  {
1615  mutt_buffer_printf(err, "%s", _("missing parameter"));
1616  goto cleanup;
1617  }
1618  if (!entry->eat_arg(pat, flags, &ps, err))
1619  {
1620  goto cleanup;
1621  }
1622  }
1623  implicit = true;
1624  break;
1625  }
1626 
1627  case '(':
1628  {
1629  p = find_matching_paren(ps.dptr + 1);
1630  if (p[0] != ')')
1631  {
1632  mutt_buffer_printf(err, _("mismatched parentheses: %s"), ps.dptr);
1633  goto cleanup;
1634  }
1635  /* compile the sub-expression */
1636  buf = mutt_str_substr_dup(ps.dptr + 1, p);
1637  tmp = mutt_pattern_comp(buf, flags, err);
1638  FREE(&buf);
1639  if (!tmp)
1640  goto cleanup;
1641  struct Pattern *pat = SLIST_FIRST(tmp);
1642  if (last)
1643  SLIST_NEXT(SLIST_FIRST(last), entries) = pat;
1644  else
1645  curlist = tmp;
1646  last = tmp;
1647  pat = SLIST_FIRST(tmp);
1648  pat->pat_not ^= pat_not;
1649  pat->all_addr |= all_addr;
1650  pat->is_alias |= is_alias;
1651  pat_not = false;
1652  all_addr = false;
1653  is_alias = false;
1654  ps.dptr = p + 1; /* restore location */
1655  break;
1656  }
1657 
1658  default:
1659  mutt_buffer_printf(err, _("error in pattern at: %s"), ps.dptr);
1660  goto cleanup;
1661  }
1662  }
1663  if (!curlist)
1664  {
1665  mutt_buffer_strcpy(err, _("empty pattern"));
1666  return NULL;
1667  }
1668  if (curlist != tmp)
1669  FREE(&tmp);
1670  if (SLIST_NEXT(SLIST_FIRST(curlist), entries))
1671  {
1672  tmp = mutt_pattern_node_new();
1673  struct Pattern *pat = SLIST_FIRST(tmp);
1674  pat->op = pat_or ? MUTT_PAT_OR : MUTT_PAT_AND;
1675  pat->child = curlist;
1676  curlist = tmp;
1677  }
1678 
1679  return curlist;
1680 
1681 cleanup:
1682  mutt_pattern_free(&curlist);
1683  return NULL;
1684 }
1685 
1695 static bool perform_and(struct PatternList *pat, PatternExecFlags flags,
1696  struct Mailbox *m, struct Email *e, struct PatternCache *cache)
1697 {
1698  struct Pattern *p = NULL;
1699 
1700  SLIST_FOREACH(p, pat, entries)
1701  {
1702  if (mutt_pattern_exec(p, flags, m, e, cache) <= 0)
1703  return false;
1704  }
1705  return true;
1706 }
1707 
1717 static int perform_or(struct PatternList *pat, PatternExecFlags flags,
1718  struct Mailbox *m, struct Email *e, struct PatternCache *cache)
1719 {
1720  struct Pattern *p = NULL;
1721 
1722  SLIST_FOREACH(p, pat, entries)
1723  {
1724  if (mutt_pattern_exec(p, flags, m, e, cache) > 0)
1725  return true;
1726  }
1727  return false;
1728 }
1729 
1739 static int match_addrlist(struct Pattern *pat, bool match_personal, int n, ...)
1740 {
1741  va_list ap;
1742 
1743  va_start(ap, n);
1744  for (; n; n--)
1745  {
1746  struct AddressList *al = va_arg(ap, struct AddressList *);
1747  struct Address *a = NULL;
1748  TAILQ_FOREACH(a, al, entries)
1749  {
1750  if (pat->all_addr ^ ((!pat->is_alias || mutt_alias_reverse_lookup(a)) &&
1751  ((a->mailbox && patmatch(pat, a->mailbox)) ||
1752  (match_personal && a->personal && patmatch(pat, a->personal)))))
1753  {
1754  va_end(ap);
1755  return !pat->all_addr; /* Found match, or non-match if all_addr */
1756  }
1757  }
1758  }
1759  va_end(ap);
1760  return pat->all_addr; /* No matches, or all matches if all_addr */
1761 }
1762 
1769 static bool match_reference(struct Pattern *pat, struct ListHead *refs)
1770 {
1771  struct ListNode *np = NULL;
1772  STAILQ_FOREACH(np, refs, entries)
1773  {
1774  if (patmatch(pat, np->data))
1775  return true;
1776  }
1777  return false;
1778 }
1779 
1790 static int mutt_is_predicate_recipient(bool all_addr, struct Envelope *e, addr_predicate_t p)
1791 {
1792  struct AddressList *als[] = { &e->to, &e->cc };
1793  for (size_t i = 0; i < mutt_array_size(als); ++i)
1794  {
1795  struct AddressList *al = als[i];
1796  struct Address *a = NULL;
1797  TAILQ_FOREACH(a, al, entries)
1798  {
1799  if (all_addr ^ p(a))
1800  return !all_addr;
1801  }
1802  }
1803  return all_addr;
1804 }
1805 
1813 int mutt_is_subscribed_list_recipient(bool all_addr, struct Envelope *e)
1814 {
1816 }
1817 
1825 int mutt_is_list_recipient(bool all_addr, struct Envelope *e)
1826 {
1827  return mutt_is_predicate_recipient(all_addr, e, &mutt_is_mail_list);
1828 }
1829 
1838 static int match_user(int all_addr, struct AddressList *al1, struct AddressList *al2)
1839 {
1840  struct Address *a = NULL;
1841  if (al1)
1842  {
1843  TAILQ_FOREACH(a, al1, entries)
1844  {
1845  if (all_addr ^ mutt_addr_is_user(a))
1846  return !all_addr;
1847  }
1848  }
1849 
1850  if (al2)
1851  {
1852  TAILQ_FOREACH(a, al2, entries)
1853  {
1854  if (all_addr ^ mutt_addr_is_user(a))
1855  return !all_addr;
1856  }
1857  }
1858  return all_addr;
1859 }
1860 
1874 static int match_threadcomplete(struct PatternList *pat, PatternExecFlags flags,
1875  struct Mailbox *m, struct MuttThread *t,
1876  int left, int up, int right, int down)
1877 {
1878  if (!t)
1879  return 0;
1880 
1881  int a;
1882  struct Email *e = t->message;
1883  if (e)
1884  if (mutt_pattern_exec(SLIST_FIRST(pat), flags, m, e, NULL))
1885  return 1;
1886 
1887  if (up && (a = match_threadcomplete(pat, flags, m, t->parent, 1, 1, 1, 0)))
1888  return a;
1889  if (right && t->parent && (a = match_threadcomplete(pat, flags, m, t->next, 0, 0, 1, 1)))
1890  {
1891  return a;
1892  }
1893  if (left && t->parent && (a = match_threadcomplete(pat, flags, m, t->prev, 1, 0, 0, 1)))
1894  {
1895  return a;
1896  }
1897  if (down && (a = match_threadcomplete(pat, flags, m, t->child, 1, 0, 1, 1)))
1898  return a;
1899  return 0;
1900 }
1901 
1912 static int match_threadparent(struct PatternList *pat, PatternExecFlags flags,
1913  struct Mailbox *m, struct MuttThread *t)
1914 {
1915  if (!t || !t->parent || !t->parent->message)
1916  return 0;
1917 
1918  return mutt_pattern_exec(SLIST_FIRST(pat), flags, m, t->parent->message, NULL);
1919 }
1920 
1931 static int match_threadchildren(struct PatternList *pat, PatternExecFlags flags,
1932  struct Mailbox *m, struct MuttThread *t)
1933 {
1934  if (!t || !t->child)
1935  return 0;
1936 
1937  for (t = t->child; t; t = t->next)
1938  if (t->message && mutt_pattern_exec(SLIST_FIRST(pat), flags, m, t->message, NULL))
1939  return 1;
1940 
1941  return 0;
1942 }
1943 
1951 static bool match_content_type(const struct Pattern *pat, struct Body *b)
1952 {
1953  if (!b)
1954  return false;
1955 
1956  char buf[256];
1957  snprintf(buf, sizeof(buf), "%s/%s", TYPE(b), b->subtype);
1958 
1959  if (patmatch(pat, buf))
1960  return true;
1961  if (match_content_type(pat, b->parts))
1962  return true;
1963  if (match_content_type(pat, b->next))
1964  return true;
1965  return false;
1966 }
1967 
1974 static bool match_update_dynamic_date(struct Pattern *pat)
1975 {
1976  struct Buffer *err = mutt_buffer_pool_get();
1977 
1978  bool rc = eval_date_minmax(pat, pat->p.str, err);
1980 
1981  return rc;
1982 }
1983 
1992 static bool match_mime_content_type(const struct Pattern *pat,
1993  struct Mailbox *m, struct Email *e)
1994 {
1996  return match_content_type(pat, e->content);
1997 }
1998 
2006 static void set_pattern_cache_value(int *cache_entry, int value)
2007 {
2008  *cache_entry = (value != 0) ? 2 : 1;
2009 }
2010 
2017 static int get_pattern_cache_value(int cache_entry)
2018 {
2019  return cache_entry == 2;
2020 }
2021 
2027 static int is_pattern_cache_set(int cache_entry)
2028 {
2029  return cache_entry != 0;
2030 }
2031 
2040 static int msg_search_sendmode(struct Email *e, struct Pattern *pat)
2041 {
2042  bool match = false;
2043  char *buf = NULL;
2044  size_t blen = 0;
2045  FILE *fp = NULL;
2046 
2047  if ((pat->op == MUTT_PAT_HEADER) || (pat->op == MUTT_PAT_WHOLE_MSG))
2048  {
2049  struct Buffer *tempfile = mutt_buffer_pool_get();
2050  mutt_buffer_mktemp(tempfile);
2051  fp = mutt_file_fopen(mutt_b2s(tempfile), "w+");
2052  if (!fp)
2053  {
2054  mutt_perror(mutt_b2s(tempfile));
2055  mutt_buffer_pool_release(&tempfile);
2056  return 0;
2057  }
2058 
2060  fflush(fp);
2061  fseek(fp, 0, 0);
2062 
2063  while ((buf = mutt_file_read_line(buf, &blen, fp, NULL, 0)) != NULL)
2064  {
2065  if (patmatch(pat, buf) == 0)
2066  {
2067  match = true;
2068  break;
2069  }
2070  }
2071 
2072  FREE(&buf);
2073  mutt_file_fclose(&fp);
2074  unlink(mutt_b2s(tempfile));
2075  mutt_buffer_pool_release(&tempfile);
2076 
2077  if (match)
2078  return match;
2079  }
2080 
2081  if ((pat->op == MUTT_PAT_BODY) || (pat->op == MUTT_PAT_WHOLE_MSG))
2082  {
2083  fp = mutt_file_fopen(e->content->filename, "r");
2084  if (!fp)
2085  {
2087  return 0;
2088  }
2089 
2090  while ((buf = mutt_file_read_line(buf, &blen, fp, NULL, 0)) != NULL)
2091  {
2092  if (patmatch(pat, buf) == 0)
2093  {
2094  match = true;
2095  break;
2096  }
2097  }
2098 
2099  FREE(&buf);
2100  mutt_file_fclose(&fp);
2101  }
2102 
2103  return match;
2104 }
2105 
2122  struct Mailbox *m, struct Email *e, struct PatternCache *cache)
2123 {
2124  switch (pat->op)
2125  {
2126  case MUTT_PAT_AND:
2127  return pat->pat_not ^ (perform_and(pat->child, flags, m, e, cache) > 0);
2128  case MUTT_PAT_OR:
2129  return pat->pat_not ^ (perform_or(pat->child, flags, m, e, cache) > 0);
2130  case MUTT_PAT_THREAD:
2131  return pat->pat_not ^
2132  match_threadcomplete(pat->child, flags, m, e->thread, 1, 1, 1, 1);
2133  case MUTT_PAT_PARENT:
2134  return pat->pat_not ^ match_threadparent(pat->child, flags, m, e->thread);
2135  case MUTT_PAT_CHILDREN:
2136  return pat->pat_not ^ match_threadchildren(pat->child, flags, m, e->thread);
2137  case MUTT_ALL:
2138  return !pat->pat_not;
2139  case MUTT_EXPIRED:
2140  return pat->pat_not ^ e->expired;
2141  case MUTT_SUPERSEDED:
2142  return pat->pat_not ^ e->superseded;
2143  case MUTT_FLAG:
2144  return pat->pat_not ^ e->flagged;
2145  case MUTT_TAG:
2146  return pat->pat_not ^ e->tagged;
2147  case MUTT_NEW:
2148  return pat->pat_not ? e->old || e->read : !(e->old || e->read);
2149  case MUTT_UNREAD:
2150  return pat->pat_not ? e->read : !e->read;
2151  case MUTT_REPLIED:
2152  return pat->pat_not ^ e->replied;
2153  case MUTT_OLD:
2154  return pat->pat_not ? (!e->old || e->read) : (e->old && !e->read);
2155  case MUTT_READ:
2156  return pat->pat_not ^ e->read;
2157  case MUTT_DELETED:
2158  return pat->pat_not ^ e->deleted;
2159  case MUTT_PAT_MESSAGE:
2160  return pat->pat_not ^ ((EMSG(e) >= pat->min) && (EMSG(e) <= pat->max));
2161  case MUTT_PAT_DATE:
2162  if (pat->dynamic)
2164  return pat->pat_not ^ (e->date_sent >= pat->min && e->date_sent <= pat->max);
2166  if (pat->dynamic)
2168  return pat->pat_not ^ (e->received >= pat->min && e->received <= pat->max);
2169  case MUTT_PAT_BODY:
2170  case MUTT_PAT_HEADER:
2171  case MUTT_PAT_WHOLE_MSG:
2172  if (pat->sendmode)
2173  {
2174  if (!e->content || !e->content->filename)
2175  return 0;
2176  return pat->pat_not ^ msg_search_sendmode(e, pat);
2177  }
2178  /* m can be NULL in certain cases, such as when replying to a message
2179  * from the attachment menu and the user has a reply-hook using "~e".
2180  * This is also the case when message scoring. */
2181  if (!m)
2182  return 0;
2183 #ifdef USE_IMAP
2184  /* IMAP search sets e->matched at search compile time */
2185  if ((m->type == MUTT_IMAP) && pat->string_match)
2186  return e->matched;
2187 #endif
2188  return pat->pat_not ^ msg_search(m, pat, e->msgno);
2189  case MUTT_PAT_SERVERSEARCH:
2190 #ifdef USE_IMAP
2191  if (!m)
2192  return 0;
2193  if (m->type == MUTT_IMAP)
2194  {
2195  if (pat->string_match)
2196  return e->matched;
2197  return 0;
2198  }
2199  mutt_error(_("error: server custom search only supported with IMAP"));
2200  return 0;
2201 #else
2202  mutt_error(_("error: server custom search only supported with IMAP"));
2203  return -1;
2204 #endif
2205  case MUTT_PAT_SENDER:
2206  if (!e->env)
2207  return 0;
2208  return pat->pat_not ^ match_addrlist(pat, (flags & MUTT_MATCH_FULL_ADDRESS),
2209  1, &e->env->sender);
2210  case MUTT_PAT_FROM:
2211  if (!e->env)
2212  return 0;
2213  return pat->pat_not ^
2214  match_addrlist(pat, (flags & MUTT_MATCH_FULL_ADDRESS), 1, &e->env->from);
2215  case MUTT_PAT_TO:
2216  if (!e->env)
2217  return 0;
2218  return pat->pat_not ^
2219  match_addrlist(pat, (flags & MUTT_MATCH_FULL_ADDRESS), 1, &e->env->to);
2220  case MUTT_PAT_CC:
2221  if (!e->env)
2222  return 0;
2223  return pat->pat_not ^
2224  match_addrlist(pat, (flags & MUTT_MATCH_FULL_ADDRESS), 1, &e->env->cc);
2225  case MUTT_PAT_SUBJECT:
2226  if (!e->env)
2227  return 0;
2228  return pat->pat_not ^ (e->env->subject && patmatch(pat, e->env->subject));
2229  case MUTT_PAT_ID:
2230  case MUTT_PAT_ID_EXTERNAL:
2231  if (!e->env)
2232  return 0;
2233  return pat->pat_not ^ (e->env->message_id && patmatch(pat, e->env->message_id));
2234  case MUTT_PAT_SCORE:
2235  return pat->pat_not ^ (e->score >= pat->min &&
2236  (pat->max == MUTT_MAXRANGE || e->score <= pat->max));
2237  case MUTT_PAT_SIZE:
2238  return pat->pat_not ^
2239  (e->content->length >= pat->min &&
2240  (pat->max == MUTT_MAXRANGE || e->content->length <= pat->max));
2241  case MUTT_PAT_REFERENCE:
2242  if (!e->env)
2243  return 0;
2244  return pat->pat_not ^ (match_reference(pat, &e->env->references) ||
2245  match_reference(pat, &e->env->in_reply_to));
2246  case MUTT_PAT_ADDRESS:
2247  if (!e->env)
2248  return 0;
2249  return pat->pat_not ^ match_addrlist(pat, (flags & MUTT_MATCH_FULL_ADDRESS),
2250  4, &e->env->from, &e->env->sender,
2251  &e->env->to, &e->env->cc);
2252  case MUTT_PAT_RECIPIENT:
2253  if (!e->env)
2254  return 0;
2255  return pat->pat_not ^ match_addrlist(pat, (flags & MUTT_MATCH_FULL_ADDRESS),
2256  2, &e->env->to, &e->env->cc);
2257  case MUTT_PAT_LIST: /* known list, subscribed or not */
2258  {
2259  if (!e->env)
2260  return 0;
2261 
2262  int result;
2263  if (cache)
2264  {
2265  int *cache_entry = pat->all_addr ? &cache->list_all : &cache->list_one;
2266  if (!is_pattern_cache_set(*cache_entry))
2267  {
2268  set_pattern_cache_value(cache_entry,
2269  mutt_is_list_recipient(pat->all_addr, e->env));
2270  }
2271  result = get_pattern_cache_value(*cache_entry);
2272  }
2273  else
2274  result = mutt_is_list_recipient(pat->all_addr, e->env);
2275  return pat->pat_not ^ result;
2276  }
2278  {
2279  if (!e->env)
2280  return 0;
2281 
2282  int result;
2283  if (cache)
2284  {
2285  int *cache_entry = pat->all_addr ? &cache->sub_all : &cache->sub_one;
2286  if (!is_pattern_cache_set(*cache_entry))
2287  {
2289  cache_entry, mutt_is_subscribed_list_recipient(pat->all_addr, e->env));
2290  }
2291  result = get_pattern_cache_value(*cache_entry);
2292  }
2293  else
2294  result = mutt_is_subscribed_list_recipient(pat->all_addr, e->env);
2295  return pat->pat_not ^ result;
2296  }
2298  {
2299  if (!e->env)
2300  return 0;
2301 
2302  int result;
2303  if (cache)
2304  {
2305  int *cache_entry = pat->all_addr ? &cache->pers_recip_all : &cache->pers_recip_one;
2306  if (!is_pattern_cache_set(*cache_entry))
2307  {
2309  cache_entry, match_user(pat->all_addr, &e->env->to, &e->env->cc));
2310  }
2311  result = get_pattern_cache_value(*cache_entry);
2312  }
2313  else
2314  result = match_user(pat->all_addr, &e->env->to, &e->env->cc);
2315  return pat->pat_not ^ result;
2316  }
2318  {
2319  if (!e->env)
2320  return 0;
2321 
2322  int result;
2323  if (cache)
2324  {
2325  int *cache_entry = pat->all_addr ? &cache->pers_from_all : &cache->pers_from_one;
2326  if (!is_pattern_cache_set(*cache_entry))
2327  {
2328  set_pattern_cache_value(cache_entry,
2329  match_user(pat->all_addr, &e->env->from, NULL));
2330  }
2331  result = get_pattern_cache_value(*cache_entry);
2332  }
2333  else
2334  result = match_user(pat->all_addr, &e->env->from, NULL);
2335  return pat->pat_not ^ result;
2336  }
2337  case MUTT_PAT_COLLAPSED:
2338  return pat->pat_not ^ (e->collapsed && e->num_hidden > 1);
2339  case MUTT_PAT_CRYPT_SIGN:
2340  if (!WithCrypto)
2341  break;
2342  return pat->pat_not ^ ((e->security & SEC_SIGN) ? 1 : 0);
2344  if (!WithCrypto)
2345  break;
2346  return pat->pat_not ^ ((e->security & SEC_GOODSIGN) ? 1 : 0);
2348  if (!WithCrypto)
2349  break;
2350  return pat->pat_not ^ ((e->security & SEC_ENCRYPT) ? 1 : 0);
2351  case MUTT_PAT_PGP_KEY:
2352  if (!(WithCrypto & APPLICATION_PGP))
2353  break;
2354  return pat->pat_not ^ ((e->security & PGP_KEY) == PGP_KEY);
2355  case MUTT_PAT_XLABEL:
2356  if (!e->env)
2357  return 0;
2358  return pat->pat_not ^ (e->env->x_label && patmatch(pat, e->env->x_label));
2359  case MUTT_PAT_DRIVER_TAGS:
2360  {
2361  char *tags = driver_tags_get(&e->tags);
2362  bool rc = (pat->pat_not ^ (tags && patmatch(pat, tags)));
2363  FREE(&tags);
2364  return rc;
2365  }
2366  case MUTT_PAT_HORMEL:
2367  if (!e->env)
2368  return 0;
2369  return pat->pat_not ^ (e->env->spam.data && patmatch(pat, e->env->spam.data));
2370  case MUTT_PAT_DUPLICATED:
2371  return pat->pat_not ^ (e->thread && e->thread->duplicate_thread);
2372  case MUTT_PAT_MIMEATTACH:
2373  if (!m)
2374  return 0;
2375  {
2376  int count = mutt_count_body_parts(m, e);
2377  return pat->pat_not ^ (count >= pat->min &&
2378  (pat->max == MUTT_MAXRANGE || count <= pat->max));
2379  }
2380  case MUTT_PAT_MIMETYPE:
2381  if (!m)
2382  return 0;
2383  return pat->pat_not ^ match_mime_content_type(pat, m, e);
2384  case MUTT_PAT_UNREFERENCED:
2385  return pat->pat_not ^ (e->thread && !e->thread->child);
2386  case MUTT_PAT_BROKEN:
2387  return pat->pat_not ^ (e->thread && e->thread->fake_thread);
2388 #ifdef USE_NNTP
2389  case MUTT_PAT_NEWSGROUPS:
2390  if (!e->env)
2391  return 0;
2392  return pat->pat_not ^ (e->env->newsgroups && patmatch(pat, e->env->newsgroups));
2393 #endif
2394  }
2395  mutt_error(_("error: unknown op %d (report this error)"), pat->op);
2396  return -1;
2397 }
2398 
2404 static void quote_simple(const char *str, struct Buffer *buf)
2405 {
2406  mutt_buffer_reset(buf);
2407  mutt_buffer_addch(buf, '"');
2408  while (*str)
2409  {
2410  if ((*str == '\\') || (*str == '"'))
2411  mutt_buffer_addch(buf, '\\');
2412  mutt_buffer_addch(buf, *str++);
2413  }
2414  mutt_buffer_addch(buf, '"');
2415 }
2416 
2422 void mutt_check_simple(struct Buffer *buf, const char *simple)
2423 {
2424  bool do_simple = true;
2425 
2426  for (const char *p = mutt_b2s(buf); p && (p[0] != '\0'); p++)
2427  {
2428  if ((p[0] == '\\') && (p[1] != '\0'))
2429  p++;
2430  else if ((p[0] == '~') || (p[0] == '=') || (p[0] == '%'))
2431  {
2432  do_simple = false;
2433  break;
2434  }
2435  }
2436 
2437  /* XXX - is mutt_str_strcasecmp() right here, or should we use locale's
2438  * equivalences? */
2439 
2440  if (do_simple) /* yup, so spoof a real request */
2441  {
2442  /* convert old tokens into the new format */
2443  if ((mutt_str_strcasecmp("all", mutt_b2s(buf)) == 0) ||
2444  (mutt_str_strcmp("^", mutt_b2s(buf)) == 0) ||
2445  (mutt_str_strcmp(".", mutt_b2s(buf)) == 0)) /* ~A is more efficient */
2446  {
2447  mutt_buffer_strcpy(buf, "~A");
2448  }
2449  else if (mutt_str_strcasecmp("del", mutt_b2s(buf)) == 0)
2450  mutt_buffer_strcpy(buf, "~D");
2451  else if (mutt_str_strcasecmp("flag", mutt_b2s(buf)) == 0)
2452  mutt_buffer_strcpy(buf, "~F");
2453  else if (mutt_str_strcasecmp("new", mutt_b2s(buf)) == 0)
2454  mutt_buffer_strcpy(buf, "~N");
2455  else if (mutt_str_strcasecmp("old", mutt_b2s(buf)) == 0)
2456  mutt_buffer_strcpy(buf, "~O");
2457  else if (mutt_str_strcasecmp("repl", mutt_b2s(buf)) == 0)
2458  mutt_buffer_strcpy(buf, "~Q");
2459  else if (mutt_str_strcasecmp("read", mutt_b2s(buf)) == 0)
2460  mutt_buffer_strcpy(buf, "~R");
2461  else if (mutt_str_strcasecmp("tag", mutt_b2s(buf)) == 0)
2462  mutt_buffer_strcpy(buf, "~T");
2463  else if (mutt_str_strcasecmp("unread", mutt_b2s(buf)) == 0)
2464  mutt_buffer_strcpy(buf, "~U");
2465  else
2466  {
2467  struct Buffer *tmp = mutt_buffer_pool_get();
2468  quote_simple(mutt_b2s(buf), tmp);
2469  mutt_file_expand_fmt(buf, simple, mutt_b2s(tmp));
2471  }
2472  }
2473 }
2474 
2481 static struct MuttThread *top_of_thread(struct Email *e)
2482 {
2483  if (!e)
2484  return NULL;
2485 
2486  struct MuttThread *t = e->thread;
2487 
2488  while (t && t->parent)
2489  t = t->parent;
2490 
2491  return t;
2492 }
2493 
2501 {
2502  if (!e || !Context || !Context->mailbox)
2503  return false;
2504 
2505  struct MuttThread *me = top_of_thread(e);
2506  if (!me)
2507  return false;
2508 
2509  struct Mailbox *m = Context->mailbox;
2510 
2511  m->vcount = 0;
2512  Context->vsize = 0;
2513  Context->collapsed = false;
2514 
2515  for (int i = 0; i < m->msg_count; i++)
2516  {
2517  e = m->emails[i];
2518  if (!e)
2519  break;
2520 
2521  e->vnum = -1;
2522  e->limited = false;
2523  e->collapsed = false;
2524  e->num_hidden = 0;
2525 
2526  if (top_of_thread(e) == me)
2527  {
2528  struct Body *body = e->content;
2529 
2530  e->vnum = m->vcount;
2531  e->limited = true;
2532  m->v2r[m->vcount] = i;
2533  m->vcount++;
2534  Context->vsize += (body->length + body->offset - body->hdr_offset);
2535  }
2536  }
2537  return true;
2538 }
2539 
2547 int mutt_pattern_func(int op, char *prompt)
2548 {
2549  if (!Context || !Context->mailbox)
2550  return -1;
2551 
2552  struct Buffer err;
2553  int rc = -1;
2554  struct Progress progress;
2555  struct Buffer *buf = mutt_buffer_pool_get();
2556  struct Mailbox *m = Context->mailbox;
2557 
2559  if (prompt || (op != MUTT_LIMIT))
2560  {
2561  if ((mutt_buffer_get_field(prompt, buf, MUTT_PATTERN | MUTT_CLEAR) != 0) ||
2562  mutt_buffer_is_empty(buf))
2563  {
2565  return -1;
2566  }
2567  }
2568 
2569  mutt_message(_("Compiling search pattern..."));
2570 
2571  char *simple = mutt_buffer_strdup(buf);
2573 
2574  mutt_buffer_init(&err);
2575  err.dsize = 256;
2576  err.data = mutt_mem_malloc(err.dsize);
2577  struct PatternList *pat = mutt_pattern_comp(buf->data, MUTT_PC_FULL_MSG, &err);
2578  if (!pat)
2579  {
2580  mutt_error("%s", err.data);
2581  goto bail;
2582  }
2583 
2584 #ifdef USE_IMAP
2585  if ((m->type == MUTT_IMAP) && (imap_search(m, pat) < 0))
2586  goto bail;
2587 #endif
2588 
2589  mutt_progress_init(&progress, _("Executing command on matching messages..."),
2590  MUTT_PROGRESS_READ, (op == MUTT_LIMIT) ? m->msg_count : m->vcount);
2591 
2592  if (op == MUTT_LIMIT)
2593  {
2594  m->vcount = 0;
2595  Context->vsize = 0;
2596  Context->collapsed = false;
2597  int padding = mx_msg_padding_size(m);
2598 
2599  for (int i = 0; i < m->msg_count; i++)
2600  {
2601  struct Email *e = m->emails[i];
2602  if (!e)
2603  break;
2604 
2605  mutt_progress_update(&progress, i, -1);
2606  /* new limit pattern implicitly uncollapses all threads */
2607  e->vnum = -1;
2608  e->limited = false;
2609  e->collapsed = false;
2610  e->num_hidden = 0;
2611  if (mutt_pattern_exec(SLIST_FIRST(pat), MUTT_MATCH_FULL_ADDRESS, m, e, NULL))
2612  {
2613  e->vnum = m->vcount;
2614  e->limited = true;
2615  m->v2r[m->vcount] = i;
2616  m->vcount++;
2617  struct Body *b = e->content;
2618  Context->vsize += b->length + b->offset - b->hdr_offset + padding;
2619  }
2620  }
2621  }
2622  else
2623  {
2624  for (int i = 0; i < m->vcount; i++)
2625  {
2626  struct Email *e = mutt_get_virt_email(Context->mailbox, i);
2627  if (!e)
2628  continue;
2629  mutt_progress_update(&progress, i, -1);
2630  if (mutt_pattern_exec(SLIST_FIRST(pat), MUTT_MATCH_FULL_ADDRESS, m, e, NULL))
2631  {
2632  switch (op)
2633  {
2634  case MUTT_UNDELETE:
2635  mutt_set_flag(m, e, MUTT_PURGE, false);
2636  /* fallthrough */
2637  case MUTT_DELETE:
2638  mutt_set_flag(m, e, MUTT_DELETE, (op == MUTT_DELETE));
2639  break;
2640  case MUTT_TAG:
2641  case MUTT_UNTAG:
2642  mutt_set_flag(m, e, MUTT_TAG, (op == MUTT_TAG));
2643  break;
2644  }
2645  }
2646  }
2647  }
2648 
2649  mutt_clear_error();
2650 
2651  if (op == MUTT_LIMIT)
2652  {
2653  /* drop previous limit pattern */
2654  FREE(&Context->pattern);
2656 
2657  if (m->msg_count && !m->vcount)
2658  mutt_error(_("No messages matched criteria"));
2659 
2660  /* record new limit pattern, unless match all */
2661  const char *pbuf = buf->data;
2662  while (*pbuf == ' ')
2663  pbuf++;
2664  if (mutt_str_strcmp(pbuf, "~A") != 0)
2665  {
2666  Context->pattern = simple;
2667  simple = NULL; /* don't clobber it */
2669  }
2670  }
2671 
2672  rc = 0;
2673 
2674 bail:
2676  FREE(&simple);
2677  mutt_pattern_free(&pat);
2678  FREE(&err.data);
2679 
2680  return rc;
2681 }
2682 
2690 int mutt_search_command(int cur, int op)
2691 {
2692  struct Progress progress;
2693 
2694  if (!*LastSearch || ((op != OP_SEARCH_NEXT) && (op != OP_SEARCH_OPPOSITE)))
2695  {
2696  char buf[256];
2697  mutt_str_strfcpy(buf, (LastSearch[0] != '\0') ? LastSearch : "", sizeof(buf));
2698  if ((mutt_get_field(((op == OP_SEARCH) || (op == OP_SEARCH_NEXT)) ?
2699  _("Search for: ") :
2700  _("Reverse search for: "),
2701  buf, sizeof(buf), MUTT_CLEAR | MUTT_PATTERN) != 0) ||
2702  !buf[0])
2703  {
2704  return -1;
2705  }
2706 
2707  if ((op == OP_SEARCH) || (op == OP_SEARCH_NEXT))
2708  OptSearchReverse = false;
2709  else
2710  OptSearchReverse = true;
2711 
2712  /* compare the *expanded* version of the search pattern in case
2713  * $simple_search has changed while we were searching */
2714  struct Buffer *tmp = mutt_buffer_pool_get();
2715  mutt_buffer_strcpy(tmp, buf);
2717 
2718  if (!SearchPattern || (mutt_str_strcmp(mutt_b2s(tmp), LastSearchExpn) != 0))
2719  {
2720  struct Buffer err;
2721  mutt_buffer_init(&err);
2722  OptSearchInvalid = true;
2723  mutt_str_strfcpy(LastSearch, buf, sizeof(LastSearch));
2725  mutt_message(_("Compiling search pattern..."));
2727  err.dsize = 256;
2728  err.data = mutt_mem_malloc(err.dsize);
2730  if (!SearchPattern)
2731  {
2733  mutt_error("%s", err.data);
2734  FREE(&err.data);
2735  LastSearch[0] = '\0';
2736  LastSearchExpn[0] = '\0';
2737  return -1;
2738  }
2739  FREE(&err.data);
2740  mutt_clear_error();
2741  }
2742 
2744  }
2745 
2746  if (OptSearchInvalid)
2747  {
2748  for (int i = 0; i < Context->mailbox->msg_count; i++)
2749  Context->mailbox->emails[i]->searched = false;
2750 #ifdef USE_IMAP
2751  if ((Context->mailbox->type == MUTT_IMAP) &&
2753  return -1;
2754 #endif
2755  OptSearchInvalid = false;
2756  }
2757 
2758  int incr = OptSearchReverse ? -1 : 1;
2759  if (op == OP_SEARCH_OPPOSITE)
2760  incr = -incr;
2761 
2762  mutt_progress_init(&progress, _("Searching..."), MUTT_PROGRESS_READ,
2763  Context->mailbox->vcount);
2764 
2765  for (int i = cur + incr, j = 0; j != Context->mailbox->vcount; j++)
2766  {
2767  const char *msg = NULL;
2768  mutt_progress_update(&progress, j, -1);
2769  if (i > Context->mailbox->vcount - 1)
2770  {
2771  i = 0;
2772  if (C_WrapSearch)
2773  msg = _("Search wrapped to top");
2774  else
2775  {
2776  mutt_message(_("Search hit bottom without finding match"));
2777  return -1;
2778  }
2779  }
2780  else if (i < 0)
2781  {
2782  i = Context->mailbox->vcount - 1;
2783  if (C_WrapSearch)
2784  msg = _("Search wrapped to bottom");
2785  else
2786  {
2787  mutt_message(_("Search hit top without finding match"));
2788  return -1;
2789  }
2790  }
2791 
2792  struct Email *e = mutt_get_virt_email(Context->mailbox, i);
2793  if (e->searched)
2794  {
2795  /* if we've already evaluated this message, use the cached value */
2796  if (e->matched)
2797  {
2798  mutt_clear_error();
2799  if (msg && *msg)
2800  mutt_message(msg);
2801  return i;
2802  }
2803  }
2804  else
2805  {
2806  /* remember that we've already searched this message */
2807  e->searched = true;
2809  Context->mailbox, e, NULL);
2810  if (e->matched > 0)
2811  {
2812  mutt_clear_error();
2813  if (msg && *msg)
2814  mutt_message(msg);
2815  return i;
2816  }
2817  }
2818 
2819  if (SigInt)
2820  {
2821  mutt_error(_("Search interrupted"));
2822  SigInt = 0;
2823  return -1;
2824  }
2825 
2826  i += incr;
2827  }
2828 
2829  mutt_error(_("Not found"));
2830  return -1;
2831 }
bool(* addr_predicate_t)(const struct Address *a)
Test an Address for some condition.
Definition: pattern.c:207
#define MUTT_PDR_ABSOLUTE
Absolute pattern range.
Definition: pattern.c:105
Pattern matches date received.
Definition: pattern.h:118
struct Email ** emails
Array of Emails.
Definition: mailbox.h:99
Convenience wrapper for the gui headers.
struct MuttThread * next
Next sibling Thread.
Definition: thread.h:47
struct PatternList * child
Arguments to logical operation.
Definition: pattern.h:63
Deleted messages.
Definition: mutt.h:99
The "current" mailbox.
Definition: context.h:37
static const char * mailbox_path(const struct Mailbox *m)
Get the Mailbox&#39;s path string.
Definition: mailbox.h:192
Pattern matches email&#39;s header.
Definition: pattern.h:125
int mutt_pattern_func(int op, char *prompt)
Perform some Pattern matching.
Definition: pattern.c:2547
char * mutt_str_skip_whitespace(const char *p)
Find the first non-whitespace character in a string.
Definition: string.c:746
regex_t * regex
Compiled regex, for non-pattern matching.
Definition: pattern.h:65
#define mutt_buffer_mktemp(buf)
Definition: muttlib.h:81
Pattern matches MIME type.
Definition: pattern.h:147
char * filename
when sending a message, this is the file to which this structure refers
Definition: body.h:46
enum MailboxType type
Mailbox type.
Definition: mailbox.h:105
Pattern matches &#39;Date:&#39; field.
Definition: pattern.h:117
#define NONULL(x)
Definition: string2.h:37
void * mutt_mem_calloc(size_t nmemb, size_t size)
Allocate zeroed memory on the heap.
Definition: memory.c:50
Miscellaneous email parsing routines.
int msg_count
Total number of messages.
Definition: mailbox.h:91
void mutt_buffer_reset(struct Buffer *buf)
Reset an existing Buffer.
Definition: buffer.c:79
#define mutt_set_flag(m, e, flag, bf)
Definition: protos.h:70
#define MUTT_PDR_WINDOW
Extend the range in both directions using &#39;*&#39;.
Definition: pattern.c:104
int flags
Pattern flags, e.g. MUTT_PC_FULL_MSG.
Definition: pattern.c:170
pid_t filter_create(const char *cmd, FILE **fp_in, FILE **fp_out, FILE **fp_err)
Set up filter program.
Definition: filter.c:206
#define WithCrypto
Definition: lib.h:163
IMAP network mailbox.
bool group_match
Check a group of Addresses.
Definition: pattern.h:55
The envelope/body of an email.
Definition: email.h:37
#define MUTT_CLEAR
Clear input if printable character is pressed.
Definition: mutt.h:62
struct Buffer * mutt_buffer_pool_get(void)
Get a Buffer from the pool.
Definition: pool.c:101
struct Email * mutt_get_virt_email(struct Mailbox *m, int vnum)
Get a virtual Email.
Definition: context.c:413
static void adjust_date_range(struct tm *min, struct tm *max)
Put a date range in the correct order.
Definition: pattern.c:582
#define mutt_perror(...)
Definition: logging.h:85
regex_t cooked
Compiled form.
Definition: pattern.c:137
static bool match_reference(struct Pattern *pat, struct ListHead *refs)
Match references against a Pattern.
Definition: pattern.c:1769
if(!test_colorize_)
Definition: acutest.h:499
static bool perform_and(struct PatternList *pat, PatternExecFlags flags, struct Mailbox *m, struct Email *e, struct PatternCache *cache)
Perform a logical AND on a set of Patterns.
Definition: pattern.c:1695
Config/command parsing.
#define TAILQ_FOREACH(var, head, field)
Definition: queue.h:718
Pattern matches newsgroup.
Definition: pattern.h:149
#define MUTT_CHARCONV
Do character set conversions.
Definition: state.h:36
Structs that make up an email.
String processing routines to generate the mail index.
The "currently-open" mailbox.
#define MEGA
Definition: pattern.c:95
struct tm mutt_date_localtime(time_t t)
Converts calendar time to a broken-down time structure expressed in user timezone.
Definition: date.c:661
Pattern matches email&#39;s score.
Definition: pattern.h:130
#define MUTT_PDR_NO_FLAGS
No flags are set.
Definition: pattern.c:101
Single symbol.
Definition: pattern.c:149
static void quote_simple(const char *str, struct Buffer *buf)
Apply simple quoting to a string.
Definition: pattern.c:2404
#define mutt_message(...)
Definition: logging.h:83
int pers_recip_all
^~p
Definition: pattern.h:92
void mutt_check_simple(struct Buffer *buf, const char *simple)
Convert a simple search into a real request.
Definition: pattern.c:2422
Range is valid.
Definition: pattern.c:123
struct MuttThread * thread
Thread of Emails.
Definition: email.h:94
static int match_threadchildren(struct PatternList *pat, PatternExecFlags flags, struct Mailbox *m, struct MuttThread *t)
Match Pattern against an email&#39;s children.
Definition: pattern.c:1931
WHERE SIG_ATOMIC_VOLATILE_T SigInt
true after SIGINT is received
Definition: globals.h:79
static int match_addrlist(struct Pattern *pat, bool match_personal, int n,...)
Match a Pattern against and Address list.
Definition: pattern.c:1739
#define SEC_ENCRYPT
Email is encrypted.
Definition: lib.h:125
Messages in limited view.
Definition: mutt.h:103
Message is part of a broken thread.
Definition: pattern.h:121
bool mutt_limit_current_thread(struct Email *e)
Limit the email view to the current thread.
Definition: pattern.c:2500
bool mutt_is_mail_list(const struct Address *addr)
Is this the email address of a mailing list? - Implements addr_predicate_t.
Definition: hdrline.c:114
NeoMutt Logging.
void mutt_buffer_pool_release(struct Buffer **pbuf)
Free a Buffer from the pool.
Definition: pool.c:112
Pattern matches email&#39;s size.
Definition: pattern.h:131
struct Body * content
List of MIME parts.
Definition: email.h:90
#define SLIST_INIT(head)
Definition: queue.h:255
int imap_search(struct Mailbox *m, const struct PatternList *pat)
Find a matching mailbox.
Definition: imap.c:1361
#define MUTT_TOKEN_COMMENT
Don&#39;t reap comments.
Definition: mutt.h:76
char * mutt_buffer_strdup(struct Buffer *buf)
Copy a Buffer&#39;s string.
Definition: buffer.c:432
#define RANGE_LT
Definition: pattern.c:114
A postponed Email, just the envelope info.
Definition: sendlib.h:62
String manipulation buffer.
Definition: buffer.h:33
Range contains syntax error.
Definition: pattern.c:124
void mutt_parse_mime_message(struct Mailbox *m, struct Email *e)
Parse a MIME email.
Definition: mutt_parse.c:48
Messages to be un-deleted.
Definition: mutt.h:97
Flagged messages.
Definition: mutt.h:100
#define MUTT_PDR_ERROR
Invalid pattern.
Definition: pattern.c:107
Pattern matches &#39;From:&#39; field.
Definition: pattern.h:116
#define RANGE_DOLLAR
Definition: pattern.c:113
LOFF_T offset
offset where the actual data begins
Definition: body.h:44
void mutt_date_normalize_time(struct tm *tm)
Fix the contents of a struct tm.
Definition: date.c:297
#define MUTT_MAXRANGE
Definition: pattern.c:98
Email is from the user.
Definition: pattern.h:137
#define CH_FROM
Retain the "From " message separator?
Definition: copy.h:55
#define _(a)
Definition: message.h:28
static char LastSearch[256]
last pattern searched for
Definition: pattern.c:199
char * mutt_file_read_line(char *line, size_t *size, FILE *fp, int *line_num, int flags)
Read a line from a file.
Definition: file.c:667
WHERE bool C_WrapSearch
Config: Wrap around when the search hits the end.
Definition: globals.h:258
Email is on mailing list.
Definition: pattern.h:134
struct Body * next
next attachment in the list
Definition: body.h:53
An email address.
Definition: address.h:34
#define MUTT_DATE_NOW
Constant representing the &#39;current time&#39;, see: mutt_date_gmtime(), mutt_date_localtime() ...
Definition: date.h:37
struct MuttThread * parent
Parent of this Thread.
Definition: thread.h:45
FILE * fp_out
File to write to.
Definition: state.h:47
static char * find_matching_paren(char *s)
Find the matching parenthesis.
Definition: pattern.c:1361
char * mailbox
Mailbox and host address.
Definition: address.h:37
Messages to be purged (bypass trash)
Definition: mutt.h:98
Range requires Context, but none available.
Definition: pattern.c:125
bool is_multi
Multiple case (only for ~I pattern now)
Definition: pattern.h:60
size_t mutt_str_strlen(const char *a)
Calculate the length of a string, safely.
Definition: string.c:692
Pattern matches a child email.
Definition: pattern.h:111
static int match_user(int all_addr, struct AddressList *al1, struct AddressList *al2)
Matches the user&#39;s email Address.
Definition: pattern.c:1838
bool searched
Email has been searched.
Definition: email.h:67
int mutt_buffer_printf(struct Buffer *buf, const char *fmt,...)
Format a string overwriting a Buffer.
Definition: buffer.c:160
bool mutt_group_match(struct Group *g, const char *s)
Does a string match an entry in a Group?
Definition: group.c:325
bool expired
Already expired?
Definition: email.h:52
All user-callable functions.
EatRangeError
Error codes for eat_range_by_regex()
Definition: pattern.c:121
Pattern matches email&#39;s Message-Id.
Definition: pattern.h:122
FILE * fp_in
File to read from.
Definition: state.h:46
A progress bar.
Definition: progress.h:49
#define MUTT_PATTERN
Pattern mode - only used for history classes.
Definition: mutt.h:64
Representation of a single alias to an email address.
static const char * get_date(const char *s, struct tm *t, struct Buffer *err)
Parse a (partial) date in dd/mm/yyyy format.
Definition: pattern.c:410
void mutt_list_free(struct ListHead *h)
Free a List AND its strings.
Definition: list.c:123
#define mutt_get_field(field, buf, buflen, complete)
Definition: curs_lib.h:90
RangeType
Type of range.
Definition: pattern.c:143
Messages that have been replied to.
Definition: mutt.h:93
int vcount
The number of virtual messages.
Definition: mailbox.h:102
The body of an email.
Definition: body.h:34
A simple (non-regex) pattern.
Definition: pattern.h:49
static const char * get_offset(struct tm *tm, const char *s, int sign)
Calculate a symbolic offset.
Definition: pattern.c:356
int sub_all
^~u
Definition: pattern.h:90
Pattern matches message number.
Definition: pattern.h:129
Hundreds of global variables to back the user variables.
static int match_threadcomplete(struct PatternList *pat, PatternExecFlags flags, struct Mailbox *m, struct MuttThread *t, int left, int up, int right, int down)
Match a Pattern against an email thread.
Definition: pattern.c:1874
int mutt_rfc822_write_header(FILE *fp, struct Envelope *env, struct Body *attach, enum MuttWriteHeaderMode mode, bool privacy, bool hide_protected_subject)
Write out one RFC822 header line.
Definition: sendlib.c:2325
Email is on subscribed mailing list.
Definition: pattern.h:135
Email Address Handling.
static int msg_search_sendmode(struct Email *e, struct Pattern *pat)
Search in send-mode.
Definition: pattern.c:2040
Some miscellaneous functions.
#define MUTT_PC_PATTERN_DYNAMIC
Enable runtime date range evaluation.
Definition: pattern.h:43
static int is_pattern_cache_set(int cache_entry)
Is a given Pattern cached?
Definition: pattern.c:2027
#define mutt_array_size(x)
Definition: memory.h:33
bool is_alias
Is there an alias for this Address?
Definition: pattern.h:57
StateFlags flags
Flags, e.g. MUTT_DISPLAY.
Definition: state.h:49
int mx_msg_close(struct Mailbox *m, struct Message **msg)
Close a message.
Definition: mx.c:1171
bool crypt_valid_passphrase(SecurityFlags flags)
Check that we have a usable passphrase, ask if not.
Definition: crypt.c:146
#define MUTT_PDR_PLUS
Extend the range using &#39;+&#39;.
Definition: pattern.c:103
size_t dsize
Length of data.
Definition: buffer.h:37
All messages.
Definition: mutt.h:89
bool tagged
Email is tagged.
Definition: email.h:44
bool read
Email is read.
Definition: email.h:51
void mutt_progress_update(struct Progress *progress, size_t pos, int percent)
Update the state of the progress bar.
Definition: progress.c:212
RangeSide
Which side of the range.
Definition: pattern.c:157
int op
Operation to perform, e.g. MUTT_PAT_SCORE.
Definition: pattern.c:169
#define RANGE_REL_RX
Definition: pattern.c:80
#define KILO
Definition: pattern.c:94
bool dynamic
Evaluate date ranges at run time.
Definition: pattern.h:58
struct ListHead in_reply_to
in-reply-to header content
Definition: envelope.h:82
char * message_id
Message ID.
Definition: envelope.h:69
bool all_addr
All Addresses in the list must match.
Definition: pattern.h:53
struct MuttThread * prev
Previous sibling Thread.
Definition: thread.h:48
struct Mailbox * mailbox
Definition: context.h:51
Many unsorted constants and some structs.
Log at debug level 2.
Definition: logging.h:41
API for mailboxes.
bool old
Email is seen, but unread.
Definition: email.h:50
struct AddressList from
Email&#39;s &#39;From&#39; list.
Definition: envelope.h:57
static int scan_range_num(struct Buffer *s, regmatch_t pmatch[], int group, int kind)
Parse a number range.
Definition: pattern.c:892
Duplicate message.
Definition: pattern.h:119
static const struct PatternFlags * lookup_tag(char tag)
Lookup a pattern modifier.
Definition: pattern.c:1347
int mutt_file_fclose(FILE **fp)
Close a FILE handle (and NULL the pointer)
Definition: file.c:153
struct Envelope * env
Envelope information.
Definition: email.h:89
Convenience wrapper for the core headers.
#define REG_COMP(preg, regex, cflags)
Compile a regular expression.
Definition: regex3.h:53
va_start(args, fmt)
struct ListNode * mutt_list_find(const struct ListHead *h, const char *data)
Find a string in a List.
Definition: list.c:103
#define SKIPWS(ch)
Definition: string2.h:47
struct AddressList cc
Email&#39;s &#39;Cc&#39; list.
Definition: envelope.h:59
Progress tracks elements, according to C_ReadInc.
Definition: progress.h:41
bool pat_not
Pattern should be inverted (not)
Definition: pattern.h:52
bool string_match
Check a string for a match.
Definition: pattern.h:54
struct Menu * menu
Needed for pattern compilation.
Definition: context.h:47
struct Address * mutt_alias_reverse_lookup(const struct Address *a)
Does the user have an alias for the given address.
Definition: alias.c:545
int mutt_buffer_add_printf(struct Buffer *buf, const char *fmt,...)
Format a string appending a Buffer.
Definition: buffer.c:203
bool superseded
Got superseded?
Definition: email.h:53
bool limited
Is this message in a limited view?
Definition: email.h:74
bool mutt_file_map_lines(mutt_file_map_t func, void *user_data, FILE *fp, int flags)
Process lines of text read from a file pointer.
Definition: file.c:767
#define RANGE_ABS_RX
Definition: pattern.c:84
static struct PatternList * mutt_pattern_node_new(void)
Create a new list containing a Pattern.
Definition: pattern.c:1419
void mutt_clear_error(void)
Clear the message line (bottom line of screen)
Definition: mutt_logging.c:113
char * mutt_path_escape(const char *src)
Escapes single quotes in a path for a command string.
Definition: path.c:522
Progress bar.
int min
Minimum for range checks.
Definition: pattern.h:61
bool ign_case
Ignore case for local string_match searches.
Definition: pattern.h:56
#define SLIST_NEXT(elm, field)
Definition: queue.h:269
struct TagList tags
For drivers that support server tagging.
Definition: email.h:102
size_t mutt_buffer_addstr(struct Buffer *buf, const char *s)
Add a string to a Buffer.
Definition: buffer.c:225
int score
Message score.
Definition: email.h:88
char * subtype
content-type subtype
Definition: body.h:37
time_t date_sent
Time when the message was sent (UTC)
Definition: email.h:81
struct ListNode * mutt_list_insert_tail(struct ListHead *h, char *s)
Append a string to the end of a List.
Definition: list.c:65
Left side of range.
Definition: pattern.c:159
void mutt_str_remove_trailing_ws(char *s)
Trim trailing whitespace from a string.
Definition: string.c:760
Old messages.
Definition: mutt.h:92
off_t vsize
Definition: context.h:39
#define mutt_b2s(buf)
Definition: buffer.h:41
int mutt_pattern_exec(struct Pattern *pat, PatternExecFlags flags, struct Mailbox *m, struct Email *e, struct PatternCache *cache)
Match a pattern against an email header.
Definition: pattern.c:2121
struct PatternList * mutt_pattern_comp(const char *s, PatternCompFlags flags, struct Buffer *err)
Create a Pattern.
Definition: pattern.c:1435
Greater-than range.
Definition: pattern.c:148
int pers_from_one
~P
Definition: pattern.h:95
Regular expression representing a range.
Definition: pattern.c:131
struct MuttThread * child
Child of this Thread.
Definition: thread.h:46
#define MUTT_PDR_MINUS
Pattern contains a range.
Definition: pattern.c:102
#define RANGE_RX_GROUPS
Definition: pattern.c:92
bool duplicate_thread
Duplicated Email in Thread.
Definition: thread.h:37
Prototypes for many functions.
Server-side pattern matches.
Definition: pattern.h:144
Pattern matches &#39;Cc:&#39; field.
Definition: pattern.h:113
int pers_recip_one
~p
Definition: pattern.h:93
static char LastSearchExpn[1024]
expanded version of LastSearch
Definition: pattern.c:200
#define APPLICATION_PGP
Use PGP to encrypt/sign.
Definition: lib.h:137
Message is unreferenced in the thread.
Definition: pattern.h:120
#define SLIST_FIRST(head)
Definition: queue.h:228
A local copy of an email.
Definition: mx.h:83
LOFF_T length
length (in bytes) of attachment
Definition: body.h:45
bool fake_thread
Emails grouped by Subject.
Definition: thread.h:36
Messages to be deleted.
Definition: mutt.h:96
A mailbox.
Definition: mailbox.h:81
Pattern matches keyword/label.
Definition: pattern.h:143
int mutt_extract_token(struct Buffer *dest, struct Buffer *tok, TokenFlags flags)
Extract one token from a string.
Definition: init.c:414
void * mutt_mem_malloc(size_t size)
Allocate memory on the heap.
Definition: memory.c:90
static struct MuttThread * top_of_thread(struct Email *e)
Find the first email in the current thread.
Definition: pattern.c:2481
static bool eval_date_minmax(struct Pattern *pat, const char *s, struct Buffer *err)
Evaluate a date-range pattern against &#39;now&#39;.
Definition: pattern.c:620
Either pattern can match.
Definition: pattern.h:108
char * driver_tags_get(struct TagList *list)
Get tags.
Definition: tags.c:142
size_t num_hidden
Number of hidden messages in this view.
Definition: email.h:75
char * dptr
Current read/write position.
Definition: buffer.h:36
static int mutt_is_predicate_recipient(bool all_addr, struct Envelope *e, addr_predicate_t p)
Test an Envelopes Addresses using a predicate function.
Definition: pattern.c:1790
Pattern matches email&#39;s spam score.
Definition: pattern.h:126
struct PatternList * limit_pattern
Compiled limit pattern.
Definition: context.h:41
void mutt_file_expand_fmt(struct Buffer *dest, const char *fmt, const char *src)
Replace s in a string with a filename.
Definition: file.c:1441
Superseded messages.
Definition: mutt.h:105
Tagged messages.
Definition: mutt.h:101
Message is encrypted.
Definition: pattern.h:141
char * data
Pointer to data.
Definition: buffer.h:35
#define CH_DECODE
Do RFC2047 header decoding.
Definition: copy.h:53
struct Body * parts
parts of a multipart or message/rfc822
Definition: body.h:54
Pattern matches &#39;Subject:&#39; field.
Definition: pattern.h:115
size_t mutt_str_strfcpy(char *dest, const char *src, size_t dsize)
Copy a string into a buffer (guaranteeing NUL-termination)
Definition: string.c:776
bool C_ThoroughSearch
Config: Decode headers and messages before searching them.
Definition: pattern.c:74
int list_all
^~l
Definition: pattern.h:88
New messages.
Definition: mutt.h:91
Messages that have been read.
Definition: mutt.h:94
void mutt_pattern_free(struct PatternList **pat)
Free a Pattern.
Definition: pattern.c:1383
GUI present the user with a selectable list.
static int get_pattern_cache_value(int cache_entry)
Get pattern cache value.
Definition: pattern.c:2017
#define RANGE_GT
Definition: pattern.c:115
#define mutt_file_mkstemp()
Definition: file.h:105
Email is addressed to the user.
Definition: pattern.h:136
API for encryption/signing of emails.
Message has PGP key.
Definition: pattern.h:142
#define RANGE_LT_RX
Definition: pattern.c:87
bool collapsed
Is this message part of a collapsed thread?
Definition: email.h:73
short op
Operation, e.g. MUTT_PAT_SCORE.
Definition: pattern.h:51
Pattern matches parent.
Definition: pattern.h:110
int vnum
Virtual message number.
Definition: email.h:87
static int perform_or(struct PatternList *pat, PatternExecFlags flags, struct Mailbox *m, struct Email *e, struct PatternCache *cache)
Perform a logical OR on a set of Patterns.
Definition: pattern.c:1717
WHERE bool OptSearchInvalid
(pseudo) used to invalidate the search pattern
Definition: options.h:53
#define STAILQ_FOREACH(var, head, field)
Definition: queue.h:349
#define RANGE_BARE_RX
Definition: pattern.c:91
void mutt_progress_init(struct Progress *progress, const char *msg, enum ProgressType type, size_t size)
Set up a progress bar.
Definition: progress.c:153
const char * raw
Regex as string.
Definition: pattern.c:133
size_t mutt_buffer_addch(struct Buffer *buf, char c)
Add a single character to a Buffer.
Definition: buffer.c:240
static bool eat_query(struct Pattern *pat, int flags, struct Buffer *s, struct Buffer *err)
Parse a query for an external search program - Implements Pattern::eat_arg()
Definition: pattern.c:280
SecurityFlags security
bit 0-10: flags, bit 11,12: application, bit 13: traditional pgp See: ncrypt/lib.h pgplib...
Definition: email.h:39
struct Email * message
Email this Thread refers to.
Definition: thread.h:49
#define MUTT_MATCH_FULL_ADDRESS
Match the full address.
Definition: pattern.h:76
static bool eat_regex(struct Pattern *pat, int flags, struct Buffer *s, struct Buffer *err)
Parse a regex - Implements Pattern::eat_arg()
Definition: pattern.c:212
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: pattern.c:266
#define MUTT_PDR_ERRORDONE
Definition: pattern.c:109
size_t mutt_buffer_strcpy(struct Buffer *buf, const char *s)
Copy a string into a Buffer.
Definition: buffer.c:312
LOFF_T offset
Where in the stream does this message begin?
Definition: email.h:83
Expired messages.
Definition: mutt.h:104
#define mutt_buffer_get_field(field, buf, complete)
Definition: curs_lib.h:84
WHERE char * C_SimpleSearch
Config: Pattern to search for when search doesn&#39;t contain ~&#39;s.
Definition: globals.h:141
int pers_from_all
^~P
Definition: pattern.h:94
An Email conversation.
Definition: thread.h:34
#define SEC_SIGN
Email is signed.
Definition: lib.h:126
char * personal
Real name of address.
Definition: address.h:36
int * v2r
Mapping from virtual to real msgno.
Definition: mailbox.h:101
Unread messages.
Definition: mutt.h:95
Keep track when processing files.
#define TYPE(body)
Definition: mime.h:83
int tag
Character used to represent this operation, e.g. &#39;A&#39; for &#39;~A&#39;.
Definition: pattern.c:168
static bool eat_message_range(struct Pattern *pat, int flags, struct Buffer *s, struct Buffer *err)
Parse a range of message numbers - Implements Pattern::eat_arg()
Definition: pattern.c:1032
static struct PatternList * SearchPattern
current search pattern
Definition: pattern.c:198
Pattern matches email&#39;s body.
Definition: pattern.h:124
static bool msg_search(struct Mailbox *m, struct Pattern *pat, int msgno)
Search an email.
Definition: pattern.c:1134
char * data
String.
Definition: list.h:35
char * subject
Email&#39;s subject.
Definition: envelope.h:66
Duplicate the structure of an entire email.
union Pattern::@2 p
#define RANGE_CIRCUM
Definition: pattern.c:112
#define PGP_KEY
Definition: lib.h:146
Log at debug level 1.
Definition: logging.h:40
int n
Definition: acutest.h:492
bool flagged
Marked important?
Definition: email.h:43
char * newsgroups
List of newsgroups.
Definition: envelope.h:75
static void order_range(struct Pattern *pat)
Put a range in order.
Definition: pattern.c:961
char * mutt_str_strdup(const char *str)
Copy a string, safely.
Definition: string.c:380
#define EMSG(e)
Definition: pattern.c:96
bool collapsed
Are all threads collapsed?
Definition: context.h:49
char * str
String, if string_match is set.
Definition: pattern.h:67
Message is signed.
Definition: pattern.h:139
Range is invalid.
Definition: pattern.c:151
bool deleted
Email is deleted.
Definition: email.h:45
int mutt_copy_header(FILE *fp_in, struct Email *e, FILE *fp_out, CopyHeaderFlags chflags, const char *prefix, int wraplen)
Copy Email header.
Definition: copy.c:392
bool sendmode
Evaluate searches in send-mode.
Definition: pattern.h:59
uint8_t PatternExecFlags
Flags for mutt_pattern_exec(), e.g. MUTT_MATCH_FULL_ADDRESS.
Definition: pattern.h:74
#define mutt_error(...)
Definition: logging.h:84
bool replied
Email has been replied to.
Definition: email.h:54
static int scan_range_slot(struct Buffer *s, regmatch_t pmatch[], int grp, int side, int kind)
Parse a range of message numbers.
Definition: pattern.c:925
struct Group * group
Address group if group_match is set.
Definition: pattern.h:66
static int match_threadparent(struct PatternList *pat, PatternExecFlags flags, struct Mailbox *m, struct MuttThread *t)
Match Pattern against an email&#39;s parent.
Definition: pattern.c:1912
int mutt_str_strcasecmp(const char *a, const char *b)
Compare two strings ignoring case, safely.
Definition: string.c:654
Thread is collapsed.
Definition: pattern.h:114
FILE * fp
pointer to the message data
Definition: mx.h:85
int mutt_is_subscribed_list_recipient(bool all_addr, struct Envelope *e)
Matches subscribed mailing lists.
Definition: pattern.c:1813
#define MUTT_PC_SEND_MODE_SEARCH
Allow send-mode body searching.
Definition: pattern.h:44
Pattern matches sender.
Definition: pattern.h:128
Both patterns must match.
Definition: pattern.h:107
#define FREE(x)
Definition: memory.h:40
#define MUTT_PDR_DONE
Pattern parse successfully.
Definition: pattern.c:106
bool mutt_addr_is_user(const struct Address *addr)
Does the address belong to the user.
Definition: alias.c:686
Message is crypographically verified.
Definition: pattern.h:140
int max
Maximum for range checks.
Definition: pattern.h:62
#define MUTT_PC_FULL_MSG
Enable body and header matching.
Definition: pattern.h:42
Pattern matches &#39;References:&#39; or &#39;In-Reply-To:&#39; field.
Definition: pattern.h:132
Keep track when processing files.
Definition: state.h:44
static bool match_update_dynamic_date(struct Pattern *pat)
Update a dynamic date pattern.
Definition: pattern.c:1974
Pattern matches message tags.
Definition: pattern.h:145
#define SLIST_FOREACH(var, head, field)
Definition: queue.h:230
static int report_regerror(int regerr, regex_t *preg, struct Buffer *err)
Create a regex error message.
Definition: pattern.c:838
struct AddressList to
Email&#39;s &#39;To&#39; list.
Definition: envelope.h:58
struct AddressList sender
Email&#39;s sender.
Definition: envelope.h:61
Pattern matches any address field.
Definition: pattern.h:138
User is a recipient of the email.
Definition: pattern.h:133
struct ListHead multi_cases
Multiple strings for ~I pattern.
Definition: pattern.h:68
int mutt_body_handler(struct Body *b, struct State *s)
Handler for the Body of an email.
Definition: handler.c:1550
int lgrp
Paren group matching the left side.
Definition: pattern.c:134
static bool match_mime_content_type(const struct Pattern *pat, struct Mailbox *m, struct Email *e)
Match a Pattern against an email&#39;s Content-Type.
Definition: pattern.c:1992
bool mutt_is_subscribed_list(const struct Address *addr)
Is this the email address of a user-subscribed mailing list? - Implements addr_predicate_t.
Definition: hdrline.c:126
Messages to be un-tagged.
Definition: mutt.h:102
Pattern matches email thread.
Definition: pattern.h:109
Absolute range.
Definition: pattern.c:146
int mutt_count_body_parts(struct Mailbox *m, struct Email *e)
Count the MIME Body parts.
Definition: mutt_parse.c:205
Handling of global boolean variables.
#define mutt_debug(LEVEL,...)
Definition: logging.h:81
Mapping between user character and internal constant.
Definition: pattern.c:166
uint8_t PatternCompFlags
Flags for mutt_pattern_comp(), e.g. MUTT_PC_FULL_MSG.
Definition: pattern.h:40
Miscellaneous functions for sending an email.
uint16_t ParseDateRangeFlags
Flags for parse_date_range(), e.g. MUTT_PDR_MINUS.
Definition: pattern.c:100
int mx_msg_padding_size(struct Mailbox *m)
Bytes of padding between messages - Wrapper for MxOps::msg_padding_size()
Definition: mx.c:1528
long hdr_offset
Offset in stream where the headers begin.
Definition: body.h:42
struct Buffer * mutt_buffer_init(struct Buffer *buf)
Initialise a new Buffer.
Definition: buffer.c:46
Pattern matches &#39;To:&#39; field.
Definition: pattern.h:112
int const char int line
Definition: acutest.h:617
static void set_pattern_cache_value(int *cache_entry, int value)
Sets a value in the PatternCache cache entry.
Definition: pattern.c:2006
int current
Current entry.
Definition: mutt_menu.h:87
Cache commonly-used patterns.
Definition: pattern.h:86
int rgrp
Paren group matching the right side.
Definition: pattern.c:135
Pattern matches raw email text.
Definition: pattern.h:127
static bool eat_range(struct Pattern *pat, int flags, struct Buffer *s, struct Buffer *err)
Parse a number range - Implements Pattern::eat_arg()
Definition: pattern.c:748
WHERE char * C_ExternalSearchCommand
Config: External search command.
Definition: globals.h:112
void mutt_list_clear(struct ListHead *h)
Free a list, but NOT its strings.
Definition: list.c:168
Convenience wrapper for the library headers.
FILE * mutt_file_fopen(const char *path, const char *mode)
Call fopen() safely.
Definition: file.c:588
struct ListHead references
message references (in reverse order)
Definition: envelope.h:81
A List node for strings.
Definition: list.h:33
static bool match_content_type(const struct Pattern *pat, struct Body *b)
Match a Pattern against an Attachment&#39;s Content-Type.
Definition: pattern.c:1951
Match patterns to emails.
int filter_wait(pid_t pid)
Wait for the exit of a process and return its status.
Definition: filter.c:217
time_t mutt_date_make_time(struct tm *t, bool local)
Convert struct tm to time_t
Definition: date.c:231
bool mutt_buffer_is_empty(const struct Buffer *buf)
Is the Buffer empty?
Definition: buffer.c:252
Decide how to display email content.
&#39;IMAP&#39; Mailbox type
Definition: mailbox.h:53
char * mutt_str_substr_dup(const char *begin, const char *end)
Duplicate a sub-string.
Definition: string.c:605
char * pattern
Limit pattern string.
Definition: context.h:40
bool mutt_mb_is_lower(const char *s)
Does a multi-byte string contain only lowercase characters?
Definition: mbyte.c:358
char * x_label
X-Label.
Definition: envelope.h:72
#define MUTT_TOKEN_PATTERN
~%=!| are terms (for patterns)
Definition: mutt.h:75
int mutt_search_command(int cur, int op)
Perform a search.
Definition: pattern.c:2690
static bool eat_date(struct Pattern *pat, int flags, struct Buffer *s, struct Buffer *err)
Parse a date pattern - Implements Pattern::eat_arg()
Definition: pattern.c:1076
int mutt_is_list_recipient(bool all_addr, struct Envelope *e)
Matches known mailing lists.
Definition: pattern.c:1825
Pattern matches number of attachments.
Definition: pattern.h:146
#define SEC_GOODSIGN
Email has a valid signature.
Definition: lib.h:127
int list_one
~l
Definition: pattern.h:89
static unsigned char * pbuf
Cache PGP data packet.
Definition: pgppacket.c:38
struct Group * mutt_pattern_group(const char *pat)
Match a pattern to a Group.
Definition: group.c:66
int mutt_str_strcmp(const char *a, const char *b)
Compare two strings, safely.
Definition: string.c:641
struct Message * mx_msg_open(struct Mailbox *m, int msgno)
return a stream pointer for a message
Definition: mx.c:1123
WHERE bool OptSearchReverse
(pseudo) used by ci_search_command
Definition: options.h:54
bool(* eat_arg)(struct Pattern *pat, int flags, struct Buffer *s, struct Buffer *err)
Function to parse a pattern.
Definition: pattern.c:180
#define RANGE_GT_RX
Definition: pattern.c:88
static int eat_range_by_regex(struct Pattern *pat, struct Buffer *s, int kind, struct Buffer *err)
Parse a range given as a regex.
Definition: pattern.c:978
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: pattern.c:496
time_t received
Time when the message was placed in the mailbox.
Definition: email.h:82
Message-Id is among results from an external query.
Definition: pattern.h:123
static bool patmatch(const struct Pattern *pat, const char *buf)
Compare a string to a Pattern.
Definition: pattern.c:1115
Less-than range.
Definition: pattern.c:147
The header of an Email.
Definition: envelope.h:54
bool ready
Compiled yet?
Definition: pattern.c:136
struct Buffer spam
Spam header.
Definition: envelope.h:80
bool matched
Search matches this Email.
Definition: email.h:68
int msgno
Number displayed to the user.
Definition: email.h:86
int sub_one
~u
Definition: pattern.h:91
char * mutt_rfc822_read_line(FILE *fp, char *line, size_t *linelen)
Read a header line from a file.
Definition: parse.c:1063
Relative range.
Definition: pattern.c:145
#define RANGE_DOT
Definition: pattern.c:111
static bool is_context_available(struct Buffer *s, regmatch_t pmatch[], int kind, struct Buffer *err)
Do we need a Context for this Pattern?
Definition: pattern.c:857
#define SLIST_INSERT_HEAD(head, elm, field)
Definition: queue.h:264
Right side of range.
Definition: pattern.c:160
va_end(args)