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