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