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