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