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