NeoMutt  2018-07-16 +2481-68dcde
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 <sys/stat.h>
40 #include <time.h>
41 #include "mutt/mutt.h"
42 #include "address/lib.h"
43 #include "email/lib.h"
44 #include "core/lib.h"
45 #include "mutt.h"
46 #include "pattern.h"
47 #include "alias.h"
48 #include "context.h"
49 #include "copy.h"
50 #include "curs_lib.h"
51 #include "filter.h"
52 #include "globals.h"
53 #include "handler.h"
54 #include "hdrline.h"
55 #include "mutt_logging.h"
56 #include "mutt_menu.h"
57 #include "mutt_parse.h"
58 #include "mx.h"
59 #include "ncrypt/ncrypt.h"
60 #include "opcodes.h"
61 #include "options.h"
62 #include "progress.h"
63 #include "protos.h"
64 #include "state.h"
65 #ifdef USE_IMAP
66 #include "imap/imap.h"
67 #endif
68 
69 /* These Config Variables are only used in pattern.c */
71 
72 // clang-format off
73 /* The regexes in a modern format */
74 #define RANGE_NUM_RX "([[:digit:]]+|0x[[:xdigit:]]+)[MmKk]?"
75 #define RANGE_REL_SLOT_RX "[[:blank:]]*([.^$]|-?" RANGE_NUM_RX ")?[[:blank:]]*"
76 #define RANGE_REL_RX "^" RANGE_REL_SLOT_RX "," RANGE_REL_SLOT_RX
77 
78 /* Almost the same, but no negative numbers allowed */
79 #define RANGE_ABS_SLOT_RX "[[:blank:]]*([.^$]|" RANGE_NUM_RX ")?[[:blank:]]*"
80 #define RANGE_ABS_RX "^" RANGE_ABS_SLOT_RX "-" RANGE_ABS_SLOT_RX
81 
82 /* First group is intentionally empty */
83 #define RANGE_LT_RX "^()[[:blank:]]*(<[[:blank:]]*" RANGE_NUM_RX ")[[:blank:]]*"
84 #define RANGE_GT_RX "^()[[:blank:]]*(>[[:blank:]]*" RANGE_NUM_RX ")[[:blank:]]*"
85 
86 /* Single group for min and max */
87 #define RANGE_BARE_RX "^[[:blank:]]*([.^$]|" RANGE_NUM_RX ")[[:blank:]]*"
88 #define RANGE_RX_GROUPS 5
89 
90 #define KILO 1024
91 #define MEGA 1048576
92 #define EMSG(e) (((e)->msgno) + 1)
93 #define CTX_MSGNO(ctx) \
94  (EMSG((ctx)->mailbox->emails[(ctx)->mailbox->v2r[(ctx)->menu->current]]))
95 
96 #define MUTT_MAXRANGE -1
97 
98 typedef uint16_t ParseDateRangeFlags;
99 #define MUTT_PDR_NO_FLAGS 0
100 #define MUTT_PDR_MINUS (1 << 0)
101 #define MUTT_PDR_PLUS (1 << 1)
102 #define MUTT_PDR_WINDOW (1 << 2)
103 #define MUTT_PDR_ABSOLUTE (1 << 3)
104 #define MUTT_PDR_DONE (1 << 4)
105 #define MUTT_PDR_ERROR (1 << 8)
106 
107 #define MUTT_PDR_ERRORDONE (MUTT_PDR_ERROR | MUTT_PDR_DONE)
108 
109 #define RANGE_DOT '.'
110 #define RANGE_CIRCUM '^'
111 #define RANGE_DOLLAR '$'
112 #define RANGE_LT '<'
113 #define RANGE_GT '>'
114 // clang-format on
115 
120 {
124 };
125 
130 {
131  const char *raw;
132  int lgrp;
133  int rgrp;
134  bool ready;
135  regex_t cooked;
136 };
137 
142 {
148  /* add new ones HERE */
150 };
151 
156 {
159 };
160 
169 typedef bool pattern_eat_t(struct Pattern *pat, int flags, struct Buffer *s,
170  struct Buffer *err);
171 
176 {
177  int tag;
178  int op;
179  int flags;
181 };
182 
183 // clang-format off
189 static struct RangeRegex range_regexes[] = {
190  [RANGE_K_REL] = { RANGE_REL_RX, 1, 3, 0, { 0 } },
191  [RANGE_K_ABS] = { RANGE_ABS_RX, 1, 3, 0, { 0 } },
192  [RANGE_K_LT] = { RANGE_LT_RX, 1, 2, 0, { 0 } },
193  [RANGE_K_GT] = { RANGE_GT_RX, 2, 1, 0, { 0 } },
194  [RANGE_K_BARE] = { RANGE_BARE_RX, 1, 1, 0, { 0 } },
195 };
196 // clang-format on
197 
198 static struct PatternList *SearchPattern = NULL;
199 static char LastSearch[256] = { 0 };
200 static char LastSearchExpn[1024] = { 0 };
205 static bool eat_regex(struct Pattern *pat, int flags, struct Buffer *s, struct Buffer *err)
206 {
207  struct Buffer buf;
208 
209  mutt_buffer_init(&buf);
210  char *pexpr = s->dptr;
211  if ((mutt_extract_token(&buf, s, MUTT_TOKEN_PATTERN | MUTT_TOKEN_COMMENT) != 0) || !buf.data)
212  {
213  mutt_buffer_printf(err, _("Error in expression: %s"), pexpr);
214  FREE(&buf.data);
215  return false;
216  }
217  if (buf.data[0] == '\0')
218  {
219  mutt_buffer_printf(err, "%s", _("Empty expression"));
220  FREE(&buf.data);
221  return false;
222  }
223 
224  if (pat->string_match)
225  {
226  pat->p.str = mutt_str_strdup(buf.data);
227  pat->ign_case = mutt_mb_is_lower(buf.data);
228  FREE(&buf.data);
229  }
230  else if (pat->group_match)
231  {
232  pat->p.group = mutt_pattern_group(buf.data);
233  FREE(&buf.data);
234  }
235  else
236  {
237  pat->p.regex = mutt_mem_malloc(sizeof(regex_t));
238  int case_flags = mutt_mb_is_lower(buf.data) ? REG_ICASE : 0;
239  int rc = REG_COMP(pat->p.regex, buf.data, REG_NEWLINE | REG_NOSUB | case_flags);
240  if (rc != 0)
241  {
242  char errmsg[256];
243  regerror(rc, pat->p.regex, errmsg, sizeof(errmsg));
244  mutt_buffer_add_printf(err, "'%s': %s", buf.data, errmsg);
245  FREE(&buf.data);
246  FREE(&pat->p.regex);
247  return false;
248  }
249  FREE(&buf.data);
250  }
251 
252  return true;
253 }
254 
259 static bool add_query_msgid(char *line, int line_num, void *user_data)
260 {
261  struct ListHead *msgid_list = (struct ListHead *) (user_data);
262  char *nows = mutt_str_skip_whitespace(line);
263  if (!*nows)
264  return true;
266  mutt_list_insert_tail(msgid_list, mutt_str_strdup(nows));
267  return true;
268 }
269 
273 static bool eat_query(struct Pattern *pat, int flags, struct Buffer *s, struct Buffer *err)
274 {
275  struct Buffer cmd_buf;
276  struct Buffer tok_buf;
277  FILE *fp = NULL;
278 
280  {
281  mutt_buffer_printf(err, "%s", _("No search command defined"));
282  return false;
283  }
284 
285  mutt_buffer_init(&tok_buf);
286  char *pexpr = s->dptr;
287  if ((mutt_extract_token(&tok_buf, s, MUTT_TOKEN_PATTERN | MUTT_TOKEN_COMMENT) != 0) ||
288  !tok_buf.data)
289  {
290  mutt_buffer_printf(err, _("Error in expression: %s"), pexpr);
291  return false;
292  }
293  if (*tok_buf.data == '\0')
294  {
295  mutt_buffer_printf(err, "%s", _("Empty expression"));
296  FREE(&tok_buf.data);
297  return false;
298  }
299 
300  mutt_buffer_init(&cmd_buf);
302  mutt_buffer_addch(&cmd_buf, ' ');
303  if (!Context || !Context->mailbox)
304  {
305  mutt_buffer_addch(&cmd_buf, '/');
306  }
307  else
308  {
309  char *escaped_folder = mutt_path_escape(mailbox_path(Context->mailbox));
310  mutt_debug(LL_DEBUG2, "escaped folder path: %s\n", escaped_folder);
311  mutt_buffer_addch(&cmd_buf, '\'');
312  mutt_buffer_addstr(&cmd_buf, escaped_folder);
313  mutt_buffer_addch(&cmd_buf, '\'');
314  }
315  mutt_buffer_addch(&cmd_buf, ' ');
316  mutt_buffer_addstr(&cmd_buf, tok_buf.data);
317  FREE(&tok_buf.data);
318 
319  mutt_message(_("Running search command: %s ..."), cmd_buf.data);
320  pat->is_multi = true;
322  pid_t pid = mutt_create_filter(cmd_buf.data, NULL, &fp, NULL);
323  if (pid < 0)
324  {
325  mutt_buffer_printf(err, "unable to fork command: %s\n", cmd_buf.data);
326  FREE(&cmd_buf.data);
327  return false;
328  }
329 
331  mutt_file_fclose(&fp);
332  mutt_wait_filter(pid);
333  FREE(&cmd_buf.data);
334  return true;
335 }
336 
349 static const char *get_offset(struct tm *tm, const char *s, int sign)
350 {
351  char *ps = NULL;
352  int offset = strtol(s, &ps, 0);
353  if (((sign < 0) && (offset > 0)) || ((sign > 0) && (offset < 0)))
354  offset = -offset;
355 
356  switch (*ps)
357  {
358  case 'y':
359  tm->tm_year += offset;
360  break;
361  case 'm':
362  tm->tm_mon += offset;
363  break;
364  case 'w':
365  tm->tm_mday += 7 * offset;
366  break;
367  case 'd':
368  tm->tm_mday += offset;
369  break;
370  case 'H':
371  tm->tm_hour += offset;
372  break;
373  case 'M':
374  tm->tm_min += offset;
375  break;
376  case 'S':
377  tm->tm_sec += offset;
378  break;
379  default:
380  return s;
381  }
383  return ps + 1;
384 }
385 
402 static const char *get_date(const char *s, struct tm *t, struct Buffer *err)
403 {
404  char *p = NULL;
405  struct tm tm = mutt_date_localtime(MUTT_DATE_NOW);
406 
407  t->tm_mday = strtol(s, &p, 10);
408  if ((t->tm_mday < 1) || (t->tm_mday > 31))
409  {
410  mutt_buffer_printf(err, _("Invalid day of month: %s"), s);
411  return NULL;
412  }
413  if (*p != '/')
414  {
415  /* fill in today's month and year */
416  t->tm_mon = tm.tm_mon;
417  t->tm_year = tm.tm_year;
418  return p;
419  }
420  p++;
421  t->tm_mon = strtol(p, &p, 10) - 1;
422  if ((t->tm_mon < 0) || (t->tm_mon > 11))
423  {
424  mutt_buffer_printf(err, _("Invalid month: %s"), p);
425  return NULL;
426  }
427  if (*p != '/')
428  {
429  t->tm_year = tm.tm_year;
430  return p;
431  }
432  p++;
433  t->tm_year = strtol(p, &p, 10);
434  if (t->tm_year < 70) /* year 2000+ */
435  t->tm_year += 100;
436  else if (t->tm_year > 1900)
437  t->tm_year -= 1900;
438  return p;
439 }
440 
451 static const char *parse_date_range(const char *pc, struct tm *min, struct tm *max,
452  bool have_min, struct tm *base_min, struct Buffer *err)
453 {
455  while (*pc && ((flags & MUTT_PDR_DONE) == 0))
456  {
457  const char *pt = NULL;
458  char ch = *pc++;
459  SKIPWS(pc);
460  switch (ch)
461  {
462  case '-':
463  {
464  /* try a range of absolute date minus offset of Ndwmy */
465  pt = get_offset(min, pc, -1);
466  if (pc == pt)
467  {
468  if (flags == MUTT_PDR_NO_FLAGS)
469  { /* nothing yet and no offset parsed => absolute date? */
470  if (!get_date(pc, max, err))
471  flags |= (MUTT_PDR_ABSOLUTE | MUTT_PDR_ERRORDONE); /* done bad */
472  else
473  {
474  /* reestablish initial base minimum if not specified */
475  if (!have_min)
476  memcpy(min, base_min, sizeof(struct tm));
477  flags |= (MUTT_PDR_ABSOLUTE | MUTT_PDR_DONE); /* done good */
478  }
479  }
480  else
481  flags |= MUTT_PDR_ERRORDONE;
482  }
483  else
484  {
485  pc = pt;
486  if ((flags == MUTT_PDR_NO_FLAGS) && !have_min)
487  { /* the very first "-3d" without a previous absolute date */
488  max->tm_year = min->tm_year;
489  max->tm_mon = min->tm_mon;
490  max->tm_mday = min->tm_mday;
491  }
492  flags |= MUTT_PDR_MINUS;
493  }
494  break;
495  }
496  case '+':
497  { /* enlarge plus range */
498  pt = get_offset(max, pc, 1);
499  if (pc == pt)
500  flags |= MUTT_PDR_ERRORDONE;
501  else
502  {
503  pc = pt;
504  flags |= MUTT_PDR_PLUS;
505  }
506  break;
507  }
508  case '*':
509  { /* enlarge window in both directions */
510  pt = get_offset(min, pc, -1);
511  if (pc == pt)
512  flags |= MUTT_PDR_ERRORDONE;
513  else
514  {
515  pc = get_offset(max, pc, 1);
516  flags |= MUTT_PDR_WINDOW;
517  }
518  break;
519  }
520  default:
521  flags |= MUTT_PDR_ERRORDONE;
522  }
523  SKIPWS(pc);
524  }
525  if ((flags & MUTT_PDR_ERROR) && !(flags & MUTT_PDR_ABSOLUTE))
526  { /* get_date has its own error message, don't overwrite it here */
527  mutt_buffer_printf(err, _("Invalid relative date: %s"), pc - 1);
528  }
529  return (flags & MUTT_PDR_ERROR) ? NULL : pc;
530 }
531 
537 static void adjust_date_range(struct tm *min, struct tm *max)
538 {
539  if ((min->tm_year > max->tm_year) ||
540  ((min->tm_year == max->tm_year) && (min->tm_mon > max->tm_mon)) ||
541  ((min->tm_year == max->tm_year) && (min->tm_mon == max->tm_mon) &&
542  (min->tm_mday > max->tm_mday)))
543  {
544  int tmp;
545 
546  tmp = min->tm_year;
547  min->tm_year = max->tm_year;
548  max->tm_year = tmp;
549 
550  tmp = min->tm_mon;
551  min->tm_mon = max->tm_mon;
552  max->tm_mon = tmp;
553 
554  tmp = min->tm_mday;
555  min->tm_mday = max->tm_mday;
556  max->tm_mday = tmp;
557 
558  min->tm_hour = 0;
559  min->tm_min = 0;
560  min->tm_sec = 0;
561  max->tm_hour = 23;
562  max->tm_min = 59;
563  max->tm_sec = 59;
564  }
565 }
566 
575 static bool eval_date_minmax(struct Pattern *pat, const char *s, struct Buffer *err)
576 {
577  /* the '0' time is Jan 1, 1970 UTC, so in order to prevent a negative time
578  * when doing timezone conversion, we use Jan 2, 1970 UTC as the base here */
579  struct tm min = { 0 };
580  min.tm_mday = 2;
581  min.tm_year = 70;
582 
583  /* Arbitrary year in the future. Don't set this too high or
584  * mutt_date_make_time() returns something larger than will fit in a time_t
585  * on some systems */
586  struct tm max = { 0 };
587  max.tm_year = 130;
588  max.tm_mon = 11;
589  max.tm_mday = 31;
590  max.tm_hour = 23;
591  max.tm_min = 59;
592  max.tm_sec = 59;
593 
594  if (strchr("<>=", s[0]))
595  {
596  /* offset from current time
597  * <3d less than three days ago
598  * >3d more than three days ago
599  * =3d exactly three days ago */
600  struct tm *tm = NULL;
601  bool exact = false;
602 
603  if (s[0] == '<')
604  {
606  tm = &min;
607  }
608  else
609  {
611  tm = &max;
612 
613  if (s[0] == '=')
614  exact = true;
615  }
616 
617  /* Reset the HMS unless we are relative matching using one of those
618  * offsets. */
619  char *offset_type = NULL;
620  strtol(s + 1, &offset_type, 0);
621  if (!(*offset_type && strchr("HMS", *offset_type)))
622  {
623  tm->tm_hour = 23;
624  tm->tm_min = 59;
625  tm->tm_sec = 59;
626  }
627 
628  /* force negative offset */
629  get_offset(tm, s + 1, -1);
630 
631  if (exact)
632  {
633  /* start at the beginning of the day in question */
634  memcpy(&min, &max, sizeof(max));
635  min.tm_hour = 0;
636  min.tm_sec = 0;
637  min.tm_min = 0;
638  }
639  }
640  else
641  {
642  const char *pc = s;
643 
644  bool have_min = false;
645  bool until_now = false;
646  if (isdigit((unsigned char) *pc))
647  {
648  /* minimum date specified */
649  pc = get_date(pc, &min, err);
650  if (!pc)
651  {
652  return false;
653  }
654  have_min = true;
655  SKIPWS(pc);
656  if (*pc == '-')
657  {
658  const char *pt = pc + 1;
659  SKIPWS(pt);
660  until_now = (*pt == '\0');
661  }
662  }
663 
664  if (!until_now)
665  { /* max date or relative range/window */
666 
667  struct tm base_min;
668 
669  if (!have_min)
670  { /* save base minimum and set current date, e.g. for "-3d+1d" */
671  memcpy(&base_min, &min, sizeof(base_min));
673  min.tm_hour = 0;
674  min.tm_sec = 0;
675  min.tm_min = 0;
676  }
677 
678  /* preset max date for relative offsets,
679  * if nothing follows we search for messages on a specific day */
680  max.tm_year = min.tm_year;
681  max.tm_mon = min.tm_mon;
682  max.tm_mday = min.tm_mday;
683 
684  if (!parse_date_range(pc, &min, &max, have_min, &base_min, err))
685  { /* bail out on any parsing error */
686  return false;
687  }
688  }
689  }
690 
691  /* Since we allow two dates to be specified we'll have to adjust that. */
692  adjust_date_range(&min, &max);
693 
694  pat->min = mutt_date_make_time(&min, true);
695  pat->max = mutt_date_make_time(&max, true);
696 
697  return true;
698 }
699 
703 static bool eat_range(struct Pattern *pat, int flags, struct Buffer *s, struct Buffer *err)
704 {
705  char *tmp = NULL;
706  bool do_exclusive = false;
707  bool skip_quote = false;
708 
709  /* If simple_search is set to "~m %s", the range will have double quotes
710  * around it... */
711  if (*s->dptr == '"')
712  {
713  s->dptr++;
714  skip_quote = true;
715  }
716  if (*s->dptr == '<')
717  do_exclusive = true;
718  if ((*s->dptr != '-') && (*s->dptr != '<'))
719  {
720  /* range minimum */
721  if (*s->dptr == '>')
722  {
723  pat->max = MUTT_MAXRANGE;
724  pat->min = strtol(s->dptr + 1, &tmp, 0) + 1; /* exclusive range */
725  }
726  else
727  pat->min = strtol(s->dptr, &tmp, 0);
728  if (toupper((unsigned char) *tmp) == 'K') /* is there a prefix? */
729  {
730  pat->min *= 1024;
731  tmp++;
732  }
733  else if (toupper((unsigned char) *tmp) == 'M')
734  {
735  pat->min *= 1048576;
736  tmp++;
737  }
738  if (*s->dptr == '>')
739  {
740  s->dptr = tmp;
741  return true;
742  }
743  if (*tmp != '-')
744  {
745  /* exact value */
746  pat->max = pat->min;
747  s->dptr = tmp;
748  return true;
749  }
750  tmp++;
751  }
752  else
753  {
754  s->dptr++;
755  tmp = s->dptr;
756  }
757 
758  if (isdigit((unsigned char) *tmp))
759  {
760  /* range maximum */
761  pat->max = strtol(tmp, &tmp, 0);
762  if (toupper((unsigned char) *tmp) == 'K')
763  {
764  pat->max *= 1024;
765  tmp++;
766  }
767  else if (toupper((unsigned char) *tmp) == 'M')
768  {
769  pat->max *= 1048576;
770  tmp++;
771  }
772  if (do_exclusive)
773  (pat->max)--;
774  }
775  else
776  pat->max = MUTT_MAXRANGE;
777 
778  if (skip_quote && (*tmp == '"'))
779  tmp++;
780 
781  SKIPWS(tmp);
782  s->dptr = tmp;
783  return true;
784 }
785 
793 static int report_regerror(int regerr, regex_t *preg, struct Buffer *err)
794 {
795  size_t ds = err->dsize;
796 
797  if (regerror(regerr, preg, err->data, ds) > ds)
798  mutt_debug(LL_DEBUG2, "warning: buffer too small for regerror\n");
799  /* The return value is fixed, exists only to shorten code at callsite */
800  return RANGE_E_SYNTAX;
801 }
802 
812 static bool is_context_available(struct Buffer *s, regmatch_t pmatch[],
813  int kind, struct Buffer *err)
814 {
815  const char *context_req_chars[] = {
816  [RANGE_K_REL] = ".0123456789",
817  [RANGE_K_ABS] = ".",
818  [RANGE_K_LT] = "",
819  [RANGE_K_GT] = "",
820  [RANGE_K_BARE] = ".",
821  };
822 
823  /* First decide if we're going to need the context at all.
824  * Relative patterns need it if they contain a dot or a number.
825  * Absolute patterns only need it if they contain a dot. */
826  char *context_loc = strpbrk(s->dptr + pmatch[0].rm_so, context_req_chars[kind]);
827  if (!context_loc || (context_loc >= &s->dptr[pmatch[0].rm_eo]))
828  return true;
829 
830  /* We need a current message. Do we actually have one? */
831  if (Context && Context->menu)
832  return true;
833 
834  /* Nope. */
835  mutt_buffer_strcpy(err, _("No current message"));
836  return false;
837 }
838 
847 static int scan_range_num(struct Buffer *s, regmatch_t pmatch[], int group, int kind)
848 {
849  int num = (int) strtol(&s->dptr[pmatch[group].rm_so], NULL, 0);
850  unsigned char c = (unsigned char) (s->dptr[pmatch[group].rm_eo - 1]);
851  if (toupper(c) == 'K')
852  num *= KILO;
853  else if (toupper(c) == 'M')
854  num *= MEGA;
855  switch (kind)
856  {
857  case RANGE_K_REL:
858  return num + CTX_MSGNO(Context);
859  case RANGE_K_LT:
860  return num - 1;
861  case RANGE_K_GT:
862  return num + 1;
863  default:
864  return num;
865  }
866 }
867 
877 static int scan_range_slot(struct Buffer *s, regmatch_t pmatch[], int grp, int side, int kind)
878 {
879  /* This means the left or right subpattern was empty, e.g. ",." */
880  if ((pmatch[grp].rm_so == -1) || (pmatch[grp].rm_so == pmatch[grp].rm_eo))
881  {
882  if (side == RANGE_S_LEFT)
883  return 1;
884  else if (side == RANGE_S_RIGHT)
885  return Context->mailbox->msg_count;
886  }
887  /* We have something, so determine what */
888  unsigned char c = (unsigned char) (s->dptr[pmatch[grp].rm_so]);
889  switch (c)
890  {
891  case RANGE_CIRCUM:
892  return 1;
893  case RANGE_DOLLAR:
894  return Context->mailbox->msg_count;
895  case RANGE_DOT:
896  return CTX_MSGNO(Context);
897  case RANGE_LT:
898  case RANGE_GT:
899  return scan_range_num(s, pmatch, grp + 1, kind);
900  default:
901  /* Only other possibility: a number */
902  return scan_range_num(s, pmatch, grp, kind);
903  }
904 }
905 
910 static void order_range(struct Pattern *pat)
911 {
912  if (pat->min <= pat->max)
913  return;
914  int num = pat->min;
915  pat->min = pat->max;
916  pat->max = num;
917 }
918 
927 static int eat_range_by_regex(struct Pattern *pat, struct Buffer *s, int kind,
928  struct Buffer *err)
929 {
930  int regerr;
931  regmatch_t pmatch[RANGE_RX_GROUPS];
932  struct RangeRegex *pspec = &range_regexes[kind];
933 
934  /* First time through, compile the big regex */
935  if (!pspec->ready)
936  {
937  regerr = regcomp(&pspec->cooked, pspec->raw, REG_EXTENDED);
938  if (regerr != 0)
939  return report_regerror(regerr, &pspec->cooked, err);
940  pspec->ready = true;
941  }
942 
943  /* Match the pattern buffer against the compiled regex.
944  * No match means syntax error. */
945  regerr = regexec(&pspec->cooked, s->dptr, RANGE_RX_GROUPS, pmatch, 0);
946  if (regerr != 0)
947  return report_regerror(regerr, &pspec->cooked, err);
948 
949  if (!is_context_available(s, pmatch, kind, err))
950  return RANGE_E_CTX;
951 
952  /* Snarf the contents of the two sides of the range. */
953  pat->min = scan_range_slot(s, pmatch, pspec->lgrp, RANGE_S_LEFT, kind);
954  pat->max = scan_range_slot(s, pmatch, pspec->rgrp, RANGE_S_RIGHT, kind);
955  mutt_debug(LL_DEBUG1, "pat->min=%d pat->max=%d\n", pat->min, pat->max);
956 
957  /* Special case for a bare 0. */
958  if ((kind == RANGE_K_BARE) && (pat->min == 0) && (pat->max == 0))
959  {
960  if (!Context->menu)
961  {
962  mutt_buffer_strcpy(err, _("No current message"));
963  return RANGE_E_CTX;
964  }
965  pat->max = CTX_MSGNO(Context);
966  pat->min = pat->max;
967  }
968 
969  /* Since we don't enforce order, we must swap bounds if they're backward */
970  order_range(pat);
971 
972  /* Slide pointer past the entire match. */
973  s->dptr += pmatch[0].rm_eo;
974  return RANGE_E_OK;
975 }
976 
980 static bool eat_message_range(struct Pattern *pat, int flags, struct Buffer *s,
981  struct Buffer *err)
982 {
983  bool skip_quote = false;
984 
985  /* We need a Context for pretty much anything. */
986  if (!Context)
987  {
988  mutt_buffer_strcpy(err, _("No Context"));
989  return false;
990  }
991 
992  /* If simple_search is set to "~m %s", the range will have double quotes
993  * around it... */
994  if (*s->dptr == '"')
995  {
996  s->dptr++;
997  skip_quote = true;
998  }
999 
1000  for (int i_kind = 0; i_kind != RANGE_K_INVALID; i_kind++)
1001  {
1002  switch (eat_range_by_regex(pat, s, i_kind, err))
1003  {
1004  case RANGE_E_CTX:
1005  /* This means it matched syntactically but lacked context.
1006  * No point in continuing. */
1007  break;
1008  case RANGE_E_SYNTAX:
1009  /* Try another syntax, then */
1010  continue;
1011  case RANGE_E_OK:
1012  if (skip_quote && (*s->dptr == '"'))
1013  s->dptr++;
1014  SKIPWS(s->dptr);
1015  return true;
1016  }
1017  }
1018  return false;
1019 }
1020 
1024 static bool eat_date(struct Pattern *pat, int flags, struct Buffer *s, struct Buffer *err)
1025 {
1026  struct Buffer *tmp = mutt_buffer_pool_get();
1027  bool rc = false;
1028 
1029  char *pexpr = s->dptr;
1031  {
1032  snprintf(err->data, err->dsize, _("Error in expression: %s"), pexpr);
1033  goto out;
1034  }
1035 
1036  if (mutt_buffer_is_empty(tmp))
1037  {
1038  snprintf(err->data, err->dsize, "%s", _("Empty expression"));
1039  goto out;
1040  }
1041 
1042  if (flags & MUTT_PC_PATTERN_DYNAMIC)
1043  {
1044  pat->dynamic = true;
1045  pat->p.str = mutt_str_strdup(tmp->data);
1046  }
1047 
1048  rc = eval_date_minmax(pat, tmp->data, err);
1049 
1050 out:
1052 
1053  return rc;
1054 }
1055 
1063 static bool patmatch(const struct Pattern *pat, const char *buf)
1064 {
1065  if (pat->is_multi)
1066  return (mutt_list_find(&pat->p.multi_cases, buf) != NULL);
1067  else if (pat->string_match)
1068  return pat->ign_case ? strcasestr(buf, pat->p.str) : strstr(buf, pat->p.str);
1069  else if (pat->group_match)
1070  return mutt_group_match(pat->p.group, buf);
1071  else
1072  return (regexec(pat->p.regex, buf, 0, NULL, 0) == 0);
1073 }
1074 
1083 static bool msg_search(struct Mailbox *m, struct Pattern *pat, int msgno)
1084 {
1085  bool match = false;
1086  struct Message *msg = mx_msg_open(m, msgno);
1087  if (!msg)
1088  {
1089  return match;
1090  }
1091 
1092  FILE *fp = NULL;
1093  long lng = 0;
1094  struct Email *e = m->emails[msgno];
1095 #ifdef USE_FMEMOPEN
1096  char *temp = NULL;
1097  size_t tempsize;
1098 #else
1099  struct stat st;
1100 #endif
1101 
1102  if (C_ThoroughSearch)
1103  {
1104  /* decode the header / body */
1105  struct State s = { 0 };
1106  s.fp_in = msg->fp;
1107  s.flags = MUTT_CHARCONV;
1108 #ifdef USE_FMEMOPEN
1109  s.fp_out = open_memstream(&temp, &tempsize);
1110  if (!s.fp_out)
1111  {
1112  mutt_perror(_("Error opening 'memory stream'"));
1113  return false;
1114  }
1115 #else
1116  s.fp_out = mutt_file_mkstemp();
1117  if (!s.fp_out)
1118  {
1119  mutt_perror(_("Can't create temporary file"));
1120  return false;
1121  }
1122 #endif
1123 
1124  if (pat->op != MUTT_PAT_BODY)
1125  mutt_copy_header(msg->fp, e, s.fp_out, CH_FROM | CH_DECODE, NULL);
1126 
1127  if (pat->op != MUTT_PAT_HEADER)
1128  {
1130 
1131  if ((WithCrypto != 0) && (e->security & SEC_ENCRYPT) &&
1133  {
1134  mx_msg_close(m, &msg);
1135  if (s.fp_out)
1136  {
1138 #ifdef USE_FMEMOPEN
1139  FREE(&temp);
1140 #endif
1141  }
1142  return false;
1143  }
1144 
1145  fseeko(msg->fp, e->offset, SEEK_SET);
1146  mutt_body_handler(e->content, &s);
1147  }
1148 
1149 #ifdef USE_FMEMOPEN
1150  fclose(s.fp_out);
1151  lng = tempsize;
1152 
1153  if (tempsize)
1154  {
1155  fp = fmemopen(temp, tempsize, "r");
1156  if (!fp)
1157  {
1158  mutt_perror(_("Error re-opening 'memory stream'"));
1159  return false;
1160  }
1161  }
1162  else
1163  { /* fmemopen can't handle empty buffers */
1164  fp = mutt_file_fopen("/dev/null", "r");
1165  if (!fp)
1166  {
1167  mutt_perror(_("Error opening /dev/null"));
1168  return false;
1169  }
1170  }
1171 #else
1172  fp = s.fp_out;
1173  fflush(fp);
1174  fseek(fp, 0, SEEK_SET);
1175  fstat(fileno(fp), &st);
1176  lng = (long) st.st_size;
1177 #endif
1178  }
1179  else
1180  {
1181  /* raw header / body */
1182  fp = msg->fp;
1183  if (pat->op != MUTT_PAT_BODY)
1184  {
1185  fseeko(fp, e->offset, SEEK_SET);
1186  lng = e->content->offset - e->offset;
1187  }
1188  if (pat->op != MUTT_PAT_HEADER)
1189  {
1190  if (pat->op == MUTT_PAT_BODY)
1191  fseeko(fp, e->content->offset, SEEK_SET);
1192  lng += e->content->length;
1193  }
1194  }
1195 
1196  size_t blen = 256;
1197  char *buf = mutt_mem_malloc(blen);
1198 
1199  /* search the file "fp" */
1200  while (lng > 0)
1201  {
1202  if (pat->op == MUTT_PAT_HEADER)
1203  {
1204  buf = mutt_rfc822_read_line(fp, buf, &blen);
1205  if (*buf == '\0')
1206  break;
1207  }
1208  else if (!fgets(buf, blen - 1, fp))
1209  break; /* don't loop forever */
1210  if (patmatch(pat, buf))
1211  {
1212  match = true;
1213  break;
1214  }
1215  lng -= mutt_str_strlen(buf);
1216  }
1217 
1218  FREE(&buf);
1219 
1220  mx_msg_close(m, &msg);
1221 
1222  if (C_ThoroughSearch)
1223  {
1224  mutt_file_fclose(&fp);
1225 #ifdef USE_FMEMOPEN
1226  if (tempsize)
1227  FREE(&temp);
1228 #endif
1229  }
1230 
1231  return match;
1232 }
1233 
1234 // clang-format off
1238 static const struct PatternFlags Flags[] = {
1239  { 'A', MUTT_ALL, 0, NULL },
1241  { 'B', MUTT_PAT_WHOLE_MSG, MUTT_PC_FULL_MSG, eat_regex },
1242  { 'c', MUTT_PAT_CC, 0, eat_regex },
1243  { 'C', MUTT_PAT_RECIPIENT, 0, eat_regex },
1244  { 'd', MUTT_PAT_DATE, 0, eat_date },
1245  { 'D', MUTT_DELETED, 0, NULL },
1246  { 'e', MUTT_PAT_SENDER, 0, eat_regex },
1247  { 'E', MUTT_EXPIRED, 0, NULL },
1248  { 'f', MUTT_PAT_FROM, 0, eat_regex },
1249  { 'F', MUTT_FLAG, 0, NULL },
1250  { 'g', MUTT_PAT_CRYPT_SIGN, 0, NULL },
1251  { 'G', MUTT_PAT_CRYPT_ENCRYPT, 0, NULL },
1252  { 'h', MUTT_PAT_HEADER, MUTT_PC_FULL_MSG, eat_regex },
1253  { 'H', MUTT_PAT_HORMEL, 0, eat_regex },
1254  { 'i', MUTT_PAT_ID, 0, eat_regex },
1255  { 'I', MUTT_PAT_ID_EXTERNAL, 0, eat_query },
1256  { 'k', MUTT_PAT_PGP_KEY, 0, NULL },
1257  { 'l', MUTT_PAT_LIST, 0, NULL },
1258  { 'L', MUTT_PAT_ADDRESS, 0, eat_regex },
1259  { 'm', MUTT_PAT_MESSAGE, 0, eat_message_range },
1260  { 'M', MUTT_PAT_MIMETYPE, MUTT_PC_FULL_MSG, eat_regex },
1261  { 'n', MUTT_PAT_SCORE, 0, eat_range },
1262  { 'N', MUTT_NEW, 0, NULL },
1263  { 'O', MUTT_OLD, 0, NULL },
1264  { 'p', MUTT_PAT_PERSONAL_RECIP, 0, NULL },
1265  { 'P', MUTT_PAT_PERSONAL_FROM, 0, NULL },
1266  { 'Q', MUTT_REPLIED, 0, NULL },
1267  { 'r', MUTT_PAT_DATE_RECEIVED, 0, eat_date },
1268  { 'R', MUTT_READ, 0, NULL },
1269  { 's', MUTT_PAT_SUBJECT, 0, eat_regex },
1270  { 'S', MUTT_SUPERSEDED, 0, NULL },
1271  { 't', MUTT_PAT_TO, 0, eat_regex },
1272  { 'T', MUTT_TAG, 0, NULL },
1273  { 'u', MUTT_PAT_SUBSCRIBED_LIST, 0, NULL },
1274  { 'U', MUTT_UNREAD, 0, NULL },
1275  { 'v', MUTT_PAT_COLLAPSED, 0, NULL },
1276  { 'V', MUTT_PAT_CRYPT_VERIFIED, 0, NULL },
1277 #ifdef USE_NNTP
1278  { 'w', MUTT_PAT_NEWSGROUPS, 0, eat_regex },
1279 #endif
1280  { 'x', MUTT_PAT_REFERENCE, 0, eat_regex },
1281  { 'X', MUTT_PAT_MIMEATTACH, 0, eat_range },
1282  { 'y', MUTT_PAT_XLABEL, 0, eat_regex },
1283  { 'Y', MUTT_PAT_DRIVER_TAGS, 0, eat_regex },
1284  { 'z', MUTT_PAT_SIZE, 0, eat_range },
1285  { '=', MUTT_PAT_DUPLICATED, 0, NULL },
1286  { '$', MUTT_PAT_UNREFERENCED, 0, NULL },
1287  { '#', MUTT_PAT_BROKEN, 0, NULL },
1288  { '/', MUTT_PAT_SERVERSEARCH, 0, eat_regex },
1289  { 0, 0, 0, NULL },
1290 };
1291 // clang-format on
1292 
1298 static const struct PatternFlags *lookup_tag(char tag)
1299 {
1300  for (int i = 0; Flags[i].tag; i++)
1301  if (Flags[i].tag == tag)
1302  return &Flags[i];
1303  return NULL;
1304 }
1305 
1312 static /* const */ char *find_matching_paren(/* const */ char *s)
1313 {
1314  int level = 1;
1315 
1316  for (; *s; s++)
1317  {
1318  if (*s == '(')
1319  level++;
1320  else if (*s == ')')
1321  {
1322  level--;
1323  if (level == 0)
1324  break;
1325  }
1326  }
1327  return s;
1328 }
1329 
1334 void mutt_pattern_free(struct PatternList **pat)
1335 {
1336  if (!pat || !*pat)
1337  return;
1338 
1339  struct Pattern *np = SLIST_FIRST(*pat), *next = NULL;
1340 
1341  while (np)
1342  {
1343  next = SLIST_NEXT(np, entries);
1344 
1345  if (np->is_multi)
1347  else if (np->string_match || np->dynamic)
1348  FREE(&np->p.str);
1349  else if (np->group_match)
1350  np->p.group = NULL;
1351  else if (np->p.regex)
1352  {
1353  regfree(np->p.regex);
1354  FREE(&np->p.regex);
1355  }
1356 
1357  mutt_pattern_free(&np->child);
1358  FREE(&np);
1359 
1360  np = next;
1361  }
1362 
1363  FREE(pat);
1364 }
1365 
1370 static struct PatternList *mutt_pattern_node_new(void)
1371 {
1372  struct PatternList *h = mutt_mem_calloc(1, sizeof(struct PatternList));
1373  SLIST_INIT(h);
1374  struct Pattern *p = mutt_mem_calloc(1, sizeof(struct Pattern));
1375  SLIST_INSERT_HEAD(h, p, entries);
1376  return h;
1377 }
1378 
1386 struct PatternList *mutt_pattern_comp(const char *s, PatternCompFlags flags, struct Buffer *err)
1387 {
1388  /* curlist when assigned will always point to a list containing at least one node
1389  * with a Pattern value. */
1390  struct PatternList *curlist = NULL;
1391  struct PatternList *tmp = NULL, *tmp2 = NULL;
1392  struct PatternList *last = NULL;
1393  bool pat_not = false;
1394  bool all_addr = false;
1395  bool pat_or = false;
1396  bool implicit = true; /* used to detect logical AND operator */
1397  bool is_alias = false;
1398  short thread_op;
1399  const struct PatternFlags *entry = NULL;
1400  char *p = NULL;
1401  char *buf = NULL;
1402  struct Buffer ps;
1403 
1404  mutt_buffer_init(&ps);
1405  ps.dptr = (char *) s;
1406  ps.dsize = mutt_str_strlen(s);
1407 
1408  while (*ps.dptr)
1409  {
1410  SKIPWS(ps.dptr);
1411  switch (*ps.dptr)
1412  {
1413  case '^':
1414  ps.dptr++;
1415  all_addr = !all_addr;
1416  break;
1417  case '!':
1418  ps.dptr++;
1419  pat_not = !pat_not;
1420  break;
1421  case '@':
1422  ps.dptr++;
1423  is_alias = !is_alias;
1424  break;
1425  case '|':
1426  if (!pat_or)
1427  {
1428  if (!curlist)
1429  {
1430  mutt_buffer_printf(err, _("error in pattern at: %s"), ps.dptr);
1431  return NULL;
1432  }
1433 
1434  struct Pattern *pat = SLIST_FIRST(curlist);
1435 
1436  if (SLIST_NEXT(pat, entries))
1437  {
1438  /* A & B | C == (A & B) | C */
1439  tmp = mutt_pattern_node_new();
1440  pat = SLIST_FIRST(tmp);
1441  pat->op = MUTT_PAT_AND;
1442  pat->child = curlist;
1443 
1444  curlist = tmp;
1445  last = curlist;
1446  }
1447 
1448  pat_or = true;
1449  }
1450  ps.dptr++;
1451  implicit = false;
1452  pat_not = false;
1453  all_addr = false;
1454  is_alias = false;
1455  break;
1456  case '%':
1457  case '=':
1458  case '~':
1459  {
1460  struct Pattern *pat = NULL;
1461  if (ps.dptr[1] == '\0')
1462  {
1463  mutt_buffer_printf(err, _("missing pattern: %s"), ps.dptr);
1464  goto cleanup;
1465  }
1466  thread_op = 0;
1467  if (ps.dptr[1] == '(')
1468  thread_op = MUTT_PAT_THREAD;
1469  else if ((ps.dptr[1] == '<') && (ps.dptr[2] == '('))
1470  thread_op = MUTT_PAT_PARENT;
1471  else if ((ps.dptr[1] == '>') && (ps.dptr[2] == '('))
1472  thread_op = MUTT_PAT_CHILDREN;
1473  if (thread_op)
1474  {
1475  ps.dptr++; /* skip ~ */
1476  if ((thread_op == MUTT_PAT_PARENT) || (thread_op == MUTT_PAT_CHILDREN))
1477  ps.dptr++;
1478  p = find_matching_paren(ps.dptr + 1);
1479  if (p[0] != ')')
1480  {
1481  mutt_buffer_printf(err, _("mismatched parentheses: %s"), ps.dptr);
1482  goto cleanup;
1483  }
1484  tmp = mutt_pattern_node_new();
1485  pat = SLIST_FIRST(tmp);
1486  pat->op = thread_op;
1487  if (last)
1488  SLIST_NEXT(SLIST_FIRST(last), entries) = pat;
1489  else
1490  curlist = tmp;
1491  last = tmp;
1492  pat->pat_not ^= pat_not;
1493  pat->all_addr |= all_addr;
1494  pat->is_alias |= is_alias;
1495  pat_not = false;
1496  all_addr = false;
1497  is_alias = false;
1498  /* compile the sub-expression */
1499  buf = mutt_str_substr_dup(ps.dptr + 1, p);
1500  tmp2 = mutt_pattern_comp(buf, flags, err);
1501  if (!tmp2)
1502  {
1503  FREE(&buf);
1504  goto cleanup;
1505  }
1506  FREE(&buf);
1507  pat->child = tmp2;
1508  ps.dptr = p + 1; /* restore location */
1509  break;
1510  }
1511  if (implicit && pat_or)
1512  {
1513  /* A | B & C == (A | B) & C */
1514  tmp = mutt_pattern_node_new();
1515  pat = SLIST_FIRST(tmp);
1516  pat->op = MUTT_PAT_OR;
1517  pat->child = curlist;
1518  curlist = tmp;
1519  last = tmp;
1520  pat_or = false;
1521  }
1522 
1523  tmp = mutt_pattern_node_new();
1524  pat = SLIST_FIRST(tmp);
1525  pat->pat_not = pat_not;
1526  pat->all_addr = all_addr;
1527  pat->is_alias = is_alias;
1528  pat->string_match = (ps.dptr[0] == '=');
1529  pat->group_match = (ps.dptr[0] == '%');
1530  pat_not = false;
1531  all_addr = false;
1532  is_alias = false;
1533 
1534  if (last)
1535  SLIST_NEXT(SLIST_FIRST(last), entries) = pat;
1536  else
1537  curlist = tmp;
1538  if (curlist != last)
1539  FREE(&last);
1540  last = tmp;
1541 
1542  ps.dptr++; /* move past the ~ */
1543  entry = lookup_tag(*ps.dptr);
1544  if (!entry)
1545  {
1546  mutt_buffer_printf(err, _("%c: invalid pattern modifier"), *ps.dptr);
1547  goto cleanup;
1548  }
1549  if (entry->flags && ((flags & entry->flags) == 0))
1550  {
1551  mutt_buffer_printf(err, _("%c: not supported in this mode"), *ps.dptr);
1552  goto cleanup;
1553  }
1554  pat->op = entry->op;
1555 
1556  ps.dptr++; /* eat the operator and any optional whitespace */
1557  SKIPWS(ps.dptr);
1558 
1559  if (entry->eat_arg)
1560  {
1561  if (ps.dptr[0] == '\0')
1562  {
1563  mutt_buffer_printf(err, "%s", _("missing parameter"));
1564  goto cleanup;
1565  }
1566  if (!entry->eat_arg(pat, flags, &ps, err))
1567  {
1568  goto cleanup;
1569  }
1570  }
1571  implicit = true;
1572  break;
1573  }
1574 
1575  case '(':
1576  {
1577  p = find_matching_paren(ps.dptr + 1);
1578  if (p[0] != ')')
1579  {
1580  mutt_buffer_printf(err, _("mismatched parentheses: %s"), ps.dptr);
1581  goto cleanup;
1582  }
1583  /* compile the sub-expression */
1584  buf = mutt_str_substr_dup(ps.dptr + 1, p);
1585  tmp = mutt_pattern_comp(buf, flags, err);
1586  FREE(&buf);
1587  if (!tmp)
1588  goto cleanup;
1589  struct Pattern *pat = SLIST_FIRST(tmp);
1590  if (last)
1591  SLIST_NEXT(SLIST_FIRST(last), entries) = pat;
1592  else
1593  curlist = tmp;
1594  last = tmp;
1595  pat = SLIST_FIRST(tmp);
1596  pat->pat_not ^= pat_not;
1597  pat->all_addr |= all_addr;
1598  pat->is_alias |= is_alias;
1599  pat_not = false;
1600  all_addr = false;
1601  is_alias = false;
1602  ps.dptr = p + 1; /* restore location */
1603  break;
1604  }
1605 
1606  default:
1607  mutt_buffer_printf(err, _("error in pattern at: %s"), ps.dptr);
1608  goto cleanup;
1609  }
1610  }
1611  if (!curlist)
1612  {
1613  mutt_buffer_strcpy(err, _("empty pattern"));
1614  return NULL;
1615  }
1616  if (curlist != tmp)
1617  FREE(&tmp);
1618  if (SLIST_NEXT(SLIST_FIRST(curlist), entries))
1619  {
1620  tmp = mutt_pattern_node_new();
1621  struct Pattern *pat = SLIST_FIRST(tmp);
1622  pat->op = pat_or ? MUTT_PAT_OR : MUTT_PAT_AND;
1623  pat->child = curlist;
1624  curlist = tmp;
1625  }
1626 
1627  return curlist;
1628 
1629 cleanup:
1630  mutt_pattern_free(&curlist);
1631  return NULL;
1632 }
1633 
1643 static bool perform_and(struct PatternList *pat, PatternExecFlags flags,
1644  struct Mailbox *m, struct Email *e, struct PatternCache *cache)
1645 {
1646  struct Pattern *p = NULL;
1647 
1648  SLIST_FOREACH(p, pat, entries)
1649  {
1650  if (mutt_pattern_exec(p, flags, m, e, cache) <= 0)
1651  return false;
1652  }
1653  return true;
1654 }
1655 
1665 static int perform_or(struct PatternList *pat, PatternExecFlags flags,
1666  struct Mailbox *m, struct Email *e, struct PatternCache *cache)
1667 {
1668  struct Pattern *p = NULL;
1669 
1670  SLIST_FOREACH(p, pat, entries)
1671  {
1672  if (mutt_pattern_exec(p, flags, m, e, cache) > 0)
1673  return true;
1674  }
1675  return false;
1676 }
1677 
1687 static int match_addrlist(struct Pattern *pat, bool match_personal, int n, ...)
1688 {
1689  va_list ap;
1690 
1691  va_start(ap, n);
1692  for (; n; n--)
1693  {
1694  struct AddressList *al = va_arg(ap, struct AddressList *);
1695  struct Address *a = NULL;
1696  TAILQ_FOREACH(a, al, entries)
1697  {
1698  if (pat->all_addr ^ ((!pat->is_alias || mutt_alias_reverse_lookup(a)) &&
1699  ((a->mailbox && patmatch(pat, a->mailbox)) ||
1700  (match_personal && a->personal && patmatch(pat, a->personal)))))
1701  {
1702  va_end(ap);
1703  return !pat->all_addr; /* Found match, or non-match if all_addr */
1704  }
1705  }
1706  }
1707  va_end(ap);
1708  return pat->all_addr; /* No matches, or all matches if all_addr */
1709 }
1710 
1717 static bool match_reference(struct Pattern *pat, struct ListHead *refs)
1718 {
1719  struct ListNode *np = NULL;
1720  STAILQ_FOREACH(np, refs, entries)
1721  {
1722  if (patmatch(pat, np->data))
1723  return true;
1724  }
1725  return false;
1726 }
1727 
1733 typedef bool (*addr_predicate_t)(const struct Address *a);
1734 
1745 static int mutt_is_predicate_recipient(bool all_addr, struct Envelope *e, addr_predicate_t p)
1746 {
1747  struct AddressList *als[] = { &e->to, &e->cc };
1748  for (size_t i = 0; i < mutt_array_size(als); ++i)
1749  {
1750  struct AddressList *al = als[i];
1751  struct Address *a = NULL;
1752  TAILQ_FOREACH(a, al, entries)
1753  {
1754  if (all_addr ^ p(a))
1755  return !all_addr;
1756  }
1757  }
1758  return all_addr;
1759 }
1760 
1768 int mutt_is_subscribed_list_recipient(bool all_addr, struct Envelope *e)
1769 {
1771 }
1772 
1780 int mutt_is_list_recipient(bool all_addr, struct Envelope *e)
1781 {
1782  return mutt_is_predicate_recipient(all_addr, e, &mutt_is_mail_list);
1783 }
1784 
1793 static int match_user(int all_addr, struct AddressList *al1, struct AddressList *al2)
1794 {
1795  struct Address *a = NULL;
1796  if (al1)
1797  {
1798  TAILQ_FOREACH(a, al1, entries)
1799  {
1800  if (all_addr ^ mutt_addr_is_user(a))
1801  return !all_addr;
1802  }
1803  }
1804 
1805  if (al2)
1806  {
1807  TAILQ_FOREACH(a, al2, entries)
1808  {
1809  if (all_addr ^ mutt_addr_is_user(a))
1810  return !all_addr;
1811  }
1812  }
1813  return all_addr;
1814 }
1815 
1829 static int match_threadcomplete(struct PatternList *pat, PatternExecFlags flags,
1830  struct Mailbox *m, struct MuttThread *t,
1831  int left, int up, int right, int down)
1832 {
1833  if (!t)
1834  return 0;
1835 
1836  int a;
1837  struct Email *e = t->message;
1838  if (e)
1839  if (mutt_pattern_exec(SLIST_FIRST(pat), flags, m, e, NULL))
1840  return 1;
1841 
1842  if (up && (a = match_threadcomplete(pat, flags, m, t->parent, 1, 1, 1, 0)))
1843  return a;
1844  if (right && t->parent && (a = match_threadcomplete(pat, flags, m, t->next, 0, 0, 1, 1)))
1845  {
1846  return a;
1847  }
1848  if (left && t->parent && (a = match_threadcomplete(pat, flags, m, t->prev, 1, 0, 0, 1)))
1849  {
1850  return a;
1851  }
1852  if (down && (a = match_threadcomplete(pat, flags, m, t->child, 1, 0, 1, 1)))
1853  return a;
1854  return 0;
1855 }
1856 
1867 static int match_threadparent(struct PatternList *pat, PatternExecFlags flags,
1868  struct Mailbox *m, struct MuttThread *t)
1869 {
1870  if (!t || !t->parent || !t->parent->message)
1871  return 0;
1872 
1873  return mutt_pattern_exec(SLIST_FIRST(pat), flags, m, t->parent->message, NULL);
1874 }
1875 
1886 static int match_threadchildren(struct PatternList *pat, PatternExecFlags flags,
1887  struct Mailbox *m, struct MuttThread *t)
1888 {
1889  if (!t || !t->child)
1890  return 0;
1891 
1892  for (t = t->child; t; t = t->next)
1893  if (t->message && mutt_pattern_exec(SLIST_FIRST(pat), flags, m, t->message, NULL))
1894  return 1;
1895 
1896  return 0;
1897 }
1898 
1906 static bool match_content_type(const struct Pattern *pat, struct Body *b)
1907 {
1908  if (!b)
1909  return false;
1910 
1911  char buf[256];
1912  snprintf(buf, sizeof(buf), "%s/%s", TYPE(b), b->subtype);
1913 
1914  if (patmatch(pat, buf))
1915  return true;
1916  if (match_content_type(pat, b->parts))
1917  return true;
1918  if (match_content_type(pat, b->next))
1919  return true;
1920  return false;
1921 }
1922 
1929 static bool match_update_dynamic_date(struct Pattern *pat)
1930 {
1931  struct Buffer *err = mutt_buffer_pool_get();
1932 
1933  bool rc = eval_date_minmax(pat, pat->p.str, err);
1935 
1936  return rc;
1937 }
1938 
1947 static bool match_mime_content_type(const struct Pattern *pat,
1948  struct Mailbox *m, struct Email *e)
1949 {
1951  return match_content_type(pat, e->content);
1952 }
1953 
1961 static void set_pattern_cache_value(int *cache_entry, int value)
1962 {
1963  *cache_entry = (value != 0) ? 2 : 1;
1964 }
1965 
1972 static int get_pattern_cache_value(int cache_entry)
1973 {
1974  return cache_entry == 2;
1975 }
1976 
1982 static int is_pattern_cache_set(int cache_entry)
1983 {
1984  return cache_entry != 0;
1985 }
1986 
2003  struct Mailbox *m, struct Email *e, struct PatternCache *cache)
2004 {
2005  switch (pat->op)
2006  {
2007  case MUTT_PAT_AND:
2008  return pat->pat_not ^ (perform_and(pat->child, flags, m, e, cache) > 0);
2009  case MUTT_PAT_OR:
2010  return pat->pat_not ^ (perform_or(pat->child, flags, m, e, cache) > 0);
2011  case MUTT_PAT_THREAD:
2012  return pat->pat_not ^
2013  match_threadcomplete(pat->child, flags, m, e->thread, 1, 1, 1, 1);
2014  case MUTT_PAT_PARENT:
2015  return pat->pat_not ^ match_threadparent(pat->child, flags, m, e->thread);
2016  case MUTT_PAT_CHILDREN:
2017  return pat->pat_not ^ match_threadchildren(pat->child, flags, m, e->thread);
2018  case MUTT_ALL:
2019  return !pat->pat_not;
2020  case MUTT_EXPIRED:
2021  return pat->pat_not ^ e->expired;
2022  case MUTT_SUPERSEDED:
2023  return pat->pat_not ^ e->superseded;
2024  case MUTT_FLAG:
2025  return pat->pat_not ^ e->flagged;
2026  case MUTT_TAG:
2027  return pat->pat_not ^ e->tagged;
2028  case MUTT_NEW:
2029  return pat->pat_not ? e->old || e->read : !(e->old || e->read);
2030  case MUTT_UNREAD:
2031  return pat->pat_not ? e->read : !e->read;
2032  case MUTT_REPLIED:
2033  return pat->pat_not ^ e->replied;
2034  case MUTT_OLD:
2035  return pat->pat_not ? (!e->old || e->read) : (e->old && !e->read);
2036  case MUTT_READ:
2037  return pat->pat_not ^ e->read;
2038  case MUTT_DELETED:
2039  return pat->pat_not ^ e->deleted;
2040  case MUTT_PAT_MESSAGE:
2041  return pat->pat_not ^ ((EMSG(e) >= pat->min) && (EMSG(e) <= pat->max));
2042  case MUTT_PAT_DATE:
2043  if (pat->dynamic)
2045  return pat->pat_not ^ (e->date_sent >= pat->min && e->date_sent <= pat->max);
2047  if (pat->dynamic)
2049  return pat->pat_not ^ (e->received >= pat->min && e->received <= pat->max);
2050  case MUTT_PAT_BODY:
2051  case MUTT_PAT_HEADER:
2052  case MUTT_PAT_WHOLE_MSG:
2053  /* m can be NULL in certain cases, such as when replying to a message
2054  * from the attachment menu and the user has a reply-hook using "~e" (bug
2055  * #2190).
2056  * This is also the case when message scoring. */
2057  if (!m)
2058  return 0;
2059 #ifdef USE_IMAP
2060  /* IMAP search sets e->matched at search compile time */
2061  if ((m->magic == MUTT_IMAP) && pat->string_match)
2062  return e->matched;
2063 #endif
2064  return pat->pat_not ^ msg_search(m, pat, e->msgno);
2065  case MUTT_PAT_SERVERSEARCH:
2066 #ifdef USE_IMAP
2067  if (!m)
2068  return 0;
2069  if (m->magic == MUTT_IMAP)
2070  {
2071  if (pat->string_match)
2072  return e->matched;
2073  return 0;
2074  }
2075  mutt_error(_("error: server custom search only supported with IMAP"));
2076  return 0;
2077 #else
2078  mutt_error(_("error: server custom search only supported with IMAP"));
2079  return -1;
2080 #endif
2081  case MUTT_PAT_SENDER:
2082  if (!e->env)
2083  return 0;
2084  return pat->pat_not ^ match_addrlist(pat, (flags & MUTT_MATCH_FULL_ADDRESS),
2085  1, &e->env->sender);
2086  case MUTT_PAT_FROM:
2087  if (!e->env)
2088  return 0;
2089  return pat->pat_not ^
2090  match_addrlist(pat, (flags & MUTT_MATCH_FULL_ADDRESS), 1, &e->env->from);
2091  case MUTT_PAT_TO:
2092  if (!e->env)
2093  return 0;
2094  return pat->pat_not ^
2095  match_addrlist(pat, (flags & MUTT_MATCH_FULL_ADDRESS), 1, &e->env->to);
2096  case MUTT_PAT_CC:
2097  if (!e->env)
2098  return 0;
2099  return pat->pat_not ^
2100  match_addrlist(pat, (flags & MUTT_MATCH_FULL_ADDRESS), 1, &e->env->cc);
2101  case MUTT_PAT_SUBJECT:
2102  if (!e->env)
2103  return 0;
2104  return pat->pat_not ^ (e->env->subject && patmatch(pat, e->env->subject));
2105  case MUTT_PAT_ID:
2106  case MUTT_PAT_ID_EXTERNAL:
2107  if (!e->env)
2108  return 0;
2109  return pat->pat_not ^ (e->env->message_id && patmatch(pat, e->env->message_id));
2110  case MUTT_PAT_SCORE:
2111  return pat->pat_not ^ (e->score >= pat->min &&
2112  (pat->max == MUTT_MAXRANGE || e->score <= pat->max));
2113  case MUTT_PAT_SIZE:
2114  return pat->pat_not ^
2115  (e->content->length >= pat->min &&
2116  (pat->max == MUTT_MAXRANGE || e->content->length <= pat->max));
2117  case MUTT_PAT_REFERENCE:
2118  if (!e->env)
2119  return 0;
2120  return pat->pat_not ^ (match_reference(pat, &e->env->references) ||
2121  match_reference(pat, &e->env->in_reply_to));
2122  case MUTT_PAT_ADDRESS:
2123  if (!e->env)
2124  return 0;
2125  return pat->pat_not ^ match_addrlist(pat, (flags & MUTT_MATCH_FULL_ADDRESS),
2126  4, &e->env->from, &e->env->sender,
2127  &e->env->to, &e->env->cc);
2128  case MUTT_PAT_RECIPIENT:
2129  if (!e->env)
2130  return 0;
2131  return pat->pat_not ^ match_addrlist(pat, (flags & MUTT_MATCH_FULL_ADDRESS),
2132  2, &e->env->to, &e->env->cc);
2133  case MUTT_PAT_LIST: /* known list, subscribed or not */
2134  {
2135  if (!e->env)
2136  return 0;
2137 
2138  int result;
2139  if (cache)
2140  {
2141  int *cache_entry = pat->all_addr ? &cache->list_all : &cache->list_one;
2142  if (!is_pattern_cache_set(*cache_entry))
2143  {
2144  set_pattern_cache_value(cache_entry,
2145  mutt_is_list_recipient(pat->all_addr, e->env));
2146  }
2147  result = get_pattern_cache_value(*cache_entry);
2148  }
2149  else
2150  result = mutt_is_list_recipient(pat->all_addr, e->env);
2151  return pat->pat_not ^ result;
2152  }
2154  {
2155  if (!e->env)
2156  return 0;
2157 
2158  int result;
2159  if (cache)
2160  {
2161  int *cache_entry = pat->all_addr ? &cache->sub_all : &cache->sub_one;
2162  if (!is_pattern_cache_set(*cache_entry))
2163  {
2165  cache_entry, mutt_is_subscribed_list_recipient(pat->all_addr, e->env));
2166  }
2167  result = get_pattern_cache_value(*cache_entry);
2168  }
2169  else
2170  result = mutt_is_subscribed_list_recipient(pat->all_addr, e->env);
2171  return pat->pat_not ^ result;
2172  }
2174  {
2175  if (!e->env)
2176  return 0;
2177 
2178  int result;
2179  if (cache)
2180  {
2181  int *cache_entry = pat->all_addr ? &cache->pers_recip_all : &cache->pers_recip_one;
2182  if (!is_pattern_cache_set(*cache_entry))
2183  {
2185  cache_entry, match_user(pat->all_addr, &e->env->to, &e->env->cc));
2186  }
2187  result = get_pattern_cache_value(*cache_entry);
2188  }
2189  else
2190  result = match_user(pat->all_addr, &e->env->to, &e->env->cc);
2191  return pat->pat_not ^ result;
2192  }
2194  {
2195  if (!e->env)
2196  return 0;
2197 
2198  int result;
2199  if (cache)
2200  {
2201  int *cache_entry = pat->all_addr ? &cache->pers_from_all : &cache->pers_from_one;
2202  if (!is_pattern_cache_set(*cache_entry))
2203  set_pattern_cache_value(cache_entry,
2204  match_user(pat->all_addr, &e->env->from, NULL));
2205  result = get_pattern_cache_value(*cache_entry);
2206  }
2207  else
2208  result = match_user(pat->all_addr, &e->env->from, NULL);
2209  return pat->pat_not ^ result;
2210  }
2211  case MUTT_PAT_COLLAPSED:
2212  return pat->pat_not ^ (e->collapsed && e->num_hidden > 1);
2213  case MUTT_PAT_CRYPT_SIGN:
2214  if (!WithCrypto)
2215  break;
2216  return pat->pat_not ^ ((e->security & SEC_SIGN) ? 1 : 0);
2218  if (!WithCrypto)
2219  break;
2220  return pat->pat_not ^ ((e->security & SEC_GOODSIGN) ? 1 : 0);
2222  if (!WithCrypto)
2223  break;
2224  return pat->pat_not ^ ((e->security & SEC_ENCRYPT) ? 1 : 0);
2225  case MUTT_PAT_PGP_KEY:
2226  if (!(WithCrypto & APPLICATION_PGP))
2227  break;
2228  return pat->pat_not ^ ((e->security & PGP_KEY) == PGP_KEY);
2229  case MUTT_PAT_XLABEL:
2230  if (!e->env)
2231  return 0;
2232  return pat->pat_not ^ (e->env->x_label && patmatch(pat, e->env->x_label));
2233  case MUTT_PAT_DRIVER_TAGS:
2234  {
2235  char *tags = driver_tags_get(&e->tags);
2236  bool rc = (pat->pat_not ^ (tags && patmatch(pat, tags)));
2237  FREE(&tags);
2238  return rc;
2239  }
2240  case MUTT_PAT_HORMEL:
2241  if (!e->env)
2242  return 0;
2243  return pat->pat_not ^ (e->env->spam.data && patmatch(pat, e->env->spam.data));
2244  case MUTT_PAT_DUPLICATED:
2245  return pat->pat_not ^ (e->thread && e->thread->duplicate_thread);
2246  case MUTT_PAT_MIMEATTACH:
2247  if (!m)
2248  return 0;
2249  {
2250  int count = mutt_count_body_parts(m, e);
2251  return pat->pat_not ^ (count >= pat->min &&
2252  (pat->max == MUTT_MAXRANGE || count <= pat->max));
2253  }
2254  case MUTT_PAT_MIMETYPE:
2255  if (!m)
2256  return 0;
2257  return pat->pat_not ^ match_mime_content_type(pat, m, e);
2258  case MUTT_PAT_UNREFERENCED:
2259  return pat->pat_not ^ (e->thread && !e->thread->child);
2260  case MUTT_PAT_BROKEN:
2261  return pat->pat_not ^ (e->thread && e->thread->fake_thread);
2262 #ifdef USE_NNTP
2263  case MUTT_PAT_NEWSGROUPS:
2264  if (!e->env)
2265  return 0;
2266  return pat->pat_not ^ (e->env->newsgroups && patmatch(pat, e->env->newsgroups));
2267 #endif
2268  }
2269  mutt_error(_("error: unknown op %d (report this error)"), pat->op);
2270  return -1;
2271 }
2272 
2278 static void quote_simple(const char *str, struct Buffer *buf)
2279 {
2280  mutt_buffer_reset(buf);
2281  mutt_buffer_addch(buf, '"');
2282  while (*str)
2283  {
2284  if ((*str == '\\') || (*str == '"'))
2285  mutt_buffer_addch(buf, '\\');
2286  mutt_buffer_addch(buf, *str++);
2287  }
2288  mutt_buffer_addch(buf, '"');
2289 }
2290 
2296 void mutt_check_simple(struct Buffer *buf, const char *simple)
2297 {
2298  bool do_simple = true;
2299 
2300  for (const char *p = mutt_b2s(buf); p && (p[0] != '\0'); p++)
2301  {
2302  if ((p[0] == '\\') && (p[1] != '\0'))
2303  p++;
2304  else if ((p[0] == '~') || (p[0] == '=') || (p[0] == '%'))
2305  {
2306  do_simple = false;
2307  break;
2308  }
2309  }
2310 
2311  /* XXX - is mutt_str_strcasecmp() right here, or should we use locale's
2312  * equivalences? */
2313 
2314  if (do_simple) /* yup, so spoof a real request */
2315  {
2316  /* convert old tokens into the new format */
2317  if ((mutt_str_strcasecmp("all", mutt_b2s(buf)) == 0) ||
2318  (mutt_str_strcmp("^", mutt_b2s(buf)) == 0) ||
2319  (mutt_str_strcmp(".", mutt_b2s(buf)) == 0)) /* ~A is more efficient */
2320  {
2321  mutt_buffer_strcpy(buf, "~A");
2322  }
2323  else if (mutt_str_strcasecmp("del", mutt_b2s(buf)) == 0)
2324  mutt_buffer_strcpy(buf, "~D");
2325  else if (mutt_str_strcasecmp("flag", mutt_b2s(buf)) == 0)
2326  mutt_buffer_strcpy(buf, "~F");
2327  else if (mutt_str_strcasecmp("new", mutt_b2s(buf)) == 0)
2328  mutt_buffer_strcpy(buf, "~N");
2329  else if (mutt_str_strcasecmp("old", mutt_b2s(buf)) == 0)
2330  mutt_buffer_strcpy(buf, "~O");
2331  else if (mutt_str_strcasecmp("repl", mutt_b2s(buf)) == 0)
2332  mutt_buffer_strcpy(buf, "~Q");
2333  else if (mutt_str_strcasecmp("read", mutt_b2s(buf)) == 0)
2334  mutt_buffer_strcpy(buf, "~R");
2335  else if (mutt_str_strcasecmp("tag", mutt_b2s(buf)) == 0)
2336  mutt_buffer_strcpy(buf, "~T");
2337  else if (mutt_str_strcasecmp("unread", mutt_b2s(buf)) == 0)
2338  mutt_buffer_strcpy(buf, "~U");
2339  else
2340  {
2341  struct Buffer *tmp = mutt_buffer_pool_get();
2342  quote_simple(mutt_b2s(buf), tmp);
2343  mutt_file_expand_fmt(buf, simple, mutt_b2s(tmp));
2345  }
2346  }
2347 }
2348 
2355 static struct MuttThread *top_of_thread(struct Email *e)
2356 {
2357  if (!e)
2358  return NULL;
2359 
2360  struct MuttThread *t = e->thread;
2361 
2362  while (t && t->parent)
2363  t = t->parent;
2364 
2365  return t;
2366 }
2367 
2375 {
2376  if (!e)
2377  return false;
2378 
2379  struct MuttThread *me = top_of_thread(e);
2380  if (!me)
2381  return false;
2382 
2383  Context->mailbox->vcount = 0;
2384  Context->vsize = 0;
2385  Context->collapsed = false;
2386 
2387  for (int i = 0; i < Context->mailbox->msg_count; i++)
2388  {
2389  Context->mailbox->emails[i]->vnum = -1;
2390  Context->mailbox->emails[i]->limited = false;
2391  Context->mailbox->emails[i]->collapsed = false;
2392  Context->mailbox->emails[i]->num_hidden = 0;
2393 
2394  if (top_of_thread(Context->mailbox->emails[i]) == me)
2395  {
2396  struct Body *body = Context->mailbox->emails[i]->content;
2397 
2399  Context->mailbox->emails[i]->limited = true;
2401  Context->mailbox->vcount++;
2402  Context->vsize += (body->length + body->offset - body->hdr_offset);
2403  }
2404  }
2405  return true;
2406 }
2407 
2415 int mutt_pattern_func(int op, char *prompt)
2416 {
2417  struct Buffer err;
2418  int rc = -1;
2419  struct Progress progress;
2420  struct Buffer *buf = mutt_buffer_pool_get();
2421 
2423  if (prompt || (op != MUTT_LIMIT))
2424  {
2425  if ((mutt_get_field(prompt, buf->data, buf->dsize, MUTT_PATTERN | MUTT_CLEAR) != 0) ||
2426  !(mutt_b2s(buf)[0]))
2427  {
2429  return -1;
2430  }
2431  }
2432  mutt_buffer_fix_dptr(buf);
2433 
2434  mutt_message(_("Compiling search pattern..."));
2435 
2436  char *simple = mutt_str_strdup(mutt_b2s(buf));
2438 
2439  mutt_buffer_init(&err);
2440  err.dsize = 256;
2441  err.data = mutt_mem_malloc(err.dsize);
2442  struct PatternList *pat = mutt_pattern_comp(buf->data, MUTT_PC_FULL_MSG, &err);
2443  if (!pat)
2444  {
2445  mutt_error("%s", err.data);
2446  goto bail;
2447  }
2448 
2449 #ifdef USE_IMAP
2450  if ((Context->mailbox->magic == MUTT_IMAP) && (imap_search(Context->mailbox, pat) < 0))
2451  goto bail;
2452 #endif
2453 
2455  &progress, _("Executing command on matching messages..."), MUTT_PROGRESS_READ,
2457 
2458  if (op == MUTT_LIMIT)
2459  {
2460  Context->mailbox->vcount = 0;
2461  Context->vsize = 0;
2462  Context->collapsed = false;
2463  int padding = mx_msg_padding_size(Context->mailbox);
2464 
2465  for (int i = 0; i < Context->mailbox->msg_count; i++)
2466  {
2467  mutt_progress_update(&progress, i, -1);
2468  /* new limit pattern implicitly uncollapses all threads */
2469  Context->mailbox->emails[i]->vnum = -1;
2470  Context->mailbox->emails[i]->limited = false;
2471  Context->mailbox->emails[i]->collapsed = false;
2472  Context->mailbox->emails[i]->num_hidden = 0;
2474  Context->mailbox, Context->mailbox->emails[i], NULL))
2475  {
2477  Context->mailbox->emails[i]->limited = true;
2479  Context->mailbox->vcount++;
2480  struct Body *b = Context->mailbox->emails[i]->content;
2481  Context->vsize += b->length + b->offset - b->hdr_offset + padding;
2482  }
2483  }
2484  }
2485  else
2486  {
2487  for (int i = 0; i < Context->mailbox->vcount; i++)
2488  {
2489  mutt_progress_update(&progress, i, -1);
2491  Context->mailbox->emails[Context->mailbox->v2r[i]], NULL))
2492  {
2493  switch (op)
2494  {
2495  case MUTT_UNDELETE:
2498  MUTT_PURGE, false);
2499  /* fallthrough */
2500  case MUTT_DELETE:
2503  MUTT_DELETE, (op == MUTT_DELETE));
2504  break;
2505  case MUTT_TAG:
2506  case MUTT_UNTAG:
2509  MUTT_TAG, (op == MUTT_TAG));
2510  break;
2511  }
2512  }
2513  }
2514  }
2515 
2516  mutt_clear_error();
2517 
2518  if (op == MUTT_LIMIT)
2519  {
2520  /* drop previous limit pattern */
2521  FREE(&Context->pattern);
2523 
2525  mutt_error(_("No messages matched criteria"));
2526 
2527  /* record new limit pattern, unless match all */
2528  const char *pbuf = buf->data;
2529  while (*pbuf == ' ')
2530  pbuf++;
2531  if (mutt_str_strcmp(pbuf, "~A") != 0)
2532  {
2533  Context->pattern = simple;
2534  simple = NULL; /* don't clobber it */
2536  }
2537  }
2538 
2539  rc = 0;
2540 
2541 bail:
2543  FREE(&simple);
2544  mutt_pattern_free(&pat);
2545  FREE(&err.data);
2546 
2547  return rc;
2548 }
2549 
2557 int mutt_search_command(int cur, int op)
2558 {
2559  struct Progress progress;
2560 
2561  if (!*LastSearch || ((op != OP_SEARCH_NEXT) && (op != OP_SEARCH_OPPOSITE)))
2562  {
2563  char buf[256];
2564  mutt_str_strfcpy(buf, (LastSearch[0] != '\0') ? LastSearch : "", sizeof(buf));
2565  if ((mutt_get_field(((op == OP_SEARCH) || (op == OP_SEARCH_NEXT)) ?
2566  _("Search for: ") :
2567  _("Reverse search for: "),
2568  buf, sizeof(buf), MUTT_CLEAR | MUTT_PATTERN) != 0) ||
2569  !buf[0])
2570  {
2571  return -1;
2572  }
2573 
2574  if ((op == OP_SEARCH) || (op == OP_SEARCH_NEXT))
2575  OptSearchReverse = false;
2576  else
2577  OptSearchReverse = true;
2578 
2579  /* compare the *expanded* version of the search pattern in case
2580  * $simple_search has changed while we were searching */
2581  struct Buffer *tmp = mutt_buffer_pool_get();
2582  mutt_buffer_strcpy(tmp, buf);
2584 
2585  if (!SearchPattern || (mutt_str_strcmp(mutt_b2s(tmp), LastSearchExpn) != 0))
2586  {
2587  struct Buffer err;
2588  mutt_buffer_init(&err);
2589  OptSearchInvalid = true;
2590  mutt_str_strfcpy(LastSearch, buf, sizeof(LastSearch));
2592  mutt_message(_("Compiling search pattern..."));
2594  err.dsize = 256;
2595  err.data = mutt_mem_malloc(err.dsize);
2597  if (!SearchPattern)
2598  {
2600  mutt_error("%s", err.data);
2601  FREE(&err.data);
2602  LastSearch[0] = '\0';
2603  LastSearchExpn[0] = '\0';
2604  return -1;
2605  }
2606  FREE(&err.data);
2607  mutt_clear_error();
2608  }
2609 
2611  }
2612 
2613  if (OptSearchInvalid)
2614  {
2615  for (int i = 0; i < Context->mailbox->msg_count; i++)
2616  Context->mailbox->emails[i]->searched = false;
2617 #ifdef USE_IMAP
2618  if ((Context->mailbox->magic == MUTT_IMAP) &&
2620  return -1;
2621 #endif
2622  OptSearchInvalid = false;
2623  }
2624 
2625  int incr = OptSearchReverse ? -1 : 1;
2626  if (op == OP_SEARCH_OPPOSITE)
2627  incr = -incr;
2628 
2629  mutt_progress_init(&progress, _("Searching..."), MUTT_PROGRESS_READ,
2630  Context->mailbox->vcount);
2631 
2632  for (int i = cur + incr, j = 0; j != Context->mailbox->vcount; j++)
2633  {
2634  const char *msg = NULL;
2635  mutt_progress_update(&progress, j, -1);
2636  if (i > Context->mailbox->vcount - 1)
2637  {
2638  i = 0;
2639  if (C_WrapSearch)
2640  msg = _("Search wrapped to top");
2641  else
2642  {
2643  mutt_message(_("Search hit bottom without finding match"));
2644  return -1;
2645  }
2646  }
2647  else if (i < 0)
2648  {
2649  i = Context->mailbox->vcount - 1;
2650  if (C_WrapSearch)
2651  msg = _("Search wrapped to bottom");
2652  else
2653  {
2654  mutt_message(_("Search hit top without finding match"));
2655  return -1;
2656  }
2657  }
2658 
2659  struct Email *e = Context->mailbox->emails[Context->mailbox->v2r[i]];
2660  if (e->searched)
2661  {
2662  /* if we've already evaluated this message, use the cached value */
2663  if (e->matched)
2664  {
2665  mutt_clear_error();
2666  if (msg && *msg)
2667  mutt_message(msg);
2668  return i;
2669  }
2670  }
2671  else
2672  {
2673  /* remember that we've already searched this message */
2674  e->searched = true;
2676  Context->mailbox, e, NULL);
2677  if (e->matched > 0)
2678  {
2679  mutt_clear_error();
2680  if (msg && *msg)
2681  mutt_message(msg);
2682  return i;
2683  }
2684  }
2685 
2686  if (SigInt)
2687  {
2688  mutt_error(_("Search interrupted"));
2689  SigInt = 0;
2690  return -1;
2691  }
2692 
2693  i += incr;
2694  }
2695 
2696  mutt_error(_("Not found"));
2697  return -1;
2698 }
pid_t mutt_create_filter(const char *s, FILE **fp_in, FILE **fp_out, FILE **fp_err)
Set up filter program.
Definition: filter.c:217
bool(* addr_predicate_t)(const struct Address *a)
typedef addr_predicate_t - Test an Address for some condition
Definition: pattern.c:1733
#define MUTT_PDR_ABSOLUTE
Absolute pattern range.
Definition: pattern.c:103
Pattern matches date received.
Definition: pattern.h:117
struct Email ** emails
Array of Emails.
Definition: mailbox.h:110
struct MuttThread * next
Next sibling Thread.
Definition: thread.h:47
struct PatternList * child
Arguments to logical operation.
Definition: pattern.h:62
Deleted messages.
Definition: mutt.h:107
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:194
Pattern matches email&#39;s header.
Definition: pattern.h:124
int mutt_pattern_func(int op, char *prompt)
Perform some Pattern matching.
Definition: pattern.c:2415
char * mutt_str_skip_whitespace(const char *p)
Find the first non-whitespace character in a string.
Definition: string.c:720
regex_t * regex
Compiled regex, for non-pattern matching.
Definition: pattern.h:64
Pattern matches MIME type.
Definition: pattern.h:146
pattern_eat_t * eat_arg
Callback function to parse the argument.
Definition: pattern.c:180
Pattern matches &#39;Date:&#39; field.
Definition: pattern.h:116
#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:102
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:69
#define MUTT_PDR_WINDOW
Extend the range in both directions using &#39;*&#39;.
Definition: pattern.c:102
int flags
Pattern flags, e.g. MUTT_PC_FULL_MSG.
Definition: pattern.c:179
bool group_match
Check a group of Addresses.
Definition: pattern.h:55
The envelope/body of an email.
Definition: email.h:39
#define MUTT_CLEAR
Clear input if printable character is pressed.
Definition: mutt.h:70
struct Buffer * mutt_buffer_pool_get(void)
Get a Buffer from the pool.
Definition: pool.c:100
static void adjust_date_range(struct tm *min, struct tm *max)
Put a date range in the correct order.
Definition: pattern.c:537
#define mutt_perror(...)
Definition: logging.h:85
regex_t cooked
Compiled form.
Definition: pattern.c:135
static bool match_reference(struct Pattern *pat, struct ListHead *refs)
Match references against a Pattern.
Definition: pattern.c:1717
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:1643
#define TAILQ_FOREACH(var, head, field)
Definition: queue.h:719
GUI miscellaneous curses (window drawing) routines.
Pattern matches newsgroup.
Definition: pattern.h:148
#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:91
struct tm mutt_date_localtime(time_t t)
Converts calendar time to a broken-down time structure expressed in user timezone.
Definition: date.c:736
Pattern matches email&#39;s score.
Definition: pattern.h:129
#define MUTT_PDR_NO_FLAGS
No flags are set.
Definition: pattern.c:99
Single symbol.
Definition: pattern.c:147
static void quote_simple(const char *str, struct Buffer *buf)
Apply simple quoting to a string.
Definition: pattern.c:2278
#define mutt_message(...)
Definition: logging.h:83
int pers_recip_all
^~p
Definition: pattern.h:91
void mutt_check_simple(struct Buffer *buf, const char *simple)
Convert a simple search into a real request.
Definition: pattern.c:2296
Range is valid.
Definition: pattern.c:121
struct MuttThread * thread
Thread of Emails.
Definition: email.h:96
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:1886
WHERE SIG_ATOMIC_VOLATILE_T SigInt
true after SIGINT is received
Definition: globals.h:84
static int match_addrlist(struct Pattern *pat, bool match_personal, int n,...)
Match a Pattern against and Address list.
Definition: pattern.c:1687
Pass files through external commands (filters)
Messages in limited view.
Definition: mutt.h:111
Message is part of a broken thread.
Definition: pattern.h:120
bool mutt_limit_current_thread(struct Email *e)
Limit the email view to the current thread.
Definition: pattern.c:2374
bool mutt_is_mail_list(const struct Address *addr)
Is this the email address of a mailing list?
Definition: hdrline.c:101
NeoMutt Logging.
void mutt_buffer_pool_release(struct Buffer **pbuf)
Free a Buffer from the pool.
Definition: pool.c:111
Pattern matches email&#39;s size.
Definition: pattern.h:130
struct Body * content
List of MIME parts.
Definition: email.h:92
#define SLIST_INIT(head)
Definition: queue.h:256
int imap_search(struct Mailbox *m, const struct PatternList *pat)
Find a matching mailbox.
Definition: imap.c:1350
#define MUTT_TOKEN_COMMENT
Don&#39;t reap comments.
Definition: mutt.h:84
#define RANGE_LT
Definition: pattern.c:112
String manipulation buffer.
Definition: buffer.h:33
Range contains syntax error.
Definition: pattern.c:122
void mutt_parse_mime_message(struct Mailbox *m, struct Email *e)
Parse a MIME email.
Definition: mutt_parse.c:48
Messages to be un-deleted.
Definition: mutt.h:105
Flagged messages.
Definition: mutt.h:108
#define MUTT_PDR_ERROR
Invalid pattern.
Definition: pattern.c:105
Pattern matches &#39;From:&#39; field.
Definition: pattern.h:115
#define RANGE_DOLLAR
Definition: pattern.c:111
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:290
#define MUTT_MAXRANGE
Definition: pattern.c:96
Email is from the user.
Definition: pattern.h:136
#define CH_FROM
Retain the "From " message separator?
Definition: copy.h:55
#define _(a)
Definition: message.h:28
static char LastSearch[256]
last pattern searched for
Definition: pattern.c:199
WHERE bool C_WrapSearch
Config: Wrap around when the search hits the end.
Definition: globals.h:272
Email is on mailing list.
Definition: pattern.h:133
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:36
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:1312
char * mailbox
Mailbox and host address.
Definition: address.h:37
Messages to be purged (bypass trash)
Definition: mutt.h:106
Range requires Context, but none available.
Definition: pattern.c:123
bool is_multi
Multiple case (only for ~I pattern now)
Definition: pattern.h:59
size_t mutt_str_strlen(const char *a)
Calculate the length of a string, safely.
Definition: string.c:666
Pattern matches a child email.
Definition: pattern.h:110
static int match_user(int all_addr, struct AddressList *al1, struct AddressList *al2)
Matches the user&#39;s email Address.
Definition: pattern.c:1793
bool searched
Email has been searched.
Definition: email.h:69
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:323
#define SEC_ENCRYPT
Email is encrypted.
Definition: ncrypt.h:122
bool expired
Already expired?
Definition: email.h:54
All user-callable functions.
EatRangeError
Error codes for eat_range_by_regex()
Definition: pattern.c:119
Pattern matches email&#39;s Message-Id.
Definition: pattern.h:121
FILE * fp_in
File to read from.
Definition: state.h:46
A progress bar.
Definition: progress.h:48
#define MUTT_PATTERN
Pattern mode - only used for history classes.
Definition: mutt.h:72
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:402
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:86
RangeType
Type of range.
Definition: pattern.c:141
Messages that have been replied to.
Definition: mutt.h:101
int vcount
The number of virtual messages.
Definition: mailbox.h:113
The body of an email.
Definition: body.h:34
A simple (non-regex) pattern.
Definition: pattern.h:49
static const char * get_offset(struct tm *tm, const char *s, int sign)
Calculate a symbolic offset.
Definition: pattern.c:349
int mutt_copy_header(FILE *fp_in, struct Email *e, FILE *fp_out, CopyHeaderFlags chflags, const char *prefix)
Copy Email header.
Definition: copy.c:391
int sub_all
^~u
Definition: pattern.h:89
Pattern matches message number.
Definition: pattern.h:128
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:1829
Email is on subscribed mailing list.
Definition: pattern.h:134
Email Address Handling.
#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:1982
#define mutt_array_size(x)
Definition: memory.h:33
bool is_alias
Is there an alias for this Address?
Definition: pattern.h:57
StateFlags flags
Flags, e.g. MUTT_DISPLAY.
Definition: state.h:49
int mx_msg_close(struct Mailbox *m, struct Message **msg)
Close a message.
Definition: mx.c:1087
bool crypt_valid_passphrase(SecurityFlags flags)
Check that we have a usable passphrase, ask if not.
Definition: crypt.c:147
#define MUTT_PDR_PLUS
Extend the range using &#39;+&#39;.
Definition: pattern.c:101
size_t dsize
Length of data.
Definition: buffer.h:37
All messages.
Definition: mutt.h:97
bool tagged
Email is tagged.
Definition: email.h:46
bool read
Email is read.
Definition: email.h:53
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:155
int op
Operation to perform, e.g. MUTT_PAT_SCORE.
Definition: pattern.c:178
#define RANGE_REL_RX
Definition: pattern.c:76
#define KILO
Definition: pattern.c:90
bool dynamic
Evaluate date ranges at run time.
Definition: pattern.h:58
struct ListHead in_reply_to
in-reply-to header content
Definition: envelope.h:82
char * message_id
Message ID.
Definition: envelope.h:69
bool all_addr
All Addresses in the list must match.
Definition: pattern.h:53
struct MuttThread * prev
Previous sibling Thread.
Definition: thread.h:48
struct Mailbox * mailbox
Definition: context.h:50
Log at debug level 2.
Definition: logging.h:57
API for mailboxes.
bool old
Email is seen, but unread.
Definition: email.h:52
#define SEC_GOODSIGN
Email has a valid signature.
Definition: ncrypt.h:124
struct AddressList from
Email&#39;s &#39;From&#39; list.
Definition: envelope.h:57
enum MailboxType magic
Mailbox type.
Definition: mailbox.h:116
static int scan_range_num(struct Buffer *s, regmatch_t pmatch[], int group, int kind)
Parse a number range.
Definition: pattern.c:847
Duplicate message.
Definition: pattern.h:118
static const struct PatternFlags * lookup_tag(char tag)
Lookup a pattern modifier.
Definition: pattern.c:1298
int mutt_file_fclose(FILE **fp)
Close a FILE handle (and NULL the pointer)
Definition: file.c:150
IMAP network mailbox.
struct Envelope * env
Envelope information.
Definition: email.h:91
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:40
bool pat_not
Pattern should be inverted (not)
Definition: pattern.h:52
bool string_match
Check a string for a match.
Definition: pattern.h:54
struct Menu * menu
Needed for pattern compilation.
Definition: context.h:46
struct Address * mutt_alias_reverse_lookup(const struct Address *a)
Does the user have an alias for the given address.
Definition: alias.c:542
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:55
bool limited
Is this message in a limited view?
Definition: email.h:76
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:762
#define RANGE_ABS_RX
Definition: pattern.c:80
static struct PatternList * mutt_pattern_node_new(void)
Create a new list containing a Pattern.
Definition: pattern.c:1370
void mutt_clear_error(void)
Clear the message line (bottom line of screen)
Definition: mutt_logging.c:109
Progress bar.
int min
Minimum for range checks.
Definition: pattern.h:60
bool ign_case
Ignore case for local string_match searches.
Definition: pattern.h:56
#define SLIST_NEXT(elm, field)
Definition: queue.h:270
struct TagList tags
For drivers that support server tagging.
Definition: email.h:108
size_t mutt_buffer_addstr(struct Buffer *buf, const char *s)
Add a string to a Buffer.
Definition: buffer.c:225
void mutt_buffer_fix_dptr(struct Buffer *buf)
Move the dptr to end of the Buffer.
Definition: buffer.c:181
int score
Message score.
Definition: email.h:90
char * subtype
content-type subtype
Definition: body.h:37
time_t date_sent
Time when the message was sent (UTC)
Definition: email.h:83
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:157
void mutt_str_remove_trailing_ws(char *s)
Trim trailing whitespace from a string.
Definition: string.c:734
Old messages.
Definition: mutt.h:100
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:2002
struct PatternList * mutt_pattern_comp(const char *s, PatternCompFlags flags, struct Buffer *err)
Create a Pattern.
Definition: pattern.c:1386
Greater-than range.
Definition: pattern.c:146
int pers_from_one
~P
Definition: pattern.h:94
Regular expression representing a range.
Definition: pattern.c:129
struct MuttThread * child
Child of this Thread.
Definition: thread.h:46
#define MUTT_PDR_MINUS
Pattern contains a range.
Definition: pattern.c:100
#define RANGE_RX_GROUPS
Definition: pattern.c:88
bool duplicate_thread
Duplicated Email in Thread.
Definition: thread.h:37
Prototypes for many functions.
Server-side pattern matches.
Definition: pattern.h:143
Pattern matches &#39;Cc:&#39; field.
Definition: pattern.h:112
const char * line
Definition: common.c:36
void mutt_buffer_strcpy(struct Buffer *buf, const char *s)
Copy a string into a Buffer.
Definition: buffer.c:310
#define SEC_SIGN
Email is signed.
Definition: ncrypt.h:123
int pers_recip_one
~p
Definition: pattern.h:92
static char LastSearchExpn[1024]
expanded version of LastSearch
Definition: pattern.c:200
Message is unreferenced in the thread.
Definition: pattern.h:119
#define SLIST_FIRST(head)
Definition: queue.h:229
A local copy of an email.
Definition: mx.h:81
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:104
A mailbox.
Definition: mailbox.h:92
Pattern matches keyword/label.
Definition: pattern.h:142
int mutt_extract_token(struct Buffer *dest, struct Buffer *tok, TokenFlags flags)
Extract one token from a string.
Definition: init.c:2649
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:2355
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:575
Either pattern can match.
Definition: pattern.h:107
char * driver_tags_get(struct TagList *list)
Get tags.
Definition: tags.c:141
size_t num_hidden
Number of hidden messages in this view.
Definition: email.h:77
#define PGP_KEY
Definition: ncrypt.h:143
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:1745
Pattern matches email&#39;s spam score.
Definition: pattern.h:125
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:1432
Superseded messages.
Definition: mutt.h:113
Tagged messages.
Definition: mutt.h:109
Message is encrypted.
Definition: pattern.h:140
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:114
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:750
bool C_ThoroughSearch
Config: Decode headers and messages before searching them.
Definition: pattern.c:70
int list_all
^~l
Definition: pattern.h:87
New messages.
Definition: mutt.h:99
Messages that have been read.
Definition: mutt.h:102
void mutt_pattern_free(struct PatternList **pat)
Free a Pattern.
Definition: pattern.c:1334
GUI present the user with a selectable list.
static int get_pattern_cache_value(int cache_entry)
Get pattern cache value.
Definition: pattern.c:1972
#define RANGE_GT
Definition: pattern.c:113
#define mutt_file_mkstemp()
Definition: file.h:105
Email is addressed to the user.
Definition: pattern.h:135
Message has PGP key.
Definition: pattern.h:141
#define RANGE_LT_RX
Definition: pattern.c:83
bool collapsed
Is this message part of a collapsed thread?
Definition: email.h:75
short op
Operation, e.g. MUTT_PAT_SCORE.
Definition: pattern.h:51
Pattern matches parent.
Definition: pattern.h:109
int vnum
Virtual message number.
Definition: email.h:89
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:1665
WHERE bool OptSearchInvalid
(pseudo) used to invalidate the search pat
Definition: options.h:50
API for encryption/signing of emails.
#define STAILQ_FOREACH(var, head, field)
Definition: queue.h:350
#define RANGE_BARE_RX
Definition: pattern.c:87
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:131
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:273
SecurityFlags security
bit 0-10: flags, bit 11,12: application, bit 13: traditional pgp See: ncrypt/ncrypt.h pgplib.h, smime.h
Definition: email.h:41
struct Email * message
Email this Thread refers to.
Definition: thread.h:49
#define MUTT_MATCH_FULL_ADDRESS
Match the full address.
Definition: pattern.h:75
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:205
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:259
#define MUTT_PDR_ERRORDONE
Definition: pattern.c:107
LOFF_T offset
Where in the stream does this message begin?
Definition: email.h:85
Expired messages.
Definition: mutt.h:112
#define APPLICATION_PGP
Use PGP to encrypt/sign.
Definition: ncrypt.h:134
WHERE char * C_SimpleSearch
Config: Pattern to search for when search doesn&#39;t contain ~&#39;s.
Definition: globals.h:145
int pers_from_all
^~P
Definition: pattern.h:93
An Email conversation.
Definition: thread.h:34
char * personal
Real name of address.
Definition: address.h:36
int * v2r
Mapping from virtual to real msgno.
Definition: mailbox.h:112
Unread messages.
Definition: mutt.h:103
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:177
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:980
static struct PatternList * SearchPattern
current search pattern
Definition: pattern.c:198
Pattern matches email&#39;s body.
Definition: pattern.h:123
static bool msg_search(struct Mailbox *m, struct Pattern *pat, int msgno)
Search an email.
Definition: pattern.c:1083
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:110
Log at debug level 1.
Definition: logging.h:56
bool flagged
Marked important?
Definition: email.h:45
char * newsgroups
List of newsgroups.
Definition: envelope.h:75
static void order_range(struct Pattern *pat)
Put a range in order.
Definition: pattern.c:910
char * mutt_str_strdup(const char *str)
Copy a string, safely.
Definition: string.c:380
#define EMSG(e)
Definition: pattern.c:92
bool collapsed
Are all threads collapsed?
Definition: context.h:48
char * str
String, if string_match is set.
Definition: pattern.h:66
Message is signed.
Definition: pattern.h:138
Range is invalid.
Definition: pattern.c:149
bool deleted
Email is deleted.
Definition: email.h:47
uint8_t PatternExecFlags
Flags for mutt_pattern_exec(), e.g. MUTT_MATCH_FULL_ADDRESS.
Definition: pattern.h:73
#define mutt_error(...)
Definition: logging.h:84
bool replied
Email has been replied to.
Definition: email.h:56
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:877
struct Group * group
Address group if group_match is set.
Definition: pattern.h:65
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:1867
int mutt_str_strcasecmp(const char *a, const char *b)
Compare two strings ignoring case, safely.
Definition: string.c:628
Thread is collapsed.
Definition: pattern.h:113
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:169
FILE * fp
pointer to the message data
Definition: mx.h:83
int mutt_is_subscribed_list_recipient(bool all_addr, struct Envelope *e)
Matches subscribed mailing lists.
Definition: pattern.c:1768
Pattern matches sender.
Definition: pattern.h:127
Both patterns must match.
Definition: pattern.h:106
#define FREE(x)
Definition: memory.h:40
#define MUTT_PDR_DONE
Pattern parse successfully.
Definition: pattern.c:104
bool mutt_addr_is_user(const struct Address *addr)
Does the address belong to the user.
Definition: alias.c:683
Message is crypographically verified.
Definition: pattern.h:139
int max
Maximum for range checks.
Definition: pattern.h:61
#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:131
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:1929
Pattern matches message tags.
Definition: pattern.h:144
#define SLIST_FOREACH(var, head, field)
Definition: queue.h:231
static int report_regerror(int regerr, regex_t *preg, struct Buffer *err)
Create a regex error message.
Definition: pattern.c:793
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:137
User is a recipient of the email.
Definition: pattern.h:132
struct ListHead multi_cases
Multiple strings for ~I pattern.
Definition: pattern.h:67
int mutt_body_handler(struct Body *b, struct State *s)
Handler for the Body of an email.
Definition: handler.c:1545
int lgrp
Paren group matching the left side.
Definition: pattern.c:132
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:1947
bool mutt_is_subscribed_list(const struct Address *addr)
Is this the email address of a user-subscribed mailing list?
Definition: hdrline.c:113
Messages to be un-tagged.
Definition: mutt.h:110
Pattern matches email thread.
Definition: pattern.h:108
Absolute range.
Definition: pattern.c:144
int mutt_count_body_parts(struct Mailbox *m, struct Email *e)
Count the MIME Body parts.
Definition: mutt_parse.c:214
Handling of global boolean variables.
#define mutt_debug(LEVEL,...)
Definition: logging.h:81
Mapping between user character and internal constant.
Definition: pattern.c:175
uint8_t PatternCompFlags
Flags for mutt_pattern_comp(), e.g. MUTT_PC_FULL_MSG.
Definition: pattern.h:41
uint16_t ParseDateRangeFlags
Flags for parse_date_range(), e.g. MUTT_PDR_MINUS.
Definition: pattern.c:98
int mx_msg_padding_size(struct Mailbox *m)
Bytes of padding between messages - Wrapper for MxOps::msg_padding_size.
Definition: mx.c:1462
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:111
static void set_pattern_cache_value(int *cache_entry, int value)
Sets a value in the PatternCache cache entry.
Definition: pattern.c:1961
Cache commonly-used patterns.
Definition: pattern.h:85
int rgrp
Paren group matching the right side.
Definition: pattern.c:133
Pattern matches raw email text.
Definition: pattern.h:126
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:703
WHERE char * C_ExternalSearchCommand
Config: External search command.
Definition: globals.h:115
void mutt_list_clear(struct ListHead *h)
Free a list, but NOT its strings.
Definition: list.c:168
FILE * mutt_file_fopen(const char *path, const char *mode)
Call fopen() safely.
Definition: file.c:583
int mutt_wait_filter(pid_t pid)
Wait for the exit of a process and return its status.
Definition: filter.c:228
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:1906
Match patterns to emails.
time_t mutt_date_make_time(struct tm *t, bool local)
Convert struct tm to time_t
Definition: date.c:224
bool mutt_buffer_is_empty(const struct Buffer *buf)
Is the Buffer empty?
Definition: buffer.c:252
Decide how to display email content.
#define CTX_MSGNO(ctx)
Definition: pattern.c:93
&#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:579
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:83
int mutt_search_command(int cur, int op)
Perform a search.
Definition: pattern.c:2557
#define WithCrypto
Definition: ncrypt.h:160
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:1024
int mutt_is_list_recipient(bool all_addr, struct Envelope *e)
Matches known mailing lists.
Definition: pattern.c:1780
Pattern matches number of attachments.
Definition: pattern.h:145
Convenience wrapper for the library headers.
int list_one
~l
Definition: pattern.h:88
static unsigned char * pbuf
Cache PGP data packet.
Definition: pgppacket.c:37
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:615
struct Message * mx_msg_open(struct Mailbox *m, int msgno)
return a stream pointer for a message
Definition: mx.c:1039
char * mutt_path_escape(const char *src)
Escapes single quotes in a path for a command string.
Definition: path.c:558
WHERE bool OptSearchReverse
(pseudo) used by ci_search_command
Definition: options.h:51
#define RANGE_GT_RX
Definition: pattern.c:84
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:927
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:451
time_t received
Time when the message was placed in the mailbox.
Definition: email.h:84
Message-Id is among results from an external query.
Definition: pattern.h:122
static bool patmatch(const struct Pattern *pat, const char *buf)
Compare a string to a Pattern.
Definition: pattern.c:1063
Less-than range.
Definition: pattern.c:145
The header of an Email.
Definition: envelope.h:54
bool ready
Compiled yet?
Definition: pattern.c:134
struct Buffer spam
Spam header.
Definition: envelope.h:80
bool matched
Search matches this Email.
Definition: email.h:70
int msgno
Number displayed to the user.
Definition: email.h:88
int sub_one
~u
Definition: pattern.h:90
char * mutt_rfc822_read_line(FILE *fp, char *line, size_t *linelen)
Read a header line from a file.
Definition: parse.c:1054
Relative range.
Definition: pattern.c:143
#define RANGE_DOT
Definition: pattern.c:109
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:812
#define SLIST_INSERT_HEAD(head, elm, field)
Definition: queue.h:265
Right side of range.
Definition: pattern.c:158