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