mutt stable branch with some hacks
at jcs 1765 lines 46 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#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}