]> git.llucax.com Git - software/mutt-debian.git/blob - pattern.c
fix index weirdness if mailbox is emptied while inside the pager (Closes: 375530)
[software/mutt-debian.git] / pattern.c
1 /*
2  * Copyright (C) 1996-2000,2006-7 Michael R. Elkins <me@mutt.org>, and others
3  * 
4  *     This program is free software; you can redistribute it and/or modify
5  *     it under the terms of the GNU General Public License as published by
6  *     the Free Software Foundation; either version 2 of the License, or
7  *     (at your option) any later version.
8  * 
9  *     This program is distributed in the hope that it will be useful,
10  *     but WITHOUT ANY WARRANTY; without even the implied warranty of
11  *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  *     GNU General Public License for more details.
13  * 
14  *     You should have received a copy of the GNU General Public License
15  *     along with this program; if not, write to the Free Software
16  *     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
17  */ 
18
19 #if HAVE_CONFIG_H
20 # include "config.h"
21 #endif
22
23 #include "mutt.h"
24 #include "mapping.h"
25 #include "keymap.h"
26 #include "mailbox.h"
27 #include "copy.h"
28
29 #include <string.h>
30 #include <stdlib.h>
31 #include <ctype.h>
32 #include <sys/stat.h>
33 #include <unistd.h>
34 #include <stdarg.h>
35
36 #include "mutt_crypt.h"
37 #include "mutt_curses.h"
38
39 #ifdef USE_IMAP
40 #include "mx.h"
41 #include "imap/imap.h"
42 #endif
43
44 static int eat_regexp (pattern_t *pat, BUFFER *, BUFFER *);
45 static int eat_date (pattern_t *pat, BUFFER *, BUFFER *);
46 static int eat_range (pattern_t *pat, BUFFER *, BUFFER *);
47 static int patmatch (const pattern_t *pat, const char *buf);
48
49 static struct pattern_flags
50 {
51   int tag;      /* character used to represent this op */
52   int op;       /* operation to perform */
53   int class;
54   int (*eat_arg) (pattern_t *, BUFFER *, BUFFER *);
55 }
56 Flags[] =
57 {
58   { 'A', M_ALL,                 0,              NULL },
59   { 'b', M_BODY,                M_FULL_MSG,     eat_regexp },
60   { 'B', M_WHOLE_MSG,           M_FULL_MSG,     eat_regexp },
61   { 'c', M_CC,                  0,              eat_regexp },
62   { 'C', M_RECIPIENT,           0,              eat_regexp },
63   { 'd', M_DATE,                0,              eat_date },
64   { 'D', M_DELETED,             0,              NULL },
65   { 'e', M_SENDER,              0,              eat_regexp },
66   { 'E', M_EXPIRED,             0,              NULL },
67   { 'f', M_FROM,                0,              eat_regexp },
68   { 'F', M_FLAG,                0,              NULL },
69   { 'g', M_CRYPT_SIGN,          0,              NULL },
70   { 'G', M_CRYPT_ENCRYPT,       0,              NULL },
71   { 'h', M_HEADER,              M_FULL_MSG,     eat_regexp },
72   { 'H', M_HORMEL,              0,              eat_regexp },
73   { 'i', M_ID,                  0,              eat_regexp },
74   { 'k', M_PGP_KEY,             0,              NULL },
75   { 'l', M_LIST,                0,              NULL },
76   { 'L', M_ADDRESS,             0,              eat_regexp },
77   { 'm', M_MESSAGE,             0,              eat_range },
78   { 'n', M_SCORE,               0,              eat_range },
79   { 'N', M_NEW,                 0,              NULL },
80   { 'O', M_OLD,                 0,              NULL },
81   { 'p', M_PERSONAL_RECIP,      0,              NULL },
82   { 'P', M_PERSONAL_FROM,       0,              NULL },
83   { 'Q', M_REPLIED,             0,              NULL },
84   { 'r', M_DATE_RECEIVED,       0,              eat_date },
85   { 'R', M_READ,                0,              NULL },
86   { 's', M_SUBJECT,             0,              eat_regexp },
87   { 'S', M_SUPERSEDED,          0,              NULL },
88   { 't', M_TO,                  0,              eat_regexp },
89   { 'T', M_TAG,                 0,              NULL },
90   { 'u', M_SUBSCRIBED_LIST,     0,              NULL },
91   { 'U', M_UNREAD,              0,              NULL },
92   { 'v', M_COLLAPSED,           0,              NULL },
93   { 'V', M_CRYPT_VERIFIED,      0,              NULL },
94   { 'x', M_REFERENCE,           0,              eat_regexp },
95   { 'X', M_MIMEATTACH,          0,              eat_range },
96   { 'y', M_XLABEL,              0,              eat_regexp },
97   { 'z', M_SIZE,                0,              eat_range },
98   { '=', M_DUPLICATED,          0,              NULL },
99   { '$', M_UNREFERENCED,        0,              NULL },
100   { 0 }
101 };
102
103 static pattern_t *SearchPattern = NULL; /* current search pattern */
104 static char LastSearch[STRING] = { 0 }; /* last pattern searched for */
105 static char LastSearchExpn[LONG_STRING] = { 0 }; /* expanded version of
106                                                     LastSearch */
107
108 #define M_MAXRANGE -1
109
110 /* constants for parse_date_range() */
111 #define M_PDR_NONE      0x0000
112 #define M_PDR_MINUS     0x0001
113 #define M_PDR_PLUS      0x0002
114 #define M_PDR_WINDOW    0x0004
115 #define M_PDR_ABSOLUTE  0x0008
116 #define M_PDR_DONE      0x0010
117 #define M_PDR_ERROR     0x0100
118 #define M_PDR_ERRORDONE (M_PDR_ERROR | M_PDR_DONE)
119
120
121 /* if no uppercase letters are given, do a case-insensitive search */
122 int mutt_which_case (const char *s)
123 {
124   wchar_t w;
125   mbstate_t mb;
126   size_t l;
127   
128   memset (&mb, 0, sizeof (mb));
129   
130   for (; (l = mbrtowc (&w, s, MB_CUR_MAX, &mb)) != 0; s += l)
131   {
132     if (l == (size_t) -2)
133       continue; /* shift sequences */
134     if (l == (size_t) -1)
135       return 0; /* error; assume case-sensitive */
136     if (iswalpha ((wint_t) w) && iswupper ((wint_t) w))
137       return 0; /* case-sensitive */
138   }
139
140   return REG_ICASE; /* case-insensitive */
141 }
142
143 static int
144 msg_search (CONTEXT *ctx, pattern_t* pat, int msgno)
145 {
146   char tempfile[_POSIX_PATH_MAX];
147   MESSAGE *msg = NULL;
148   STATE s;
149   struct stat st;
150   FILE *fp = NULL;
151   long lng = 0;
152   int match = 0;
153   HEADER *h = ctx->hdrs[msgno];
154   char *buf;
155   size_t blen;
156
157   if ((msg = mx_open_message (ctx, msgno)) != NULL)
158   {
159     if (option (OPTTHOROUGHSRC))
160     {
161       /* decode the header / body */
162       memset (&s, 0, sizeof (s));
163       s.fpin = msg->fp;
164       s.flags = M_CHARCONV;
165       mutt_mktemp (tempfile);
166       if ((s.fpout = safe_fopen (tempfile, "w+")) == NULL)
167       {
168         mutt_perror (tempfile);
169         return (0);
170       }
171
172       if (pat->op != M_BODY)
173         mutt_copy_header (msg->fp, h, s.fpout, CH_FROM | CH_DECODE, NULL);
174
175       if (pat->op != M_HEADER)
176       {
177         mutt_parse_mime_message (ctx, h);
178
179         if (WithCrypto && (h->security & ENCRYPT)
180             && !crypt_valid_passphrase(h->security))
181         {
182           mx_close_message (&msg);
183           if (s.fpout)
184           {
185             fclose (s.fpout);
186             unlink (tempfile);
187           }
188           return (0);
189         }
190
191         fseeko (msg->fp, h->offset, 0);
192         mutt_body_handler (h->content, &s);
193       }
194
195       fp = s.fpout;
196       fflush (fp);
197       fseek (fp, 0, 0);
198       fstat (fileno (fp), &st);
199       lng = (long) st.st_size;
200     }
201     else
202     {
203       /* raw header / body */
204       fp = msg->fp;
205       if (pat->op != M_BODY)
206       {
207         fseeko (fp, h->offset, 0);
208         lng = h->content->offset - h->offset;
209       }
210       if (pat->op != M_HEADER)
211       {
212         if (pat->op == M_BODY)
213           fseeko (fp, h->content->offset, 0);
214         lng += h->content->length;
215       }
216     }
217
218     blen = STRING;
219     buf = safe_malloc (blen);
220
221     /* search the file "fp" */
222     while (lng > 0)
223     {
224       if (pat->op == M_HEADER)
225       {
226         if (*(buf = mutt_read_rfc822_line (fp, buf, &blen)) == '\0')
227           break;
228       }
229       else if (fgets (buf, blen - 1, fp) == NULL)
230         break; /* don't loop forever */
231       if (patmatch (pat, buf) == 0)
232       {
233         match = 1;
234         break;
235       }
236       lng -= mutt_strlen (buf);
237     }
238
239     FREE (&buf);
240     
241     mx_close_message (&msg);
242
243     if (option (OPTTHOROUGHSRC))
244     {
245       fclose (fp);
246       unlink (tempfile);
247     }
248   }
249
250   return match;
251 }
252
253 static int eat_regexp (pattern_t *pat, BUFFER *s, BUFFER *err)
254 {
255   BUFFER buf;
256   int r;
257
258   memset (&buf, 0, sizeof (buf));
259   if (mutt_extract_token (&buf, s, M_TOKEN_PATTERN | M_TOKEN_COMMENT) != 0 ||
260       !buf.data)
261   {
262     snprintf (err->data, err->dsize, _("Error in expression: %s"), s->dptr);
263     return (-1);
264   }
265   if (!*buf.data)
266   {
267     snprintf (err->data, err->dsize, _("Empty expression"));
268     return (-1);
269   }
270
271 #if 0
272   /* If there are no RE metacharacters, use simple search anyway */
273   if (!pat->stringmatch && !strpbrk (buf.data, "|[{.*+?^$"))
274     pat->stringmatch = 1;
275 #endif
276
277   if (pat->stringmatch)
278   {
279     pat->p.str = safe_strdup (buf.data);
280     FREE (&buf.data);
281   }
282   else if (pat->groupmatch)
283   {
284     pat->p.g = mutt_pattern_group (buf.data);
285     FREE (&buf.data);
286   }
287   else
288   {
289     pat->p.rx = safe_malloc (sizeof (regex_t));
290     r = REGCOMP (pat->p.rx, buf.data, REG_NEWLINE | REG_NOSUB | mutt_which_case (buf.data));
291     FREE (&buf.data);
292     if (r)
293     {
294       regerror (r, pat->p.rx, err->data, err->dsize);
295       regfree (pat->p.rx);
296       FREE (&pat->p.rx);
297       return (-1);
298     }
299   }
300
301   return 0;
302 }
303
304 int eat_range (pattern_t *pat, BUFFER *s, BUFFER *err)
305 {
306   char *tmp;
307   int do_exclusive = 0;
308   int skip_quote = 0;
309   
310   /*
311    * If simple_search is set to "~m %s", the range will have double quotes 
312    * around it...
313    */
314   if (*s->dptr == '"')
315   {
316     s->dptr++;
317     skip_quote = 1;
318   }
319   if (*s->dptr == '<')
320     do_exclusive = 1;
321   if ((*s->dptr != '-') && (*s->dptr != '<'))
322   {
323     /* range minimum */
324     if (*s->dptr == '>')
325     {
326       pat->max = M_MAXRANGE;
327       pat->min = strtol (s->dptr + 1, &tmp, 0) + 1; /* exclusive range */
328     }
329     else
330       pat->min = strtol (s->dptr, &tmp, 0);
331     if (toupper ((unsigned char) *tmp) == 'K') /* is there a prefix? */
332     {
333       pat->min *= 1024;
334       tmp++;
335     }
336     else if (toupper ((unsigned char) *tmp) == 'M')
337     {
338       pat->min *= 1048576;
339       tmp++;
340     }
341     if (*s->dptr == '>')
342     {
343       s->dptr = tmp;
344       return 0;
345     }
346     if (*tmp != '-')
347     {
348       /* exact value */
349       pat->max = pat->min;
350       s->dptr = tmp;
351       return 0;
352     }
353     tmp++;
354   }
355   else
356   {
357     s->dptr++;
358     tmp = s->dptr;
359   }
360   
361   if (isdigit ((unsigned char) *tmp))
362   {
363     /* range maximum */
364     pat->max = strtol (tmp, &tmp, 0);
365     if (toupper ((unsigned char) *tmp) == 'K')
366     {
367       pat->max *= 1024;
368       tmp++;
369     }
370     else if (toupper ((unsigned char) *tmp) == 'M')
371     {
372       pat->max *= 1048576;
373       tmp++;
374     }
375     if (do_exclusive)
376       (pat->max)--;
377   }
378   else
379     pat->max = M_MAXRANGE;
380
381   if (skip_quote && *tmp == '"')
382     tmp++;
383
384   SKIPWS (tmp);
385   s->dptr = tmp;
386   return 0;
387 }
388
389 static const char *getDate (const char *s, struct tm *t, BUFFER *err)
390 {
391   char *p;
392   time_t now = time (NULL);
393   struct tm *tm = localtime (&now);
394
395   t->tm_mday = strtol (s, &p, 10);
396   if (t->tm_mday < 1 || t->tm_mday > 31)
397   {
398     snprintf (err->data, err->dsize, _("Invalid day of month: %s"), s);
399     return NULL;
400   }
401   if (*p != '/')
402   {
403     /* fill in today's month and year */
404     t->tm_mon = tm->tm_mon;
405     t->tm_year = tm->tm_year;
406     return p;
407   }
408   p++;
409   t->tm_mon = strtol (p, &p, 10) - 1;
410   if (t->tm_mon < 0 || t->tm_mon > 11)
411   {
412     snprintf (err->data, err->dsize, _("Invalid month: %s"), p);
413     return NULL;
414   }
415   if (*p != '/')
416   {
417     t->tm_year = tm->tm_year;
418     return p;
419   }
420   p++;
421   t->tm_year = strtol (p, &p, 10);
422   if (t->tm_year < 70) /* year 2000+ */
423     t->tm_year += 100;
424   else if (t->tm_year > 1900)
425     t->tm_year -= 1900;
426   return p;
427 }
428
429 /* Ny   years
430    Nm   months
431    Nw   weeks
432    Nd   days */
433 static const char *get_offset (struct tm *tm, const char *s, int sign)
434 {
435   char *ps;
436   int offset = strtol (s, &ps, 0);
437   if ((sign < 0 && offset > 0) || (sign > 0 && offset < 0))
438     offset = -offset;
439
440   switch (*ps)
441   {
442     case 'y':
443       tm->tm_year += offset;
444       break;
445     case 'm':
446       tm->tm_mon += offset;
447       break;
448     case 'w':
449       tm->tm_mday += 7 * offset;
450       break;
451     case 'd':
452       tm->tm_mday += offset;
453       break;
454     default:
455       return s;
456   }
457   mutt_normalize_time (tm);
458   return (ps + 1);
459 }
460
461 static void adjust_date_range (struct tm *min, struct tm *max)
462 {
463   if (min->tm_year > max->tm_year
464       || (min->tm_year == max->tm_year && min->tm_mon > max->tm_mon)
465       || (min->tm_year == max->tm_year && min->tm_mon == max->tm_mon
466         && min->tm_mday > max->tm_mday))
467   {
468     int tmp;
469     
470     tmp = min->tm_year;
471     min->tm_year = max->tm_year;
472     max->tm_year = tmp;
473       
474     tmp = min->tm_mon;
475     min->tm_mon = max->tm_mon;
476     max->tm_mon = tmp;
477       
478     tmp = min->tm_mday;
479     min->tm_mday = max->tm_mday;
480     max->tm_mday = tmp;
481     
482     min->tm_hour = min->tm_min = min->tm_sec = 0;
483     max->tm_hour = 23;
484     max->tm_min = max->tm_sec = 59;
485   }
486 }
487
488 static const char * parse_date_range (const char* pc, struct tm *min,
489     struct tm *max, int haveMin, struct tm *baseMin, BUFFER *err)
490 {
491   int flag = M_PDR_NONE;        
492   while (*pc && ((flag & M_PDR_DONE) == 0))
493   {
494     const char *pt;
495     char ch = *pc++;
496     SKIPWS (pc);
497     switch (ch)
498     {
499       case '-':
500       {
501         /* try a range of absolute date minus offset of Ndwmy */
502         pt = get_offset (min, pc, -1);
503         if (pc == pt)
504         {
505           if (flag == M_PDR_NONE)
506           { /* nothing yet and no offset parsed => absolute date? */
507             if (!getDate (pc, max, err))
508               flag |= (M_PDR_ABSOLUTE | M_PDR_ERRORDONE);  /* done bad */
509             else
510             {
511               /* reestablish initial base minimum if not specified */
512               if (!haveMin)
513                 memcpy (min, baseMin, sizeof(struct tm));
514               flag |= (M_PDR_ABSOLUTE | M_PDR_DONE);  /* done good */
515             }
516           }
517           else
518             flag |= M_PDR_ERRORDONE;
519         }
520         else
521         {
522           pc = pt;
523           if (flag == M_PDR_NONE && !haveMin)
524           { /* the very first "-3d" without a previous absolute date */
525             max->tm_year = min->tm_year;
526             max->tm_mon = min->tm_mon;
527             max->tm_mday = min->tm_mday;
528           }
529           flag |= M_PDR_MINUS;
530         }
531       }
532       break;
533       case '+':
534       { /* enlarge plusRange */
535         pt = get_offset (max, pc, 1);
536         if (pc == pt)
537           flag |= M_PDR_ERRORDONE;
538         else
539         {
540           pc = pt;
541           flag |= M_PDR_PLUS;
542         }
543       }
544       break;
545       case '*':
546       { /* enlarge window in both directions */
547         pt = get_offset (min, pc, -1);
548         if (pc == pt)
549           flag |= M_PDR_ERRORDONE;
550         else
551         {
552           pc = get_offset (max, pc, 1);
553           flag |= M_PDR_WINDOW;
554         }
555       }
556       break;
557       default:
558         flag |= M_PDR_ERRORDONE;
559     }
560     SKIPWS (pc);
561   }
562   if ((flag & M_PDR_ERROR) && !(flag & M_PDR_ABSOLUTE))
563   { /* getDate has its own error message, don't overwrite it here */
564     snprintf (err->data, err->dsize, _("Invalid relative date: %s"), pc-1);
565   }
566   return ((flag & M_PDR_ERROR) ? NULL : pc);
567 }
568
569 static int eat_date (pattern_t *pat, BUFFER *s, BUFFER *err)
570 {
571   BUFFER buffer;
572   struct tm min, max;
573
574   memset (&buffer, 0, sizeof (buffer));
575   if (mutt_extract_token (&buffer, s, M_TOKEN_COMMENT | M_TOKEN_PATTERN) != 0
576       || !buffer.data)
577   {
578     strfcpy (err->data, _("error in expression"), err->dsize);
579     return (-1);
580   }
581
582   memset (&min, 0, sizeof (min));
583   /* the `0' time is Jan 1, 1970 UTC, so in order to prevent a negative time
584      when doing timezone conversion, we use Jan 2, 1970 UTC as the base
585      here */
586   min.tm_mday = 2;
587   min.tm_year = 70;
588
589   memset (&max, 0, sizeof (max));
590
591   /* Arbitrary year in the future.  Don't set this too high
592      or mutt_mktime() returns something larger than will
593      fit in a time_t on some systems */
594   max.tm_year = 130;
595   max.tm_mon = 11;
596   max.tm_mday = 31;
597   max.tm_hour = 23;
598   max.tm_min = 59;
599   max.tm_sec = 59;
600
601   if (strchr ("<>=", buffer.data[0]))
602   {
603     /* offset from current time
604        <3d      less than three days ago
605        >3d      more than three days ago
606        =3d      exactly three days ago */
607     time_t now = time (NULL);
608     struct tm *tm = localtime (&now);
609     int exact = 0;
610
611     if (buffer.data[0] == '<')
612     {
613       memcpy (&min, tm, sizeof (min));
614       tm = &min;
615     }
616     else
617     {
618       memcpy (&max, tm, sizeof (max));
619       tm = &max;
620
621       if (buffer.data[0] == '=')
622         exact++;
623     }
624     tm->tm_hour = 23;
625     tm->tm_min = tm->tm_sec = 59;
626
627     /* force negative offset */
628     get_offset (tm, buffer.data + 1, -1);
629
630     if (exact)
631     {
632       /* start at the beginning of the day in question */
633       memcpy (&min, &max, sizeof (max));
634       min.tm_hour = min.tm_sec = min.tm_min = 0;
635     }
636   }
637   else
638   {
639     const char *pc = buffer.data;
640
641     int haveMin = FALSE;
642     int untilNow = FALSE;
643     if (isdigit ((unsigned char)*pc))
644     {
645       /* mininum date specified */
646       if ((pc = getDate (pc, &min, err)) == NULL)
647       {
648         FREE (&buffer.data);
649         return (-1);
650       }
651       haveMin = TRUE;
652       SKIPWS (pc);
653       if (*pc == '-')
654       {
655         const char *pt = pc + 1;
656         SKIPWS (pt);
657         untilNow = (*pt == '\0');
658       }
659     }
660
661     if (!untilNow)
662     { /* max date or relative range/window */
663
664       struct tm baseMin;
665
666       if (!haveMin)
667       { /* save base minimum and set current date, e.g. for "-3d+1d" */
668         time_t now = time (NULL);
669         struct tm *tm = localtime (&now);
670         memcpy (&baseMin, &min, sizeof(baseMin));
671         memcpy (&min, tm, sizeof (min));
672         min.tm_hour = min.tm_sec = min.tm_min = 0;
673       }
674       
675       /* preset max date for relative offsets,
676          if nothing follows we search for messages on a specific day */
677       max.tm_year = min.tm_year;
678       max.tm_mon = min.tm_mon;
679       max.tm_mday = min.tm_mday;
680
681       if (!parse_date_range (pc, &min, &max, haveMin, &baseMin, err))
682       { /* bail out on any parsing error */
683         FREE (&buffer.data);
684         return (-1);
685       }
686     }
687   }
688
689   /* Since we allow two dates to be specified we'll have to adjust that. */
690   adjust_date_range (&min, &max);
691
692   pat->min = mutt_mktime (&min, 1);
693   pat->max = mutt_mktime (&max, 1);
694
695   FREE (&buffer.data);
696
697   return 0;
698 }
699
700 static int patmatch (const pattern_t* pat, const char* buf)
701 {
702   if (pat->stringmatch)
703     return !strstr (buf, pat->p.str);
704   else if (pat->groupmatch)
705     return !mutt_group_match (pat->p.g, buf);
706   else
707     return regexec (pat->p.rx, buf, 0, NULL, 0);
708 }
709
710 static struct pattern_flags *lookup_tag (char tag)
711 {
712   int i;
713
714   for (i = 0; Flags[i].tag; i++)
715     if (Flags[i].tag == tag)
716       return (&Flags[i]);
717   return NULL;
718 }
719
720 static /* const */ char *find_matching_paren (/* const */ char *s)
721 {
722   int level = 1;
723
724   for (; *s; s++)
725   {
726     if (*s == '(')
727       level++;
728     else if (*s == ')')
729     {
730       level--;
731       if (!level)
732         break;
733     }
734   }
735   return s;
736 }
737
738 void mutt_pattern_free (pattern_t **pat)
739 {
740   pattern_t *tmp;
741
742   while (*pat)
743   {
744     tmp = *pat;
745     *pat = (*pat)->next;
746
747     if (tmp->stringmatch)
748       FREE (&tmp->p.str);
749     else if (tmp->groupmatch)
750       tmp->p.g = NULL;
751     else if (tmp->p.rx)
752     {
753       regfree (tmp->p.rx);
754       FREE (&tmp->p.rx);
755     }
756
757     if (tmp->child)
758       mutt_pattern_free (&tmp->child);
759     FREE (&tmp);
760   }
761 }
762
763 pattern_t *mutt_pattern_comp (/* const */ char *s, int flags, BUFFER *err)
764 {
765   pattern_t *curlist = NULL;
766   pattern_t *tmp, *tmp2;
767   pattern_t *last = NULL;
768   int not = 0;
769   int alladdr = 0;
770   int or = 0;
771   int implicit = 1;     /* used to detect logical AND operator */
772   struct pattern_flags *entry;
773   char *p;
774   char *buf;
775   BUFFER ps;
776
777   memset (&ps, 0, sizeof (ps));
778   ps.dptr = s;
779   ps.dsize = mutt_strlen (s);
780
781   while (*ps.dptr)
782   {
783     SKIPWS (ps.dptr);
784     switch (*ps.dptr)
785     {
786       case '^':
787         ps.dptr++;
788         alladdr = !alladdr;
789         break;
790       case '!':
791         ps.dptr++;
792         not = !not;
793         break;
794       case '|':
795         if (!or)
796         {
797           if (!curlist)
798           {
799             snprintf (err->data, err->dsize, _("error in pattern at: %s"), ps.dptr);
800             return NULL;
801           }
802           if (curlist->next)
803           {
804             /* A & B | C == (A & B) | C */
805             tmp = new_pattern ();
806             tmp->op = M_AND;
807             tmp->child = curlist;
808
809             curlist = tmp;
810             last = curlist;
811           }
812
813           or = 1;
814         }
815         ps.dptr++;
816         implicit = 0;
817         not = 0;
818         alladdr = 0;
819         break;
820       case '%':
821       case '=':
822       case '~':
823         if (*(ps.dptr + 1) == '(') 
824         {
825           ps.dptr ++; /* skip ~ */
826           p = find_matching_paren (ps.dptr + 1);
827           if (*p != ')')
828           {
829             snprintf (err->data, err->dsize, _("mismatched brackets: %s"), ps.dptr);
830             mutt_pattern_free (&curlist);
831             return NULL;
832           }
833           tmp = new_pattern ();
834           tmp->op = M_THREAD;
835           if (last)
836             last->next = tmp;
837           else
838             curlist = tmp;
839           last = tmp;
840           tmp->not ^= not;
841           tmp->alladdr |= alladdr;
842           not = 0;
843           alladdr = 0;
844           /* compile the sub-expression */
845           buf = mutt_substrdup (ps.dptr + 1, p);
846           if ((tmp2 = mutt_pattern_comp (buf, flags, err)) == NULL)
847           {
848             FREE (&buf);
849             mutt_pattern_free (&curlist);
850             return NULL;
851           }
852           FREE (&buf);
853           tmp->child = tmp2;
854           ps.dptr = p + 1; /* restore location */
855           break;
856         }
857         if (implicit && or)
858         {
859           /* A | B & C == (A | B) & C */
860           tmp = new_pattern ();
861           tmp->op = M_OR;
862           tmp->child = curlist;
863           curlist = tmp;
864           last = tmp;
865           or = 0;
866         }
867
868         tmp = new_pattern ();
869         tmp->not = not;
870         tmp->alladdr = alladdr;
871         tmp->stringmatch = (*ps.dptr == '=') ? 1 : 0;
872         tmp->groupmatch  = (*ps.dptr == '%') ? 1 : 0;
873         not = 0;
874         alladdr = 0;
875
876         if (last)
877           last->next = tmp;
878         else
879           curlist = tmp;
880         last = tmp;
881
882         ps.dptr++; /* move past the ~ */
883         if ((entry = lookup_tag (*ps.dptr)) == NULL)
884         {
885           snprintf (err->data, err->dsize, _("%c: invalid pattern modifier"), *ps.dptr);
886           mutt_pattern_free (&curlist);
887           return NULL;
888         }
889         if (entry->class && (flags & entry->class) == 0)
890         {
891           snprintf (err->data, err->dsize, _("%c: not supported in this mode"), *ps.dptr);
892           mutt_pattern_free (&curlist);
893           return NULL;
894         }
895         tmp->op = entry->op;
896
897         ps.dptr++; /* eat the operator and any optional whitespace */
898         SKIPWS (ps.dptr);
899
900         if (entry->eat_arg)
901         {
902           if (!*ps.dptr)
903           {
904             snprintf (err->data, err->dsize, _("missing parameter"));
905             mutt_pattern_free (&curlist);
906             return NULL;
907           }
908           if (entry->eat_arg (tmp, &ps, err) == -1)
909           {
910             mutt_pattern_free (&curlist);
911             return NULL;
912           }
913         }
914         implicit = 1;
915         break;
916       case '(':
917         p = find_matching_paren (ps.dptr + 1);
918         if (*p != ')')
919         {
920           snprintf (err->data, err->dsize, _("mismatched parenthesis: %s"), ps.dptr);
921           mutt_pattern_free (&curlist);
922           return NULL;
923         }
924         /* compile the sub-expression */
925         buf = mutt_substrdup (ps.dptr + 1, p);
926         if ((tmp = mutt_pattern_comp (buf, flags, err)) == NULL)
927         {
928           FREE (&buf);
929           mutt_pattern_free (&curlist);
930           return NULL;
931         }
932         FREE (&buf);
933         if (last)
934           last->next = tmp;
935         else
936           curlist = tmp;
937         last = tmp;
938         tmp->not ^= not;
939         tmp->alladdr |= alladdr;
940         not = 0;
941         alladdr = 0;
942         ps.dptr = p + 1; /* restore location */
943         break;
944       default:
945         snprintf (err->data, err->dsize, _("error in pattern at: %s"), ps.dptr);
946         mutt_pattern_free (&curlist);
947         return NULL;
948     }
949   }
950   if (!curlist)
951   {
952     strfcpy (err->data, _("empty pattern"), err->dsize);
953     return NULL;
954   }
955   if (curlist->next)
956   {
957     tmp = new_pattern ();
958     tmp->op = or ? M_OR : M_AND;
959     tmp->child = curlist;
960     curlist = tmp;
961   }
962   return (curlist);
963 }
964
965 static int
966 perform_and (pattern_t *pat, pattern_exec_flag flags, CONTEXT *ctx, HEADER *hdr)
967 {
968   for (; pat; pat = pat->next)
969     if (mutt_pattern_exec (pat, flags, ctx, hdr) <= 0)
970       return 0;
971   return 1;
972 }
973
974 static int
975 perform_or (struct pattern_t *pat, pattern_exec_flag flags, CONTEXT *ctx, HEADER *hdr)
976 {
977   for (; pat; pat = pat->next)
978     if (mutt_pattern_exec (pat, flags, ctx, hdr) > 0)
979       return 1;
980   return 0;
981 }
982
983 static int match_adrlist (pattern_t *pat, int match_personal, int n, ...)
984 {
985   va_list ap;
986   ADDRESS *a;
987
988   va_start (ap, n);
989   for ( ; n ; n --)
990   {
991     for (a = va_arg (ap, ADDRESS *) ; a ; a = a->next)
992     {
993       if (pat->alladdr ^ ((a->mailbox && patmatch (pat, a->mailbox) == 0) ||
994            (match_personal && a->personal && patmatch (pat, a->personal) == 0)))
995       {
996         va_end (ap);
997         return (! pat->alladdr); /* Found match, or non-match if alladdr */
998       }
999     }
1000   }
1001   va_end (ap);
1002   return pat->alladdr; /* No matches, or all matches if alladdr */
1003 }
1004
1005 static int match_reference (pattern_t *pat, LIST *refs)
1006 {
1007   for (; refs; refs = refs->next)
1008     if (patmatch (pat, refs->data) == 0)
1009       return 1;
1010   return 0;
1011 }
1012
1013 /*
1014  * Matches subscribed mailing lists
1015  */
1016 int mutt_is_list_recipient (int alladdr, ADDRESS *a1, ADDRESS *a2)
1017 {
1018   for (; a1 ; a1 = a1->next)
1019     if (alladdr ^ mutt_is_subscribed_list (a1))
1020       return (! alladdr);
1021   for (; a2 ; a2 = a2->next)
1022     if (alladdr ^ mutt_is_subscribed_list (a2))
1023       return (! alladdr);
1024   return alladdr;
1025 }
1026
1027 /*
1028  * Matches known mailing lists
1029  * The function name may seem a little bit misleading: It checks all
1030  * recipients in To and Cc for known mailing lists, subscribed or not.
1031  */
1032 int mutt_is_list_cc (int alladdr, ADDRESS *a1, ADDRESS *a2)
1033 {
1034   for (; a1 ; a1 = a1->next)
1035     if (alladdr ^ mutt_is_mail_list (a1))
1036       return (! alladdr);
1037   for (; a2 ; a2 = a2->next)
1038     if (alladdr ^ mutt_is_mail_list (a2))
1039       return (! alladdr);
1040   return alladdr;
1041 }
1042
1043 static int match_user (int alladdr, ADDRESS *a1, ADDRESS *a2)
1044 {
1045   for (; a1 ; a1 = a1->next)
1046     if (alladdr ^ mutt_addr_is_user (a1))
1047       return (! alladdr);
1048   for (; a2 ; a2 = a2->next)
1049     if (alladdr ^ mutt_addr_is_user (a2))
1050       return (! alladdr);
1051   return alladdr;
1052 }
1053
1054 static int match_threadcomplete(struct pattern_t *pat, pattern_exec_flag flags, CONTEXT *ctx, THREAD *t,int left,int up,int right,int down)
1055 {
1056   int a;
1057   HEADER *h;
1058
1059   if(!t)
1060     return 0;
1061   h = t->message;
1062   if(h)
1063     if(mutt_pattern_exec(pat, flags, ctx, h))
1064       return 1;
1065
1066   if(up && (a=match_threadcomplete(pat, flags, ctx, t->parent,1,1,1,0)))
1067     return a;
1068   if(right && t->parent && (a=match_threadcomplete(pat, flags, ctx, t->next,0,0,1,1)))
1069     return a;
1070   if(left && t->parent && (a=match_threadcomplete(pat, flags, ctx, t->prev,1,0,0,1)))
1071     return a;
1072   if(down && (a=match_threadcomplete(pat, flags, ctx, t->child,1,0,1,1)))
1073     return a;
1074   return 0;
1075 }
1076
1077 /* flags
1078         M_MATCH_FULL_ADDRESS    match both personal and machine address */
1079 int
1080 mutt_pattern_exec (struct pattern_t *pat, pattern_exec_flag flags, CONTEXT *ctx, HEADER *h)
1081 {
1082   switch (pat->op)
1083   {
1084     case M_AND:
1085       return (pat->not ^ (perform_and (pat->child, flags, ctx, h) > 0));
1086     case M_OR:
1087       return (pat->not ^ (perform_or (pat->child, flags, ctx, h) > 0));
1088     case M_THREAD:
1089       return (pat->not ^ match_threadcomplete(pat->child, flags, ctx, h->thread, 1, 1, 1, 1));
1090     case M_ALL:
1091       return (!pat->not);
1092     case M_EXPIRED:
1093       return (pat->not ^ h->expired);
1094     case M_SUPERSEDED:
1095       return (pat->not ^ h->superseded);
1096     case M_FLAG:
1097       return (pat->not ^ h->flagged);
1098     case M_TAG:
1099       return (pat->not ^ h->tagged);
1100     case M_NEW:
1101       return (pat->not ? h->old || h->read : !(h->old || h->read));
1102     case M_UNREAD:
1103       return (pat->not ? h->read : !h->read);
1104     case M_REPLIED:
1105       return (pat->not ^ h->replied);
1106     case M_OLD:
1107       return (pat->not ? (!h->old || h->read) : (h->old && !h->read));
1108     case M_READ:
1109       return (pat->not ^ h->read);
1110     case M_DELETED:
1111       return (pat->not ^ h->deleted);
1112     case M_MESSAGE:
1113       return (pat->not ^ (h->msgno >= pat->min - 1 && (pat->max == M_MAXRANGE ||
1114                                                    h->msgno <= pat->max - 1)));
1115     case M_DATE:
1116       return (pat->not ^ (h->date_sent >= pat->min && h->date_sent <= pat->max));
1117     case M_DATE_RECEIVED:
1118       return (pat->not ^ (h->received >= pat->min && h->received <= pat->max));
1119     case M_BODY:
1120     case M_HEADER:
1121     case M_WHOLE_MSG:
1122       /*
1123        * ctx can be NULL in certain cases, such as when replying to a message from the attachment menu and
1124        * the user has a reply-hook using "~h" (bug #2190).
1125        */
1126       if (!ctx)
1127               return 0;
1128 #ifdef USE_IMAP
1129       /* IMAP search sets h->matched at search compile time */
1130       if (ctx->magic == M_IMAP && pat->stringmatch)
1131         return (h->matched);
1132 #endif
1133       return (pat->not ^ msg_search (ctx, pat, h->msgno));
1134     case M_SENDER:
1135       return (pat->not ^ match_adrlist (pat, flags & M_MATCH_FULL_ADDRESS, 1,
1136                                         h->env->sender));
1137     case M_FROM:
1138       return (pat->not ^ match_adrlist (pat, flags & M_MATCH_FULL_ADDRESS, 1,
1139                                         h->env->from));
1140     case M_TO:
1141       return (pat->not ^ match_adrlist (pat, flags & M_MATCH_FULL_ADDRESS, 1,
1142                                         h->env->to));
1143     case M_CC:
1144       return (pat->not ^ match_adrlist (pat, flags & M_MATCH_FULL_ADDRESS, 1,
1145                                         h->env->cc));
1146     case M_SUBJECT:
1147       return (pat->not ^ (h->env->subject && patmatch (pat, h->env->subject) == 0));
1148     case M_ID:
1149       return (pat->not ^ (h->env->message_id && patmatch (pat, h->env->message_id) == 0));
1150     case M_SCORE:
1151       return (pat->not ^ (h->score >= pat->min && (pat->max == M_MAXRANGE ||
1152                                                    h->score <= pat->max)));
1153     case M_SIZE:
1154       return (pat->not ^ (h->content->length >= pat->min && (pat->max == M_MAXRANGE || h->content->length <= pat->max)));
1155     case M_REFERENCE:
1156       return (pat->not ^ match_reference (pat, h->env->references));
1157     case M_ADDRESS:
1158       return (pat->not ^ match_adrlist (pat, flags & M_MATCH_FULL_ADDRESS, 4,
1159                                         h->env->from, h->env->sender,
1160                                         h->env->to, h->env->cc));
1161     case M_RECIPIENT:
1162            return (pat->not ^ match_adrlist (pat, flags & M_MATCH_FULL_ADDRESS,
1163                                              2, h->env->to, h->env->cc));
1164     case M_LIST:        /* known list, subscribed or not */
1165       return (pat->not ^ mutt_is_list_cc (pat->alladdr, h->env->to, h->env->cc));
1166     case M_SUBSCRIBED_LIST:
1167       return (pat->not ^ mutt_is_list_recipient (pat->alladdr, h->env->to, h->env->cc));
1168     case M_PERSONAL_RECIP:
1169       return (pat->not ^ match_user (pat->alladdr, h->env->to, h->env->cc));
1170     case M_PERSONAL_FROM:
1171       return (pat->not ^ match_user (pat->alladdr, h->env->from, NULL));
1172     case M_COLLAPSED:
1173       return (pat->not ^ (h->collapsed && h->num_hidden > 1));
1174    case M_CRYPT_SIGN:
1175      if (!WithCrypto)
1176        break;
1177      return (pat->not ^ ((h->security & SIGN) ? 1 : 0));
1178    case M_CRYPT_VERIFIED:
1179      if (!WithCrypto)
1180        break;
1181      return (pat->not ^ ((h->security & GOODSIGN) ? 1 : 0));
1182    case M_CRYPT_ENCRYPT:
1183      if (!WithCrypto)
1184        break;
1185      return (pat->not ^ ((h->security & ENCRYPT) ? 1 : 0));
1186    case M_PGP_KEY:
1187      if (!(WithCrypto & APPLICATION_PGP))
1188        break;
1189      return (pat->not ^ ((h->security & APPLICATION_PGP) && (h->security & PGPKEY)));
1190     case M_XLABEL:
1191       return (pat->not ^ (h->env->x_label && patmatch (pat, h->env->x_label) == 0));
1192     case M_HORMEL:
1193       return (pat->not ^ (h->env->spam && h->env->spam->data && patmatch (pat, h->env->spam->data) == 0));
1194     case M_DUPLICATED:
1195       return (pat->not ^ (h->thread && h->thread->duplicate_thread));
1196     case M_MIMEATTACH:
1197       {
1198       int count = mutt_count_body_parts (ctx, h);
1199       return (pat->not ^ (count >= pat->min && (pat->max == M_MAXRANGE ||
1200                                                 count <= pat->max)));
1201       }
1202     case M_UNREFERENCED:
1203       return (pat->not ^ (h->thread && !h->thread->child));
1204   }
1205   mutt_error (_("error: unknown op %d (report this error)."), pat->op);
1206   return (-1);
1207 }
1208
1209 static void quote_simple(char *tmp, size_t len, const char *p)
1210 {
1211   int i = 0;
1212   
1213   tmp[i++] = '"';
1214   while (*p && i < len - 3)
1215   {
1216     if (*p == '\\' || *p == '"')
1217       tmp[i++] = '\\';
1218     tmp[i++] = *p++;
1219   }
1220   tmp[i++] = '"';
1221   tmp[i] = 0;
1222 }
1223   
1224 /* convert a simple search into a real request */
1225 void mutt_check_simple (char *s, size_t len, const char *simple)
1226 {
1227   char tmp[LONG_STRING];
1228   int do_simple = 1;
1229   char *p;
1230
1231   for (p = s; p && *p; p++)
1232   {
1233     if (*p == '\\' && *(p + 1))
1234       p++;
1235     else if (*p == '~' || *p == '=' || *p == '%')
1236     {
1237       do_simple = 0;
1238       break;
1239     }
1240   }
1241
1242   /* XXX - is ascii_strcasecmp() right here, or should we use locale's
1243    * equivalences?
1244    */
1245
1246   if (do_simple) /* yup, so spoof a real request */
1247   {
1248     /* convert old tokens into the new format */
1249     if (ascii_strcasecmp ("all", s) == 0 ||
1250         !mutt_strcmp ("^", s) || !mutt_strcmp (".", s)) /* ~A is more efficient */
1251       strfcpy (s, "~A", len);
1252     else if (ascii_strcasecmp ("del", s) == 0)
1253       strfcpy (s, "~D", len);
1254     else if (ascii_strcasecmp ("flag", s) == 0)
1255       strfcpy (s, "~F", len);
1256     else if (ascii_strcasecmp ("new", s) == 0)
1257       strfcpy (s, "~N", len);
1258     else if (ascii_strcasecmp ("old", s) == 0)
1259       strfcpy (s, "~O", len);
1260     else if (ascii_strcasecmp ("repl", s) == 0)
1261       strfcpy (s, "~Q", len);
1262     else if (ascii_strcasecmp ("read", s) == 0)
1263       strfcpy (s, "~R", len);
1264     else if (ascii_strcasecmp ("tag", s) == 0)
1265       strfcpy (s, "~T", len);
1266     else if (ascii_strcasecmp ("unread", s) == 0)
1267       strfcpy (s, "~U", len);
1268     else
1269     {
1270       quote_simple (tmp, sizeof(tmp), s);
1271       mutt_expand_fmt (s, len, simple, tmp);
1272     }
1273   }
1274 }
1275
1276 int mutt_pattern_func (int op, char *prompt)
1277 {
1278   pattern_t *pat;
1279   char buf[LONG_STRING] = "", *simple, error[STRING];
1280   BUFFER err;
1281   int i;
1282   progress_t progress;
1283
1284   strfcpy (buf, NONULL (Context->pattern), sizeof (buf));
1285   if (mutt_get_field (prompt, buf, sizeof (buf), M_PATTERN | M_CLEAR) != 0 || !buf[0])
1286     return (-1);
1287
1288   mutt_message _("Compiling search pattern...");
1289   
1290   simple = safe_strdup (buf);
1291   mutt_check_simple (buf, sizeof (buf), NONULL (SimpleSearch));
1292
1293   err.data = error;
1294   err.dsize = sizeof (error);
1295   if ((pat = mutt_pattern_comp (buf, M_FULL_MSG, &err)) == NULL)
1296   {
1297     FREE (&simple);
1298     mutt_error ("%s", err.data);
1299     return (-1);
1300   }
1301
1302 #ifdef USE_IMAP
1303   if (Context->magic == M_IMAP && imap_search (Context, pat) < 0)
1304     return -1;
1305 #endif
1306
1307   mutt_progress_init (&progress, _("Executing command on matching messages..."),
1308                       M_PROGRESS_MSG, ReadInc,
1309                       (op == M_LIMIT) ? Context->msgcount : Context->vcount);
1310
1311 #define THIS_BODY Context->hdrs[i]->content
1312
1313   if (op == M_LIMIT)
1314   {
1315     Context->vcount    = 0;
1316     Context->vsize     = 0;
1317     Context->collapsed = 0;
1318
1319     for (i = 0; i < Context->msgcount; i++)
1320     {
1321       mutt_progress_update (&progress, i, -1);
1322       /* new limit pattern implicitly uncollapses all threads */
1323       Context->hdrs[i]->virtual = -1;
1324       Context->hdrs[i]->limited = 0;
1325       Context->hdrs[i]->collapsed = 0;
1326       Context->hdrs[i]->num_hidden = 0;
1327       if (mutt_pattern_exec (pat, M_MATCH_FULL_ADDRESS, Context, Context->hdrs[i]))
1328       {
1329         Context->hdrs[i]->virtual = Context->vcount;
1330         Context->hdrs[i]->limited = 1;
1331         Context->v2r[Context->vcount] = i;
1332         Context->vcount++;
1333         Context->vsize+=THIS_BODY->length + THIS_BODY->offset -
1334           THIS_BODY->hdr_offset;
1335       }
1336     }
1337   }
1338   else
1339   {
1340     for (i = 0; i < Context->vcount; i++)
1341     {
1342       mutt_progress_update (&progress, i, -1);
1343       if (mutt_pattern_exec (pat, M_MATCH_FULL_ADDRESS, Context, Context->hdrs[Context->v2r[i]]))
1344       {
1345         switch (op)
1346         {
1347           case M_DELETE:
1348           case M_UNDELETE:
1349             mutt_set_flag (Context, Context->hdrs[Context->v2r[i]], M_DELETE, 
1350                           (op == M_DELETE));
1351             break;
1352           case M_TAG:
1353           case M_UNTAG:
1354             mutt_set_flag (Context, Context->hdrs[Context->v2r[i]], M_TAG, 
1355                            (op == M_TAG));
1356             break;
1357         }
1358       }
1359     }
1360   }
1361
1362 #undef THIS_BODY
1363
1364   mutt_clear_error ();
1365
1366   if (op == M_LIMIT)
1367   {
1368     /* drop previous limit pattern */
1369     FREE (&Context->pattern);
1370     if (Context->limit_pattern)
1371       mutt_pattern_free (&Context->limit_pattern);
1372
1373     if (Context->msgcount && !Context->vcount)
1374       mutt_error _("No messages matched criteria.");
1375
1376     /* record new limit pattern, unless match all */
1377     if (mutt_strcmp (buf, "~A") != 0)
1378     {
1379       Context->pattern = simple;
1380       simple = NULL; /* don't clobber it */
1381       Context->limit_pattern = mutt_pattern_comp (buf, M_FULL_MSG, &err);
1382     }
1383   }
1384   FREE (&simple);
1385   mutt_pattern_free (&pat);
1386   return 0;
1387 }
1388
1389 int mutt_search_command (int cur, int op)
1390 {
1391   int i, j;
1392   char buf[STRING];
1393   char temp[LONG_STRING];
1394   char error[STRING];
1395   BUFFER err;
1396   int incr;
1397   HEADER *h;
1398   progress_t progress;
1399   const char* msg = NULL;
1400
1401   if (op != OP_SEARCH_NEXT && op != OP_SEARCH_OPPOSITE)
1402   {
1403     strfcpy (buf, LastSearch, sizeof (buf));
1404     if (mutt_get_field ((op == OP_SEARCH) ? _("Search for: ") :
1405                       _("Reverse search for: "), buf, sizeof (buf),
1406                       M_CLEAR | M_PATTERN) != 0 || !buf[0])
1407       return (-1);
1408
1409     if (op == OP_SEARCH)
1410       unset_option (OPTSEARCHREVERSE);
1411     else
1412       set_option (OPTSEARCHREVERSE);
1413
1414     /* compare the *expanded* version of the search pattern in case 
1415        $simple_search has changed while we were searching */
1416     strfcpy (temp, buf, sizeof (temp));
1417     mutt_check_simple (temp, sizeof (temp), NONULL (SimpleSearch));
1418
1419     if (!SearchPattern || mutt_strcmp (temp, LastSearchExpn))
1420     {
1421       set_option (OPTSEARCHINVALID);
1422       strfcpy (LastSearch, buf, sizeof (LastSearch));
1423       mutt_message _("Compiling search pattern...");
1424       mutt_pattern_free (&SearchPattern);
1425       err.data = error;
1426       err.dsize = sizeof (error);
1427       if ((SearchPattern = mutt_pattern_comp (temp, M_FULL_MSG, &err)) == NULL)
1428       {
1429         mutt_error ("%s", error);
1430         return (-1);
1431       }
1432       mutt_clear_error ();
1433     }
1434   }
1435   else if (!SearchPattern)
1436   {
1437     mutt_error _("No search pattern.");
1438     return (-1);
1439   }
1440
1441   if (option (OPTSEARCHINVALID))
1442   {
1443     for (i = 0; i < Context->msgcount; i++)
1444       Context->hdrs[i]->searched = 0;
1445 #ifdef USE_IMAP
1446     if (Context->magic == M_IMAP && imap_search (Context, SearchPattern) < 0)
1447       return -1;
1448 #endif
1449     unset_option (OPTSEARCHINVALID);
1450   }
1451
1452   incr = (option (OPTSEARCHREVERSE)) ? -1 : 1;
1453   if (op == OP_SEARCH_OPPOSITE)
1454     incr = -incr;
1455
1456   mutt_progress_init (&progress, _("Searching..."), M_PROGRESS_MSG,
1457                       ReadInc, Context->vcount);
1458
1459   for (i = cur + incr, j = 0 ; j != Context->vcount; j++)
1460   {
1461     mutt_progress_update (&progress, j, -1);
1462     if (i > Context->vcount - 1)
1463     {
1464       i = 0;
1465       if (option (OPTWRAPSEARCH))
1466         msg = _("Search wrapped to top.");
1467       else 
1468       {
1469         mutt_message _("Search hit bottom without finding match");
1470         return (-1);
1471       }
1472     }
1473     else if (i < 0)
1474     {
1475       i = Context->vcount - 1;
1476       if (option (OPTWRAPSEARCH))
1477         msg = _("Search wrapped to bottom.");
1478       else 
1479       {
1480         mutt_message _("Search hit top without finding match");
1481         return (-1);
1482       }
1483     }
1484
1485     h = Context->hdrs[Context->v2r[i]];
1486     if (h->searched)
1487     {
1488       /* if we've already evaulated this message, use the cached value */
1489       if (h->matched)
1490       {
1491         mutt_clear_error();
1492         if (msg && *msg)
1493           mutt_message (msg);
1494         return i;
1495       }
1496     }
1497     else
1498     {
1499       /* remember that we've already searched this message */
1500       h->searched = 1;
1501       if ((h->matched = (mutt_pattern_exec (SearchPattern, M_MATCH_FULL_ADDRESS, Context, h) > 0)))
1502       {
1503         mutt_clear_error();
1504         if (msg && *msg)
1505           mutt_message (msg);
1506         return i;
1507       }
1508     }
1509
1510     if (SigInt)
1511     {
1512       mutt_error _("Search interrupted.");
1513       SigInt = 0;
1514       return (-1);
1515     }
1516
1517     i += incr;
1518   }
1519
1520   mutt_error _("Not found.");
1521   return (-1);
1522 }