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