mutt stable branch with some hacks
1/*
2 * Copyright (C) 1996-2000,2006-2007,2010 Michael R. Elkins <me@mutt.org>, and others
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 */
18
19#if HAVE_CONFIG_H
20# include "config.h"
21#endif
22
23#include "mutt.h"
24#include "mapping.h"
25#include "keymap.h"
26#include "mailbox.h"
27#include "copy.h"
28
29#include <string.h>
30#include <stdlib.h>
31#include <ctype.h>
32#include <sys/stat.h>
33#include <unistd.h>
34#include <stdarg.h>
35
36#include "mutt_crypt.h"
37#include "mutt_curses.h"
38#include "group.h"
39
40#ifdef USE_IMAP
41#include "mx.h"
42#include "imap/imap.h"
43#endif
44
45static int eat_regexp (pattern_t *pat, BUFFER *, BUFFER *);
46static int eat_date (pattern_t *pat, BUFFER *, BUFFER *);
47static int eat_range (pattern_t *pat, BUFFER *, BUFFER *);
48static int patmatch (const pattern_t *pat, const char *buf);
49
50static const struct pattern_flags
51{
52 int tag; /* character used to represent this op */
53 int op; /* operation to perform */
54 int class;
55 int (*eat_arg) (pattern_t *, BUFFER *, BUFFER *);
56}
57Flags[] =
58{
59 { 'A', MUTT_ALL, 0, NULL },
60 { 'b', MUTT_BODY, MUTT_FULL_MSG, eat_regexp },
61 { 'B', MUTT_WHOLE_MSG, MUTT_FULL_MSG, eat_regexp },
62 { 'c', MUTT_CC, 0, eat_regexp },
63 { 'C', MUTT_RECIPIENT, 0, eat_regexp },
64 { 'd', MUTT_DATE, 0, eat_date },
65 { 'D', MUTT_DELETED, 0, NULL },
66 { 'e', MUTT_SENDER, 0, eat_regexp },
67 { 'E', MUTT_EXPIRED, 0, NULL },
68 { 'f', MUTT_FROM, 0, eat_regexp },
69 { 'F', MUTT_FLAG, 0, NULL },
70 { 'g', MUTT_CRYPT_SIGN, 0, NULL },
71 { 'G', MUTT_CRYPT_ENCRYPT, 0, NULL },
72 { 'h', MUTT_HEADER, MUTT_FULL_MSG, eat_regexp },
73 { 'H', MUTT_HORMEL, 0, eat_regexp },
74 { 'i', MUTT_ID, 0, eat_regexp },
75 { 'k', MUTT_PGP_KEY, 0, NULL },
76 { 'l', MUTT_LIST, 0, NULL },
77 { 'L', MUTT_ADDRESS, 0, eat_regexp },
78 { 'm', MUTT_MESSAGE, 0, eat_range },
79 { 'n', MUTT_SCORE, 0, eat_range },
80 { 'N', MUTT_NEW, 0, NULL },
81 { 'O', MUTT_OLD, 0, NULL },
82 { 'p', MUTT_PERSONAL_RECIP, 0, NULL },
83 { 'P', MUTT_PERSONAL_FROM, 0, NULL },
84 { 'Q', MUTT_REPLIED, 0, NULL },
85 { 'r', MUTT_DATE_RECEIVED, 0, eat_date },
86 { 'R', MUTT_READ, 0, NULL },
87 { 's', MUTT_SUBJECT, 0, eat_regexp },
88 { 'S', MUTT_SUPERSEDED, 0, NULL },
89 { 't', MUTT_TO, 0, eat_regexp },
90 { 'T', MUTT_TAG, 0, NULL },
91 { 'u', MUTT_SUBSCRIBED_LIST, 0, NULL },
92 { 'U', MUTT_UNREAD, 0, NULL },
93 { 'v', MUTT_COLLAPSED, 0, NULL },
94 { 'V', MUTT_CRYPT_VERIFIED, 0, NULL },
95 { 'x', MUTT_REFERENCE, 0, eat_regexp },
96 { 'X', MUTT_MIMEATTACH, 0, eat_range },
97 { 'y', MUTT_XLABEL, 0, eat_regexp },
98 { 'z', MUTT_SIZE, 0, eat_range },
99 { '=', MUTT_DUPLICATED, 0, NULL },
100 { '$', MUTT_UNREFERENCED, 0, NULL },
101 { 0, 0, 0, NULL }
102};
103
104static pattern_t *SearchPattern = NULL; /* current search pattern */
105static char LastSearch[STRING] = { 0 }; /* last pattern searched for */
106static char LastSearchExpn[LONG_STRING] = { 0 }; /* expanded version of
107 LastSearch */
108
109#define MUTT_MAXRANGE -1
110
111/* constants for parse_date_range() */
112#define MUTT_PDR_NONE 0x0000
113#define MUTT_PDR_MINUS 0x0001
114#define MUTT_PDR_PLUS 0x0002
115#define MUTT_PDR_WINDOW 0x0004
116#define MUTT_PDR_ABSOLUTE 0x0008
117#define MUTT_PDR_DONE 0x0010
118#define MUTT_PDR_ERROR 0x0100
119#define MUTT_PDR_ERRORDONE (MUTT_PDR_ERROR | MUTT_PDR_DONE)
120
121
122/* if no uppercase letters are given, do a case-insensitive search */
123int mutt_which_case (const char *s)
124{
125 wchar_t w;
126 mbstate_t mb;
127 size_t l;
128
129 memset (&mb, 0, sizeof (mb));
130
131 for (; (l = mbrtowc (&w, s, MB_CUR_MAX, &mb)) != 0; s += l)
132 {
133 if (l == (size_t) -2)
134 continue; /* shift sequences */
135 if (l == (size_t) -1)
136 return 0; /* error; assume case-sensitive */
137 if (iswalpha ((wint_t) w) && iswupper ((wint_t) w))
138 return 0; /* case-sensitive */
139 }
140
141 return REG_ICASE; /* case-insensitive */
142}
143
144static int
145msg_search (CONTEXT *ctx, pattern_t* pat, int msgno)
146{
147 char tempfile[_POSIX_PATH_MAX];
148 MESSAGE *msg = NULL;
149 STATE s;
150 struct stat st;
151 FILE *fp = NULL;
152 long lng = 0;
153 int match = 0;
154 HEADER *h = ctx->hdrs[msgno];
155 char *buf;
156 size_t blen;
157
158 if ((msg = mx_open_message (ctx, msgno)) != NULL)
159 {
160 if (option (OPTTHOROUGHSRC))
161 {
162 /* decode the header / body */
163 memset (&s, 0, sizeof (s));
164 s.fpin = msg->fp;
165 s.flags = MUTT_CHARCONV;
166 mutt_mktemp (tempfile, sizeof (tempfile));
167 if ((s.fpout = safe_fopen (tempfile, "w+")) == NULL)
168 {
169 mutt_perror (tempfile);
170 return (0);
171 }
172
173 if (pat->op != MUTT_BODY)
174 mutt_copy_header (msg->fp, h, s.fpout, CH_FROM | CH_DECODE, NULL);
175
176 if (pat->op != MUTT_HEADER)
177 {
178 mutt_parse_mime_message (ctx, h);
179
180 if (WithCrypto && (h->security & ENCRYPT)
181 && !crypt_valid_passphrase(h->security))
182 {
183 mx_close_message (ctx, &msg);
184 if (s.fpout)
185 {
186 safe_fclose (&s.fpout);
187 unlink (tempfile);
188 }
189 return (0);
190 }
191
192 fseeko (msg->fp, h->offset, 0);
193 mutt_body_handler (h->content, &s);
194 }
195
196 fp = s.fpout;
197 fflush (fp);
198 fseek (fp, 0, 0);
199 fstat (fileno (fp), &st);
200 lng = (long) st.st_size;
201 }
202 else
203 {
204 /* raw header / body */
205 fp = msg->fp;
206 if (pat->op != MUTT_BODY)
207 {
208 fseeko (fp, h->offset, 0);
209 lng = h->content->offset - h->offset;
210 }
211 if (pat->op != MUTT_HEADER)
212 {
213 if (pat->op == MUTT_BODY)
214 fseeko (fp, h->content->offset, 0);
215 lng += h->content->length;
216 }
217 }
218
219 blen = STRING;
220 buf = safe_malloc (blen);
221
222 /* search the file "fp" */
223 while (lng > 0)
224 {
225 if (pat->op == MUTT_HEADER)
226 {
227 if (*(buf = mutt_read_rfc822_line (fp, buf, &blen)) == '\0')
228 break;
229 }
230 else if (fgets (buf, blen - 1, fp) == NULL)
231 break; /* don't loop forever */
232 if (patmatch (pat, buf) == 0)
233 {
234 match = 1;
235 break;
236 }
237 lng -= mutt_strlen (buf);
238 }
239
240 FREE (&buf);
241
242 mx_close_message (ctx, &msg);
243
244 if (option (OPTTHOROUGHSRC))
245 {
246 safe_fclose (&fp);
247 unlink (tempfile);
248 }
249 }
250
251 return match;
252}
253
254static int eat_regexp (pattern_t *pat, BUFFER *s, BUFFER *err)
255{
256 BUFFER buf;
257 char errmsg[STRING];
258 int r;
259 char *pexpr;
260
261 mutt_buffer_init (&buf);
262 pexpr = s->dptr;
263 if (mutt_extract_token (&buf, s, MUTT_TOKEN_PATTERN | MUTT_TOKEN_COMMENT) != 0 ||
264 !buf.data)
265 {
266 snprintf (err->data, err->dsize, _("Error in expression: %s"), pexpr);
267 return (-1);
268 }
269 if (!*buf.data)
270 {
271 snprintf (err->data, err->dsize, _("Empty expression"));
272 return (-1);
273 }
274
275#if 0
276 /* If there are no RE metacharacters, use simple search anyway */
277 if (!pat->stringmatch && !strpbrk (buf.data, "|[{.*+?^$"))
278 pat->stringmatch = 1;
279#endif
280
281 if (pat->stringmatch)
282 {
283 pat->p.str = safe_strdup (buf.data);
284 pat->ign_case = mutt_which_case (buf.data) == REG_ICASE;
285 FREE (&buf.data);
286 }
287 else if (pat->groupmatch)
288 {
289 pat->p.g = mutt_pattern_group (buf.data);
290 FREE (&buf.data);
291 }
292 else
293 {
294 pat->p.rx = safe_malloc (sizeof (regex_t));
295 r = REGCOMP (pat->p.rx, buf.data, REG_NEWLINE | REG_NOSUB | mutt_which_case (buf.data));
296 if (r)
297 {
298 regerror (r, pat->p.rx, errmsg, sizeof (errmsg));
299 mutt_buffer_printf (err, "'%s': %s", buf.data, errmsg);
300 FREE (&buf.data);
301 FREE (&pat->p.rx);
302 return (-1);
303 }
304 FREE (&buf.data);
305 }
306
307 return 0;
308}
309
310int eat_range (pattern_t *pat, BUFFER *s, BUFFER *err)
311{
312 char *tmp;
313 int do_exclusive = 0;
314 int skip_quote = 0;
315
316 /*
317 * If simple_search is set to "~m %s", the range will have double quotes
318 * around it...
319 */
320 if (*s->dptr == '"')
321 {
322 s->dptr++;
323 skip_quote = 1;
324 }
325 if (*s->dptr == '<')
326 do_exclusive = 1;
327 if ((*s->dptr != '-') && (*s->dptr != '<'))
328 {
329 /* range minimum */
330 if (*s->dptr == '>')
331 {
332 pat->max = MUTT_MAXRANGE;
333 pat->min = strtol (s->dptr + 1, &tmp, 0) + 1; /* exclusive range */
334 }
335 else
336 pat->min = strtol (s->dptr, &tmp, 0);
337 if (toupper ((unsigned char) *tmp) == 'K') /* is there a prefix? */
338 {
339 pat->min *= 1024;
340 tmp++;
341 }
342 else if (toupper ((unsigned char) *tmp) == 'M')
343 {
344 pat->min *= 1048576;
345 tmp++;
346 }
347 if (*s->dptr == '>')
348 {
349 s->dptr = tmp;
350 return 0;
351 }
352 if (*tmp != '-')
353 {
354 /* exact value */
355 pat->max = pat->min;
356 s->dptr = tmp;
357 return 0;
358 }
359 tmp++;
360 }
361 else
362 {
363 s->dptr++;
364 tmp = s->dptr;
365 }
366
367 if (isdigit ((unsigned char) *tmp))
368 {
369 /* range maximum */
370 pat->max = strtol (tmp, &tmp, 0);
371 if (toupper ((unsigned char) *tmp) == 'K')
372 {
373 pat->max *= 1024;
374 tmp++;
375 }
376 else if (toupper ((unsigned char) *tmp) == 'M')
377 {
378 pat->max *= 1048576;
379 tmp++;
380 }
381 if (do_exclusive)
382 (pat->max)--;
383 }
384 else
385 pat->max = MUTT_MAXRANGE;
386
387 if (skip_quote && *tmp == '"')
388 tmp++;
389
390 SKIPWS (tmp);
391 s->dptr = tmp;
392 return 0;
393}
394
395static const char *getDate (const char *s, struct tm *t, BUFFER *err)
396{
397 char *p;
398 time_t now = time (NULL);
399 struct tm *tm = localtime (&now);
400
401 t->tm_mday = strtol (s, &p, 10);
402 if (t->tm_mday < 1 || t->tm_mday > 31)
403 {
404 snprintf (err->data, err->dsize, _("Invalid day of month: %s"), s);
405 return NULL;
406 }
407 if (*p != '/')
408 {
409 /* fill in today's month and year */
410 t->tm_mon = tm->tm_mon;
411 t->tm_year = tm->tm_year;
412 return p;
413 }
414 p++;
415 t->tm_mon = strtol (p, &p, 10) - 1;
416 if (t->tm_mon < 0 || t->tm_mon > 11)
417 {
418 snprintf (err->data, err->dsize, _("Invalid month: %s"), p);
419 return NULL;
420 }
421 if (*p != '/')
422 {
423 t->tm_year = tm->tm_year;
424 return p;
425 }
426 p++;
427 t->tm_year = strtol (p, &p, 10);
428 if (t->tm_year < 70) /* year 2000+ */
429 t->tm_year += 100;
430 else if (t->tm_year > 1900)
431 t->tm_year -= 1900;
432 return p;
433}
434
435/* Ny years
436 Nm months
437 Nw weeks
438 Nd days */
439static const char *get_offset (struct tm *tm, const char *s, int sign)
440{
441 char *ps;
442 int offset = strtol (s, &ps, 0);
443 if ((sign < 0 && offset > 0) || (sign > 0 && offset < 0))
444 offset = -offset;
445
446 switch (*ps)
447 {
448 case 'y':
449 tm->tm_year += offset;
450 break;
451 case 'm':
452 tm->tm_mon += offset;
453 break;
454 case 'w':
455 tm->tm_mday += 7 * offset;
456 break;
457 case 'd':
458 tm->tm_mday += offset;
459 break;
460 default:
461 return s;
462 }
463 mutt_normalize_time (tm);
464 return (ps + 1);
465}
466
467static void adjust_date_range (struct tm *min, struct tm *max)
468{
469 if (min->tm_year > max->tm_year
470 || (min->tm_year == max->tm_year && min->tm_mon > max->tm_mon)
471 || (min->tm_year == max->tm_year && min->tm_mon == max->tm_mon
472 && min->tm_mday > max->tm_mday))
473 {
474 int tmp;
475
476 tmp = min->tm_year;
477 min->tm_year = max->tm_year;
478 max->tm_year = tmp;
479
480 tmp = min->tm_mon;
481 min->tm_mon = max->tm_mon;
482 max->tm_mon = tmp;
483
484 tmp = min->tm_mday;
485 min->tm_mday = max->tm_mday;
486 max->tm_mday = tmp;
487
488 min->tm_hour = min->tm_min = min->tm_sec = 0;
489 max->tm_hour = 23;
490 max->tm_min = max->tm_sec = 59;
491 }
492}
493
494static const char * parse_date_range (const char* pc, struct tm *min,
495 struct tm *max, int haveMin, struct tm *baseMin, BUFFER *err)
496{
497 int flag = MUTT_PDR_NONE;
498 while (*pc && ((flag & MUTT_PDR_DONE) == 0))
499 {
500 const char *pt;
501 char ch = *pc++;
502 SKIPWS (pc);
503 switch (ch)
504 {
505 case '-':
506 {
507 /* try a range of absolute date minus offset of Ndwmy */
508 pt = get_offset (min, pc, -1);
509 if (pc == pt)
510 {
511 if (flag == MUTT_PDR_NONE)
512 { /* nothing yet and no offset parsed => absolute date? */
513 if (!getDate (pc, max, err))
514 flag |= (MUTT_PDR_ABSOLUTE | MUTT_PDR_ERRORDONE); /* done bad */
515 else
516 {
517 /* reestablish initial base minimum if not specified */
518 if (!haveMin)
519 memcpy (min, baseMin, sizeof(struct tm));
520 flag |= (MUTT_PDR_ABSOLUTE | MUTT_PDR_DONE); /* done good */
521 }
522 }
523 else
524 flag |= MUTT_PDR_ERRORDONE;
525 }
526 else
527 {
528 pc = pt;
529 if (flag == MUTT_PDR_NONE && !haveMin)
530 { /* the very first "-3d" without a previous absolute date */
531 max->tm_year = min->tm_year;
532 max->tm_mon = min->tm_mon;
533 max->tm_mday = min->tm_mday;
534 }
535 flag |= MUTT_PDR_MINUS;
536 }
537 }
538 break;
539 case '+':
540 { /* enlarge plusRange */
541 pt = get_offset (max, pc, 1);
542 if (pc == pt)
543 flag |= MUTT_PDR_ERRORDONE;
544 else
545 {
546 pc = pt;
547 flag |= MUTT_PDR_PLUS;
548 }
549 }
550 break;
551 case '*':
552 { /* enlarge window in both directions */
553 pt = get_offset (min, pc, -1);
554 if (pc == pt)
555 flag |= MUTT_PDR_ERRORDONE;
556 else
557 {
558 pc = get_offset (max, pc, 1);
559 flag |= MUTT_PDR_WINDOW;
560 }
561 }
562 break;
563 default:
564 flag |= MUTT_PDR_ERRORDONE;
565 }
566 SKIPWS (pc);
567 }
568 if ((flag & MUTT_PDR_ERROR) && !(flag & MUTT_PDR_ABSOLUTE))
569 { /* getDate has its own error message, don't overwrite it here */
570 snprintf (err->data, err->dsize, _("Invalid relative date: %s"), pc-1);
571 }
572 return ((flag & MUTT_PDR_ERROR) ? NULL : pc);
573}
574
575static int eat_date (pattern_t *pat, BUFFER *s, BUFFER *err)
576{
577 BUFFER buffer;
578 struct tm min, max;
579 char *pexpr;
580
581 mutt_buffer_init (&buffer);
582 pexpr = s->dptr;
583 if (mutt_extract_token (&buffer, s, MUTT_TOKEN_COMMENT | MUTT_TOKEN_PATTERN) != 0
584 || !buffer.data)
585 {
586 snprintf (err->data, err->dsize, _("Error in expression: %s"), pexpr);
587 return (-1);
588 }
589 if (!*buffer.data)
590 {
591 snprintf (err->data, err->dsize, _("Empty expression"));
592 return (-1);
593 }
594
595 memset (&min, 0, sizeof (min));
596 /* the `0' time is Jan 1, 1970 UTC, so in order to prevent a negative time
597 when doing timezone conversion, we use Jan 2, 1970 UTC as the base
598 here */
599 min.tm_mday = 2;
600 min.tm_year = 70;
601
602 memset (&max, 0, sizeof (max));
603
604 /* Arbitrary year in the future. Don't set this too high
605 or mutt_mktime() returns something larger than will
606 fit in a time_t on some systems */
607 max.tm_year = 130;
608 max.tm_mon = 11;
609 max.tm_mday = 31;
610 max.tm_hour = 23;
611 max.tm_min = 59;
612 max.tm_sec = 59;
613
614 if (strchr ("<>=", buffer.data[0]))
615 {
616 /* offset from current time
617 <3d less than three days ago
618 >3d more than three days ago
619 =3d exactly three days ago */
620 time_t now = time (NULL);
621 struct tm *tm = localtime (&now);
622 int exact = 0;
623
624 if (buffer.data[0] == '<')
625 {
626 memcpy (&min, tm, sizeof (min));
627 tm = &min;
628 }
629 else
630 {
631 memcpy (&max, tm, sizeof (max));
632 tm = &max;
633
634 if (buffer.data[0] == '=')
635 exact++;
636 }
637 tm->tm_hour = 23;
638 tm->tm_min = tm->tm_sec = 59;
639
640 /* force negative offset */
641 get_offset (tm, buffer.data + 1, -1);
642
643 if (exact)
644 {
645 /* start at the beginning of the day in question */
646 memcpy (&min, &max, sizeof (max));
647 min.tm_hour = min.tm_sec = min.tm_min = 0;
648 }
649 }
650 else
651 {
652 const char *pc = buffer.data;
653
654 int haveMin = FALSE;
655 int untilNow = FALSE;
656 if (isdigit ((unsigned char)*pc))
657 {
658 /* minimum date specified */
659 if ((pc = getDate (pc, &min, err)) == NULL)
660 {
661 FREE (&buffer.data);
662 return (-1);
663 }
664 haveMin = TRUE;
665 SKIPWS (pc);
666 if (*pc == '-')
667 {
668 const char *pt = pc + 1;
669 SKIPWS (pt);
670 untilNow = (*pt == '\0');
671 }
672 }
673
674 if (!untilNow)
675 { /* max date or relative range/window */
676
677 struct tm baseMin;
678
679 if (!haveMin)
680 { /* save base minimum and set current date, e.g. for "-3d+1d" */
681 time_t now = time (NULL);
682 struct tm *tm = localtime (&now);
683 memcpy (&baseMin, &min, sizeof(baseMin));
684 memcpy (&min, tm, sizeof (min));
685 min.tm_hour = min.tm_sec = min.tm_min = 0;
686 }
687
688 /* preset max date for relative offsets,
689 if nothing follows we search for messages on a specific day */
690 max.tm_year = min.tm_year;
691 max.tm_mon = min.tm_mon;
692 max.tm_mday = min.tm_mday;
693
694 if (!parse_date_range (pc, &min, &max, haveMin, &baseMin, err))
695 { /* bail out on any parsing error */
696 FREE (&buffer.data);
697 return (-1);
698 }
699 }
700 }
701
702 /* Since we allow two dates to be specified we'll have to adjust that. */
703 adjust_date_range (&min, &max);
704
705 pat->min = mutt_mktime (&min, 1);
706 pat->max = mutt_mktime (&max, 1);
707
708 FREE (&buffer.data);
709
710 return 0;
711}
712
713static int patmatch (const pattern_t* pat, const char* buf)
714{
715 if (pat->stringmatch)
716 return pat->ign_case ? !strcasestr (buf, pat->p.str) :
717 !strstr (buf, pat->p.str);
718 else if (pat->groupmatch)
719 return !mutt_group_match (pat->p.g, buf);
720 else
721 return regexec (pat->p.rx, buf, 0, NULL, 0);
722}
723
724static const struct pattern_flags *lookup_tag (char tag)
725{
726 int i;
727
728 for (i = 0; Flags[i].tag; i++)
729 if (Flags[i].tag == tag)
730 return (&Flags[i]);
731 return NULL;
732}
733
734static /* const */ char *find_matching_paren (/* const */ char *s)
735{
736 int level = 1;
737
738 for (; *s; s++)
739 {
740 if (*s == '(')
741 level++;
742 else if (*s == ')')
743 {
744 level--;
745 if (!level)
746 break;
747 }
748 }
749 return s;
750}
751
752void mutt_pattern_free (pattern_t **pat)
753{
754 pattern_t *tmp;
755
756 while (*pat)
757 {
758 tmp = *pat;
759 *pat = (*pat)->next;
760
761 if (tmp->stringmatch)
762 FREE (&tmp->p.str);
763 else if (tmp->groupmatch)
764 tmp->p.g = NULL;
765 else if (tmp->p.rx)
766 {
767 regfree (tmp->p.rx);
768 FREE (&tmp->p.rx);
769 }
770
771 if (tmp->child)
772 mutt_pattern_free (&tmp->child);
773 FREE (&tmp);
774 }
775}
776
777pattern_t *mutt_pattern_comp (/* const */ char *s, int flags, BUFFER *err)
778{
779 pattern_t *curlist = NULL;
780 pattern_t *tmp, *tmp2;
781 pattern_t *last = NULL;
782 int not = 0;
783 int alladdr = 0;
784 int or = 0;
785 int implicit = 1; /* used to detect logical AND operator */
786 int isalias = 0;
787 const struct pattern_flags *entry;
788 char *p;
789 char *buf;
790 BUFFER ps;
791
792 mutt_buffer_init (&ps);
793 ps.dptr = s;
794 ps.dsize = mutt_strlen (s);
795
796 while (*ps.dptr)
797 {
798 SKIPWS (ps.dptr);
799 switch (*ps.dptr)
800 {
801 case '^':
802 ps.dptr++;
803 alladdr = !alladdr;
804 break;
805 case '!':
806 ps.dptr++;
807 not = !not;
808 break;
809 case '@':
810 ps.dptr++;
811 isalias = !isalias;
812 break;
813 case '|':
814 if (!or)
815 {
816 if (!curlist)
817 {
818 snprintf (err->data, err->dsize, _("error in pattern at: %s"), ps.dptr);
819 return NULL;
820 }
821 if (curlist->next)
822 {
823 /* A & B | C == (A & B) | C */
824 tmp = new_pattern ();
825 tmp->op = MUTT_AND;
826 tmp->child = curlist;
827
828 curlist = tmp;
829 last = curlist;
830 }
831
832 or = 1;
833 }
834 ps.dptr++;
835 implicit = 0;
836 not = 0;
837 alladdr = 0;
838 isalias = 0;
839 break;
840 case '%':
841 case '=':
842 case '~':
843 if (!*(ps.dptr + 1))
844 {
845 snprintf (err->data, err->dsize, _("missing pattern: %s"), ps.dptr);
846 mutt_pattern_free (&curlist);
847 return NULL;
848 }
849 if (*(ps.dptr + 1) == '(')
850 {
851 ps.dptr ++; /* skip ~ */
852 p = find_matching_paren (ps.dptr + 1);
853 if (*p != ')')
854 {
855 snprintf (err->data, err->dsize, _("mismatched brackets: %s"), ps.dptr);
856 mutt_pattern_free (&curlist);
857 return NULL;
858 }
859 tmp = new_pattern ();
860 tmp->op = MUTT_THREAD;
861 if (last)
862 last->next = tmp;
863 else
864 curlist = tmp;
865 last = tmp;
866 tmp->not ^= not;
867 tmp->alladdr |= alladdr;
868 tmp->isalias |= isalias;
869 not = 0;
870 alladdr = 0;
871 isalias = 0;
872 /* compile the sub-expression */
873 buf = mutt_substrdup (ps.dptr + 1, p);
874 if ((tmp2 = mutt_pattern_comp (buf, flags, err)) == NULL)
875 {
876 FREE (&buf);
877 mutt_pattern_free (&curlist);
878 return NULL;
879 }
880 FREE (&buf);
881 tmp->child = tmp2;
882 ps.dptr = p + 1; /* restore location */
883 break;
884 }
885 if (implicit && or)
886 {
887 /* A | B & C == (A | B) & C */
888 tmp = new_pattern ();
889 tmp->op = MUTT_OR;
890 tmp->child = curlist;
891 curlist = tmp;
892 last = tmp;
893 or = 0;
894 }
895
896 tmp = new_pattern ();
897 tmp->not = not;
898 tmp->alladdr = alladdr;
899 tmp->isalias = isalias;
900 tmp->stringmatch = (*ps.dptr == '=') ? 1 : 0;
901 tmp->groupmatch = (*ps.dptr == '%') ? 1 : 0;
902 not = 0;
903 alladdr = 0;
904 isalias = 0;
905
906 if (last)
907 last->next = tmp;
908 else
909 curlist = tmp;
910 last = tmp;
911
912 ps.dptr++; /* move past the ~ */
913 if ((entry = lookup_tag (*ps.dptr)) == NULL)
914 {
915 snprintf (err->data, err->dsize, _("%c: invalid pattern modifier"), *ps.dptr);
916 mutt_pattern_free (&curlist);
917 return NULL;
918 }
919 if (entry->class && (flags & entry->class) == 0)
920 {
921 snprintf (err->data, err->dsize, _("%c: not supported in this mode"), *ps.dptr);
922 mutt_pattern_free (&curlist);
923 return NULL;
924 }
925 tmp->op = entry->op;
926
927 ps.dptr++; /* eat the operator and any optional whitespace */
928 SKIPWS (ps.dptr);
929
930 if (entry->eat_arg)
931 {
932 if (!*ps.dptr)
933 {
934 snprintf (err->data, err->dsize, _("missing parameter"));
935 mutt_pattern_free (&curlist);
936 return NULL;
937 }
938 if (entry->eat_arg (tmp, &ps, err) == -1)
939 {
940 mutt_pattern_free (&curlist);
941 return NULL;
942 }
943 }
944 implicit = 1;
945 break;
946 case '(':
947 p = find_matching_paren (ps.dptr + 1);
948 if (*p != ')')
949 {
950 snprintf (err->data, err->dsize, _("mismatched parenthesis: %s"), ps.dptr);
951 mutt_pattern_free (&curlist);
952 return NULL;
953 }
954 /* compile the sub-expression */
955 buf = mutt_substrdup (ps.dptr + 1, p);
956 if ((tmp = mutt_pattern_comp (buf, flags, err)) == NULL)
957 {
958 FREE (&buf);
959 mutt_pattern_free (&curlist);
960 return NULL;
961 }
962 FREE (&buf);
963 if (last)
964 last->next = tmp;
965 else
966 curlist = tmp;
967 last = tmp;
968 tmp->not ^= not;
969 tmp->alladdr |= alladdr;
970 tmp->isalias |= isalias;
971 not = 0;
972 alladdr = 0;
973 isalias = 0;
974 ps.dptr = p + 1; /* restore location */
975 break;
976 default:
977 snprintf (err->data, err->dsize, _("error in pattern at: %s"), ps.dptr);
978 mutt_pattern_free (&curlist);
979 return NULL;
980 }
981 }
982 if (!curlist)
983 {
984 strfcpy (err->data, _("empty pattern"), err->dsize);
985 return NULL;
986 }
987 if (curlist->next)
988 {
989 tmp = new_pattern ();
990 tmp->op = or ? MUTT_OR : MUTT_AND;
991 tmp->child = curlist;
992 curlist = tmp;
993 }
994 return (curlist);
995}
996
997static int
998perform_and (pattern_t *pat, pattern_exec_flag flags, CONTEXT *ctx, HEADER *hdr, pattern_cache_t *cache)
999{
1000 for (; pat; pat = pat->next)
1001 if (mutt_pattern_exec (pat, flags, ctx, hdr, cache) <= 0)
1002 return 0;
1003 return 1;
1004}
1005
1006static int
1007perform_or (struct pattern_t *pat, pattern_exec_flag flags, CONTEXT *ctx, HEADER *hdr, pattern_cache_t *cache)
1008{
1009 for (; pat; pat = pat->next)
1010 if (mutt_pattern_exec (pat, flags, ctx, hdr, cache) > 0)
1011 return 1;
1012 return 0;
1013}
1014
1015static int match_adrlist (pattern_t *pat, int match_personal, int n, ...)
1016{
1017 va_list ap;
1018 ADDRESS *a;
1019
1020 va_start (ap, n);
1021 for ( ; n ; n --)
1022 {
1023 for (a = va_arg (ap, ADDRESS *) ; a ; a = a->next)
1024 {
1025 if (pat->alladdr ^
1026 ((!pat->isalias || alias_reverse_lookup (a)) &&
1027 ((a->mailbox && !patmatch (pat, a->mailbox)) ||
1028 (match_personal && a->personal && !patmatch (pat, a->personal) ))))
1029 {
1030 va_end (ap);
1031 return (! pat->alladdr); /* Found match, or non-match if alladdr */
1032 }
1033 }
1034 }
1035 va_end (ap);
1036 return pat->alladdr; /* No matches, or all matches if alladdr */
1037}
1038
1039static int match_reference (pattern_t *pat, LIST *refs)
1040{
1041 for (; refs; refs = refs->next)
1042 if (patmatch (pat, refs->data) == 0)
1043 return 1;
1044 return 0;
1045}
1046
1047/*
1048 * Matches subscribed mailing lists
1049 */
1050int mutt_is_list_recipient (int alladdr, ADDRESS *a1, ADDRESS *a2)
1051{
1052 for (; a1 ; a1 = a1->next)
1053 if (alladdr ^ mutt_is_subscribed_list (a1))
1054 return (! alladdr);
1055 for (; a2 ; a2 = a2->next)
1056 if (alladdr ^ mutt_is_subscribed_list (a2))
1057 return (! alladdr);
1058 return alladdr;
1059}
1060
1061/*
1062 * Matches known mailing lists
1063 * The function name may seem a little bit misleading: It checks all
1064 * recipients in To and Cc for known mailing lists, subscribed or not.
1065 */
1066int mutt_is_list_cc (int alladdr, ADDRESS *a1, ADDRESS *a2)
1067{
1068 for (; a1 ; a1 = a1->next)
1069 if (alladdr ^ mutt_is_mail_list (a1))
1070 return (! alladdr);
1071 for (; a2 ; a2 = a2->next)
1072 if (alladdr ^ mutt_is_mail_list (a2))
1073 return (! alladdr);
1074 return alladdr;
1075}
1076
1077static int match_user (int alladdr, ADDRESS *a1, ADDRESS *a2)
1078{
1079 for (; a1 ; a1 = a1->next)
1080 if (alladdr ^ mutt_addr_is_user (a1))
1081 return (! alladdr);
1082 for (; a2 ; a2 = a2->next)
1083 if (alladdr ^ mutt_addr_is_user (a2))
1084 return (! alladdr);
1085 return alladdr;
1086}
1087
1088static int match_threadcomplete(struct pattern_t *pat, pattern_exec_flag flags, CONTEXT *ctx, THREAD *t,int left,int up,int right,int down)
1089{
1090 int a;
1091 HEADER *h;
1092
1093 if(!t)
1094 return 0;
1095 h = t->message;
1096 if(h)
1097 if(mutt_pattern_exec(pat, flags, ctx, h, NULL))
1098 return 1;
1099
1100 if(up && (a=match_threadcomplete(pat, flags, ctx, t->parent,1,1,1,0)))
1101 return a;
1102 if(right && t->parent && (a=match_threadcomplete(pat, flags, ctx, t->next,0,0,1,1)))
1103 return a;
1104 if(left && t->parent && (a=match_threadcomplete(pat, flags, ctx, t->prev,1,0,0,1)))
1105 return a;
1106 if(down && (a=match_threadcomplete(pat, flags, ctx, t->child,1,0,1,1)))
1107 return a;
1108 return 0;
1109}
1110
1111
1112/* Sets a value in the pattern_cache_t cache entry.
1113 * Normalizes the "true" value to 2. */
1114static void set_pattern_cache_value (int *cache_entry, int value)
1115{
1116 *cache_entry = value ? 2 : 1;
1117}
1118
1119/* Returns 1 if the cache value is set and has a true value.
1120 * 0 otherwise (even if unset!) */
1121static int get_pattern_cache_value (int cache_entry)
1122{
1123 return cache_entry == 2;
1124}
1125
1126static int is_pattern_cache_set (int cache_entry)
1127{
1128 return cache_entry != 0;
1129}
1130
1131
1132/*
1133 * flags: MUTT_MATCH_FULL_ADDRESS - match both personal and machine address
1134 * cache: For repeated matches against the same HEADER, passing in non-NULL will
1135 * store some of the cacheable pattern matches in this structure. */
1136int
1137mutt_pattern_exec (struct pattern_t *pat, pattern_exec_flag flags, CONTEXT *ctx, HEADER *h,
1138 pattern_cache_t *cache)
1139{
1140 int result;
1141 int *cache_entry;
1142
1143 switch (pat->op)
1144 {
1145 case MUTT_AND:
1146 return (pat->not ^ (perform_and (pat->child, flags, ctx, h, cache) > 0));
1147 case MUTT_OR:
1148 return (pat->not ^ (perform_or (pat->child, flags, ctx, h, cache) > 0));
1149 case MUTT_THREAD:
1150 return (pat->not ^ match_threadcomplete(pat->child, flags, ctx, h->thread, 1, 1, 1, 1));
1151 case MUTT_ALL:
1152 return (!pat->not);
1153 case MUTT_EXPIRED:
1154 return (pat->not ^ h->expired);
1155 case MUTT_SUPERSEDED:
1156 return (pat->not ^ h->superseded);
1157 case MUTT_FLAG:
1158 return (pat->not ^ h->flagged);
1159 case MUTT_TAG:
1160 return (pat->not ^ h->tagged);
1161 case MUTT_NEW:
1162 return (pat->not ? h->old || h->read : !(h->old || h->read));
1163 case MUTT_UNREAD:
1164 return (pat->not ? h->read : !h->read);
1165 case MUTT_REPLIED:
1166 return (pat->not ^ h->replied);
1167 case MUTT_OLD:
1168 return (pat->not ? (!h->old || h->read) : (h->old && !h->read));
1169 case MUTT_READ:
1170 return (pat->not ^ h->read);
1171 case MUTT_DELETED:
1172 return (pat->not ^ h->deleted);
1173 case MUTT_MESSAGE:
1174 return (pat->not ^ (h->msgno >= pat->min - 1 && (pat->max == MUTT_MAXRANGE ||
1175 h->msgno <= pat->max - 1)));
1176 case MUTT_DATE:
1177 return (pat->not ^ (h->date_sent >= pat->min && h->date_sent <= pat->max));
1178 case MUTT_DATE_RECEIVED:
1179 return (pat->not ^ (h->received >= pat->min && h->received <= pat->max));
1180 case MUTT_BODY:
1181 case MUTT_HEADER:
1182 case MUTT_WHOLE_MSG:
1183 /*
1184 * ctx can be NULL in certain cases, such as when replying to a message from the attachment menu and
1185 * the user has a reply-hook using "~h" (bug #2190).
1186 * This is also the case when message scoring.
1187 */
1188 if (!ctx)
1189 return 0;
1190#ifdef USE_IMAP
1191 /* IMAP search sets h->matched at search compile time */
1192 if (ctx->magic == MUTT_IMAP && pat->stringmatch)
1193 return (h->matched);
1194#endif
1195 return (pat->not ^ msg_search (ctx, pat, h->msgno));
1196 case MUTT_SENDER:
1197 return (pat->not ^ match_adrlist (pat, flags & MUTT_MATCH_FULL_ADDRESS, 1,
1198 h->env->sender));
1199 case MUTT_FROM:
1200 return (pat->not ^ match_adrlist (pat, flags & MUTT_MATCH_FULL_ADDRESS, 1,
1201 h->env->from));
1202 case MUTT_TO:
1203 return (pat->not ^ match_adrlist (pat, flags & MUTT_MATCH_FULL_ADDRESS, 1,
1204 h->env->to));
1205 case MUTT_CC:
1206 return (pat->not ^ match_adrlist (pat, flags & MUTT_MATCH_FULL_ADDRESS, 1,
1207 h->env->cc));
1208 case MUTT_SUBJECT:
1209 return (pat->not ^ (h->env->subject && patmatch (pat, h->env->subject) == 0));
1210 case MUTT_ID:
1211 return (pat->not ^ (h->env->message_id && patmatch (pat, h->env->message_id) == 0));
1212 case MUTT_SCORE:
1213 return (pat->not ^ (h->score >= pat->min && (pat->max == MUTT_MAXRANGE ||
1214 h->score <= pat->max)));
1215 case MUTT_SIZE:
1216 return (pat->not ^ (h->content->length >= pat->min && (pat->max == MUTT_MAXRANGE || h->content->length <= pat->max)));
1217 case MUTT_REFERENCE:
1218 return (pat->not ^ (match_reference (pat, h->env->references) ||
1219 match_reference (pat, h->env->in_reply_to)));
1220 case MUTT_ADDRESS:
1221 return (pat->not ^ match_adrlist (pat, flags & MUTT_MATCH_FULL_ADDRESS, 4,
1222 h->env->from, h->env->sender,
1223 h->env->to, h->env->cc));
1224 case MUTT_RECIPIENT:
1225 return (pat->not ^ match_adrlist (pat, flags & MUTT_MATCH_FULL_ADDRESS,
1226 2, h->env->to, h->env->cc));
1227 case MUTT_LIST: /* known list, subscribed or not */
1228 if (cache)
1229 {
1230 cache_entry = pat->alladdr ? &cache->list_all : &cache->list_one;
1231 if (!is_pattern_cache_set (*cache_entry))
1232 set_pattern_cache_value (cache_entry,
1233 mutt_is_list_cc (pat->alladdr, h->env->to, h->env->cc));
1234 result = get_pattern_cache_value (*cache_entry);
1235 }
1236 else
1237 result = mutt_is_list_cc (pat->alladdr, h->env->to, h->env->cc);
1238 return (pat->not ^ result);
1239 case MUTT_SUBSCRIBED_LIST:
1240 if (cache)
1241 {
1242 cache_entry = pat->alladdr ? &cache->sub_all : &cache->sub_one;
1243 if (!is_pattern_cache_set (*cache_entry))
1244 set_pattern_cache_value (cache_entry,
1245 mutt_is_list_recipient (pat->alladdr, h->env->to, h->env->cc));
1246 result = get_pattern_cache_value (*cache_entry);
1247 }
1248 else
1249 result = mutt_is_list_recipient (pat->alladdr, h->env->to, h->env->cc);
1250 return (pat->not ^ result);
1251 case MUTT_PERSONAL_RECIP:
1252 if (cache)
1253 {
1254 cache_entry = pat->alladdr ? &cache->pers_recip_all : &cache->pers_recip_one;
1255 if (!is_pattern_cache_set (*cache_entry))
1256 set_pattern_cache_value (cache_entry,
1257 match_user (pat->alladdr, h->env->to, h->env->cc));
1258 result = get_pattern_cache_value (*cache_entry);
1259 }
1260 else
1261 result = match_user (pat->alladdr, h->env->to, h->env->cc);
1262 return (pat->not ^ result);
1263 case MUTT_PERSONAL_FROM:
1264 if (cache)
1265 {
1266 cache_entry = pat->alladdr ? &cache->pers_from_all : &cache->pers_from_one;
1267 if (!is_pattern_cache_set (*cache_entry))
1268 set_pattern_cache_value (cache_entry,
1269 match_user (pat->alladdr, h->env->from, NULL));
1270 result = get_pattern_cache_value (*cache_entry);
1271 }
1272 else
1273 result = match_user (pat->alladdr, h->env->from, NULL);
1274 return (pat->not ^ result);
1275 case MUTT_COLLAPSED:
1276 return (pat->not ^ (h->collapsed && h->num_hidden > 1));
1277 case MUTT_CRYPT_SIGN:
1278 if (!WithCrypto)
1279 break;
1280 return (pat->not ^ ((h->security & SIGN) ? 1 : 0));
1281 case MUTT_CRYPT_VERIFIED:
1282 if (!WithCrypto)
1283 break;
1284 return (pat->not ^ ((h->security & GOODSIGN) ? 1 : 0));
1285 case MUTT_CRYPT_ENCRYPT:
1286 if (!WithCrypto)
1287 break;
1288 return (pat->not ^ ((h->security & ENCRYPT) ? 1 : 0));
1289 case MUTT_PGP_KEY:
1290 if (!(WithCrypto & APPLICATION_PGP))
1291 break;
1292 return (pat->not ^ ((h->security & APPLICATION_PGP) && (h->security & PGPKEY)));
1293 case MUTT_XLABEL:
1294 return (pat->not ^ (h->env->x_label && patmatch (pat, h->env->x_label) == 0));
1295 case MUTT_HORMEL:
1296 return (pat->not ^ (h->env->spam && h->env->spam->data && patmatch (pat, h->env->spam->data) == 0));
1297 case MUTT_DUPLICATED:
1298 return (pat->not ^ (h->thread && h->thread->duplicate_thread));
1299 case MUTT_MIMEATTACH:
1300 if (!ctx)
1301 return 0;
1302 {
1303 int count = mutt_count_body_parts (ctx, h);
1304 return (pat->not ^ (count >= pat->min && (pat->max == MUTT_MAXRANGE ||
1305 count <= pat->max)));
1306 }
1307 case MUTT_UNREFERENCED:
1308 return (pat->not ^ (h->thread && !h->thread->child));
1309 }
1310 mutt_error (_("error: unknown op %d (report this error)."), pat->op);
1311 return (-1);
1312}
1313
1314static void quote_simple(char *tmp, size_t len, const char *p)
1315{
1316 int i = 0;
1317
1318 tmp[i++] = '"';
1319 while (*p && i < len - 3)
1320 {
1321 if (*p == '\\' || *p == '"')
1322 tmp[i++] = '\\';
1323 tmp[i++] = *p++;
1324 }
1325 tmp[i++] = '"';
1326 tmp[i] = 0;
1327}
1328
1329/* convert a simple search into a real request */
1330void mutt_check_simple (char *s, size_t len, const char *simple)
1331{
1332 char tmp[LONG_STRING];
1333 int do_simple = 1;
1334 char *p;
1335
1336 for (p = s; p && *p; p++)
1337 {
1338 if (*p == '\\' && *(p + 1))
1339 p++;
1340 else if (*p == '~' || *p == '=' || *p == '%')
1341 {
1342 do_simple = 0;
1343 break;
1344 }
1345 }
1346
1347 /* XXX - is ascii_strcasecmp() right here, or should we use locale's
1348 * equivalences?
1349 */
1350
1351 if (do_simple) /* yup, so spoof a real request */
1352 {
1353 /* convert old tokens into the new format */
1354 if (ascii_strcasecmp ("all", s) == 0 ||
1355 !mutt_strcmp ("^", s) || !mutt_strcmp (".", s)) /* ~A is more efficient */
1356 strfcpy (s, "~A", len);
1357 else if (ascii_strcasecmp ("del", s) == 0)
1358 strfcpy (s, "~D", len);
1359 else if (ascii_strcasecmp ("flag", s) == 0)
1360 strfcpy (s, "~F", len);
1361 else if (ascii_strcasecmp ("new", s) == 0)
1362 strfcpy (s, "~N", len);
1363 else if (ascii_strcasecmp ("old", s) == 0)
1364 strfcpy (s, "~O", len);
1365 else if (ascii_strcasecmp ("repl", s) == 0)
1366 strfcpy (s, "~Q", len);
1367 else if (ascii_strcasecmp ("read", s) == 0)
1368 strfcpy (s, "~R", len);
1369 else if (ascii_strcasecmp ("tag", s) == 0)
1370 strfcpy (s, "~T", len);
1371 else if (ascii_strcasecmp ("unread", s) == 0)
1372 strfcpy (s, "~U", len);
1373 else
1374 {
1375 quote_simple (tmp, sizeof(tmp), s);
1376 mutt_expand_fmt (s, len, simple, tmp);
1377 }
1378 }
1379}
1380
1381int mutt_pattern_func (int op, char *prompt)
1382{
1383 pattern_t *pat;
1384 char buf[LONG_STRING] = "", *simple;
1385 BUFFER err;
1386 int i;
1387 progress_t progress;
1388
1389 strfcpy (buf, NONULL (Context->pattern), sizeof (buf));
1390 if (mutt_get_field (prompt, buf, sizeof (buf), MUTT_PATTERN | MUTT_CLEAR) != 0 || !buf[0])
1391 return (-1);
1392
1393 mutt_message _("Compiling search pattern...");
1394
1395 simple = safe_strdup (buf);
1396 mutt_check_simple (buf, sizeof (buf), NONULL (SimpleSearch));
1397
1398 mutt_buffer_init (&err);
1399 err.dsize = STRING;
1400 err.data = safe_malloc(err.dsize);
1401 if ((pat = mutt_pattern_comp (buf, MUTT_FULL_MSG, &err)) == NULL)
1402 {
1403 FREE (&simple);
1404 mutt_error ("%s", err.data);
1405 FREE (&err.data);
1406 return (-1);
1407 }
1408
1409#ifdef USE_IMAP
1410 if (Context->magic == MUTT_IMAP && imap_search (Context, pat) < 0)
1411 return -1;
1412#endif
1413
1414 mutt_progress_init (&progress, _("Executing command on matching messages..."),
1415 MUTT_PROGRESS_MSG, ReadInc,
1416 (op == MUTT_LIMIT) ? Context->msgcount : Context->vcount);
1417
1418#define THIS_BODY Context->hdrs[i]->content
1419
1420 if (op == MUTT_LIMIT)
1421 {
1422 Context->vcount = 0;
1423 Context->vsize = 0;
1424 Context->collapsed = 0;
1425
1426 for (i = 0; i < Context->msgcount; i++)
1427 {
1428 mutt_progress_update (&progress, i, -1);
1429 /* new limit pattern implicitly uncollapses all threads */
1430 Context->hdrs[i]->virtual = -1;
1431 Context->hdrs[i]->limited = 0;
1432 Context->hdrs[i]->collapsed = 0;
1433 Context->hdrs[i]->num_hidden = 0;
1434 if (mutt_pattern_exec (pat, MUTT_MATCH_FULL_ADDRESS, Context, Context->hdrs[i], NULL))
1435 {
1436 Context->hdrs[i]->virtual = Context->vcount;
1437 Context->hdrs[i]->limited = 1;
1438 Context->v2r[Context->vcount] = i;
1439 Context->vcount++;
1440 Context->vsize+=THIS_BODY->length + THIS_BODY->offset -
1441 THIS_BODY->hdr_offset;
1442 }
1443 }
1444 }
1445 else
1446 {
1447 for (i = 0; i < Context->vcount; i++)
1448 {
1449 mutt_progress_update (&progress, i, -1);
1450 if (mutt_pattern_exec (pat, MUTT_MATCH_FULL_ADDRESS, Context, Context->hdrs[Context->v2r[i]], NULL))
1451 {
1452 switch (op)
1453 {
1454 case MUTT_UNDELETE:
1455 mutt_set_flag (Context, Context->hdrs[Context->v2r[i]], MUTT_PURGE,
1456 0);
1457 case MUTT_DELETE:
1458 mutt_set_flag (Context, Context->hdrs[Context->v2r[i]], MUTT_DELETE,
1459 (op == MUTT_DELETE));
1460 break;
1461 case MUTT_TAG:
1462 case MUTT_UNTAG:
1463 mutt_set_flag (Context, Context->hdrs[Context->v2r[i]], MUTT_TAG,
1464 (op == MUTT_TAG));
1465 break;
1466 }
1467 }
1468 }
1469 }
1470
1471#undef THIS_BODY
1472
1473 mutt_clear_error ();
1474
1475 if (op == MUTT_LIMIT)
1476 {
1477 /* drop previous limit pattern */
1478 FREE (&Context->pattern);
1479 if (Context->limit_pattern)
1480 mutt_pattern_free (&Context->limit_pattern);
1481
1482 if (Context->msgcount && !Context->vcount)
1483 mutt_error _("No messages matched criteria.");
1484
1485 /* record new limit pattern, unless match all */
1486 if (mutt_strcmp (buf, "~A") != 0)
1487 {
1488 Context->pattern = simple;
1489 simple = NULL; /* don't clobber it */
1490 Context->limit_pattern = mutt_pattern_comp (buf, MUTT_FULL_MSG, &err);
1491 }
1492 }
1493 FREE (&simple);
1494 mutt_pattern_free (&pat);
1495 FREE (&err.data);
1496
1497 return 0;
1498}
1499
1500int mutt_search_command (int cur, int op)
1501{
1502 int i, j;
1503 char buf[STRING];
1504 char temp[LONG_STRING];
1505 int incr;
1506 HEADER *h;
1507 progress_t progress;
1508 const char* msg = NULL;
1509
1510 if (!*LastSearch || (op != OP_SEARCH_NEXT && op != OP_SEARCH_OPPOSITE))
1511 {
1512 strfcpy (buf, *LastSearch ? LastSearch : "", sizeof (buf));
1513 if (mutt_get_field ((op == OP_SEARCH || op == OP_SEARCH_NEXT) ?
1514 _("Search for: ") : _("Reverse search for: "),
1515 buf, sizeof (buf),
1516 MUTT_CLEAR | MUTT_PATTERN) != 0 || !buf[0])
1517 return (-1);
1518
1519 if (op == OP_SEARCH || op == OP_SEARCH_NEXT)
1520 unset_option (OPTSEARCHREVERSE);
1521 else
1522 set_option (OPTSEARCHREVERSE);
1523
1524 /* compare the *expanded* version of the search pattern in case
1525 $simple_search has changed while we were searching */
1526 strfcpy (temp, buf, sizeof (temp));
1527 mutt_check_simple (temp, sizeof (temp), NONULL (SimpleSearch));
1528
1529 if (!SearchPattern || mutt_strcmp (temp, LastSearchExpn))
1530 {
1531 BUFFER err;
1532 mutt_buffer_init (&err);
1533 set_option (OPTSEARCHINVALID);
1534 strfcpy (LastSearch, buf, sizeof (LastSearch));
1535 mutt_message _("Compiling search pattern...");
1536 mutt_pattern_free (&SearchPattern);
1537 err.dsize = STRING;
1538 err.data = safe_malloc (err.dsize);
1539 if ((SearchPattern = mutt_pattern_comp (temp, MUTT_FULL_MSG, &err)) == NULL)
1540 {
1541 mutt_error ("%s", err.data);
1542 FREE (&err.data);
1543 LastSearch[0] = '\0';
1544 return (-1);
1545 }
1546 mutt_clear_error ();
1547 }
1548 }
1549
1550 if (option (OPTSEARCHINVALID))
1551 {
1552 for (i = 0; i < Context->msgcount; i++)
1553 Context->hdrs[i]->searched = 0;
1554#ifdef USE_IMAP
1555 if (Context->magic == MUTT_IMAP && imap_search (Context, SearchPattern) < 0)
1556 return -1;
1557#endif
1558 unset_option (OPTSEARCHINVALID);
1559 }
1560
1561 incr = (option (OPTSEARCHREVERSE)) ? -1 : 1;
1562 if (op == OP_SEARCH_OPPOSITE)
1563 incr = -incr;
1564
1565 mutt_progress_init (&progress, _("Searching..."), MUTT_PROGRESS_MSG,
1566 ReadInc, Context->vcount);
1567
1568 for (i = cur + incr, j = 0 ; j != Context->vcount; j++)
1569 {
1570 mutt_progress_update (&progress, j, -1);
1571 if (i > Context->vcount - 1)
1572 {
1573 i = 0;
1574 if (option (OPTWRAPSEARCH))
1575 msg = _("Search wrapped to top.");
1576 else
1577 {
1578 mutt_message _("Search hit bottom without finding match");
1579 return (-1);
1580 }
1581 }
1582 else if (i < 0)
1583 {
1584 i = Context->vcount - 1;
1585 if (option (OPTWRAPSEARCH))
1586 msg = _("Search wrapped to bottom.");
1587 else
1588 {
1589 mutt_message _("Search hit top without finding match");
1590 return (-1);
1591 }
1592 }
1593
1594 h = Context->hdrs[Context->v2r[i]];
1595 if (h->searched)
1596 {
1597 /* if we've already evaluated this message, use the cached value */
1598 if (h->matched)
1599 {
1600 mutt_clear_error();
1601 if (msg && *msg)
1602 mutt_message (msg);
1603 return i;
1604 }
1605 }
1606 else
1607 {
1608 /* remember that we've already searched this message */
1609 h->searched = 1;
1610 if ((h->matched = (mutt_pattern_exec (SearchPattern, MUTT_MATCH_FULL_ADDRESS, Context, h, NULL) > 0)))
1611 {
1612 mutt_clear_error();
1613 if (msg && *msg)
1614 mutt_message (msg);
1615 return i;
1616 }
1617 }
1618
1619 if (SigInt)
1620 {
1621 mutt_error _("Search interrupted.");
1622 SigInt = 0;
1623 return (-1);
1624 }
1625
1626 i += incr;
1627 }
1628
1629 mutt_error _("Not found.");
1630 return (-1);
1631}