mutt stable branch with some hacks
at master 1631 lines 42 kB view raw
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}