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