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